diff --git a/scripts/configure-chrome-defaults.ps1 b/scripts/configure-chrome-defaults.ps1
new file mode 100644
index 0000000..69cc68a
--- /dev/null
+++ b/scripts/configure-chrome-defaults.ps1
@@ -0,0 +1,126 @@
+<#
+.SYNOPSIS
+ Make Chrome the default browser and pin it to the taskbar, as far as Windows
+ permits that from a script.
+
+.DESCRIPTION
+ Which is not very far, and it is worth knowing why before reading the code.
+
+ The default-browser association lives under UserChoice, whose ProgId is
+ validated against a hash Windows will only generate from its own Settings
+ dialog — and since February 2024 a kernel driver (UCPD.sys) blocks writes to
+ that key regardless. Tools that forge the hash exist; they are working around
+ a security control and Microsoft breaks them on a schedule.
+
+ Taskbar pinning lost its shell verb years ago for the same reason: to stop
+ installers helping themselves to the taskbar.
+
+ What is supported is policy, and policy applies when a user profile is
+ created — so both settings here take effect for the NEXT new user to sign in,
+ not for the profile running this. For the current profile the honest answers
+ are a desktop shortcut, which this makes, and two clicks in Settings, which
+ this opens for you.
+
+ Needs elevation: both policies are machine-wide.
+
+.EXAMPLE
+ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\configure-chrome-defaults.ps1
+#>
+[CmdletBinding()]
+param(
+ # Skip opening the Settings page at the end.
+ [switch]$Quiet
+)
+
+$ErrorActionPreference = 'Stop'
+
+$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
+if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole(
+ [Security.Principal.WindowsBuiltInRole]::Administrator)) {
+ throw 'Run this elevated — both policies are machine-wide.'
+}
+
+$chrome = @("$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
+ "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
+ "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe") |
+ Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
+if (-not $chrome) { throw 'Google Chrome is not installed.' }
+
+$dir = Join-Path $env:ProgramData 'AutoFirmer'
+New-Item -ItemType Directory -Path $dir -Force | Out-Null
+
+# ── Default browser ─────────────────────────────────────────────────────────
+# Only the associations named here are overridden; everything absent from the
+# file keeps whatever Windows would have chosen.
+$assoc = Join-Path $dir 'default-associations.xml'
+@'
+
+
+
+
+
+
+
+'@ | Set-Content -Path $assoc -Encoding UTF8
+
+$sys = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System'
+if (-not (Test-Path $sys)) { New-Item -Path $sys -Force | Out-Null }
+New-ItemProperty -Path $sys -Name 'DefaultAssociationsConfiguration' `
+ -Value $assoc -PropertyType String -Force | Out-Null
+Write-Host "Default-browser policy written: $assoc"
+
+# ── Taskbar pin ─────────────────────────────────────────────────────────────
+# A taskbar layout with no start layout beside it: the collection below adds the
+# pin, and PinListPlacement="Replace" would wipe the user's own pins, so it is
+# deliberately absent — ours is appended to whatever is already there.
+$lnk = Join-Path $dir 'Google Chrome.lnk'
+$shell = New-Object -ComObject WScript.Shell
+$shortcut = $shell.CreateShortcut($lnk)
+$shortcut.TargetPath = $chrome
+$shortcut.Save()
+
+$taskbar = Join-Path $dir 'TaskbarLayoutModification.xml'
+@"
+
+
+
+
+
+
+
+
+
+
+"@ | Set-Content -Path $taskbar -Encoding UTF8
+
+$explorer = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer'
+if (-not (Test-Path $explorer)) { New-Item -Path $explorer -Force | Out-Null }
+New-ItemProperty -Path $explorer -Name 'StartLayoutFile' -Value $taskbar -PropertyType ExpandString -Force | Out-Null
+# 0 so the Start menu itself stays the user's to rearrange; only the taskbar
+# collection above is applied.
+New-ItemProperty -Path $explorer -Name 'LockedStartLayout' -Value 0 -PropertyType DWord -Force | Out-Null
+Write-Host "Taskbar pin policy written: $taskbar"
+
+# ── The current profile ─────────────────────────────────────────────────────
+$desktop = [Environment]::GetFolderPath('CommonDesktopDirectory')
+$deskLnk = $shell.CreateShortcut((Join-Path $desktop 'Google Chrome.lnk'))
+$deskLnk.TargetPath = $chrome
+$deskLnk.Save()
+Write-Host 'Desktop shortcut created.'
+
+Write-Host @'
+
+Both policies apply when a user profile is first created, so this profile is
+unchanged. For this one:
+
+ * Default browser: the Settings page is opening now - pick Google Chrome
+ under "Web browser". Windows allows no other way.
+ * Taskbar: right-click Chrome and choose "Pin to taskbar".
+'@
+
+if (-not $Quiet) { Start-Process 'ms-settings:defaultapps' }
diff --git a/scripts/enroll-chrome-management.ps1 b/scripts/enroll-chrome-management.ps1
index b14b191..34c5836 100644
--- a/scripts/enroll-chrome-management.ps1
+++ b/scripts/enroll-chrome-management.ps1
@@ -43,7 +43,10 @@ param(
[switch]$NoElevate,
# Fail rather than installing Chrome when it is missing.
- [switch]$SkipChromeInstall
+ [switch]$SkipChromeInstall,
+
+ # Skip the default-browser and taskbar-pin policies.
+ [switch]$NoDesktopConfig
)
$ErrorActionPreference = 'Stop'
@@ -62,6 +65,7 @@ if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole(
Write-Host 'Asking for administrator rights — the enrolment token is machine-level.'
$psArgs = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', "`"$PSCommandPath`"", '-NoElevate')
if ($SkipChromeInstall) { $psArgs += '-SkipChromeInstall' }
+ if ($NoDesktopConfig) { $psArgs += '-NoDesktopConfig' }
if ($Remove) { $psArgs += '-Remove' }
else {
$psArgs += @('-Token', $Token)
@@ -177,6 +181,19 @@ if ($Mandatory) {
Write-Host 'Enrolment is now mandatory — Chrome will not start unenrolled.'
}
+# Already elevated here, so folding this in keeps the whole install to one UAC
+# prompt rather than two.
+if (-not $NoDesktopConfig) {
+ $defaults = Join-Path (Split-Path -Parent $PSCommandPath) 'configure-chrome-defaults.ps1'
+ if (Test-Path $defaults) {
+ try {
+ & $defaults -Quiet
+ } catch {
+ Write-Warning "Default-browser and taskbar policies skipped: $($_.Exception.Message)"
+ }
+ }
+}
+
Write-Host @"
Next: