Windows: make in-app update work without Get-FileHash - #198
Merged
Conversation
The Windows installer aborted with "'Get-FileHash' is not recognized" during in-app self-update, while the same update run from cmd succeeded (issue #174). Two users hit it independently on v0.2.1 -> v0.2.2. Two independent causes, both fixed here. Get-FileHash and Expand-Archive are not language built-ins: they live in Microsoft.PowerShell.Utility and Microsoft.PowerShell.Archive, added in PowerShell 4.0 and 5.0. They are absent under a 2.0 engine and whenever a trimmed image or an overridden PSModulePath keeps those modules from loading. install.ps1 now hashes and unzips through .NET, which needs no module, and keeps the cmdlets plus certutil only as fallbacks. Checksum verification stays mandatory: a checksum that cannot be computed is a failure, never a skip, since it is the only thing standing between a tampered download and an executable the user then runs. The updater also spawned a bare "powershell.exe", resolved against the user's PATH rather than ours -- which is why the same script behaved differently from cmd. It now names the system copy under %SystemRoot%, falling back to the bare name when that variable is unset. Extraction walks zip entries rather than calling ExtractToDirectory, which throws on .NET Framework when the destination already exists, as the staging dir always does. Verified against PowerShell 7.7: hashes match shasum byte for byte on the normal path and with Get-FileHash removed, and a full verify-and-extract run succeeds with both cmdlets absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #174.
What was broken
In-app update failed on Windows with
install script exited with code 1:The same update run from
cmdworked, which is the detail that points at the second cause below.Cause 1: the installer assumed cmdlets that are not always there
Get-FileHashandExpand-Archiveare not language built-ins. They ship inMicrosoft.PowerShell.UtilityandMicrosoft.PowerShell.Archive, added in PowerShell 4.0 and 5.0 respectively, so they are missing under a 2.0 engine and absent whenever a trimmed image or an overriddenPSModulePathstops those modules loading.install.ps1now hashes and unzips through .NET, which requires no module and is available wherever PowerShell runs at all, keeping the cmdlets andcertutilas fallbacks.Expand-Archiveis fixed alongside deliberately: it fails on exactly the same machines, so fixing only the hash would have moved the error one step later rather than removing it.Cause 2: a bare
powershell.exeresolved against the user's PATHThe updater spawned
powershell.exeby name, so which shell actually ran was decided by the user'sPATH— a trimmed, relocated or 2.0-engine copy there loses the modules above. That is why the same script behaved differently when launched fromcmd, where the name resolves to the system copy.It now names the system copy under
%SystemRoot%, falling back to the bare name when that variable is unset.On the open question in the issue
The issue asked whether a failed checksum step should abort or warn and continue. This aborts. The checksum is the only thing standing between a tampered or truncated download and an executable the user then runs, so a checksum that cannot be computed is a failure, never a skip. The fix is to be able to compute it three ways, not to make verifying optional.
Verification
install.ps1was executed against PowerShell 7.7, not just read:shasum -a 256byte for byte, on a 3 MB binary and a text fileGet-FileHashremoved (the issue Windows: in-app update fails under PowerShell (Get-FileHash not recognized), works in cmd #174 machine), hashing still returns the correct digestGet-FileHashandExpand-Archiveboth removed, a full verify-and-extract run succeedscertutil.exe is not recognizedOn the TypeScript side: 14/14 in
src/update/, and typecheck clean. The three new tests were confirmed to fail against the old bare-name code before passing against the new code.Two suite failures (
fs-glob-real,send-message-concurrency) reproduce on unmodifiedmainand are unrelated to this change.What is not covered
The end-to-end run happened on PowerShell 7 on macOS. The .NET APIs used are the ones present on Windows PowerShell 5.1, and the
ExtractToDirectorybehaviour that differs there is precisely why extraction walks entries — but a real 5.1 box, ideally one of the two reporters', is still worth a confirmation before release.🤖 Generated with Claude Code