Files
MBN_STOCK_WEBVIEW/scripts/Inspect-TornadoTestIsolation.ps1

219 lines
7.4 KiB
PowerShell

[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateRange(1, 65535)]
[int]$ExpectedTcpPort,
[Parameter(Mandatory = $true)]
[ValidateLength(1, 128)]
[string]$ExpectedTestWindowTitle
)
$ErrorActionPreference = 'Stop'
function Write-SafeResult {
param(
[Parameter(Mandatory = $true)]
[object]$Value,
[Parameter(Mandatory = $true)]
[int]$ExitCode
)
$Value | ConvertTo-Json -Depth 6
exit $ExitCode
}
function Test-ProgramWindowTitle {
param([AllowEmptyString()][string]$Title)
return $Title -match '(?i)(^|[^A-Z0-9])(PGM|PROGRAM)([^A-Z0-9]|$)'
}
try {
$expectedTitle = $ExpectedTestWindowTitle.Trim()
if (-not $expectedTitle.StartsWith('Tornado2', [StringComparison]::OrdinalIgnoreCase) -or
$expectedTitle -notmatch '(?i)(^|[^A-Z0-9])TEST([^A-Z0-9]|$)' -or
(Test-ProgramWindowTitle $expectedTitle)) {
Write-SafeResult -ExitCode 64 -Value ([ordered]@{
command = 'test-isolation-probe'
ready = $false
errorCode = 'invalid-test-window-title'
connectRequestIssued = $false
comActivationAttempted = $false
ktapConnectAttempted = $false
})
}
$processInspectionAvailable = $true
$tornadoProcesses = @()
try {
foreach ($process in Get-Process -ErrorAction Stop) {
try {
if (-not $process.ProcessName.StartsWith(
'Tornado2',
[StringComparison]::OrdinalIgnoreCase)) {
continue
}
$title = $process.MainWindowTitle
$tornadoProcesses += [pscustomobject]@{
Id = $process.Id
IsExpectedTest = [string]::Equals(
$title,
$expectedTitle,
[StringComparison]::OrdinalIgnoreCase)
IsProgram = Test-ProgramWindowTitle $title
}
}
catch {
$processInspectionAvailable = $false
}
finally {
$process.Dispose()
}
}
}
catch {
$processInspectionAvailable = $false
$tornadoProcesses = @()
}
$eligibleProcesses = @($tornadoProcesses | Where-Object IsExpectedTest)
$programProcesses = @($tornadoProcesses | Where-Object IsProgram)
$registryAvailable = $false
$configuredPortMatches = $false
$networkAddressIsLoopback = $false
$networkAutoStart = $false
try {
$controlKey = Get-Item -LiteralPath (
'Registry::HKEY_CURRENT_USER\Software\Visual Research Inc\Tornado2\Option\Control')
$configuredPort = [int]$controlKey.GetValue('Network TCP Port', 0)
$configuredAddress = [string]$controlKey.GetValue('Network Address', '')
$networkAutoStart = [int]$controlKey.GetValue('Network Auto Start', 0) -ne 0
$parsedAddress = $null
$addressParsed = [Net.IPAddress]::TryParse($configuredAddress, [ref]$parsedAddress)
$networkAddressIsLoopback = $addressParsed -and
[Net.IPAddress]::IsLoopback($parsedAddress)
$configuredPortMatches = $configuredPort -eq $ExpectedTcpPort
$registryAvailable = $true
}
catch {
$registryAvailable = $false
}
$listenerInspectionAvailable = $true
$listeners = @()
try {
$listeners = @(Get-NetTCPConnection `
-State Listen `
-LocalPort $ExpectedTcpPort `
-ErrorAction Stop)
}
catch [Microsoft.PowerShell.Cmdletization.Cim.CimJobException] {
# Get-NetTCPConnection uses this exception for an empty query on some Windows builds.
$listeners = @()
}
catch {
$listenerInspectionAvailable = $false
$listeners = @()
}
$listenerAddressesAreLoopback = $listeners.Count -gt 0
foreach ($listener in $listeners) {
$parsedListenerAddress = $null
$listenerAddressParsed = [Net.IPAddress]::TryParse(
[string]$listener.LocalAddress,
[ref]$parsedListenerAddress)
if (-not $listenerAddressParsed -or
-not [Net.IPAddress]::IsLoopback($parsedListenerAddress)) {
$listenerAddressesAreLoopback = $false
break
}
}
$eligibleProcessIds = @($eligibleProcesses | ForEach-Object Id)
$listenerOwnerIds = @($listeners | ForEach-Object OwningProcess | Sort-Object -Unique)
$listenerOwnedOnlyByTest = $listeners.Count -gt 0 -and
$listenerOwnerIds.Count -gt 0 -and
@($listenerOwnerIds | Where-Object { $_ -notin $eligibleProcessIds }).Count -eq 0
$reasons = [Collections.Generic.List[string]]::new()
if (-not $processInspectionAvailable) {
$reasons.Add('process-inspection-unavailable')
}
if ($tornadoProcesses.Count -ne 1) {
$reasons.Add('tornado-process-count-not-one')
}
if ($eligibleProcesses.Count -ne 1) {
$reasons.Add('test-window-not-exact-match')
}
if ($programProcesses.Count -gt 0) {
$reasons.Add('program-window-detected')
}
if (-not $registryAvailable) {
$reasons.Add('network-settings-unavailable')
}
elseif (-not $configuredPortMatches) {
$reasons.Add('network-tcp-port-mismatch')
}
if ($registryAvailable -and -not $networkAddressIsLoopback) {
$reasons.Add('network-address-not-loopback')
}
if (-not $listenerInspectionAvailable) {
$reasons.Add('listener-inspection-unavailable')
}
elseif ($listeners.Count -eq 0) {
$reasons.Add('network-server-not-listening')
}
else {
if (-not $listenerAddressesAreLoopback) {
$reasons.Add('listener-address-not-loopback')
}
if (-not $listenerOwnedOnlyByTest) {
$reasons.Add('listener-not-owned-by-test-instance')
}
}
$ready = $reasons.Count -eq 0
Write-SafeResult -ExitCode $(if ($ready) { 0 } else { 3 }) -Value ([ordered]@{
command = 'test-isolation-probe'
ready = $ready
expectedTcpPort = $ExpectedTcpPort
process = [ordered]@{
inspectionAvailable = $processInspectionAvailable
totalCount = $tornadoProcesses.Count
eligibleTestCount = $eligibleProcesses.Count
programWindowDetected = $programProcesses.Count -gt 0
}
networkServer = [ordered]@{
settingsAvailable = $registryAvailable
configuredPortMatches = $configuredPortMatches
addressIsLoopback = $networkAddressIsLoopback
autoStartEnabled = $networkAutoStart
listenerInspectionAvailable = $listenerInspectionAvailable
listenerEndpointCount = $listeners.Count
listenerAddressesAreLoopback = $listenerAddressesAreLoopback
listenerOwnedOnlyByTest = $listenerOwnedOnlyByTest
}
reasons = @($reasons)
connectRequestIssued = $false
comActivationAttempted = $false
lastKtapConnectState = 'not-attempted'
ktapConnectAttempted = $false
networkMonitoringRecordExpected = $false
networkMonitoringCheckRequired = $false
})
}
catch {
Write-SafeResult -ExitCode 70 -Value ([ordered]@{
command = 'test-isolation-probe'
ready = $false
errorCode = 'internal-probe-failure'
connectRequestIssued = $false
comActivationAttempted = $false
ktapConnectAttempted = $false
})
}