Bake in the enrolment token, and install Chrome when missing

Setup asked for a token that is the same on every machine - a token is reusable
and enrols any number of browsers - so it is a default in both scripts now and
step 7 is a plain yes/no. It is a modest secret: it lets someone enrol their own
browser into the org and nothing more, and rotating it is a console click and a
one-line change.

Both scripts also install Chrome if it is absent, from Google's enterprise MSI
and the official .deb, since enrolling a browser that is not there writes a
token nothing reads. Chromium is refused rather than substituted: no cloud
management, so it could never be enrolled - and on ARM, where Chrome is not
published at all, the Linux script says so instead of installing something that
cannot do the job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-08-31 16:18:20 -05:00
co-authored by Claude Opus 5
parent 35dd15d584
commit 389c714caf
5 changed files with 104 additions and 28 deletions
+45 -3
View File
@@ -21,8 +21,14 @@
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ParameterSetName = 'Enrol')]
[string]$Token,
# 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.
@@ -34,7 +40,10 @@ param(
# 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
[switch]$NoElevate,
# Fail rather than installing Chrome when it is missing.
[switch]$SkipChromeInstall
)
$ErrorActionPreference = 'Stop'
@@ -52,6 +61,7 @@ if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole(
# 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)
@@ -63,6 +73,38 @@ if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole(
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).'
Invoke-WebRequest -Uri 'https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi' `
-OutFile $msi -UseBasicParsing
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 }
+32 -4
View File
@@ -5,13 +5,21 @@
# from a file under /etc at startup, so this needs root, and it applies to every
# user on the machine.
#
# sudo scripts/enroll-chrome-management.sh <token>
# sudo scripts/enroll-chrome-management.sh # the built-in token
# sudo scripts/enroll-chrome-management.sh <token> # a different one
# sudo scripts/enroll-chrome-management.sh --remove
#
# Chrome only — Chromium builds have no cloud-management support, so a Chromium
# install cannot be enrolled and stays subject to the off-store restriction.
set -euo pipefail
# Defaulted so setup is hands-off. A token is reusable and enrols any number of
# browsers, which is what makes a baked-in default work. It is a modest secret:
# anyone holding it can enrol *their* browser into the org — our policies and a
# row in the console — and nothing more. Rotate it in Devices > Chrome > Managed
# browsers and change it here.
DEFAULT_TOKEN=ce4fa6cd-e940-45f6-9086-85075d6cff34
DIR=/etc/opt/chrome/policies/enrollment
TOKEN_FILE="$DIR/CloudManagementEnrollmentToken"
@@ -19,7 +27,6 @@ info() { printf ' %s\n' "$1"; }
fail() { printf '\n\033[1;31mERROR: %s\033[0m\n' "$1" >&2; exit 1; }
[ "$(id -u)" -eq 0 ] || fail "run this with sudo — the token lives under /etc"
[ $# -ge 1 ] || fail "usage: $0 <enrollment-token> | --remove"
if [ "$1" = "--remove" ]; then
rm -f "$TOKEN_FILE" "$DIR/CloudManagementEnrollmentOptions"
@@ -30,12 +37,33 @@ if [ "$1" = "--remove" ]; then
exit 0
fi
[ -d /etc/opt/chrome ] || fail "no /etc/opt/chrome — this is for Google Chrome, not Chromium"
TOKEN="${1:-$DEFAULT_TOKEN}"
# Enrolling a browser that is not installed would write a token nothing reads.
# Chromium is not a substitute: it has no cloud-management support, so it can
# never be enrolled, and Google publishes Chrome for x86_64 only — on a Pi there
# is no version of this that works, and saying so beats installing something
# that cannot do the job.
if ! command -v google-chrome >/dev/null 2>&1 && [ ! -x /opt/google/chrome/chrome ]; then
ARCH=$(uname -m)
[ "$ARCH" = "x86_64" ] || fail "Google Chrome is not published for $ARCH. Chromium cannot be enrolled — this machine cannot run the extension."
command -v apt-get >/dev/null || fail "Chrome is missing and this is not an apt system — install Google Chrome, then re-run"
info "Google Chrome is not installed — fetching it from Google (about 120 MB)"
DEB=$(mktemp --suffix=.deb)
curl -fsSL -o "$DEB" https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
|| fail "could not download Chrome"
# apt rather than dpkg: it pulls the dependencies a bare dpkg -i would leave
# half-configured.
DEBIAN_FRONTEND=noninteractive apt-get install -y "$DEB" >/dev/null || fail "Chrome install failed"
rm -f "$DEB"
info "Chrome installed"
fi
mkdir -p "$DIR"
# The file must contain the token and nothing else: no quotes, no newline noise,
# no filename extension. Chrome reads it verbatim.
printf '%s' "$1" > "$TOKEN_FILE"
printf '%s' "$TOKEN" > "$TOKEN_FILE"
chmod 644 "$TOKEN_FILE"
info "wrote $TOKEN_FILE"