From ae96e2071f55839eb6e06670ad815360bc75b642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Brandtz=C3=A6g?= Date: Tue, 28 Jul 2026 10:49:54 +0200 Subject: [PATCH 1/2] windows: fix first-run bootstrap from git bash and pwsh 7 (#291, #292) Both failures only hit the very first run, since stage1 short-circuits to the cached stage2 afterwards, and both come from the bootstrap inheriting a hostile environment: git bash and pwsh run gg.cmd through cmd.exe, so the batch branch executes with the parent shell's PATH and PSModulePath. stage2.ps1: Get-FileHash lives in a script module, and PowerShell 7 prepends its Core-only copy of Microsoft.PowerShell.Utility to PSModulePath. A child Windows PowerShell then loads that one, keeps the binary cmdlets from the snap-in, and loses the script functions - so Invoke-WebRequest works while Get-FileHash is "not recognized". Hash through .NET instead, which needs no module loading. stage1.bat: bare tar resolves to Git's GNU tar ahead of System32, which shells out to a separate gzip and reads "C:" as a remote host spec. Pin Windows' own bsdtar. A failed unpack also fell through to powershell -file on a stage2.ps1 that was never written, so the real error was buried under a confusing one - check the exit code and that stage2 actually landed. Verified on Windows Server 2022 with cold caches: cmd, pwsh 7, git bash and 32-bit cmd with GNU tar first on PATH all bootstrap, and an unwritable cache dir now reports the unpack failure instead of the PowerShell -file error. --- src/stage1/stage1.bat | 20 +++++++++++++++++++- src/stage2/stage2.ps1 | 15 ++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/stage1/stage1.bat b/src/stage1/stage1.bat index ec9e2109..9853251b 100644 --- a/src/stage1/stage1.bat +++ b/src/stage1/stage1.bat @@ -18,8 +18,26 @@ ) if not exist "%GG_CACHE_DIR%" mkdir "%GG_CACHE_DIR%" powershell -c "sc m2 ([byte[]](gc '%0' -Encoding Byte | select -Skip AAAA)) -Encoding Byte" - tar -zxf m2 -C "%GG_CACHE_DIR%" + : Bare "tar" picks up Git's GNU tar from PATH in Git Bash and on CI runners, + : which shells out to a separate gzip and reads "C:" as a remote host (#291). + : Sysnative first so a 32-bit cmd gets the native bsdtar, not SysWOW64's. + set "GG_TAR=tar" + if exist "%SystemRoot%\Sysnative\tar.exe" set "GG_TAR=%SystemRoot%\Sysnative\tar.exe" + if exist "%SystemRoot%\System32\tar.exe" set "GG_TAR=%SystemRoot%\System32\tar.exe" + "%GG_TAR%" -zxf m2 -C "%GG_CACHE_DIR%" + set "GG_UNTAR_ERR=%errorlevel%" del m2 + set "GG_TAR=" + if not "%GG_UNTAR_ERR%"=="0" ( + echo gg: failed to unpack into "%GG_CACHE_DIR%" ^(tar exit %GG_UNTAR_ERR%^) + exit /b %GG_UNTAR_ERR% + ) + : A zero exit does not prove stage2 landed, and handing PowerShell a missing + : -file is the confusing error #291 actually reported. + if not exist "%GG_CACHE_DIR%\gg-VERVER\stage2.ps1" ( + echo gg: unpack produced no stage2.ps1 in "%GG_CACHE_DIR%" + exit /b 1 + ) powershell -executionpolicy bypass -file "%GG_CACHE_DIR%\gg-VERVER\stage2.ps1" %* exit /b %errorlevel% BATCH diff --git a/src/stage2/stage2.ps1 b/src/stage2/stage2.ps1 index 89d3e2c9..b688c487 100644 --- a/src/stage2/stage2.ps1 +++ b/src/stage2/stage2.ps1 @@ -97,7 +97,20 @@ if ($hash) # stage3, so verify ourselves. Catches a tampered blob as well as # the boring case: a truncated-but-non-empty download. -ne is # case-insensitive, so uppercase vs lowercase hex is fine. - $actualHash = (Get-FileHash $tempFile -Algorithm SHA512).Hash + # Hash via .NET, not Get-FileHash: that one lives in a script module + # which PowerShell 7 shadows with its Core-only copy, so a child + # Windows PowerShell can't load it and the cmdlet vanishes (#292). + $sha512 = [System.Security.Cryptography.SHA512]::Create() + $stream = [System.IO.File]::OpenRead($tempFile) + try + { + $actualHash = [BitConverter]::ToString($sha512.ComputeHash($stream)).Replace('-', '') + } + finally + { + $stream.Dispose() + $sha512.Dispose() + } if ($actualHash -ne $hash) { Write-Host "Hash mismatch: expected $hash, got $actualHash" From 887cd368a5dedbcd9aca09d734e0b5ceae1072e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Brandtz=C3=A6g?= Date: Tue, 28 Jul 2026 11:45:38 +0200 Subject: [PATCH 2/2] stage1: trim the unpack guard back down stage1 is what you see when you cat gg.cmd, and every byte ships in it, so the last commit was far too fat there: 1631 -> 2591 bytes for two real fixes. The Sysnative probe goes - SysWOW64 has its own bsdtar, so a 32-bit cmd already resolved a working tar through the plain System32 check and the extra line never did anything. The exit-code guard goes too: we only get here when stage2.ps1 was missing, so checking that it exists afterwards says the same thing in one test and drops GG_UNTAR_ERR with it. Comments cut to the one trap worth naming, since the reasoning lives in the commit log rather than in every copy of gg.cmd. Down to 1945 bytes, +314 over the original instead of +960. Re-verified on Windows Server 2022 with cold caches: cmd, pwsh 7, git bash, and 32-bit cmd with GNU tar first on PATH, plus an unwritable cache dir still reporting the failure. --- src/stage1/stage1.bat | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/stage1/stage1.bat b/src/stage1/stage1.bat index 9853251b..55dfd105 100644 --- a/src/stage1/stage1.bat +++ b/src/stage1/stage1.bat @@ -18,24 +18,13 @@ ) if not exist "%GG_CACHE_DIR%" mkdir "%GG_CACHE_DIR%" powershell -c "sc m2 ([byte[]](gc '%0' -Encoding Byte | select -Skip AAAA)) -Encoding Byte" - : Bare "tar" picks up Git's GNU tar from PATH in Git Bash and on CI runners, - : which shells out to a separate gzip and reads "C:" as a remote host (#291). - : Sysnative first so a 32-bit cmd gets the native bsdtar, not SysWOW64's. - set "GG_TAR=tar" - if exist "%SystemRoot%\Sysnative\tar.exe" set "GG_TAR=%SystemRoot%\Sysnative\tar.exe" - if exist "%SystemRoot%\System32\tar.exe" set "GG_TAR=%SystemRoot%\System32\tar.exe" + : Git's GNU tar shadows tar on PATH and cannot take "C:" paths (#291) + set "GG_TAR=%SystemRoot%\System32\tar.exe" + if not exist "%GG_TAR%" set "GG_TAR=tar" "%GG_TAR%" -zxf m2 -C "%GG_CACHE_DIR%" - set "GG_UNTAR_ERR=%errorlevel%" del m2 - set "GG_TAR=" - if not "%GG_UNTAR_ERR%"=="0" ( - echo gg: failed to unpack into "%GG_CACHE_DIR%" ^(tar exit %GG_UNTAR_ERR%^) - exit /b %GG_UNTAR_ERR% - ) - : A zero exit does not prove stage2 landed, and handing PowerShell a missing - : -file is the confusing error #291 actually reported. if not exist "%GG_CACHE_DIR%\gg-VERVER\stage2.ps1" ( - echo gg: unpack produced no stage2.ps1 in "%GG_CACHE_DIR%" + echo gg: could not unpack into "%GG_CACHE_DIR%" exit /b 1 ) powershell -executionpolicy bypass -file "%GG_CACHE_DIR%\gg-VERVER\stage2.ps1" %*