Vestibule/scripts/deprovision-kiosk.ps1

246 lines
9.7 KiB
PowerShell

# deprovision-kiosk.ps1 — remove the Vestibule kiosk session from this
# machine (Windows). The inverse of provision-kiosk.ps1, applied in
# reverse order:
#
# 1. AssignedAccess / Shell Launcher kiosk configuration
# 2. Automatic logon registry values (only if they point at the kiosk
# account — an unrelated autologon is never touched)
# 3. The Start Menu kiosk shortcut
# 4. Vestibule's merged policies.json — each directory is reset to its
# pre-Vestibule baseline (the backed-up operator file is reinstalled
# where one exists, Vestibule's generated file is deleted where one
# does not)
# 5. C:\ProgramData\Vestibule
# 6. Optionally the kiosk account and the Program Files install
#
# Exit codes:
# 0 success
# 10 success, reboot required to fully clear kiosk mode
# 2 unsupported platform / not elevated
# 5 removal failure
#
# Usage:
# powershell -ExecutionPolicy Bypass -File deprovision-kiosk.ps1 `
# -RemoveKioskAccount -RemoveInstall
param(
[string]$KioskUser = "VestibuleKiosk",
[string]$InstallRoot = "C:\Program Files\Vestibule",
[switch]$RemoveKioskAccount,
[switch]$RemoveInstall,
[switch]$Quiet,
[switch]$Check,
[switch]$Restart
)
$ErrorActionPreference = "Stop"
$AUMID = "Vestibule.Kiosk"
function Fail([int]$code, [string]$msg) {
Write-Host ""
Write-Host "ERROR: $msg" -ForegroundColor Red
Write-Host " exiting with code $code"
exit $code
}
function Info($msg) { Write-Host $msg }
function Ok($msg) { Write-Host " [ok] $msg" -ForegroundColor Green }
function Step($msg) { Write-Host ""; Write-Host "==> $msg" -ForegroundColor Cyan }
if ($env:OS -ne "Windows_NT") {
Fail 2 "Windows-only; on Linux use scripts/deprovision-kiosk.sh"
}
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
if (-not ([Security.Principal.WindowsPrincipal]$id).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
Fail 2 "must run elevated"
}
$rebootNeeded = $false
# ─── Discover current state ───────────────────────────────────────────
$lnkPath = Join-Path $env:ProgramData "Microsoft\Windows\Start Menu\Programs\Vestibule Kiosk.lnk"
$wl = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
$autologonUser = (Get-ItemProperty $wl -ErrorAction SilentlyContinue).DefaultUserName
$autologonOn = (Get-ItemProperty $wl -ErrorAction SilentlyContinue).AutoAdminLogon -eq "1"
$weslPresent = [bool](Get-CimClass -Namespace "root\standardcimv2\embedded" `
-ClassName "WESL_UserSetting" -ErrorAction SilentlyContinue)
$accountPresent = [bool](Get-LocalUser -Name $KioskUser -ErrorAction SilentlyContinue)
Step "Current state"
Info "kiosk account : $(if ($accountPresent) {'present'} else {'absent'})"
Info "autologon : $(if ($autologonOn) {"on (user: $autologonUser)"} else {'off'})"
Info "Shell Launcher WMI : $(if ($weslPresent) {'present'} else {'absent'})"
Info "kiosk shortcut : $(if (Test-Path $lnkPath) {'present'} else {'absent'})"
if ($Check) {
Info "(check only — no changes made)"
exit 0
}
if (-not $Quiet) {
$answer = Read-Host "Proceed with removal? [y/N]"
if ($answer -notmatch '^[Yy]') { Info "aborted"; exit 0 }
}
# ─── 1. Lockdown removal ──────────────────────────────────────────────
Step "Remove kiosk lockdown"
if ($weslPresent) {
$wesl = [wmiclass]"\\.\root\standardcimv2\embedded:WESL_UserSetting"
$removed = $false
foreach ($call in @(
{ $wesl.RemoveCustomShell($KioskUser) },
{ $wesl.RemoveCustomShell() }
)) {
try {
$r = & $call
if ($r.ReturnValue -eq 0) { $removed = $true; break }
} catch { }
}
if ($removed) { Ok "Shell Launcher custom shell removed for '$KioskUser'" }
else { Info "Shell Launcher: no custom shell to remove (or already clean)" }
}
# AssignedAccess removal: call any Remove* method the bridge exposes on
# this build. Bridge method availability varies by build; on some builds
# the only clean removal is deleting the kiosk account (which orphans and
# neutralizes the config) — handled below, and reported honestly.
try {
$class = Get-CimClass -Namespace "root\cimv2\mdm\dmmap" -ClassName "MDM_AssignedAccess" `
-ErrorAction Stop
$removeMethods = @($class.CimClassMethods | Where-Object { $_.Name -like "Remove*" })
foreach ($m in $removeMethods) {
try {
$instances = @(Get-CimInstance -Namespace "root\cimv2\mdm\dmmap" `
-ClassName "MDM_AssignedAccess" -ErrorAction Stop)
foreach ($inst in $instances) {
$res = Invoke-CimMethod -InputObject $inst -MethodName $m.Name -ErrorAction Stop
if ($res.ReturnValue -eq 0) {
Ok "AssignedAccess cleared via $($m.Name)"
}
}
$rebootNeeded = $true
} catch {
Info "bridge method $($m.Name) present but call failed: $($_.Exception.Message)"
}
}
} catch {
Info "MDM bridge not reachable — AssignedAccess config (if any) is cleared by removing the kiosk account below"
if ($accountPresent) { $rebootNeeded = $true }
}
# ─── 2. Automatic logon ───────────────────────────────────────────────
Step "Remove automatic logon"
if ($autologonOn -and $autologonUser -eq $KioskUser) {
Set-ItemProperty $wl -Name "AutoAdminLogon" -Value "0" -Type String
Remove-ItemProperty $wl -Name "DefaultUserName" -ErrorAction SilentlyContinue
Remove-ItemProperty $wl -Name "DefaultDomainName" -ErrorAction SilentlyContinue
Remove-ItemProperty $wl -Name "DefaultPassword" -ErrorAction SilentlyContinue
Ok "autologon disabled and credentials cleared"
} elseif ($autologonOn) {
Info "autologon is configured for '$autologonUser' (not ours) — left untouched"
} else {
Info "autologon already off"
}
# ─── 3. Shortcut ──────────────────────────────────────────────────────
Step "Remove kiosk shortcut"
if (Test-Path $lnkPath) {
Remove-Item $lnkPath -Force
Ok "removed $lnkPath"
} else {
Info "already absent"
}
# ─── 4. Policies ──────────────────────────────────────────────────────
Step "Reset browser policies to baseline"
# LibreWolf and Firefox share the distribution/policies.json mechanism;
# cover every install location for both browsers. Step-down per
# directory: backup present -> reinstall it; ours -> delete; else leave.
foreach ($browserExe in @(
"${env:ProgramFiles}\LibreWolf\librewolf.exe",
"${env:ProgramFiles(x86)}\LibreWolf\librewolf.exe",
"${env:LOCALAPPDATA}\Programs\LibreWolf\librewolf.exe",
"${env:ProgramFiles}\Mozilla Firefox\firefox.exe",
"${env:ProgramFiles}\Mozilla Firefox ESR\firefox.exe",
"${env:ProgramFiles(x86)}\Mozilla Firefox\firefox.exe",
"${env:ProgramFiles(x86)}\Mozilla Firefox ESR\firefox.exe",
"${env:LOCALAPPDATA}\Mozilla Firefox\firefox.exe")) {
if (-not (Test-Path $browserExe)) { continue }
$distDir = Join-Path (Split-Path -Parent $browserExe) "distribution"
$policiesPath = Join-Path $distDir "policies.json"
$bak = "$policiesPath.vestibule-bak"
if (Test-Path $bak) {
Move-Item $bak $policiesPath -Force
Ok "baseline policies.json reinstalled in $distDir (from vestibule-bak)"
} elseif (Test-Path $policiesPath) {
$content = Get-Content $policiesPath -Raw
if ($content -match "vestibule@vestibule\.kiosk") {
Remove-Item $policiesPath -Force
Ok "Vestibule-generated policies.json deleted in $distDir (no baseline on record)"
} else {
Info "policies.json in $distDir exists but is not ours — left untouched"
}
}
# librewolf.overrides.cfg is LibreWolf-only; harmless no-op for Firefox.
$cfg = Join-Path (Split-Path -Parent $browserExe) "librewolf.overrides.cfg"
if (Test-Path $cfg) { Remove-Item $cfg -Force; Ok "removed librewolf.overrides.cfg" }
}
# ─── 5. ProgramData ───────────────────────────────────────────────────
Step "Remove deployment config"
$pdDir = Join-Path $env:ProgramData "Vestibule"
if (Test-Path $pdDir) {
Remove-Item $pdDir -Recurse -Force
Ok "removed $pdDir"
} else {
Info "already absent"
}
# ─── 6. Optional: account + install tree ──────────────────────────────
if ($RemoveKioskAccount) {
Step "Remove kiosk account"
if ($accountPresent) {
# Ensure the account is not mid-logon (fast user switching) first.
try {
Remove-LocalUser -Name $KioskUser -ErrorAction Stop
Ok "removed local account '$KioskUser'"
$rebootNeeded = $true
} catch {
Fail 5 "could not remove account: $($_.Exception.Message)"
}
} else {
Info "account '$KioskUser' already absent"
}
}
if ($RemoveInstall) {
Step "Remove install tree"
if (Test-Path $InstallRoot) {
Remove-Item $InstallRoot -Recurse -Force
Ok "removed $InstallRoot"
} else {
Info "already absent"
}
}
# ─── Summary ──────────────────────────────────────────────────────────
Step "Deprovisioning complete"
Info "verify in Settings > Accounts > Other users that no kiosk entry remains"
if ($rebootNeeded) {
Info "reboot recommended to fully clear kiosk mode."
if ($Restart) {
shutdown.exe /r /t 10 /c "Vestibule kiosk removal"
exit 0
}
exit 10
}
exit 0