From 50856b8f2874deafb3427250eb86d35ae6bdb3d2 Mon Sep 17 00:00:00 2001 From: sysadmin Date: Tue, 9 Jun 2026 14:12:45 +0100 Subject: [PATCH] feat(branding): Apply-Branding orchestrator (offline/online) + placeholder assets --- windows/branding/Apply-Branding.ps1 | 76 ++++++++++++++++++++++ windows/branding/assets/SilverMetal.theme | 8 +++ windows/branding/assets/lockscreen.jpg | Bin 0 -> 36629 bytes windows/branding/assets/oemlogo.bmp | Bin 0 -> 57654 bytes windows/branding/assets/wallpaper.jpg | Bin 0 -> 36629 bytes windows/tests/Branding.Tests.ps1 | 23 +++++++ 6 files changed, 107 insertions(+) create mode 100644 windows/branding/Apply-Branding.ps1 create mode 100644 windows/branding/assets/SilverMetal.theme create mode 100644 windows/branding/assets/lockscreen.jpg create mode 100644 windows/branding/assets/oemlogo.bmp create mode 100644 windows/branding/assets/wallpaper.jpg diff --git a/windows/branding/Apply-Branding.ps1 b/windows/branding/Apply-Branding.ps1 new file mode 100644 index 0000000..ca91545 --- /dev/null +++ b/windows/branding/Apply-Branding.ps1 @@ -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 } diff --git a/windows/branding/assets/SilverMetal.theme b/windows/branding/assets/SilverMetal.theme new file mode 100644 index 0000000..f09c699 --- /dev/null +++ b/windows/branding/assets/SilverMetal.theme @@ -0,0 +1,8 @@ +[Theme] +DisplayName=SilverMetal +[Control Panel\Desktop] +Wallpaper=%SystemRoot%\Web\Wallpaper\SilverMetal\wallpaper.jpg +WallpaperStyle=10 +[VisualStyles] +SystemMode=Dark +AppMode=Dark diff --git a/windows/branding/assets/lockscreen.jpg b/windows/branding/assets/lockscreen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..43d619334d88d366874a0ba58156b1d9d62a3363 GIT binary patch literal 36629 zcmeIwH&7H&0D$55Hr(Fr-r?=Bcbqm3Nl`0;v0(s3p#!s(V73DT>WnjhnGUmdoY_hg zOqD3c#=!{Kh+?e3tmDkNs}9assg2G5_PuJ~?!15ZOLiw8i*R{qS*egxiiPeal8;1* zP!+|S?$F$+`*mH@bki_={-7BQTBc=%QX=6{ik)Iv;aJ#Cb)wN|a9CPA=ENgT)bU;- zRreW959oTp30WcMuSfEdu>IO8qh6I6Lb0W4%Veh*DTLJB(|QN~Ii%vA(P#JrrsZyE z4+}-As-mghsomWh-TOkb^+9XZ3R<2sTW^LWN^&2*A z-m-Pu_8m<-ckSM@ci;X42NQ=5A31vL_=%IJ&$P9lJ$L@X#miSZuU@-;~I}^!ba|C58GUYpCqEF5B%=G)>hEuS+U*?ow?{ zPtWy5iYkn%n$(QE2ES9>+|t?+$jq-Ch*sBjnX#;bzU)CS?R(jug*EWnjhnGUmdoY_hg zOqD3c#=!{Kh+?e3tmDkNs}9assg2G5_PuJ~?!15ZOLiw8i*R{qS*egxiiPeal8;1* zP!+|S?$F$+`*mH@bki_={-7BQTBc=%QX=6{ik)Iv;aJ#Cb)wN|a9CPA=ENgT)bU;- zRreW959oTp30WcMuSfEdu>IO8qh6I6Lb0W4%Veh*DTLJB(|QN~Ii%vA(P#JrrsZyE z4+}-As-mghsomWh-TOkb^+9XZ3R<2sTW^LWN^&2*A z-m-Pu_8m<-ckSM@ci;X42NQ=5A31vL_=%IJ&$P9lJ$L@X#miSZuU@-;~I}^!ba|C58GUYpCqEF5B%=G)>hEuS+U*?ow?{ zPtWy5iYkn%n$(QE2ES9>+|t?+$jq-Ch*sBjnX#;bzU)CS?R(jug*E$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 + } +}