param( [Parameter(Mandatory=$true)][string]$Executable, [Parameter(Mandatory=$true)][string]$OutputPath ) $ErrorActionPreference = 'Stop' if (Test-Path $OutputPath) { throw 'Do not overwrite earlier gate evidence.' } $exe = (Resolve-Path $Executable).Path $session = [Diagnostics.Process]::GetCurrentProcess().SessionId if ($session -eq 0) { throw 'Run in the interactive WeChat session, not Session 0.' } $lockPath = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) "WxAgent\ui-session-$session.lock" $before = & $exe recover-ui --timeout 30 $beforeCode = $LASTEXITCODE if ($beforeCode -ne 0) { throw 'Baseline recovery failed; a contention test would not be meaningful.' } $lease = [IO.File]::Open($lockPath, [IO.FileMode]::OpenOrCreate, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None) try { $blocked = & $exe recover-ui --timeout 3 $blockedCode = $LASTEXITCODE $errorCode = (($blocked -join [Environment]::NewLine) | ConvertFrom-Json).error # The legacy listener must honor its own duration while another process holds the gate. $listenOutput = @(& $exe chat listen --seconds 1 --timeout 10) $listenCode = $LASTEXITCODE $listenResult = ($listenOutput -join [Environment]::NewLine) | ConvertFrom-Json $messageCount = @($listenResult.messages).Count } finally { $lease.Dispose() } $after = & $exe recover-ui --timeout 30 $afterCode = $LASTEXITCODE $result = @{ SessionId=$session BaselineExit=$beforeCode ContendedExit=$blockedCode ContendedError=$errorCode ListenerExit=$listenCode UnexpectedListenerMessageCount=$messageCount RecoveryAfterReleaseExit=$afterCode Passed=($beforeCode -eq 0 -and $blockedCode -eq 124 -and $errorCode -eq 'Timeout' -and $listenCode -eq 0 -and $messageCount -eq 0 -and $afterCode -eq 0) } [IO.File]::WriteAllText([IO.Path]::GetFullPath($OutputPath), ($result | ConvertTo-Json), (New-Object Text.UTF8Encoding($false))) if (!$result.Passed) { exit 2 } exit 0