Repository navigation
fix(cli): make temporary directory cleanup non-fatal in the Windows install script #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2e933f8
266ec43
48f0bc8
3771d0d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| id: 1792687232-e2151e8b | ||
| features: | ||
| - core | ||
| targets: | ||
| - cli | ||
| type: fix | ||
| bump: patch | ||
| description: keep a failed temporary directory cleanup in the Windows install script from being reported as an installation failure | ||
| author: AshGodfrey | ||
| date: "2026-10-22" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| #Requires -Version 5.1 | ||
| <# | ||
| Exercises the generated Windows installer offline: the release download and | ||
| version lookup are replaced by local functions, the installer is run against a | ||
| locally built archive, and the installed binary, download URL, and temporary | ||
| directory cleanup are checked. On Windows a second run holds a file in the | ||
| temporary directory open to confirm a cleanup failure does not fail the install. | ||
| #> | ||
| [CmdletBinding()] | ||
| param( | ||
| [string]$InstallerPath = (Join-Path $PSScriptRoot '..\zSDKs\sdk-cli\scripts\install.ps1') | ||
| ) | ||
|
|
||
| $ErrorActionPreference = 'Stop' | ||
| Set-StrictMode -Version Latest | ||
|
|
||
| $InstallerPath = (Resolve-Path -LiteralPath $InstallerPath).Path | ||
| $isWindowsHost = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) | ||
|
|
||
| function Assert-True { | ||
| param([bool]$Condition, [string]$Message) | ||
| if (-not $Condition) { | ||
| throw "Assertion failed: $Message" | ||
| } | ||
| Write-Host "ok: $Message" | ||
| } | ||
|
|
||
| function New-InstallerSandbox { | ||
| $root = Join-Path ([System.IO.Path]::GetTempPath()) "installer-test-$([guid]::NewGuid())" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Sandbox roots are never deleted, and the second sandbox is created inside the first one's temp directory because Prompt for AI agents |
||
| foreach ($name in 'temp', 'localappdata', 'install', 'payload') { | ||
| New-Item -ItemType Directory -Path (Join-Path $root $name) -Force | Out-Null | ||
| } | ||
| $binary = Join-Path (Join-Path $root 'payload') 'cli.exe' | ||
| Set-Content -LiteralPath $binary -Value "stub binary $([guid]::NewGuid())" -NoNewline -Encoding ascii | ||
| $archive = Join-Path $root 'cli_Windows.zip' | ||
| Compress-Archive -LiteralPath $binary -DestinationPath $archive | ||
| [pscustomobject]@{ | ||
| Root = $root | ||
| Temp = Join-Path $root 'temp' | ||
| InstallDir = Join-Path $root 'install' | ||
| Binary = $binary | ||
| Archive = $archive | ||
| } | ||
| } | ||
|
|
||
| function Invoke-Installer { | ||
| param($Sandbox) | ||
| $env:TEMP = $Sandbox.Temp | ||
| $env:LOCALAPPDATA = Join-Path $Sandbox.Root 'localappdata' | ||
| $env:CLI_INSTALL_DIR = $Sandbox.InstallDir | ||
| $env:CLI_VERSION = 'v0.0.0-test' | ||
| if (-not $env:PROCESSOR_ARCHITECTURE) { | ||
| $env:PROCESSOR_ARCHITECTURE = 'AMD64' | ||
| } | ||
| $global:InstallerTestArchive = $Sandbox.Archive | ||
| $global:InstallerTestDownloads = @() | ||
| & $InstallerPath *>&1 | ForEach-Object { "$_" } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Running the installer here writes the sandbox install directory into the persistent User PATH, and nothing restores it. Each run adds another temp Prompt for AI agents |
||
| } | ||
|
|
||
| function global:Invoke-RestMethod { | ||
| throw 'the installer must not look up the latest release when CLI_VERSION is set' | ||
| } | ||
|
|
||
| function global:Invoke-WebRequest { | ||
| param([string]$Uri, [string]$OutFile, [switch]$UseBasicParsing) | ||
| $global:InstallerTestDownloads += $Uri | ||
| Copy-Item -LiteralPath $global:InstallerTestArchive -Destination $OutFile | ||
| } | ||
|
|
||
| $global:InstallerTestOpenHandle = $null | ||
| function global:Expand-Archive { | ||
| param([string]$Path, [string]$DestinationPath, [switch]$Force) | ||
| Microsoft.PowerShell.Archive\Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force:$Force | ||
| if ($global:InstallerTestHoldExtractedBinary) { | ||
| $extracted = Join-Path $DestinationPath 'cli.exe' | ||
| $global:InstallerTestOpenHandle = [System.IO.File]::Open($extracted, 'Open', 'Read', 'Read') | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| Write-Host '--- install succeeds and cleans up' | ||
| $global:InstallerTestHoldExtractedBinary = $false | ||
| $sandbox = New-InstallerSandbox | ||
| $output = Invoke-Installer $sandbox | ||
| $installed = Join-Path $sandbox.InstallDir 'cli.exe' | ||
| Assert-True (Test-Path -LiteralPath $installed) 'binary is copied into CLI_INSTALL_DIR' | ||
| Assert-True ((Get-Content -LiteralPath $installed -Raw) -eq (Get-Content -LiteralPath $sandbox.Binary -Raw)) 'installed binary matches the archived one' | ||
| Assert-True ($global:InstallerTestDownloads.Count -eq 1) 'exactly one archive download' | ||
| Assert-True ($global:InstallerTestDownloads[0] -like '*/releases/download/v0.0.0-test/cli_Windows_*.zip') "download URL names the pinned version: $($global:InstallerTestDownloads[0])" | ||
| Assert-True (@(Get-ChildItem -LiteralPath $sandbox.Temp -Force).Count -eq 0) 'temporary directory is removed' | ||
| Assert-True (($output -join "`n") -match 'Installation successful') 'installer reports success' | ||
|
|
||
| if ($isWindowsHost) { | ||
| Write-Host '--- install succeeds when the temporary directory cannot be removed' | ||
| $global:InstallerTestHoldExtractedBinary = $true | ||
| $sandbox = New-InstallerSandbox | ||
| $output = Invoke-Installer $sandbox | ||
| $installed = Join-Path $sandbox.InstallDir 'cli.exe' | ||
| Assert-True (Test-Path -LiteralPath $installed) 'binary is copied despite the held temporary file' | ||
| Assert-True (($output -join "`n") -match 'Warning: could not remove temporary directory') 'installer warns about the leftover temporary directory' | ||
| Assert-True (($output -join "`n") -match 'Installation successful') 'installer still reports success' | ||
| Assert-True (@(Get-ChildItem -LiteralPath $sandbox.Temp -Force).Count -eq 1) 'temporary directory is left in place' | ||
| $global:InstallerTestOpenHandle.Dispose() | ||
| } | ||
| else { | ||
| Write-Host '--- skipping held-file cleanup case: open files can be deleted on this platform' | ||
| } | ||
| } | ||
| finally { | ||
| if ($global:InstallerTestOpenHandle) { | ||
| $global:InstallerTestOpenHandle.Dispose() | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The default
InstallerPathuses backslash separators, which only resolve on Windows. Use forward slashes so the path also resolves under pwsh on Linux/macOS, where this script is meant to run.Prompt for AI agents