[CmdletBinding()] param() $ErrorActionPreference = 'Stop' $repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) $dll = Join-Path $repo 'tests/QVPN.Tests/bin/Release/net10.0-windows/QVPN.Tests.dll' $report = Join-Path $repo 'research/xray-upgrade-verification.json' $phase = 'preflight'; $happWasRunning = $false function Save-Phase([string]$name) { $script:phase = $name [pscustomobject]@{Completed=$false;Phase=$name;At=[DateTimeOffset]::Now.ToString('o')} | ConvertTo-Json | Set-Content -LiteralPath $report -Encoding utf8 } function Send([string]$command,[string]$value='') { $arguments=@($dll,'--client',$command); if($value){$arguments+=@('--value',$value)} $response=& dotnet @arguments | ConvertFrom-Json if($LASTEXITCODE -ne 0 -or !$response.Ok){throw "Service command failed: $command"} return $response.Status } try { $identity=[Security.Principal.WindowsIdentity]::GetCurrent() if(!([Security.Principal.WindowsPrincipal]::new($identity)).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)){throw 'Administrator required'} $version=([xml](Get-Content (Join-Path $repo 'Directory.Build.props') -Raw)).Project.PropertyGroup.Version $setup=Join-Path $repo "dist/QVPN-Setup-$version.exe" $expected=(Get-Content (Join-Path $repo "dist/QVPN-Setup-$version.sha256") -Raw).Split(' ')[0] if((Get-FileHash -LiteralPath $setup).Hash -ne $expected){throw 'Installer checksum mismatch'} $before=Send 'status'; if(!$before.Nodes.Count){throw 'Cached subscription missing'} $zurich=$before.Nodes | Where-Object {$_.Name -like '*Цюрих*'} | Select-Object -First 1 if(!$zurich){throw 'Zurich is missing from subscription'} Save-Phase 'refresh current provider parameters' $data=Join-Path $env:ProgramData 'QVPN' Copy-Item -LiteralPath (Join-Path $data 'settings.dpapi') -Destination (Join-Path $data ("settings.dpapi.before-$version-"+[DateTime]::UtcNow.ToString('yyyyMMddHHmmss'))) $refreshed=Send 'refresh' $zurich=$refreshed.Nodes | Where-Object {$_.Name -eq $zurich.Name} | Select-Object -First 1 if(!$zurich){throw 'Zurich is missing after refreshing provider parameters'} Save-Phase 'release competing HAPP tunnel' $happ=Get-Service HappService -ErrorAction SilentlyContinue $happWasRunning=$happ -and $happ.Status -eq 'Running' Get-Process Happ -ErrorAction SilentlyContinue | Where-Object {$_.Path -eq 'C:\Program Files\FlyFrogLLC\Happ\Happ.exe'} | Stop-Process -Force if($happWasRunning){Stop-Service HappService; (Get-Service HappService).WaitForStatus('Stopped',[TimeSpan]::FromSeconds(30))} Start-Sleep -Seconds 2 if(Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object {$_.Name -like '*happ*' -and $_.Status -eq 'Up'}){throw 'HAPP adapter is still active'} $null=Send 'select' $zurich.Id Save-Phase 'install Xray update preserving encrypted settings' $timer=[Diagnostics.Stopwatch]::StartNew() for($attempt=0;$attempt -lt 2;$attempt++) { $installer=Start-Process -FilePath $setup -ArgumentList '--silent' -WindowStyle Hidden -PassThru -Wait if($installer.ExitCode -eq 0){break} if($attempt -eq 1){throw 'Installer failed twice'} Start-Sleep -Seconds 5 } Save-Phase 'connect installed Xray to Zurich' $null=Send 'connect'; $timer.Restart() do { Start-Sleep -Milliseconds 250 $status=Send 'status' if($status.State -eq 'connected' -and $status.VpnCore -eq 'Xray' -and $status.XrayPid){break} }while($timer.Elapsed.TotalSeconds -lt 60) if($status.State -ne 'connected' -or $status.VpnCore -ne 'Xray'){throw 'Installed Xray connection did not complete'} if($status.ActiveName -ne $zurich.Name){throw 'Selected Zurich was not retained'} $connectSeconds=[Math]::Round($timer.Elapsed.TotalSeconds,2) Save-Phase 'verify installed Xray recovery and continuous TUN traffic' $verification=Start-Process -FilePath (Join-Path $PSHOME 'pwsh.exe') -WindowStyle Hidden -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File',(Join-Path $PSScriptRoot 'verify-stability-installed.ps1'),'-ObserveSeconds','300') -PassThru -Wait if($verification.ExitCode -ne 0){throw 'Installed recovery/stability verification failed'} # Return the competing client to the state observed before this diagnostic run. # The updated QVPN service and selected location remain installed for normal use. if($happWasRunning -and !$before.DesiredConnected){$null=Send 'disconnect'; Start-Service HappService} [pscustomobject]@{Completed=$true;At=[DateTimeOffset]::Now.ToString('o');Version=$version;ConnectionSeconds=$connectSeconds;VpnCore='Xray';Location='Zurich';KeyRetained=$true;HappStopped=$happWasRunning;HappRestored=($happWasRunning -and !$before.DesiredConnected);RecoveryReport='installed-stability-verification.json'} | ConvertTo-Json | Set-Content -LiteralPath $report -Encoding utf8 } catch { try{$null=Send 'disconnect'}catch{} if($happWasRunning){Start-Service HappService -ErrorAction SilentlyContinue} [pscustomobject]@{Completed=$false;At=[DateTimeOffset]::Now.ToString('o');Phase=$phase;Failure=$_.Exception.Message;HappRestored=$happWasRunning} | ConvertTo-Json | Set-Content -LiteralPath $report -Encoding utf8 exit 1 }