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: