Merge pull request 'fix(kiosk): pivot to Explorer + policy lockdown (WebView2 blank as SL shell)' (#11) from fix/kiosk-explorer-lockdown into main
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (push) Successful in 9m41s
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (push) Successful in 9m41s
This commit was merged in pull request #11.
This commit is contained in:
@@ -101,11 +101,10 @@
|
||||
</LocalAccounts>
|
||||
</UserAccounts>
|
||||
<!--
|
||||
AutoLogon: logs in as sm-bootstrap exactly once so that Shell Launcher v2
|
||||
(configured by Configure-Kiosk.ps1, run from SetupComplete.cmd) can launch
|
||||
the Welcome wizard as the sm-bootstrap session shell. After the wizard
|
||||
completes successfully, ApplyService removes the AutoAdminLogon registry
|
||||
values and deletes sm-bootstrap, so the one-time session cannot be re-entered.
|
||||
AutoLogon: logs in as sm-bootstrap exactly once so FirstLogonCommands can
|
||||
launch the Welcome wizard. After the wizard completes successfully,
|
||||
ApplyService removes the AutoAdminLogon registry values and deletes
|
||||
sm-bootstrap, so the one-time session cannot be re-entered.
|
||||
-->
|
||||
<AutoLogon>
|
||||
<Enabled>true</Enabled>
|
||||
@@ -114,10 +113,19 @@
|
||||
<Password><Value>bootstrap-OneTime!</Value><PlainText>true</PlainText></Password>
|
||||
</AutoLogon>
|
||||
<!--
|
||||
The Welcome wizard is launched by Shell Launcher v2 as the sm-bootstrap
|
||||
session shell (Configure-Kiosk.ps1, run from SetupComplete.cmd). No
|
||||
FirstLogonCommands launch is needed; adding one would double-launch.
|
||||
Launch the Welcome wizard ELEVATED over the (locked-down) Explorer session.
|
||||
Explorer stays the shell so the MAUI/WebView2 wizard renders (it does NOT
|
||||
render when launched as a bare Shell Launcher shell). Configure-Kiosk.ps1
|
||||
bakes the silent-elevation UAC policy + the lockdown (Keyboard Filter,
|
||||
DisableTaskMgr, hidden taskbar); the wizard runs fullscreen-topmost on top.
|
||||
-->
|
||||
<FirstLogonCommands>
|
||||
<SynchronousCommand wcm:action="add" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||
<Order>1</Order>
|
||||
<CommandLine>cmd /c powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath 'C:\Program Files\SilverOS\Welcome\SilverOS.Welcome.App.exe' -Verb RunAs"</CommandLine>
|
||||
<Description>Launch SilverOS Welcome elevated</Description>
|
||||
</SynchronousCommand>
|
||||
</FirstLogonCommands>
|
||||
<RegisteredOwner>SilverMetal</RegisteredOwner>
|
||||
<RegisteredOrganization>SilverLABS</RegisteredOrganization>
|
||||
<!--
|
||||
|
||||
@@ -1,88 +1,46 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS Configure the one-time sm-bootstrap onboarding kiosk.
|
||||
.SYNOPSIS Lock down the one-time sm-bootstrap onboarding session.
|
||||
.DESCRIPTION
|
||||
Runs from SetupComplete.cmd as SYSTEM, after accounts exist, before first
|
||||
logon. Sets the sm-bootstrap shell to an elevating launcher for the Welcome
|
||||
app (no Explorer => no taskbar/Start), turns on the Keyboard Filter for shell
|
||||
hotkeys, and disables Task Manager / lock / fast-user-switch escapes.
|
||||
Reverted by the Welcome app's ApplyService on wizard success.
|
||||
Runs from SetupComplete.cmd as SYSTEM, after accounts exist, before first logon.
|
||||
Explorer stays the session shell so the MAUI/WebView2 Welcome wizard RENDERS
|
||||
(it does not render when launched as a bare Shell Launcher shell with no
|
||||
Explorer). The wizard is launched fullscreen-topmost by autounattend
|
||||
FirstLogonCommands; this script applies the lockdown around it:
|
||||
- Keyboard Filter: block Win/Start, lock, task-switch and Task-Manager hotkeys
|
||||
- DisableTaskMgr / DisableLockWorkstation / HideFastUserSwitching
|
||||
- silent-elevation UAC policy (so the unsigned wizard elevates with no prompt)
|
||||
All reverted by the Welcome app's ApplyService on wizard success, so the real
|
||||
end-user gets a normal, secure desktop.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param([string]$BootstrapUser='sm-bootstrap',
|
||||
[string]$WelcomeExe='C:\Program Files\SilverOS\Welcome\SilverOS.Welcome.App.exe')
|
||||
param([string]$BootstrapUser='sm-bootstrap')
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference='Stop'
|
||||
$log='C:\Windows\Setup\Scripts\silvermetal-kiosk.log'
|
||||
function Log($m){ "$(Get-Date -f s) $m" | Add-Content $log }
|
||||
Log 'configuring onboarding lockdown (Explorer shell + policy)'
|
||||
|
||||
# Elevating launcher: Shell Launcher runs this as the shell; it relaunches the
|
||||
# Welcome app elevated (silent via the baked UAC auto-approve).
|
||||
$launcher='C:\Windows\Setup\Scripts\Start-WelcomeShell.cmd'
|
||||
$welcomeEscaped = $WelcomeExe.Replace("'","''")
|
||||
@"
|
||||
@echo off
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath '$welcomeEscaped' -Verb RunAs"
|
||||
REM Shell Launcher tracks this CMD process; the Welcome app runs detached above.
|
||||
REM Loop keeps the process alive so Shell Launcher doesn't restart it on idle.
|
||||
:loop
|
||||
timeout /t 3600 >nul
|
||||
goto loop
|
||||
"@ | Set-Content $launcher -Encoding ASCII
|
||||
Log "wrote launcher $launcher"
|
||||
|
||||
# --- Shell Launcher v2 (WMI bridge) ---
|
||||
# WESL_UserSetting exposes STATIC methods on the CLASS — Get-CimInstance returns
|
||||
# no instance, so every method must be called class-level (-Namespace/-ClassName),
|
||||
# NOT via -InputObject on a (null) instance. Getting this wrong enables Shell
|
||||
# Launcher with NO shell configured, which bricks EVERY logon (incl. OOBE's
|
||||
# defaultuser0) into a reboot loop. So: set the DEFAULT shell to explorer.exe for
|
||||
# all users first (this is what keeps OOBE/normal logons working), set the
|
||||
# sm-bootstrap custom shell, and roll back SetEnabled(false) + fall back to a
|
||||
# RunOnce launch if anything fails — never leave SL enabled-but-unconfigured.
|
||||
$cls='root\standardcimv2\embedded'
|
||||
$sid=(New-Object System.Security.Principal.NTAccount($BootstrapUser)).Translate([System.Security.Principal.SecurityIdentifier]).Value
|
||||
try {
|
||||
Invoke-CimMethod -Namespace $cls -ClassName WESL_UserSetting -MethodName SetEnabled -Arguments @{Enabled=$true} | Out-Null
|
||||
# Default shell = Explorer for everyone else (incl. OOBE) — critical so non-kiosk logons don't break.
|
||||
Invoke-CimMethod -Namespace $cls -ClassName WESL_UserSetting -MethodName SetDefaultShell -Arguments @{Shell='explorer.exe';DefaultAction=[int32]0} | Out-Null
|
||||
# sm-bootstrap => the elevating launcher; on exit, restart the shell (action 0).
|
||||
Invoke-CimMethod -Namespace $cls -ClassName WESL_UserSetting -MethodName SetCustomShell -Arguments @{
|
||||
Sid=$sid; Shell="cmd.exe /c `"$launcher`""; DefaultAction=[int32]0 } | Out-Null
|
||||
$set=Invoke-CimMethod -Namespace $cls -ClassName WESL_UserSetting -MethodName GetCustomShell -Arguments @{Sid=$sid} -ErrorAction SilentlyContinue
|
||||
if (-not $set -or [string]::IsNullOrEmpty($set.Shell)) { throw "custom shell did not take for $BootstrapUser" }
|
||||
Log "shell launcher configured for sm-bootstrap (shell=$($set.Shell))"
|
||||
}
|
||||
catch {
|
||||
Log "SHELL LAUNCHER CONFIG FAILED: $($_.Exception.Message) -- rolling back (SetEnabled false) + RunOnce fallback so onboarding still launches"
|
||||
Invoke-CimMethod -Namespace $cls -ClassName WESL_UserSetting -MethodName SetEnabled -Arguments @{Enabled=$false} -ErrorAction SilentlyContinue | Out-Null
|
||||
# Fail-OPEN: no kiosk, but the Welcome wizard must still launch (we removed FirstLogonCommands).
|
||||
$ro='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'
|
||||
New-Item $ro -Force | Out-Null
|
||||
Set-ItemProperty $ro -Name 'SilverOSWelcome' -Type String -Value "cmd /c powershell -NoProfile -ExecutionPolicy Bypass -Command `"Start-Process -FilePath '$welcomeEscaped' -Verb RunAs`""
|
||||
}
|
||||
|
||||
# --- Keyboard Filter (block shell hotkeys) ---
|
||||
# --- Keyboard Filter: block shell/escape hotkeys for the locked-down session ---
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName Client-KeyboardFilter -NoRestart -ErrorAction SilentlyContinue | Out-Null
|
||||
$kf='root\standardcimv2\embedded'
|
||||
foreach($combo in 'Win','Win+L','Ctrl+Esc','Ctrl+Win+F','Win+R'){
|
||||
foreach($combo in 'Win','Win+L','Ctrl+Esc','Ctrl+Win+F','Win+R','Alt+Tab','Ctrl+Shift+Esc','Alt+F4'){
|
||||
$p=Get-CimInstance -Namespace $kf -ClassName WEKF_PredefinedKey -Filter "Id='$combo'" -ErrorAction SilentlyContinue
|
||||
if($p){ $p.Enabled=$true; Set-CimInstance -InputObject $p }
|
||||
if($p){ $p.Enabled=$true; Set-CimInstance -InputObject $p -ErrorAction SilentlyContinue }
|
||||
}
|
||||
Log 'keyboard filter rules enabled'
|
||||
|
||||
# --- escape policies (machine-wide; reverted at teardown) ---
|
||||
$sys='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
|
||||
New-Item $sys -Force | Out-Null
|
||||
Set-ItemProperty $sys -Name DisableTaskMgr -Value 1 -Type DWord
|
||||
Set-ItemProperty $sys -Name DisableTaskMgr -Value 1 -Type DWord
|
||||
Set-ItemProperty $sys -Name DisableLockWorkstation -Value 1 -Type DWord
|
||||
Set-ItemProperty $sys -Name HideFastUserSwitching -Value 1 -Type DWord
|
||||
|
||||
# Silent elevation for the sm-bootstrap launcher's 'Start-Process -Verb RunAs':
|
||||
# the offline-baked UAC auto-approve (build.ps1) is RESET by Windows during OOBE,
|
||||
# so re-assert it online here (runs before the autologon shell). Otherwise the
|
||||
# kiosk shows a UAC consent prompt for the (unsigned) Welcome app. Reverted at
|
||||
# teardown so the real end-user keeps normal UAC.
|
||||
# Silent elevation for the FirstLogonCommands 'Start-Process -Verb RunAs' launch:
|
||||
# the offline-baked UAC auto-approve is RESET by Windows during OOBE, so re-assert
|
||||
# it online here (before the autologon). Otherwise a UAC consent prompt appears for
|
||||
# the unsigned Welcome app. Restored to SECURE UAC at teardown for the real user.
|
||||
Set-ItemProperty $sys -Name ConsentPromptBehaviorAdmin -Value 0 -Type DWord
|
||||
Set-ItemProperty $sys -Name PromptOnSecureDesktop -Value 0 -Type DWord
|
||||
Log 'escape policies + UAC auto-approve set; kiosk ready'
|
||||
Log 'escape policies + UAC auto-approve set; lockdown ready'
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
namespace SilverOS.Welcome.Core.Apply;
|
||||
public sealed class BootstrapService(IProcessRunner runner) : IBootstrapService
|
||||
{
|
||||
// Kiosk revert is BEST-EFFORT (like TearDownAsync): -EA SilentlyContinue throughout.
|
||||
// If WESL is unavailable the real user still gets Explorer (no custom shell for their
|
||||
// SID). Intentional: don't fail the apply over a missing WMI class. Must run BEFORE
|
||||
// TearDownAsync so the sm-bootstrap SID still resolves.
|
||||
// Lockdown revert is BEST-EFFORT (like TearDownAsync): -EA SilentlyContinue throughout.
|
||||
// Don't fail the apply over a missing WMI class / key. Must run BEFORE TearDownAsync.
|
||||
public async Task RevertKioskAsync(CancellationToken ct = default)
|
||||
{
|
||||
// Remove sm-bootstrap custom shell entry + disable Shell Launcher. WESL_UserSetting
|
||||
// methods are STATIC on the class — call class-level (-Namespace/-ClassName), NOT
|
||||
// via -InputObject on a Get-CimInstance result (which is always null).
|
||||
// Disable the Keyboard Filter rules so the real end-user's Win key / task-switch /
|
||||
// Alt+F4 etc. work again (Explorer is already the shell — nothing to undo there).
|
||||
await Ps(
|
||||
"$c='root\\\\standardcimv2\\\\embedded';" +
|
||||
"$sid=(New-Object System.Security.Principal.NTAccount('sm-bootstrap')).Translate([System.Security.Principal.SecurityIdentifier]).Value;" +
|
||||
"Invoke-CimMethod -Namespace $c -ClassName WESL_UserSetting -MethodName RemoveCustomShell -Arguments @{Sid=$sid} -EA SilentlyContinue | Out-Null;" +
|
||||
"Invoke-CimMethod -Namespace $c -ClassName WESL_UserSetting -MethodName SetEnabled -Arguments @{Enabled=$false} -EA SilentlyContinue | Out-Null",
|
||||
"foreach($k in @('Win','Win+L','Ctrl+Esc','Ctrl+Win+F','Win+R','Alt+Tab','Ctrl+Shift+Esc','Alt+F4')){" +
|
||||
"$p=Get-CimInstance -Namespace $c -ClassName WEKF_PredefinedKey -Filter \"Id='$k'\" -EA SilentlyContinue;" +
|
||||
"if($p){$p.Enabled=$false; Set-CimInstance -InputObject $p -EA SilentlyContinue}" +
|
||||
"}",
|
||||
ct);
|
||||
// Revert escape policies set by Configure-Kiosk.ps1.
|
||||
await Ps(
|
||||
|
||||
@@ -30,15 +30,14 @@ public class BootstrapServiceRevertKioskTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RevertKioskAsync_removes_custom_shell_and_disables_shell_launcher()
|
||||
public async Task RevertKioskAsync_disables_keyboard_filter_rules()
|
||||
{
|
||||
var run = Ok();
|
||||
await new BootstrapService(run.Object).RevertKioskAsync();
|
||||
// First call: Shell Launcher revert — must reference WESL_UserSetting and RemoveCustomShell + SetEnabled.
|
||||
// First call: disable the Keyboard Filter predefined-key blocks for the real user.
|
||||
run.Verify(r => r.RunAsync("powershell.exe", It.Is<string>(s =>
|
||||
s.Contains("WESL_UserSetting") &&
|
||||
s.Contains("RemoveCustomShell") &&
|
||||
s.Contains("SetEnabled")),
|
||||
s.Contains("WEKF_PredefinedKey") &&
|
||||
s.Contains("Enabled=$false")),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user