Windows PowerShell re-renders progress on every buffer, so a 120 MB download spends most of its time drawing a bar rather than transferring - minutes for what should be seconds, and indistinguishable from a stall while you watch the byte count crawl. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
160 lines
7.2 KiB
PowerShell
160 lines
7.2 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Enrol this machine's Chrome in Chrome Enterprise Core.
|
|
|
|
.DESCRIPTION
|
|
Chrome refuses to force-install an extension from outside its own web store
|
|
unless the machine counts as enterprise managed. Enrolment is the way to
|
|
qualify without joining a domain: one machine-level token, and from the next
|
|
launch Chrome takes policy from the Google Admin console and the off-store
|
|
restriction lifts. install-extension-policy.ps1 then works as intended.
|
|
|
|
The token comes from the Admin console: Devices > Chrome > Managed browsers >
|
|
the enrollment-token button. One token can enrol any number of machines, and
|
|
it is only used at enrolment.
|
|
|
|
Needs an elevated PowerShell — the token is machine-scoped, and Chrome does
|
|
not read it from the user hive.
|
|
|
|
.EXAMPLE
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\enroll-chrome-management.ps1 -Token abcd1234-...
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
# Defaulted so setup is hands-off. An enrolment token is reusable and enrols
|
|
# any number of browsers, which is what makes a baked-in default work at all.
|
|
# It is a modest secret: anyone holding it can enrol *their* browser into the
|
|
# org, which gets them our policies and a row in the console — it grants no
|
|
# access to the Google account or to anything else. Rotate it in Devices >
|
|
# Chrome > Managed browsers and change it here.
|
|
[Parameter(ParameterSetName = 'Enrol')]
|
|
[string]$Token = 'ce4fa6cd-e940-45f6-9086-85075d6cff34',
|
|
|
|
# Refuse to start Chrome at all if enrolment fails. Sound on a fleet, risky
|
|
# on a trading PC: a revoked token would leave you with no browser.
|
|
[Parameter(ParameterSetName = 'Enrol')]
|
|
[switch]$Mandatory,
|
|
|
|
[Parameter(Mandatory = $true, ParameterSetName = 'Remove')]
|
|
[switch]$Remove,
|
|
|
|
# Fail instead of asking for elevation. Also the loop-breaker: the elevated
|
|
# copy is launched with this set, so it can never spawn another.
|
|
[switch]$NoElevate,
|
|
|
|
# Fail rather than installing Chrome when it is missing.
|
|
[switch]$SkipChromeInstall
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole(
|
|
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
if ($NoElevate) {
|
|
throw 'Run this from an elevated PowerShell — the enrolment token is machine-level.'
|
|
}
|
|
|
|
# Relaunch elevated rather than making the caller do it. setup-windows.bat
|
|
# runs unelevated on purpose — nothing else it does needs admin — so this is
|
|
# the one UAC prompt in the whole install, and it is raised here where the
|
|
# arguments are already parsed and can be passed on intact.
|
|
Write-Host 'Asking for administrator rights — the enrolment token is machine-level.'
|
|
$psArgs = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', "`"$PSCommandPath`"", '-NoElevate')
|
|
if ($SkipChromeInstall) { $psArgs += '-SkipChromeInstall' }
|
|
if ($Remove) { $psArgs += '-Remove' }
|
|
else {
|
|
$psArgs += @('-Token', $Token)
|
|
if ($Mandatory) { $psArgs += '-Mandatory' }
|
|
}
|
|
|
|
$elevated = Start-Process -FilePath (Get-Process -Id $PID).Path -Verb RunAs -Wait -PassThru `
|
|
-ArgumentList $psArgs
|
|
exit $elevated.ExitCode
|
|
}
|
|
|
|
# ── Chrome itself ────────────────────────────────────────────────────────────
|
|
# Enrolling a browser that is not installed would write a token nothing ever
|
|
# reads. Chromium is deliberately not accepted as a substitute: it has no
|
|
# cloud-management support, so it can never be enrolled and would stay subject
|
|
# to the off-store restriction forever.
|
|
function Find-Chrome {
|
|
foreach ($exe in @("$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
|
|
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
|
|
"$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe")) {
|
|
if ($exe -and (Test-Path $exe)) { return $exe }
|
|
}
|
|
return $null
|
|
}
|
|
|
|
if (-not $Remove -and -not (Find-Chrome)) {
|
|
if ($SkipChromeInstall) { throw 'Google Chrome is not installed, and -SkipChromeInstall was given.' }
|
|
|
|
# Google's own enterprise MSI, which installs machine-wide and silently. We
|
|
# are already elevated by this point, so this needs no second prompt.
|
|
$msi = Join-Path $env:TEMP 'googlechromestandaloneenterprise64.msi'
|
|
Write-Host 'Google Chrome is not installed — downloading it from Google (about 120 MB).'
|
|
|
|
# Progress off for the duration. Windows PowerShell re-renders the progress
|
|
# bar on every buffer, which on a 120 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'
|
|
try {
|
|
Invoke-WebRequest -Uri 'https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi' `
|
|
-OutFile $msi -UseBasicParsing
|
|
} finally {
|
|
$ProgressPreference = $previousProgress
|
|
}
|
|
|
|
Write-Host 'Installing Chrome...'
|
|
$msiexec = Start-Process msiexec.exe -Wait -PassThru -ArgumentList @('/i', "`"$msi`"", '/qn', '/norestart')
|
|
Remove-Item $msi -ErrorAction SilentlyContinue
|
|
if ($msiexec.ExitCode -ne 0) { throw "Chrome install failed (msiexec exit $($msiexec.ExitCode))." }
|
|
if (-not (Find-Chrome)) { throw 'Chrome still not found after installing.' }
|
|
Write-Host 'Chrome installed.'
|
|
}
|
|
|
|
$key = 'HKLM:\SOFTWARE\Policies\Google\Chrome'
|
|
if (-not (Test-Path $key)) { New-Item -Path $key -Force | Out-Null }
|
|
|
|
if ($Remove) {
|
|
foreach ($name in 'CloudManagementEnrollmentToken', 'CloudManagementEnrollmentMandatory') {
|
|
if ($null -ne (Get-ItemProperty -Path $key -Name $name -ErrorAction SilentlyContinue)) {
|
|
Remove-ItemProperty -Path $key -Name $name
|
|
Write-Host "Removed $name"
|
|
}
|
|
}
|
|
Write-Host @"
|
|
|
|
Restart Chrome. Note this stops Chrome asking for policy, but does not undo the
|
|
enrolment on Google's side — delete the browser from Devices > Chrome > Managed
|
|
browsers as well, or it keeps a seat and shows as stale.
|
|
"@
|
|
exit 0
|
|
}
|
|
|
|
New-ItemProperty -Path $key -Name 'CloudManagementEnrollmentToken' `
|
|
-Value $Token -PropertyType String -Force | Out-Null
|
|
Write-Host "Wrote CloudManagementEnrollmentToken to $key"
|
|
|
|
if ($Mandatory) {
|
|
New-ItemProperty -Path $key -Name 'CloudManagementEnrollmentMandatory' `
|
|
-Value 1 -PropertyType DWord -Force | Out-Null
|
|
Write-Host 'Enrolment is now mandatory — Chrome will not start unenrolled.'
|
|
}
|
|
|
|
Write-Host @"
|
|
|
|
Next:
|
|
1. Restart Chrome completely (check Task Manager for stray chrome.exe).
|
|
2. chrome://policy should name the Admin console as a policy source, and
|
|
chrome://management should say the browser is managed.
|
|
3. The machine appears under Devices > Chrome > Managed browsers.
|
|
4. Then install the extension, which is no longer blocked:
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-extension-policy.ps1 -Browser Chrome
|
|
Or skip that and force-install it from the Admin console instead, which
|
|
covers every enrolled machine at once.
|
|
"@
|