From: Max Vilimpoc Date: Sun, 6 Sep 2026 15:44:24 +0000 (+0200) Subject: dotfiles: stop the ssh.exe check from deleting the ssh.exe it verifies X-Git-Url: https://vilimpoc.org/repos/dotfiles/commitdiff_plain/4658d6d?hp=f51da37a77ac790110def9697b57f49b821c4c75 dotfiles: stop the ssh.exe check from deleting the ssh.exe it verifies The rsync step's run-it-to-verify check has been throwing away a working ssh.exe on almost every run since it was added, on every architecture: try { $sshVer = (& $SshExe -V 2>&1 | Select-Object -First 1) } catch { } Select-Object stops the pipeline the instant it has its one object, and that is when ssh.exe has WRITTEN its banner - not when it has exited. PowerShell then tears the pipeline down, kills the still-running process, and reports -1. Measured on a healthy binary on the x64 box: 24 of 25 runs reported exit -1 through Select-Object, 25 of 25 reported 0 when the output was collected first. So collect, then take the first line. The bug hid behind its own warning. A killed process looks exactly like the failure the check exists to catch, the warning names the release's ssh.exe rather than the test, and the resulting fallback to the in-box client - the ~17MB/s one this whole step exists to avoid - reads as a property of the release. This box had been running on the slow client with core.sshCommand pointed at System32 because of it. It also made the check's stated purpose untestable: it fired whether or not the libcrypto mismatch it was written for was present. Verified by re-running the elevated half: ssh.exe now survives and the per-user half repoints core.sshCommand at C:/Tools/rsync/ssh.exe. Also teach the architecture audit where NASM actually lands. Its installer falls back to %LOCALAPPDATA%\bin\NASM when it cannot write to Program Files - already documented in setup-windows.bat, but never taught to the audit - so a present, native nasm.exe reported "- not installed" while the run still concluded that everything had resolved. winget cannot see it there either, which is the same reason it re-downloads and re-runs that installer on every single run. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01K4qCpesQL1UwJxgQy77ttG --- diff --git a/setup-windows-no-uac.ps1 b/setup-windows-no-uac.ps1 index 27f7a0e..2a104fb 100644 --- a/setup-windows-no-uac.ps1 +++ b/setup-windows-no-uac.ps1 @@ -625,7 +625,18 @@ function Invoke-ArchAudit { 'BinSkim.exe' = @((Join-Path $BinSkimDir 'BinSkim.exe')) 'rsync.exe' = @((Join-Path $RsyncDir 'rsync.exe')) 'ssh.exe' = @((Join-Path $env:WINDIR 'System32\OpenSSH\ssh.exe')) - 'nasm.exe' = @() + # NASM's installer falls back to a per-user directory when it cannot + # write to Program Files - see the NASM comment in setup-windows.bat. It + # registers nothing there, so winget cannot see it (and re-downloads and + # re-runs the installer on every run), and it puts nothing on the PATH. + # Without these candidates the audit reported "- not installed" for a + # nasm.exe that was on disk all along, and then still concluded that + # everything had resolved. + 'nasm.exe' = @( + (Join-Path $env:LOCALAPPDATA 'bin\NASM\nasm.exe') + (Join-Path $env:ProgramFiles 'NASM\nasm.exe') + (Join-Path ${env:ProgramFiles(x86)} 'NASM\nasm.exe') + ) 'iperf3.exe' = @() 'OpenCppCoverage.exe' = @((Join-Path $env:ProgramFiles 'OpenCppCoverage\OpenCppCoverage.exe'), (Join-Path ${env:ProgramFiles(x86)} 'OpenCppCoverage\OpenCppCoverage.exe')) 'vswhere.exe' = @($vsWhereExe) diff --git a/setup-windows-with-uac.ps1 b/setup-windows-with-uac.ps1 index a11e175..4b7ea94 100644 --- a/setup-windows-with-uac.ps1 +++ b/setup-windows-with-uac.ps1 @@ -436,13 +436,26 @@ try { # $global:LASTEXITCODE is cleared first because an exe that cannot start at # all throws without setting one, and the stale 0 from the previous native # command would otherwise read as success. + # + # DRAIN THE OUTPUT BEFORE READING $LASTEXITCODE. `... | Select-Object -First 1` + # was the original spelling here, and it is a trap: Select-Object stops the + # pipeline the instant it has its one object, and that is when ssh.exe has + # WRITTEN its banner - not when it has exited. PowerShell then tears the + # pipeline down, kills the still-running process, and reports -1. Measured on a + # healthy binary on an x64 box: 24 of 25 runs reported exit -1 through + # Select-Object, and 25 of 25 reported 0 when the output was collected first. + # + # So this check was deleting a perfectly good ssh.exe on almost every run, on + # every architecture, and the resulting fallback to the in-box client - the + # ~17MB/s one this whole step exists to avoid - looked like a property of the + # release rather than a bug in the test. Collect, THEN take the first line. $SshExe = Join-Path $RsyncDir 'ssh.exe' if ($WantSsh -and (Test-Path $SshExe)) { $prevEap = $ErrorActionPreference $ErrorActionPreference = 'Continue' $global:LASTEXITCODE = $null $sshVer = $null - try { $sshVer = (& $SshExe -V 2>&1 | Select-Object -First 1) } catch { } + try { $sshOut = (& $SshExe -V 2>&1); $sshVer = $sshOut | Select-Object -First 1 } catch { } finally { $ErrorActionPreference = $prevEap } if ($LASTEXITCODE -eq 0) { Write-Host " ssh.exe runs: $sshVer"