From 61d6305dedc9ba58676c241c2148942b4734d0cd Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Mon, 31 Aug 2026 17:16:45 -0500 Subject: [PATCH] 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 --- scripts/enroll-chrome-management.ps1 | 52 ++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/scripts/enroll-chrome-management.ps1 b/scripts/enroll-chrome-management.ps1 index 4bb7a5e..b14b191 100644 --- a/scripts/enroll-chrome-management.ps1 +++ b/scripts/enroll-chrome-management.ps1 @@ -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...'