SilverMetal Windows: first-boot experience & branding #6
76
windows/branding/Apply-Branding.ps1
Normal file
76
windows/branding/Apply-Branding.ps1
Normal file
@@ -0,0 +1,76 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS Apply SilverMetal Windows branding (4 layers), offline (WIM) or online.
|
||||
.DESCRIPTION
|
||||
Offline: reg-load the mounted image's SOFTWARE + default NTUSER hives, write
|
||||
values, stage assets, reg-unload. Online: write live HKLM + default-user hive.
|
||||
Design: ../docs/superpowers/specs/2026-06-09-first-boot-branding-design.md
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)][ValidateSet('Offline','Online')][string]$Mode,
|
||||
[string]$MountPath, # required for Offline
|
||||
[string]$Manifest = "$PSScriptRoot\branding.manifest.json",
|
||||
[string]$AssetsDir = "$PSScriptRoot\assets",
|
||||
[switch]$PassThru
|
||||
)
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. "$PSScriptRoot\lib\BrandingLayers.ps1"
|
||||
function Write-Stage { param($m) Write-Host "==> $m" -ForegroundColor Cyan }
|
||||
|
||||
if ($Mode -eq 'Offline' -and -not $MountPath) { throw 'Offline mode requires -MountPath.' }
|
||||
$m = Get-Content $Manifest -Raw | ConvertFrom-Json
|
||||
|
||||
# Destination paths (image-relative for offline, live for online).
|
||||
$winRoot = if ($Mode -eq 'Offline') { $MountPath } else { 'C:' }
|
||||
$logoDest = Join-Path $winRoot 'Windows\System32\oemlogo.bmp'
|
||||
$lockDir = Join-Path $winRoot 'Windows\Web\Screen\SilverMetal'
|
||||
$wallDir = Join-Path $winRoot 'Windows\Web\Wallpaper\SilverMetal'
|
||||
$themeDest = Join-Path $winRoot 'Windows\Resources\Themes'
|
||||
$lockLive = 'C:\Windows\Web\Screen\SilverMetal\' + $m.lockScreen.image
|
||||
$wallLive = 'C:\Windows\Web\Wallpaper\SilverMetal\' + $m.desktop.wallpaper
|
||||
$logoLive = 'C:\Windows\System32\oemlogo.bmp'
|
||||
|
||||
# --- stage assets ---
|
||||
Write-Stage "Stage branding assets ($Mode)"
|
||||
New-Item -ItemType Directory -Force $lockDir,$wallDir,$themeDest,(Split-Path $logoDest) | Out-Null
|
||||
Copy-Item (Join-Path $AssetsDir $m.oem.logo) $logoDest -Force
|
||||
Copy-Item (Join-Path $AssetsDir $m.lockScreen.image) (Join-Path $lockDir $m.lockScreen.image) -Force
|
||||
Copy-Item (Join-Path $AssetsDir $m.desktop.wallpaper)(Join-Path $wallDir $m.desktop.wallpaper) -Force
|
||||
Copy-Item (Join-Path $AssetsDir $m.desktop.theme) (Join-Path $themeDest $m.desktop.theme) -Force
|
||||
|
||||
$result = [ordered]@{ OemApplied=$false; LockScreenApplied=$false; DesktopApplied=$false; BitLockerApplied=$false }
|
||||
|
||||
function Invoke-WithHive {
|
||||
param([string]$HivePath,[string]$Name,[scriptblock]$Body)
|
||||
& reg load "HKLM\$Name" $HivePath | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "reg load $Name ($HivePath) failed" }
|
||||
try { & $Body "Registry::HKEY_LOCAL_MACHINE\$Name" }
|
||||
finally { [gc]::Collect(); Start-Sleep -Milliseconds 500; & reg unload "HKLM\$Name" | Out-Null }
|
||||
}
|
||||
|
||||
if ($Mode -eq 'Offline') {
|
||||
$swHive = Join-Path $MountPath 'Windows\System32\config\SOFTWARE'
|
||||
$duHive = Join-Path $MountPath 'Users\Default\NTUSER.DAT'
|
||||
Invoke-WithHive $swHive 'SM_BRAND_SW' {
|
||||
param($sw)
|
||||
Write-Stage 'OEM About'; Set-OemInformation -SoftwareRoot $sw -Manifest $m -LogoPath $logoLive; $result.OemApplied=$true
|
||||
Write-Stage 'Lock screen'; Set-LockScreen -SoftwareRoot $sw -ImagePath $lockLive -Lock:$m.lockScreen.lock; $result.LockScreenApplied=$true
|
||||
Write-Stage 'BitLocker preboot';Set-BitLockerPreboot -SoftwareRoot $sw -Manifest $m; $result.BitLockerApplied=$true
|
||||
}
|
||||
Invoke-WithHive $duHive 'SM_BRAND_DU' {
|
||||
param($du)
|
||||
Write-Stage 'Desktop'; Set-DesktopBranding -DefaultUserRoot $du -Manifest $m -WallpaperPath $wallLive; $result.DesktopApplied=$true
|
||||
}
|
||||
} else {
|
||||
Set-OemInformation -SoftwareRoot 'HKLM:\SOFTWARE' -Manifest $m -LogoPath $logoLive; $result.OemApplied=$true
|
||||
Set-LockScreen -SoftwareRoot 'HKLM:\SOFTWARE' -ImagePath $lockLive -Lock:$m.lockScreen.lock; $result.LockScreenApplied=$true
|
||||
Set-BitLockerPreboot -SoftwareRoot 'HKLM:\SOFTWARE' -Manifest $m; $result.BitLockerApplied=$true
|
||||
Invoke-WithHive 'C:\Users\Default\NTUSER.DAT' 'SM_BRAND_DU' {
|
||||
param($du) Set-DesktopBranding -DefaultUserRoot $du -Manifest $m -WallpaperPath $wallLive; $result.DesktopApplied=$true
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host 'Branding applied.' -ForegroundColor Green
|
||||
if ($PassThru) { [pscustomobject]$result }
|
||||
8
windows/branding/assets/SilverMetal.theme
Normal file
8
windows/branding/assets/SilverMetal.theme
Normal file
@@ -0,0 +1,8 @@
|
||||
[Theme]
|
||||
DisplayName=SilverMetal
|
||||
[Control Panel\Desktop]
|
||||
Wallpaper=%SystemRoot%\Web\Wallpaper\SilverMetal\wallpaper.jpg
|
||||
WallpaperStyle=10
|
||||
[VisualStyles]
|
||||
SystemMode=Dark
|
||||
AppMode=Dark
|
||||
BIN
windows/branding/assets/lockscreen.jpg
Normal file
BIN
windows/branding/assets/lockscreen.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
windows/branding/assets/oemlogo.bmp
Normal file
BIN
windows/branding/assets/oemlogo.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
windows/branding/assets/wallpaper.jpg
Normal file
BIN
windows/branding/assets/wallpaper.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -60,3 +60,26 @@ Describe 'Branding layer writers' {
|
||||
$k.RecoveryMessage | Should -Be 'SilverMetal Windows. Locked out? silverlabs.uk'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Apply-Branding -Mode Offline' {
|
||||
BeforeAll {
|
||||
# Build a throwaway "mount" tree with empty SOFTWARE + default NTUSER hives.
|
||||
$script:mount = Join-Path $env:TEMP ("sm-brandtest-" + [guid]::NewGuid().ToString('N'))
|
||||
New-Item -ItemType Directory -Force "$script:mount\Windows\System32\config","$script:mount\Users\Default" | Out-Null
|
||||
# Create two empty hive files by loading/unloading a fresh key.
|
||||
foreach ($h in @("$script:mount\Windows\System32\config\SOFTWARE","$script:mount\Users\Default\NTUSER.DAT")) {
|
||||
reg load 'HKLM\SM_SEED' (New-Item -ItemType File -Force $h | Select-Object -Expand FullName) 2>$null | Out-Null
|
||||
}
|
||||
# The above seed is best-effort; if reg can't init an empty file, the apply
|
||||
# script creates the hives via reg load of the path it expects.
|
||||
}
|
||||
AfterAll { Remove-Item $script:mount -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
|
||||
It 'applies all layers into the offline SOFTWARE hive and reports success' {
|
||||
$r = & "$PSScriptRoot\..\branding\Apply-Branding.ps1" -Mode Offline -MountPath $script:mount -PassThru
|
||||
$r.OemApplied | Should -BeTrue
|
||||
$r.LockScreenApplied | Should -BeTrue
|
||||
$r.DesktopApplied | Should -BeTrue
|
||||
$r.BitLockerApplied | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user