219 lines
7.4 KiB
PowerShell
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
|
|
})
|
|
}
|