Files
autofirmer-expanded/scripts/install-extension-policy.ps1
T
Brandon LiandClaude Opus 5 8f00d8c547 Find Edge and Chrome where they actually install
Test-BrowserInstalled probed App Paths under HKLM:\SOFTWARE only. Edge and
Chrome ship 32-bit installers, so on a 64-bit box their App Paths entries live
under WOW6432Node and a 64-bit PowerShell never saw them — the browser was
skipped, silently, and -Browser All configured nothing for it.

Checks the install directories first now, with App Paths (both registry views)
as a backstop, and prints which browsers it is configuring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 13:17:46 -05:00

180 lines
7.0 KiB
PowerShell

<#
.SYNOPSIS
Install the AutoFirmer extension from our own update server, via policy.
.DESCRIPTION
Chrome will not side-load a CRX on Windows — dropping one on
chrome://extensions is refused — and it will not update an unpacked folder.
The supported way to run an extension you host yourself is a policy:
ExtensionInstallForcelist names the extension id and the update manifest, and
from then on the browser installs it, keeps it updated on its own schedule,
and re-installs it if it goes missing.
Edge and Brave are Chromium too: same CRX, same policy, same update
manifest, only a different registry vendor key. By default this configures
every one of the three that is actually installed.
Defaults to the current user's policy hive, which needs no elevation.
-Scope Machine writes to HKLM instead, for every account on the box; that
one does need an elevated shell. Where both exist, HKLM wins.
After this runs:
* Remove the unpacked copy from chrome://extensions. It has the same id
now, and the browser will not load both.
* Restart the browser, or open chrome://policy and press "Reload policies".
* A force-installed extension cannot be disabled or removed from the
extensions page. Re-run with -Remove to take it off.
.EXAMPLE
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-extension-policy.ps1
.EXAMPLE
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-extension-policy.ps1 -Browser Edge -Scope Machine
#>
[CmdletBinding()]
param(
[ValidateSet('All', 'Chrome', 'Edge', 'Brave')]
[string]$Browser = 'All',
[ValidateSet('User', 'Machine')]
[string]$Scope = 'User',
# Derived from the signing key on the droplet. It only changes if the key does.
[string]$Id = 'lkjbblagjjbldlkeaaffhiopdlhniaan',
[string]$UpdateUrl = 'https://ext.juicerroom.com/updates.xml',
[switch]$Remove
)
$ErrorActionPreference = 'Stop'
# Vendor key under SOFTWARE\Policies, and the executable each one registers.
$vendors = @{
Chrome = 'Google\Chrome'
Edge = 'Microsoft\Edge'
Brave = 'BraveSoftware\Brave'
}
$exes = @{
Chrome = 'chrome.exe'
Edge = 'msedge.exe'
Brave = 'brave.exe'
}
# Where each one actually lands. Edge and Chrome are 32-bit installers on a
# 64-bit box, which is also why the registry probe below has to look inside
# WOW6432Node: a 64-bit PowerShell reading HKLM:\SOFTWARE sees the 64-bit view
# and misses everything a 32-bit installer wrote.
$installPaths = @{
Chrome = @("$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
"$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe")
Edge = @("${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe",
"$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe")
Brave = @("$env:ProgramFiles\BraveSoftware\Brave-Browser\Application\brave.exe",
"${env:ProgramFiles(x86)}\BraveSoftware\Brave-Browser\Application\brave.exe",
"$env:LOCALAPPDATA\BraveSoftware\Brave-Browser\Application\brave.exe")
}
function Test-BrowserInstalled([string]$Name) {
foreach ($exe in $installPaths[$Name]) {
if ($exe -and (Test-Path $exe)) { return $true }
}
# App Paths as a backstop, for an install somewhere unusual.
$keys = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\$($exes[$Name])",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\$($exes[$Name])",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\$($exes[$Name])"
)
foreach ($key in $keys) {
if (Test-Path $key) { return $true }
}
return $false
}
$hive = if ($Scope -eq 'Machine') { 'HKLM:' } else { 'HKCU:' }
if ($Scope -eq 'Machine') {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'Run this from an elevated PowerShell for -Scope Machine, or drop the flag to install it for this user only.'
}
}
$targets = if ($Browser -eq 'All') {
@('Chrome', 'Edge', 'Brave') | Where-Object { Test-BrowserInstalled $_ }
} else {
@($Browser)
}
if (-not $targets) {
throw 'Found no Chrome, Edge or Brave installation. Name one with -Browser to configure it regardless of what this can see.'
}
Write-Host "Configuring: $($targets -join ', ')"
$entry = "$Id;$UpdateUrl"
foreach ($target in $targets) {
$key = "$hive\SOFTWARE\Policies\$($vendors[$target])\ExtensionInstallForcelist"
if ($Remove) {
if (-not (Test-Path $key)) {
Write-Host "$target : nothing to remove — no $Scope forcelist."
continue
}
$props = Get-ItemProperty $key
$found = $false
foreach ($name in ($props.PSObject.Properties.Name | Where-Object { $_ -notlike 'PS*' })) {
if ($props.$name -like "$Id;*") {
Remove-ItemProperty -Path $key -Name $name
Write-Host "$target : removed entry $name ($($props.$name))"
$found = $true
}
}
if (-not $found) { Write-Host "$target : nothing to remove — $Id was not in the $Scope forcelist." }
continue
}
if (-not (Test-Path $key)) { New-Item -Path $key -Force | Out-Null }
# The forcelist is a list expressed as numbered values, so adding to it means
# finding a free number rather than overwriting whatever is already there —
# another extension may well be in the list.
$props = Get-ItemProperty $key
$existing = $props.PSObject.Properties |
Where-Object { $_.Name -notlike 'PS*' -and $_.Value -like "$Id;*" }
if ($existing) {
Set-ItemProperty -Path $key -Name $existing.Name -Value $entry
Write-Host "$target : updated $Scope forcelist entry $($existing.Name)"
} else {
$used = $props.PSObject.Properties.Name |
Where-Object { $_ -match '^\d+$' } | ForEach-Object { [int]$_ }
$slot = if ($used) { (($used | Measure-Object -Maximum).Maximum + 1) } else { 1 }
New-ItemProperty -Path $key -Name "$slot" -Value $entry -PropertyType String -Force | Out-Null
Write-Host "$target : added $Scope forcelist entry $slot"
}
Write-Host " $key"
}
if ($Remove) {
Write-Host "`nRestart the browser to let the change take effect."
exit 0
}
Write-Host @"
$entry
Next:
1. Remove the unpacked copy from chrome://extensions (same id — both cannot load).
2. Restart the browser, or open its chrome://policy page and press "Reload policies".
3. The card should read "Installed by enterprise policy".
From here the browser updates it on its own. The extension also asks for a check
as soon as the dashboard reports a newer version, so a release normally lands in
seconds rather than waiting out the browser's own cycle.
"@