Download Chrome with curl.exe, then BITS, then Invoke-WebRequest

Invoke-WebRequest pushes every buffer through the pipeline and is the slowest
of the three by a wide margin; curl.exe ships in System32 on every supported
Windows and resumes a partial file with -C -, which matters on a 166 MB
download that gets interrupted. Also checks the finished file against
Content-Length, because a truncated MSI fails in msiexec with an error that
says nothing about why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-08-31 17:16:45 -05:00
co-authored by Claude Opus 5
parent f52efd73c6
commit 61d6305ded
+42 -10
View File
@@ -92,20 +92,52 @@ if (-not $Remove -and -not (Find-Chrome)) {
# Google's own enterprise MSI, which installs machine-wide and silently. We
# are already elevated by this point, so this needs no second prompt.
$url = 'https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi'
$msi = Join-Path $env:TEMP 'googlechromestandaloneenterprise64.msi'
Write-Host 'Google Chrome is not installed — downloading it from Google (about 160 MB).'
# Progress off for the duration. Windows PowerShell re-renders the progress
# bar on every buffer, which on a 160 MB file costs far more time than the
# transfer itself — minutes instead of seconds, looking for all the world
# like a stalled download.
$previousProgress = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
# Three ways down, fastest first. Invoke-WebRequest is last for a reason: it
# pushes every buffer through the PowerShell pipeline, and on Windows
# PowerShell it also re-renders the progress bar each time, which on a file
# this size costs more than the transfer itself — minutes rather than
# seconds, and indistinguishable from a stall while you watch it crawl.
$curl = Join-Path $env:SystemRoot 'System32\curl.exe'
if (Test-Path $curl) {
# -C - resumes a part-downloaded file rather than starting the 160 MB
# again, which matters when this is retried after an interruption.
& $curl -fL --retry 3 --retry-delay 2 -C - -o $msi $url
if ($LASTEXITCODE -ne 0) { Remove-Item $msi -ErrorAction SilentlyContinue }
}
if (-not (Test-Path $msi) -and (Get-Command Start-BitsTransfer -ErrorAction SilentlyContinue)) {
try {
Start-BitsTransfer -Source $url -Destination $msi -Priority Foreground -ErrorAction Stop
} catch {
Write-Host "BITS transfer failed ($($_.Exception.Message)); falling back."
}
}
if (-not (Test-Path $msi)) {
$previousProgress = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
try {
Invoke-WebRequest -Uri $url -OutFile $msi -UseBasicParsing
} finally {
$ProgressPreference = $previousProgress
}
}
# A truncated download installs as a corrupt package, with an msiexec error
# that says nothing about the real cause.
$expected = 0
try {
Invoke-WebRequest -Uri 'https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi' `
-OutFile $msi -UseBasicParsing
} finally {
$ProgressPreference = $previousProgress
$expected = [int64](Invoke-WebRequest -Uri $url -Method Head -UseBasicParsing).Headers['Content-Length']
} catch { }
$got = (Get-Item $msi -ErrorAction SilentlyContinue).Length
if (-not $got) { throw 'Chrome download produced no file.' }
if ($expected -gt 0 -and $got -ne $expected) {
Remove-Item $msi -ErrorAction SilentlyContinue
throw "Chrome download is incomplete ($got of $expected bytes). Re-run to resume."
}
Write-Host 'Installing Chrome...'