From c09f6a26467a599d25bf300c526a2e267e56999b Mon Sep 17 00:00:00 2001 From: Jean-Paul van Ravensberg <14926452+DevSecNinja@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:56:59 +0200 Subject: [PATCH] refactor(personalization): move user settings to dotfiles Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- config/build.config.psd1 | 4 +- config/catalog.registry.psd1 | 368 ------------------ docs/autounattend.md | 10 +- docs/change-rationale.md | 26 +- docs/usage.md | 6 +- scripts/Invoke-QuickBootTest.ps1 | 5 +- site/data/catalog.json | 314 +-------------- .../Private/Resolve-CatalogOrder.ps1 | 2 +- .../Private/Resolve-CatalogSelection.ps1 | 14 +- .../Public/Export-CatalogManifest.ps1 | 2 +- .../Public/Get-BuildConfiguration.ps1 | 20 +- tests/Catalog.Schema.Tests.ps1 | 23 ++ tests/Export-CatalogManifest.Tests.ps1 | 2 +- tests/Get-BuildConfiguration.Tests.ps1 | 8 +- tests/Resolve-CatalogOrder.Tests.ps1 | 75 +--- tests/Resolve-CatalogSelection.Tests.ps1 | 54 ++- 16 files changed, 116 insertions(+), 817 deletions(-) diff --git a/config/build.config.psd1 b/config/build.config.psd1 index 71e585b..13249fe 100644 --- a/config/build.config.psd1 +++ b/config/build.config.psd1 @@ -46,9 +46,7 @@ LocalAccountName = 'Admin' # local account username (no password stored in the file) Locale = 'en-US' # UI / system language (kept English (United States)) UserLocale = 'nl-NL' # region format for dates/times/numbers = Dutch (Netherlands) - # KeyboardLayout: input locale. Left unset so the profile-driven default applies: - # most profiles => '0409:00000409' (US); the 'opinionated' profile => '0409:00020409' - # (United States-International, so English (US) types on US-International). Uncomment to pin. + # KeyboardLayout: input locale. Left unset to use '0409:00000409' (US). Uncomment to pin. # KeyboardLayout = '0409:00000409' TimeZone = 'W. Europe Standard Time' # Amsterdam (UTC+01:00, DST-aware) DiskId = 0 # target disk for the default single-partition layout diff --git a/config/catalog.registry.psd1 b/config/catalog.registry.psd1 index 41c1223..49657dd 100644 --- a/config/catalog.registry.psd1 +++ b/config/catalog.registry.psd1 @@ -226,30 +226,6 @@ Arch = @('amd64', 'arm64') }, - @{ - Id = 'reg-disable-lockscreen-spotlight' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' - Name = 'RotatingLockScreenOverlayEnabled' - Kind = 'DWord' - Value = 0 - } - Description = 'Disables Windows Spotlight lock-screen "fun facts/ads" overlays for new user profiles.' - Rationale = 'Spotlight lock-screen overlays surface suggestions/ads. Applied to the DEFAULT hive so new profiles inherit it. The specific per-user ContentDeliveryManager value is community-documented (EvidenceGrade 3), so it is opt-in only.' - Citation = 'https://learn.microsoft.com/en-us/answers/questions/1326668/how-to-disable-windows-spotlight-via-registry' - EvidenceGrade = 3 - Reversible = $true - Reversal = 'Set RotatingLockScreenOverlayEnabled to 1 (or delete it) under DEFAULT\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager.' - DefaultEnabled = $false - Unverified = $true - Arch = @('amd64', 'arm64') - }, - # --- Opt-in, impactful removal (FR-008 / Principle VI: DefaultEnabled = $false) --- # OneDrive "removal" here uses the Microsoft-documented supported policy to prevent the # OneDrive sync client rather than deleting binaries (grade 1, reversible, opt-in). @@ -303,247 +279,6 @@ Arch = @('amd64', 'arm64') }, - # --- Personalization: Netherlands regional (date/time/number) format ------------- - # The per-user "region format" (UserLocale) governs the taskbar clock/date format. It is - # applied with the Microsoft International-module cmdlets via a first-boot RunOnce so the - # logged-on user gets nl-NL formatting (24-hour HH:mm, dd-MM-yyyy) and the Netherlands home - # location, without changing the English (en-US) UI/display language. - @{ - Id = 'reg-region-format-nl' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'SOFTWARE' - Path = 'Microsoft\Windows\CurrentVersion\RunOnce' - Name = '!WimRegionFormatNL' - Kind = 'String' - Value = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command "Set-Culture -CultureInfo nl-NL; Set-WinHomeLocation -GeoId 176"' - } - Description = 'Sets the regional format to Dutch (Netherlands) — 24-hour HH:mm time and dd-MM-yyyy short date (e.g. 20:03 16-07-2026) — and the home location to the Netherlands, without changing the English UI language.' - Rationale = 'The taskbar clock/date format is driven by the per-user "region format" (UserLocale) under HKCU\Control Panel\International, not the display language. Microsoft''s supported cmdlets to set it are Set-Culture (region format) and Set-WinHomeLocation (Region > Country/region, GeoId 176 = Netherlands). nl-NL yields the 24-hour HH:mm time and dd-MM-yyyy short date the user wants. These are per-user, cannot be run against an offline image, and are applied via a first-boot RunOnce that runs in the logged-on user''s context. The English (en-US) UI/system locale is intentionally left unchanged. Kept opt-in (Profiles=opinionated) as a personal/regional preference. NOTE: Set-Culture rewrites the whole HKCU\Control Panel\International key from the locale defaults, so it also resets sDecimal/sThousand/sList — reg-number-format-us-first-logon declares RunAfter on this entry and restores them.' - Citation = 'https://learn.microsoft.com/en-us/powershell/module/international/set-culture' - EvidenceGrade = 1 - Reversible = $true - Reversal = 'Remove the !WimRegionFormatNL value under SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce before first boot, or after boot run Set-Culture -CultureInfo en-US (and Set-WinHomeLocation -GeoId 244 for the United States) to revert.' - DefaultEnabled = $false - Arch = @('amd64', 'arm64') - }, - - # --- Personalization: NL + EN input languages, both on the US-International layout --- - # Replaces the per-user input-language list so only Dutch (nl-NL) and English (en-US) - # remain, each bound to the United States-International keyboard layout (KLID 00020409), - # removing the stray plain-US (0409:00000409) layout Windows often adds. English is kept - # first so the display language stays English. - @{ - Id = 'reg-keyboard-nl-en-intl' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'SOFTWARE' - Path = 'Microsoft\Windows\CurrentVersion\RunOnce' - Name = '!WimKeyboardNlEnIntl' - Kind = 'String' - Value = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command "$l = New-WinUserLanguageList -Language ''en-US''; $l[0].InputMethodTips.Clear(); $l[0].InputMethodTips.Add(''0409:00020409''); $l.Add(''nl-NL''); $l[1].InputMethodTips.Clear(); $l[1].InputMethodTips.Add(''0413:00020409''); Set-WinUserLanguageList -LanguageList $l -Force"' - } - Description = 'Sets exactly two input languages — English (en-US) and Dutch (nl-NL) — both bound to the United States-International keyboard layout (00020409), removing the stray plain-US (00000409) layout Windows often adds.' - Rationale = 'The input-language list is a per-user setting managed through Microsoft''s Set-WinUserLanguageList cmdlet; hand-editing its serialized HKCU\Control Panel\International\User Profile blob is unreliable. Building the list with New-WinUserLanguageList and pinning each language''s InputMethodTips to the US-International layout (LANGID:KLID = 0409:00020409 for English, 0413:00020409 for Dutch) guarantees both languages type on US-International and drops the default plain-US (0409:00000409) layout that is commonly added but unwanted. English is kept as the first entry so the display language remains English. Applied via a first-boot RunOnce in the user''s context (cannot run against an offline image). Kept opt-in (Profiles=opinionated) as a personal keyboard preference.' - Citation = 'https://learn.microsoft.com/en-us/powershell/module/international/set-winuserlanguagelist' - EvidenceGrade = 1 - Reversible = $true - Reversal = 'Remove the !WimKeyboardNlEnIntl value under SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce before first boot, or after boot reset the list via Settings > Time & language > Language & region (or Set-WinUserLanguageList en-US -Force).' - DefaultEnabled = $false - Arch = @('amd64', 'arm64') - }, - - # --- Personalization: dark mode (per-user, current + future via DEFAULT hive) ----- - # AppsUseLightTheme/SystemUsesLightTheme are per-user values under the Personalize key. - # Applied to the DEFAULT hive so NEW profiles inherit dark mode; the online post-install - # path applies them to the current user (HKCU) as well (Scope=Both). Community-documented - # (no single authoritative policy page), so grade 3 / opt-in. - @{ - Id = 'reg-dark-mode-apps' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize' - Name = 'AppsUseLightTheme' - Kind = 'DWord' - Value = 0 - } - Description = 'Enables dark mode for apps (AppsUseLightTheme = 0) for new profiles and, via post-install, the current user.' - Rationale = 'Windows stores the app/system theme per user under HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize; AppsUseLightTheme = 0 selects the dark app theme (Microsoft documents these values in its theme-support guidance). Written to the DEFAULT hive so new profiles start in dark mode, and applied to the current user by the post-install path (Scope=Both). Community-documented for scripting (no single authoritative policy page), so EvidenceGrade 3 / opt-in.' - Citation = 'https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes' - EvidenceGrade = 3 - Reversible = $true - Reversal = 'Set AppsUseLightTheme to 1 (or delete it) under Software\Microsoft\Windows\CurrentVersion\Themes\Personalize in HKCU and the default-user hive.' - DefaultEnabled = $false - Unverified = $true - Arch = @('amd64', 'arm64') - }, - - @{ - Id = 'reg-dark-mode-system' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize' - Name = 'SystemUsesLightTheme' - Kind = 'DWord' - Value = 0 - } - Description = 'Enables dark mode for the Windows shell/taskbar (SystemUsesLightTheme = 0) for new profiles and, via post-install, the current user.' - Rationale = 'SystemUsesLightTheme = 0 selects the dark theme for the taskbar, Start and system surfaces (the companion to AppsUseLightTheme). Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). Community-documented for scripting, so EvidenceGrade 3 / opt-in.' - Citation = 'https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes' - EvidenceGrade = 3 - Reversible = $true - Reversal = 'Set SystemUsesLightTheme to 1 (or delete it) under Software\Microsoft\Windows\CurrentVersion\Themes\Personalize in HKCU and the default-user hive.' - DefaultEnabled = $false - Unverified = $true - Arch = @('amd64', 'arm64') - }, - - # --- Personalization: show file extensions (per-user, current + future) ----------- - @{ - Id = 'reg-show-file-extensions' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' - Name = 'HideFileExt' - Kind = 'DWord' - Value = 0 - } - Description = 'Shows known file-type extensions in File Explorer (HideFileExt = 0) for new profiles and, via post-install, the current user.' - Rationale = 'HideFileExt lives per-user under Explorer\Advanced; setting it to 0 makes File Explorer show extensions, which is a small security/clarity win (helps spot e.g. invoice.pdf.exe). Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). The exact value is community-documented rather than in a single authoritative policy page, so it is opt-in.' - Citation = 'Unverified' - EvidenceGrade = 3 - Reversible = $true - Reversal = 'Set HideFileExt to 1 (or delete it) under Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced in HKCU and the default-user hive.' - DefaultEnabled = $false - Unverified = $true - Arch = @('amd64', 'arm64') - }, - - # --- Personalization: US number format so Excel uses comma CSV delimiter ---------- - # Windows couples the Excel CSV column separator to the List separator (sList), and it - # forbids sList from equalling the decimal symbol. NL uses a decimal comma, so to get - # comma-delimited CSVs the decimal must become '.' and the thousands ',' — i.e. adopt US - # number formatting for these three NLS values only (the display language/locale is left - # unchanged). Per-user (DEFAULT hive + current user via Scope=Both). - @{ - Id = 'reg-number-format-decimal-us' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Control Panel\International' - Name = 'sDecimal' - Kind = 'String' - Value = '.' - } - Description = 'Sets the decimal symbol to a dot (sDecimal = ".") so a comma can be used as the CSV list separator (US number formatting).' - Rationale = 'sDecimal is the per-user NLS decimal-symbol override (LOCALE_SDECIMAL) under HKCU\Control Panel\International. Windows refuses to make the List separator equal the decimal symbol, so a comma CSV delimiter requires the decimal symbol to be a dot. Set to "." here (with sThousand="," and sList=",") to give Excel comma-delimited CSVs without changing the display language. Applied to new profiles (DEFAULT hive) and the current user (Scope=Both).' - Citation = 'https://learn.microsoft.com/en-us/windows/win32/intl/locale-custom-constants' - EvidenceGrade = 2 - Reversible = $true - Reversal = 'Set sDecimal back to "," (the NL default) under Control Panel\International in HKCU and the default-user hive.' - DefaultEnabled = $false - Arch = @('amd64', 'arm64') - }, - - @{ - Id = 'reg-number-format-thousands-us' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Control Panel\International' - Name = 'sThousand' - Kind = 'String' - Value = ',' - } - Description = 'Sets the digit-grouping (thousands) symbol to a comma (sThousand = ",") to complete US number formatting.' - Rationale = 'sThousand is the per-user NLS thousands-separator override (LOCALE_STHOUSAND). With the decimal symbol changed to ".", the thousands separator must move off "." (it may not equal the decimal); "," is the US convention. Applied to new profiles (DEFAULT hive) and the current user (Scope=Both).' - Citation = 'https://learn.microsoft.com/en-us/windows/win32/intl/locale-custom-constants' - EvidenceGrade = 2 - Reversible = $true - Reversal = 'Set sThousand back to "." (the NL default) under Control Panel\International in HKCU and the default-user hive.' - DefaultEnabled = $false - Arch = @('amd64', 'arm64') - }, - - @{ - Id = 'reg-number-format-list-us' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Control Panel\International' - Name = 'sList' - Kind = 'String' - Value = ',' - } - Description = 'Sets the list separator to a comma (sList = ",") so Excel imports/exports CSV files with comma-delimited columns.' - Rationale = 'sList is the per-user NLS list-separator override (LOCALE_SLIST); Excel uses it as the CSV column delimiter. Setting it to "," makes downloaded comma-separated CSVs open correctly (instead of the NL default ";"). Requires sDecimal="." (see reg-number-format-decimal-us) because Windows forbids sList == sDecimal. Applied to new profiles (DEFAULT hive) and the current user (Scope=Both). On the FIRST profile these values are destroyed again by reg-region-format-nl''s Set-Culture, so reg-number-format-us-first-logon re-applies them after it.' - Citation = 'https://learn.microsoft.com/en-us/windows/win32/intl/locale-custom-constants' - EvidenceGrade = 2 - Reversible = $true - Reversal = 'Set sList back to ";" (the NL default) under Control Panel\International in HKCU and the default-user hive.' - DefaultEnabled = $false - Arch = @('amd64', 'arm64') - }, - - # --- Personalization: re-assert the US number format after the first-logon Set-Culture --- - # Set-Culture (reg-region-format-nl) REPLACES the whole HKCU\Control Panel\International key - # with the nl-NL defaults, which wipes the sDecimal/sThousand/sList values the first user - # inherited from the DEFAULT hive — so without this entry Excel falls back to a ';' CSV - # delimiter on the very first profile. This RunOnce re-applies the three values. Windows - # does not document the order RunOnce values execute in, so ordering is belt-and-braces: - # RunAfter guarantees this value is WRITTEN last (enumeration/write order) and the 'Zz' - # value name sorts last among the !Wim* values (lexical order). - @{ - Id = 'reg-number-format-us-first-logon' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - RunAfter = @('reg-region-format-nl') - Target = @{ - Hive = 'SOFTWARE' - Path = 'Microsoft\Windows\CurrentVersion\RunOnce' - Name = '!WimZzNumberFormatUS' - Kind = 'String' - Value = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command "$i = ''HKCU:\Control Panel\International''; Set-ItemProperty -Path $i -Name ''sDecimal'' -Value ''.''; Set-ItemProperty -Path $i -Name ''sThousand'' -Value '',''; Set-ItemProperty -Path $i -Name ''sList'' -Value '',''"' - } - Description = 'Re-applies the US number format (sDecimal ".", sThousand ",", sList ",") to the first logged-on user after the regional-format RunOnce, so Excel keeps a comma CSV delimiter.' - Rationale = 'reg-number-format-{decimal,thousands,list}-us write these values to the DEFAULT hive, which new profiles inherit — but reg-region-format-nl runs Set-Culture at first logon, and Set-Culture rewrites the ENTIRE HKCU\Control Panel\International key from the nl-NL locale defaults (sDecimal ",", sThousand ".", sList ";"). The inherited overrides are therefore destroyed on the first profile and Excel opens comma-separated CSVs as a single column. Because Set-Culture is a cmdlet that cannot run offline, the correction must also happen at first logon and strictly AFTER it. Windows does not contractually document the order in which RunOnce values execute, so this entry is ordered belt-and-braces: it declares RunAfter = reg-region-format-nl (so the catalog loader always WRITES it after, which wins if values are executed in enumeration/write order) AND its value name sorts after !WimRegionFormatNL (which wins if they are executed in lexical order). Harmless (idempotent) when the regional-format entry is disabled: it just re-asserts the same values the DEFAULT hive already carries. Grade 2 because the RunOnce mechanism and the NLS value names are Microsoft-documented but the execution-order behaviour is observed, not a documented contract.' - Citation = 'https://learn.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys' - EvidenceGrade = 2 - Reversible = $true - Reversal = 'Remove the !WimZzNumberFormatUS value under SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce before first boot, or after boot set sDecimal=",", sThousand="." and sList=";" under HKCU\Control Panel\International to restore the NL defaults.' - DefaultEnabled = $false - Arch = @('amd64', 'arm64') - }, - # --- Privacy: clipboard history, local only (enable history, block cloud sync) ---- @{ Id = 'reg-clipboard-history-enable' @@ -843,109 +578,6 @@ $cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop Reversal = 'Set the ssh-agent service Start value back to 3 (Manual) — or 2 (Automatic) if you want it to start on boot — under SYSTEM\ControlSet001\Services\ssh-agent, or set "OpenSSH Authentication Agent" back to Manual/Automatic in services.msc, then start the service.' DefaultEnabled = $false Arch = @('amd64', 'arm64') - }, - - # --- Personalization: taskbar & File Explorer per-user tweaks (current + future) --- - @{ - Id = 'reg-hide-taskbar-search' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Software\Microsoft\Windows\CurrentVersion\Search' - Name = 'SearchboxTaskbarMode' - Kind = 'DWord' - Value = 0 - } - Description = 'Hides the taskbar search box/icon (SearchboxTaskbarMode = 0 = Hide) for new profiles and, via post-install, the current user.' - Rationale = 'The taskbar search presentation is the per-user SearchboxTaskbarMode value under HKCU\Software\Microsoft\Windows\CurrentVersion\Search; Microsoft documents the modes (0 = Hide, 1 = icon, 2 = icon+label, 3 = box) alongside the ConfigureSearchOnTaskbarMode policy. 0 removes the search UI from the taskbar. Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). Kept opt-in as a personal-taste taskbar preference.' - Citation = 'https://learn.microsoft.com/en-us/windows/apps/develop/settings/settings-windows-11' - EvidenceGrade = 2 - Reversible = $true - Reversal = 'Set SearchboxTaskbarMode to 3 (search box, the default) or delete it under Software\Microsoft\Windows\CurrentVersion\Search in HKCU and the default-user hive.' - DefaultEnabled = $false - Arch = @('amd64', 'arm64') - }, - - @{ - Id = 'reg-disable-task-view' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' - Name = 'ShowTaskViewButton' - Kind = 'DWord' - Value = 0 - } - Description = 'Removes the Task View button from the taskbar (ShowTaskViewButton = 0) for new profiles and, via post-install, the current user.' - Rationale = 'The Task View taskbar button is the per-user ShowTaskViewButton value under HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced, documented by Microsoft in the Windows 11 settings reference; 0 hides it (Win+Tab still works). Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). Opt-in personal-taste preference.' - Citation = 'https://learn.microsoft.com/en-us/windows/apps/develop/settings/settings-windows-11' - EvidenceGrade = 1 - Reversible = $true - Reversal = 'Set ShowTaskViewButton to 1 (or delete it) under Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced in HKCU and the default-user hive.' - DefaultEnabled = $false - Arch = @('amd64', 'arm64') - }, - - @{ - Id = 'reg-show-hidden-items' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' - Name = 'Hidden' - Kind = 'DWord' - Value = 1 - } - Description = 'Shows hidden files and folders in File Explorer (Hidden = 1) for new profiles and, via post-install, the current user.' - Rationale = 'The "show hidden files" toggle is the per-user Hidden value under HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced (1 = show, 2 = do not show). Showing hidden items is a developer/power-user convenience. Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). The exact value is community-documented rather than in a single authoritative policy page, so it is opt-in.' - Citation = 'Unverified' - EvidenceGrade = 3 - Reversible = $true - Reversal = 'Set Hidden to 2 (or delete it) under Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced in HKCU and the default-user hive.' - DefaultEnabled = $false - Unverified = $true - Arch = @('amd64', 'arm64') - }, - - # --- Personalization: Windows Spotlight as the desktop background (best-effort) ---- - # Microsoft documents the desktop background kind as the Personalization WallpaperKind - # enum (SolidColor=0, Image=1, Slideshow=2, Spotlight=3), surfaced through the - # Personalization CSP rather than a single authoritative registry value. The per-user - # HKCU\...\Explorer\Wallpapers\BackgroundType mirrors that enum, so BackgroundType=3 - # selects Spotlight. This is best-effort/community-territory (grade 3, opt-in): it may need - # an Explorer restart / sign-in to take effect and the Spotlight content-delivery must be - # available on the edition. - @{ - Id = 'reg-spotlight-desktop-background' - Type = 'Registry' - Action = 'SetRegistry' - Category = 'Personalization' - Profiles = @('opinionated') - Target = @{ - Hive = 'DEFAULT' - Path = 'Software\Microsoft\Windows\CurrentVersion\Explorer\Wallpapers' - Name = 'BackgroundType' - Kind = 'DWord' - Value = 3 - } - Description = 'Selects Windows Spotlight as the desktop background (BackgroundType = 3) for new profiles and, via post-install, the current user (best-effort).' - Rationale = 'Microsoft documents the desktop background kind as the Personalization WallpaperKind enumeration where Spotlight = 3 (settings reference). That enum is exposed through the Personalization CSP; the per-user HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Wallpapers\BackgroundType value mirrors it, so BackgroundType = 3 selects the daily Windows Spotlight image. There is no single authoritative registry-only citation for activating desktop Spotlight (it is CSP-driven), so this is a best-effort, community-territory tweak (EvidenceGrade 3, opt-in) that may require an Explorer restart or sign-in to take effect. Written to the DEFAULT hive for new profiles and the current user (Scope=Both).' - Citation = 'https://learn.microsoft.com/en-us/windows/apps/develop/settings/settings-windows-11' - EvidenceGrade = 3 - Reversible = $true - Reversal = 'Set BackgroundType to 1 (Image) or your preferred kind, or delete it, under Software\Microsoft\Windows\CurrentVersion\Explorer\Wallpapers in HKCU and the default-user hive, then reselect a background in Settings > Personalization > Background.' - DefaultEnabled = $false - Unverified = $true - Arch = @('amd64', 'arm64') } ) } diff --git a/docs/autounattend.md b/docs/autounattend.md index 6136ab1..8a8585d 100644 --- a/docs/autounattend.md +++ b/docs/autounattend.md @@ -34,7 +34,7 @@ Autounattend = @{ LocalAccountName = 'Admin' # username (NO password is stored in the file) Locale = 'en-US' # UI / system language UserLocale = 'en-US' # region format (dates/times/numbers); defaults to Locale - KeyboardLayout = '0409:00000409' # input locale; 'opinionated' profile defaults to '0409:00020409' (US-International) + KeyboardLayout = '0409:00000409' # input locale; set explicitly to choose another layout TimeZone = 'UTC' # e.g. 'W. Europe Standard Time' ProductKey = '' # edition selector (see below) DiskId = 0 @@ -165,11 +165,9 @@ the consumer ISO, or a business edition (Pro, Education, ...) off a business/vol `-Profile`, and `-Hypervisor` (`HyperV` | `VMware`) overrides, so you can test the hands-off path with `-Edition Home -UseGenericProductKey` and do a keyed build with `-ProductKey ''`. Use `-Hypervisor VMware` to run the quick boot test under VMware Workstation (NAT-connected, real DNS). -`-Profile` accepts one or more profiles (e.g. -`-Profile gaming,opinionated`); -because a quick boot test reuses the already-serviced `media\` folder it does **not** re-run debloat, -but it re-derives the answer file, so profile-driven `Autounattend` settings (such as the opinionated -United States-International keyboard) are reflected in the boot test. +`-Profile` accepts one or more profiles (e.g. `-Profile gaming,opinionated`). Because a quick boot +test reuses the already-serviced `media\` folder it does **not** re-run debloat; it only re-derives +the answer file from the resolved configuration. To boot-test several editions at once, pass `-Isolated` to each parallel window. Isolated runs get a uniquely-named `Autounattend-.xml` and ISO, and ISO authoring is serialized with a named mutex diff --git a/docs/change-rationale.md b/docs/change-rationale.md index ed70a45..dd9acb5 100644 --- a/docs/change-rationale.md +++ b/docs/change-rationale.md @@ -29,7 +29,7 @@ explains the model and highlights the notable defaults. Reversal = 'How to undo it.' DefaultEnabled = $true # grade-3 entries must be $false Profiles = @('opinionated') # optional: profile-membership tags (gaming | opinionated) - RunAfter = @('reg-region-format-nl') # optional: ids this entry must be applied after + RunAfter = @('catalog-prerequisite') # optional: ids this entry must be applied after Condition = @{ Script='...'; Description='...' } # optional: hardware/machine applicability guard Arch = @('amd64','arm64') } @@ -161,7 +161,7 @@ Most changes are independent, so the catalog is applied in authoring order. Wher to sit in the file: ```powershell -RunAfter = @('reg-region-format-nl') +RunAfter = @('catalog-prerequisite') ``` [`Import-ChangeCatalog`](../src/WindowsIsoMaker/Private/Import-ChangeCatalog.ps1) validates every @@ -188,15 +188,6 @@ cross-hive constraint. Self-references and dependency cycles are rejected at load time. -The motivating case is **first-logon `RunOnce` commands**. `reg-region-format-nl` runs -`Set-Culture`, which rewrites the whole `HKCU\Control Panel\International` key from the locale -defaults, so `reg-number-format-us-first-logon` has to restore the US number separators (which keep -Excel on a comma CSV delimiter) *after* it. Windows does not contractually document the order in -which `RunOnce` values execute, so that entry is ordered belt-and-braces: `RunAfter` guarantees its -value is **written** last (which wins if execution follows enumeration/write order), and its value -name `!WimZzNumberFormatUS` sorts after `!WimRegionFormatNL` (which wins if execution follows -lexical order). Prefer that pattern for any new ordering-sensitive `RunOnce` pair. - ### `Condition` — hardware-specific entries Some changes only make sense on particular hardware. Rather than growing a per-feature switch @@ -249,10 +240,10 @@ from three inputs, in order of increasing precedence: 1. `Profile` — the baseline set (`minimal` / `default` / `aggressive` / `gaming` / `opinionated`, where `gaming` is `default` minus the entries tagged `Profiles = @('gaming')` so Xbox / Game Bar are preserved, and `opinionated` is `aggressive` plus the entries tagged - `Profiles = @('opinionated')` personal-taste extras — reversed mouse scroll (via a helper task, - so mice paired later are covered too), Start web-search off, lock-screen Spotlight off, a - Surface-Laptop-only power button that does nothing instead of sleeping, WSL, and the United - States-International keyboard layout for English (US)). + `Profiles = @('opinionated')` machine-wide/admin extras — reversed mouse scroll (via a SYSTEM + helper task, so mice paired later are covered too), Start web-search and clipboard policies, + Recall removal, WSL/Virtual Machine Platform, services/time/hibernation settings, and Surface + power policies). `Profile` also accepts a list to combine baselines (e.g. `gaming,opinionated`): the selected profiles are UNIONed, and when `gaming` is one of them the `Profiles = @('gaming')` entries stay preserved — so `gaming,opinionated` gives aggressive debloat + opinionated tweaks @@ -262,6 +253,11 @@ from three inputs, in order of increasing precedence: Entries not applicable to the target `Architecture` are skipped automatically. +Current-user, non-admin Windows personalization is intentionally outside this catalog. It moved to +the [DevSecNinja/dotfiles](https://github.com/DevSecNinja/dotfiles) chezmoi configuration in +[dotfiles PR #711](https://github.com/DevSecNinja/dotfiles/pull/711); windows-iso-maker retains +machine-wide, offline-servicing, policy, elevation-requiring, and SYSTEM-task changes. + ## Notable defaults **Enabled by default (spec-mandated, grade 1, reversible):** diff --git a/docs/usage.md b/docs/usage.md index dc54abd..95cfe55 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -23,7 +23,7 @@ parameters and `WIM_*` environment variables exist only as optional last-mile ov | Field | Meaning | |-------|---------| | `Edition` / `Language` / `Release` / `Architecture` | Base image selection. `Architecture` is `amd64` or `arm64`. **Only the Home SKUs** (Home, Home N, Home Single Language) come from the Fido consumer ISO — cached once per architecture/release. **Every other edition** (Pro, Education, Enterprise, LTSC, IoT, ...) only installs and activates from the **business/volume ISO** (retail generic keys and volume/GVLK keys are not interchangeable), which Fido can't download; supply `IsoPath` with the matching business-editions ISO (e.g. from a Visual Studio / volume-licensing subscription). | -| `Profile` | Baseline change set: `minimal` (fewest changes), `default` (balanced), `aggressive` (most debloat), `gaming` (keeps Xbox/Game Bar), or `opinionated` (aggressive + personal-taste extras: reversed scroll, Start web-search off, Spotlight off, WSL, and the United States-International keyboard layout for English (US)). Accepts a list to combine profiles, e.g. `@('gaming','opinionated')` — the baselines are UNIONed and `gaming` keeps the gaming stack. | +| `Profile` | Baseline change set: `minimal` (fewest changes), `default` (balanced), `aggressive` (most debloat), `gaming` (keeps Xbox/Game Bar), or `opinionated` (aggressive + machine-wide/admin extras: reversed scroll, Start web-search and clipboard policies, Recall removal, WSL, services/time/hibernation settings, and Surface power policies). Accepts a list to combine profiles, e.g. `@('gaming','opinionated')` — the baselines are UNIONed and `gaming` keeps the gaming stack. | | `Toggles` | Per-id override map, e.g. `@{ 'appx-todos' = $false; 'feature-wsl' = $true }`. | | `EnableCatalogId` / `DisableCatalogId` | Force-enable / force-disable specific entries by `Id` (explicit ids win). | | `Autounattend` | Install/OOBE-time options (see [autounattend.md](autounattend.md)). | @@ -98,8 +98,8 @@ $env:WIM_CONFIG_PATH = 'config/build.arm64.psd1'; ./build.ps1 # Gaming profile: full debloat but preserve Xbox Game Bar and the Xbox provisioned apps ./build.ps1 -Profile gaming -# Game PC: aggressive debloat + opinionated tweaks (reversed scroll, US-International keyboard, -# WSL, ...) while keeping the whole Xbox / Game Bar gaming stack +# Game PC: aggressive debloat + machine-wide opinionated settings (reversed scroll, WSL, ...) +# while keeping the whole Xbox / Game Bar gaming stack ./build.ps1 -Profile gaming,opinionated # Opt in to Edge + OneDrive removal and enable WSL diff --git a/scripts/Invoke-QuickBootTest.ps1 b/scripts/Invoke-QuickBootTest.ps1 index 7367bfc..a2787a7 100644 --- a/scripts/Invoke-QuickBootTest.ps1 +++ b/scripts/Invoke-QuickBootTest.ps1 @@ -80,9 +80,8 @@ .PARAMETER Profile Debloat/customization profile(s) to apply for this run, overriding the config's Profile. Accepts a list to combine baselines (e.g. -Profile gaming,opinionated). Because a quick boot test reuses - the already-serviced media\ folder, this does NOT re-run debloat; it re-derives the answer file, - so profile-driven Autounattend settings (e.g. the opinionated United States-International - keyboard) are reflected in the boot test. + the already-serviced media\ folder, this does NOT re-run debloat; it only re-derives the answer + file from the resolved configuration. .EXAMPLE ./scripts/Invoke-QuickBootTest.ps1 diff --git a/site/data/catalog.json b/site/data/catalog.json index 547a7db..34908f3 100644 --- a/site/data/catalog.json +++ b/site/data/catalog.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "generatedUtc": "2026-07-31T08:09:54Z", + "generatedUtc": "2026-08-13T08:54:04Z", "moduleVersion": "1.0.0", "defaultProfile": "default", "profiles": [ @@ -22,7 +22,7 @@ }, { "name": "opinionated", - "description": "The aggressive set plus personal-taste extras (tagged Profiles=@('opinionated')): reversed mouse scroll, Start web-search off, lock-screen Spotlight off, a Surface-Laptop-only power button that does nothing instead of sleeping, WSL, and OEM extras such as disabling the Waves MaxxAudio service." + "description": "The aggressive set plus machine-wide/admin extras (tagged Profiles=@('opinionated')): reversed mouse scroll, Start web-search and cloud clipboard policies, Recall removal, WSL, services/time/hibernation settings, and Surface power policies." } ], "categories": [ @@ -43,7 +43,7 @@ "Registry", "ScheduledTask" ], - "entryCount": 67, + "entryCount": 53, "entries": [ { "id": "appx-clipchamp", @@ -1000,28 +1000,6 @@ "opinionated" ] }, - { - "id": "reg-disable-lockscreen-spotlight", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager!RotatingLockScreenOverlayEnabled", - "description": "Disables Windows Spotlight lock-screen \"fun facts/ads\" overlays for new user profiles.", - "rationale": "Spotlight lock-screen overlays surface suggestions/ads. Applied to the DEFAULT hive so new profiles inherit it. The specific per-user ContentDeliveryManager value is community-documented (EvidenceGrade 3), so it is opt-in only.", - "citation": "https://learn.microsoft.com/en-us/answers/questions/1326668/how-to-disable-windows-spotlight-via-registry", - "evidenceGrade": 3, - "reversible": true, - "reversal": "Set RotatingLockScreenOverlayEnabled to 1 (or delete it) under DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\ContentDeliveryManager.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, { "id": "remove-onedrive", "type": "Registry", @@ -1064,204 +1042,6 @@ "opinionated" ] }, - { - "id": "reg-region-format-nl", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce!!WimRegionFormatNL", - "description": "Sets the regional format to Dutch (Netherlands) — 24-hour HH:mm time and dd-MM-yyyy short date (e.g. 20:03 16-07-2026) — and the home location to the Netherlands, without changing the English UI language.", - "rationale": "The taskbar clock/date format is driven by the per-user \"region format\" (UserLocale) under HKCU\\Control Panel\\International, not the display language. Microsoft's supported cmdlets to set it are Set-Culture (region format) and Set-WinHomeLocation (Region > Country/region, GeoId 176 = Netherlands). nl-NL yields the 24-hour HH:mm time and dd-MM-yyyy short date the user wants. These are per-user, cannot be run against an offline image, and are applied via a first-boot RunOnce that runs in the logged-on user's context. The English (en-US) UI/system locale is intentionally left unchanged. Kept opt-in (Profiles=opinionated) as a personal/regional preference. NOTE: Set-Culture rewrites the whole HKCU\\Control Panel\\International key from the locale defaults, so it also resets sDecimal/sThousand/sList — reg-number-format-us-first-logon declares RunAfter on this entry and restores them.", - "citation": "https://learn.microsoft.com/en-us/powershell/module/international/set-culture", - "evidenceGrade": 1, - "reversible": true, - "reversal": "Remove the !WimRegionFormatNL value under SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce before first boot, or after boot run Set-Culture -CultureInfo en-US (and Set-WinHomeLocation -GeoId 244 for the United States) to revert.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-keyboard-nl-en-intl", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce!!WimKeyboardNlEnIntl", - "description": "Sets exactly two input languages — English (en-US) and Dutch (nl-NL) — both bound to the United States-International keyboard layout (00020409), removing the stray plain-US (00000409) layout Windows often adds.", - "rationale": "The input-language list is a per-user setting managed through Microsoft's Set-WinUserLanguageList cmdlet; hand-editing its serialized HKCU\\Control Panel\\International\\User Profile blob is unreliable. Building the list with New-WinUserLanguageList and pinning each language's InputMethodTips to the US-International layout (LANGID:KLID = 0409:00020409 for English, 0413:00020409 for Dutch) guarantees both languages type on US-International and drops the default plain-US (0409:00000409) layout that is commonly added but unwanted. English is kept as the first entry so the display language remains English. Applied via a first-boot RunOnce in the user's context (cannot run against an offline image). Kept opt-in (Profiles=opinionated) as a personal keyboard preference.", - "citation": "https://learn.microsoft.com/en-us/powershell/module/international/set-winuserlanguagelist", - "evidenceGrade": 1, - "reversible": true, - "reversal": "Remove the !WimKeyboardNlEnIntl value under SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce before first boot, or after boot reset the list via Settings > Time & language > Language & region (or Set-WinUserLanguageList en-US -Force).", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-dark-mode-apps", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize!AppsUseLightTheme", - "description": "Enables dark mode for apps (AppsUseLightTheme = 0) for new profiles and, via post-install, the current user.", - "rationale": "Windows stores the app/system theme per user under HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize; AppsUseLightTheme = 0 selects the dark app theme (Microsoft documents these values in its theme-support guidance). Written to the DEFAULT hive so new profiles start in dark mode, and applied to the current user by the post-install path (Scope=Both). Community-documented for scripting (no single authoritative policy page), so EvidenceGrade 3 / opt-in.", - "citation": "https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes", - "evidenceGrade": 3, - "reversible": true, - "reversal": "Set AppsUseLightTheme to 1 (or delete it) under Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-dark-mode-system", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize!SystemUsesLightTheme", - "description": "Enables dark mode for the Windows shell/taskbar (SystemUsesLightTheme = 0) for new profiles and, via post-install, the current user.", - "rationale": "SystemUsesLightTheme = 0 selects the dark theme for the taskbar, Start and system surfaces (the companion to AppsUseLightTheme). Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). Community-documented for scripting, so EvidenceGrade 3 / opt-in.", - "citation": "https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes", - "evidenceGrade": 3, - "reversible": true, - "reversal": "Set SystemUsesLightTheme to 1 (or delete it) under Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-show-file-extensions", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced!HideFileExt", - "description": "Shows known file-type extensions in File Explorer (HideFileExt = 0) for new profiles and, via post-install, the current user.", - "rationale": "HideFileExt lives per-user under Explorer\\Advanced; setting it to 0 makes File Explorer show extensions, which is a small security/clarity win (helps spot e.g. invoice.pdf.exe). Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). The exact value is community-documented rather than in a single authoritative policy page, so it is opt-in.", - "citation": "Unverified", - "evidenceGrade": 3, - "reversible": true, - "reversal": "Set HideFileExt to 1 (or delete it) under Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-number-format-decimal-us", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Control Panel\\International!sDecimal", - "description": "Sets the decimal symbol to a dot (sDecimal = \".\") so a comma can be used as the CSV list separator (US number formatting).", - "rationale": "sDecimal is the per-user NLS decimal-symbol override (LOCALE_SDECIMAL) under HKCU\\Control Panel\\International. Windows refuses to make the List separator equal the decimal symbol, so a comma CSV delimiter requires the decimal symbol to be a dot. Set to \".\" here (with sThousand=\",\" and sList=\",\") to give Excel comma-delimited CSVs without changing the display language. Applied to new profiles (DEFAULT hive) and the current user (Scope=Both).", - "citation": "https://learn.microsoft.com/en-us/windows/win32/intl/locale-custom-constants", - "evidenceGrade": 2, - "reversible": true, - "reversal": "Set sDecimal back to \",\" (the NL default) under Control Panel\\International in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-number-format-thousands-us", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Control Panel\\International!sThousand", - "description": "Sets the digit-grouping (thousands) symbol to a comma (sThousand = \",\") to complete US number formatting.", - "rationale": "sThousand is the per-user NLS thousands-separator override (LOCALE_STHOUSAND). With the decimal symbol changed to \".\", the thousands separator must move off \".\" (it may not equal the decimal); \",\" is the US convention. Applied to new profiles (DEFAULT hive) and the current user (Scope=Both).", - "citation": "https://learn.microsoft.com/en-us/windows/win32/intl/locale-custom-constants", - "evidenceGrade": 2, - "reversible": true, - "reversal": "Set sThousand back to \".\" (the NL default) under Control Panel\\International in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-number-format-list-us", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Control Panel\\International!sList", - "description": "Sets the list separator to a comma (sList = \",\") so Excel imports/exports CSV files with comma-delimited columns.", - "rationale": "sList is the per-user NLS list-separator override (LOCALE_SLIST); Excel uses it as the CSV column delimiter. Setting it to \",\" makes downloaded comma-separated CSVs open correctly (instead of the NL default \";\"). Requires sDecimal=\".\" (see reg-number-format-decimal-us) because Windows forbids sList == sDecimal. Applied to new profiles (DEFAULT hive) and the current user (Scope=Both). On the FIRST profile these values are destroyed again by reg-region-format-nl's Set-Culture, so reg-number-format-us-first-logon re-applies them after it.", - "citation": "https://learn.microsoft.com/en-us/windows/win32/intl/locale-custom-constants", - "evidenceGrade": 2, - "reversible": true, - "reversal": "Set sList back to \";\" (the NL default) under Control Panel\\International in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-number-format-us-first-logon", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce!!WimZzNumberFormatUS", - "description": "Re-applies the US number format (sDecimal \".\", sThousand \",\", sList \",\") to the first logged-on user after the regional-format RunOnce, so Excel keeps a comma CSV delimiter.", - "rationale": "reg-number-format-{decimal,thousands,list}-us write these values to the DEFAULT hive, which new profiles inherit — but reg-region-format-nl runs Set-Culture at first logon, and Set-Culture rewrites the ENTIRE HKCU\\Control Panel\\International key from the nl-NL locale defaults (sDecimal \",\", sThousand \".\", sList \";\"). The inherited overrides are therefore destroyed on the first profile and Excel opens comma-separated CSVs as a single column. Because Set-Culture is a cmdlet that cannot run offline, the correction must also happen at first logon and strictly AFTER it. Windows does not contractually document the order in which RunOnce values execute, so this entry is ordered belt-and-braces: it declares RunAfter = reg-region-format-nl (so the catalog loader always WRITES it after, which wins if values are executed in enumeration/write order) AND its value name sorts after !WimRegionFormatNL (which wins if they are executed in lexical order). Harmless (idempotent) when the regional-format entry is disabled: it just re-asserts the same values the DEFAULT hive already carries. Grade 2 because the RunOnce mechanism and the NLS value names are Microsoft-documented but the execution-order behaviour is observed, not a documented contract.", - "citation": "https://learn.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys", - "evidenceGrade": 2, - "reversible": true, - "reversal": "Remove the !WimZzNumberFormatUS value under SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce before first boot, or after boot set sDecimal=\",\", sThousand=\".\" and sList=\";\" under HKCU\\Control Panel\\International to restore the NL defaults.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, { "id": "reg-clipboard-history-enable", "type": "Registry", @@ -1504,94 +1284,6 @@ "opinionated" ] }, - { - "id": "reg-hide-taskbar-search", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Search!SearchboxTaskbarMode", - "description": "Hides the taskbar search box/icon (SearchboxTaskbarMode = 0 = Hide) for new profiles and, via post-install, the current user.", - "rationale": "The taskbar search presentation is the per-user SearchboxTaskbarMode value under HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Search; Microsoft documents the modes (0 = Hide, 1 = icon, 2 = icon+label, 3 = box) alongside the ConfigureSearchOnTaskbarMode policy. 0 removes the search UI from the taskbar. Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). Kept opt-in as a personal-taste taskbar preference.", - "citation": "https://learn.microsoft.com/en-us/windows/apps/develop/settings/settings-windows-11", - "evidenceGrade": 2, - "reversible": true, - "reversal": "Set SearchboxTaskbarMode to 3 (search box, the default) or delete it under Software\\Microsoft\\Windows\\CurrentVersion\\Search in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-disable-task-view", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced!ShowTaskViewButton", - "description": "Removes the Task View button from the taskbar (ShowTaskViewButton = 0) for new profiles and, via post-install, the current user.", - "rationale": "The Task View taskbar button is the per-user ShowTaskViewButton value under HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced, documented by Microsoft in the Windows 11 settings reference; 0 hides it (Win+Tab still works). Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). Opt-in personal-taste preference.", - "citation": "https://learn.microsoft.com/en-us/windows/apps/develop/settings/settings-windows-11", - "evidenceGrade": 1, - "reversible": true, - "reversal": "Set ShowTaskViewButton to 1 (or delete it) under Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-show-hidden-items", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced!Hidden", - "description": "Shows hidden files and folders in File Explorer (Hidden = 1) for new profiles and, via post-install, the current user.", - "rationale": "The \"show hidden files\" toggle is the per-user Hidden value under HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced (1 = show, 2 = do not show). Showing hidden items is a developer/power-user convenience. Written to the DEFAULT hive for new profiles and applied to the current user by post-install (Scope=Both). The exact value is community-documented rather than in a single authoritative policy page, so it is opt-in.", - "citation": "Unverified", - "evidenceGrade": 3, - "reversible": true, - "reversal": "Set Hidden to 2 (or delete it) under Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced in HKCU and the default-user hive.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, - { - "id": "reg-spotlight-desktop-background", - "type": "Registry", - "action": "SetRegistry", - "category": "Personalization", - "target": "DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Wallpapers!BackgroundType", - "description": "Selects Windows Spotlight as the desktop background (BackgroundType = 3) for new profiles and, via post-install, the current user (best-effort).", - "rationale": "Microsoft documents the desktop background kind as the Personalization WallpaperKind enumeration where Spotlight = 3 (settings reference). That enum is exposed through the Personalization CSP; the per-user HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Wallpapers\\BackgroundType value mirrors it, so BackgroundType = 3 selects the daily Windows Spotlight image. There is no single authoritative registry-only citation for activating desktop Spotlight (it is CSP-driven), so this is a best-effort, community-territory tweak (EvidenceGrade 3, opt-in) that may require an Explorer restart or sign-in to take effect. Written to the DEFAULT hive for new profiles and the current user (Scope=Both).", - "citation": "https://learn.microsoft.com/en-us/windows/apps/develop/settings/settings-windows-11", - "evidenceGrade": 3, - "reversible": true, - "reversal": "Set BackgroundType to 1 (Image) or your preferred kind, or delete it, under Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Wallpapers in HKCU and the default-user hive, then reselect a background in Settings > Personalization > Background.", - "defaultEnabled": false, - "arch": [ - "amd64", - "arm64" - ], - "sourceFile": "catalog.registry.psd1", - "profiles": [ - "opinionated" - ] - }, { "id": "task-reverse-mouse-scroll", "type": "ScheduledTask", diff --git a/src/WindowsIsoMaker/Private/Resolve-CatalogOrder.ps1 b/src/WindowsIsoMaker/Private/Resolve-CatalogOrder.ps1 index 189ca5e..6ab2766 100644 --- a/src/WindowsIsoMaker/Private/Resolve-CatalogOrder.ps1 +++ b/src/WindowsIsoMaker/Private/Resolve-CatalogOrder.ps1 @@ -41,7 +41,7 @@ function Resolve-CatalogOrder { values were written, NOT alphabetically). Rather than relying on the implicit order entries happen to be authored in, an entry declares its prerequisites explicitly: - RunAfter = @('reg-region-format-nl') + RunAfter = @('catalog-prerequisite') This function turns those declarations into a concrete apply order using a STABLE topological sort: among the entries whose prerequisites are all satisfied it always picks diff --git a/src/WindowsIsoMaker/Private/Resolve-CatalogSelection.ps1 b/src/WindowsIsoMaker/Private/Resolve-CatalogSelection.ps1 index f0b7298..f28c5b7 100644 --- a/src/WindowsIsoMaker/Private/Resolve-CatalogSelection.ps1 +++ b/src/WindowsIsoMaker/Private/Resolve-CatalogSelection.ps1 @@ -25,10 +25,10 @@ function Resolve-CatalogSelection { never additive features such as WSL, which stay strictly opt-in). * gaming — the default set MINUS entries tagged Profiles=@('gaming') (Xbox Game Bar / Xbox provisioned apps), so gaming functionality is preserved. - * opinionated— the aggressive set PLUS personal-taste extras tagged Profiles=@('opinionated') - (reversed mouse scroll, Start web-search off, lock-screen Spotlight off, - WSL + Virtual Machine Platform). These grade-3/additive opt-ins are in no - other profile, so this is the maintainer's "kitchen sink" preference set. + * opinionated— the aggressive set PLUS machine-wide/admin extras tagged + Profiles=@('opinionated') (reversed mouse scroll, Start web-search off, + clipboard policies, Recall removal, WSL + Virtual Machine Platform, + services/time/hibernation settings, and Surface power policies). Any id referenced by Toggles/EnableCatalogId/DisableCatalogId that does not exist in the catalog raises a terminating error. A selected entry whose `RunAfter` prerequisite was NOT @@ -222,9 +222,9 @@ function Test-CatalogEntryInProfile { return $isDefault } 'opinionated' { - # The 'aggressive' baseline PLUS personal-taste extras tagged Profiles=@('opinionated') - # (reversed mouse scroll, Start web-search off, lock-screen Spotlight off, WSL + - # Virtual Machine Platform). Those grade-3/additive opt-ins appear in no other profile. + # The 'aggressive' baseline PLUS machine-wide/admin extras tagged + # Profiles=@('opinionated'), such as reversed mouse scroll, Start web-search off, + # clipboard policies, Recall removal, WSL, and system/service settings. if ((Get-CatalogEntryProfileTag -Entry $Entry) -contains 'opinionated') { return $true } # Fall through to the aggressive baseline. if ($isDefault) { return $true } diff --git a/src/WindowsIsoMaker/Public/Export-CatalogManifest.ps1 b/src/WindowsIsoMaker/Public/Export-CatalogManifest.ps1 index b063545..6917c5e 100644 --- a/src/WindowsIsoMaker/Public/Export-CatalogManifest.ps1 +++ b/src/WindowsIsoMaker/Public/Export-CatalogManifest.ps1 @@ -42,7 +42,7 @@ function Export-CatalogManifest { [ordered]@{ Name = 'default'; Description = 'Balanced baseline: every catalog entry marked DefaultEnabled (the recommended starting point).' } [ordered]@{ Name = 'aggressive'; Description = 'Most debloat: the default set plus opt-in grade 1-2 app/capability removals (never community-graded).' } [ordered]@{ Name = 'gaming'; Description = 'The default set, but Xbox / Game Bar entries (tagged Profiles=@(''gaming'')) are preserved.' } - [ordered]@{ Name = 'opinionated'; Description = 'The aggressive set plus personal-taste extras (tagged Profiles=@(''opinionated'')): reversed mouse scroll, Start web-search off, lock-screen Spotlight off, a Surface-Laptop-only power button that does nothing instead of sleeping, WSL, and OEM extras such as disabling the Waves MaxxAudio service.' } + [ordered]@{ Name = 'opinionated'; Description = 'The aggressive set plus machine-wide/admin extras (tagged Profiles=@(''opinionated'')): reversed mouse scroll, Start web-search and cloud clipboard policies, Recall removal, WSL, services/time/hibernation settings, and Surface power policies.' } ) $profileNames = @($profiles | ForEach-Object { $_.Name }) diff --git a/src/WindowsIsoMaker/Public/Get-BuildConfiguration.ps1 b/src/WindowsIsoMaker/Public/Get-BuildConfiguration.ps1 index 3a45d9b..db4eb6d 100644 --- a/src/WindowsIsoMaker/Public/Get-BuildConfiguration.ps1 +++ b/src/WindowsIsoMaker/Public/Get-BuildConfiguration.ps1 @@ -251,8 +251,7 @@ function Get-BuildConfiguration { # --- 9. Resolve the Autounattend sub-config (merge over documented defaults). --- $autounattend = Resolve-AutounattendConfig -FileValue $resolved['Autounattend'] ` - -Language $resolved['Language'] -Architecture $resolved['Architecture'] ` - -Profile $profileList + -Language $resolved['Language'] -Architecture $resolved['Architecture'] # --- 10. Emit the BuildConfiguration object. --- return [pscustomobject]@{ @@ -295,30 +294,17 @@ function Resolve-AutounattendConfig { .PARAMETER Architecture The resolved architecture (recorded for reference; the XML processorArchitecture is set by New-AutounattendXml). - .PARAMETER Profile - The resolved profile list. When it includes 'opinionated' the default keyboard layout - becomes United States-International (0409:00020409) instead of plain US, matching the - opinionated preference to always type on US-International. An explicit KeyboardLayout in - the config file still wins. .OUTPUTS System.Collections.Hashtable #> [CmdletBinding()] [OutputType([hashtable])] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable', 'Profile', - Justification = 'Profile is the domain term for the catalog profile; it is only read here, never reassigned.')] param( [Parameter()] $FileValue, [Parameter(Mandatory = $true)] [string] $Language, - [Parameter(Mandatory = $true)] [string] $Architecture, - [Parameter()] [string[]] $Profile = @('default') + [Parameter(Mandatory = $true)] [string] $Architecture ) - # Opinionated builds default to the United States-International keyboard layout so that - # English (United States) also types on US-International (dead keys for accents), rather than - # the plain US layout. An explicit Autounattend.KeyboardLayout in the config file overrides it. - $defaultKeyboard = if ($Profile -contains 'opinionated') { '0409:00020409' } else { '0409:00000409' } - $defaults = @{ Enabled = $true SkipOobe = $true @@ -327,7 +313,7 @@ function Resolve-AutounattendConfig { CreateLocalAccount = $true LocalAccountName = 'Admin' Locale = $Language - KeyboardLayout = $defaultKeyboard + KeyboardLayout = '0409:00000409' TimeZone = 'UTC' DiskId = 0 FirstLogonCommands = @() diff --git a/tests/Catalog.Schema.Tests.ps1 b/tests/Catalog.Schema.Tests.ps1 index 1654830..0679c76 100644 --- a/tests/Catalog.Schema.Tests.ps1 +++ b/tests/Catalog.Schema.Tests.ps1 @@ -250,6 +250,29 @@ Describe 'Change catalog: documentation-backed changes (Principle II)' { } } } + + It 'excludes current-user personalization owned by DevSecNinja/dotfiles' { + $ids = @($script:RuntimeEntries | ForEach-Object { $_.Id }) + $migratedIds = @( + 'reg-dark-mode-apps', + 'reg-dark-mode-system', + 'reg-disable-lockscreen-spotlight', + 'reg-disable-task-view', + 'reg-hide-taskbar-search', + 'reg-show-file-extensions', + 'reg-show-hidden-items', + 'reg-spotlight-desktop-background', + 'reg-region-format-nl', + 'reg-keyboard-nl-en-intl', + 'reg-number-format-decimal-us', + 'reg-number-format-thousands-us', + 'reg-number-format-list-us', + 'reg-number-format-us-first-logon' + ) + foreach ($id in $migratedIds) { + $ids | Should -Not -Contain $id + } + } } Context 'Merge-blocking gate proves undocumented entries fail (FR-009, SC-004)' { diff --git a/tests/Export-CatalogManifest.Tests.ps1 b/tests/Export-CatalogManifest.Tests.ps1 index fd268e9..2414894 100644 --- a/tests/Export-CatalogManifest.Tests.ps1 +++ b/tests/Export-CatalogManifest.Tests.ps1 @@ -27,7 +27,7 @@ Describe 'Export-CatalogManifest' { } It 'reports an entry count that matches the entries array' { - $script:Manifest.entryCount | Should -BeGreaterThan 0 + $script:Manifest.entryCount | Should -Be 53 $script:Manifest.entryCount | Should -Be $script:Manifest.entries.Count } diff --git a/tests/Get-BuildConfiguration.Tests.ps1 b/tests/Get-BuildConfiguration.Tests.ps1 index 8d37feb..57c750b 100644 --- a/tests/Get-BuildConfiguration.Tests.ps1 +++ b/tests/Get-BuildConfiguration.Tests.ps1 @@ -201,7 +201,7 @@ Describe 'Get-BuildConfiguration' { } } - Context 'Profile combinations and opinionated keyboard layout' { + Context 'Profile combinations and keyboard layout' { It 'accepts a list of profiles and stores them joined' { $cfg = Get-BuildConfiguration -Path $script:DefaultConfig -Profile 'gaming', 'opinionated' $cfg.Profile | Should -Be 'gaming, opinionated' @@ -214,9 +214,9 @@ Describe 'Get-BuildConfiguration' { $ids | Should -Contain 'task-reverse-mouse-scroll' } - It 'defaults to United States-International keyboard under opinionated' { + It 'keeps the plain US keyboard under opinionated' { $cfg = Get-BuildConfiguration -Path $script:DefaultConfig -Profile 'opinionated' - $cfg.Autounattend.KeyboardLayout | Should -Be '0409:00020409' + $cfg.Autounattend.KeyboardLayout | Should -Be '0409:00000409' } It 'keeps the plain US keyboard for non-opinionated profiles' { @@ -224,7 +224,7 @@ Describe 'Get-BuildConfiguration' { $cfg.Autounattend.KeyboardLayout | Should -Be '0409:00000409' } - It 'still honours an explicit KeyboardLayout in the config under opinionated' { + It 'honours an explicit KeyboardLayout in the config under opinionated' { $customConfig = Join-Path $script:TempRoot 'kbd.psd1' @" @{ diff --git a/tests/Resolve-CatalogOrder.Tests.ps1 b/tests/Resolve-CatalogOrder.Tests.ps1 index 0a73fe6..0232acc 100644 --- a/tests/Resolve-CatalogOrder.Tests.ps1 +++ b/tests/Resolve-CatalogOrder.Tests.ps1 @@ -134,79 +134,10 @@ Describe 'Get-CatalogEntryRunAfter' { } } -Describe 'Import-ChangeCatalog RunAfter integration' { - - It 'applies the real catalog ordering so the number-format repair follows the regional format' { - InModuleScope WindowsIsoMaker { - $ids = @(Import-ChangeCatalog | ForEach-Object { $_.Id }) - $region = [Array]::IndexOf($ids, 'reg-region-format-nl') - $repair = [Array]::IndexOf($ids, 'reg-number-format-us-first-logon') - - $region | Should -BeGreaterThan -1 - $repair | Should -BeGreaterThan -1 - # Set-Culture (reg-region-format-nl) wipes sDecimal/sThousand/sList, so the repair - # RunOnce must be written — and therefore executed — after it. - $repair | Should -BeGreaterThan $region - } - } - - It 'keeps the ordering after the profile/architecture selection filters the catalog' { - InModuleScope WindowsIsoMaker { - $selected = Resolve-CatalogSelection -Catalog (Import-ChangeCatalog) -Architecture amd64 -Profile opinionated - $ids = @($selected | ForEach-Object { $_.Id }) - $ids | Should -Contain 'reg-number-format-us-first-logon' - [Array]::IndexOf($ids, 'reg-number-format-us-first-logon') | - Should -BeGreaterThan ([Array]::IndexOf($ids, 'reg-region-format-nl')) - } - } - - It 'enforces the real catalog ordering even when the entries are loaded in the wrong order' { - InModuleScope WindowsIsoMaker { - # Proves the constraint is carried by RunAfter and not merely by where the entries - # happen to sit in catalog.registry.psd1: reversing the input must still order them. - $reversed = @(Import-ChangeCatalog) - [Array]::Reverse($reversed) - $ids = @(Resolve-CatalogOrder -Catalog $reversed | ForEach-Object { $_.Id }) - [Array]::IndexOf($ids, 'reg-number-format-us-first-logon') | - Should -BeGreaterThan ([Array]::IndexOf($ids, 'reg-region-format-nl')) - } - } - - It 'reaches the offline applier in the resolved order (end to end)' { - InModuleScope WindowsIsoMaker { - # The ordering only matters if it survives all the way to the physical write, so assert - # on Set-RegistryTweaks' own per-entry results rather than on the catalog array. - $selected = Resolve-CatalogSelection -Catalog (Import-ChangeCatalog) -Architecture amd64 -Profile opinionated - $mountDir = Join-Path ([System.IO.Path]::GetTempPath()) ("wim-order-" + [guid]::NewGuid().ToString('N')) - New-Item -Path $mountDir -ItemType Directory -Force | Out-Null - try { - $applied = @(Set-RegistryTweaks -MountPath $mountDir -Catalog $selected -Architecture amd64 -WhatIf | - ForEach-Object { $_.Id }) - $applied | Should -Contain 'reg-number-format-us-first-logon' - [Array]::IndexOf($applied, 'reg-number-format-us-first-logon') | - Should -BeGreaterThan ([Array]::IndexOf($applied, 'reg-region-format-nl')) - } - finally { - Remove-Item -LiteralPath $mountDir -Recurse -Force -ErrorAction SilentlyContinue - } - } - } - - It 'keeps the repair RunOnce value name sorting last, so lexical execution order works too' { +Describe 'Import-ChangeCatalog RunAfter validation' { + It 'rejects a RunAfter id that does not exist in the catalog' { InModuleScope WindowsIsoMaker { - # Belt and braces: Windows does not document RunOnce execution order, so the repair must - # win under BOTH write order (RunAfter) and lexical order (value name). - $catalog = @(Import-ChangeCatalog) - $region = @($catalog | Where-Object { $_.Id -eq 'reg-region-format-nl' })[0] - $repair = @($catalog | Where-Object { $_.Id -eq 'reg-number-format-us-first-logon' })[0] - - $region.Target.Path | Should -Be $repair.Target.Path - ([string]::Compare($repair.Target.Name, $region.Target.Name, $true)) | - Should -BeGreaterThan 0 -Because 'the repair value name must sort after the regional-format one' - } - } - - It 'rejects a RunAfter id that does not exist in the catalog' { InModuleScope WindowsIsoMaker { $dir = Join-Path ([System.IO.Path]::GetTempPath()) ("wim-runafter-" + [guid]::NewGuid().ToString('N')) + $dir = Join-Path ([System.IO.Path]::GetTempPath()) ("wim-runafter-" + [guid]::NewGuid().ToString('N')) New-Item -Path $dir -ItemType Directory -Force | Out-Null try { $content = @' diff --git a/tests/Resolve-CatalogSelection.Tests.ps1 b/tests/Resolve-CatalogSelection.Tests.ps1 index 85ef535..f3bce24 100644 --- a/tests/Resolve-CatalogSelection.Tests.ps1 +++ b/tests/Resolve-CatalogSelection.Tests.ps1 @@ -3,8 +3,8 @@ .SYNOPSIS Tests for Resolve-CatalogSelection / Test-CatalogEntryInProfile, focused on the profile baselines (minimal | default | aggressive | gaming | opinionated). The 'gaming' profile keeps - gaming components (tagged Profiles=@('gaming')); 'opinionated' adds the maintainer's personal-taste - extras (tagged Profiles=@('opinionated'), e.g. reversed mouse scroll + WSL) on top of the aggressive + gaming components (tagged Profiles=@('gaming')); 'opinionated' adds machine-wide/admin extras + (tagged Profiles=@('opinionated'), e.g. reversed mouse scroll + WSL) on top of the aggressive baseline. #> @@ -75,13 +75,57 @@ Describe 'Opinionated profile baseline' { $opinionatedIds = @(Resolve-CatalogSelection -Catalog $catalog -Architecture amd64 -Profile opinionated | ForEach-Object { $_.Id }) $aggressiveIds = @(Resolve-CatalogSelection -Catalog $catalog -Architecture amd64 -Profile aggressive | ForEach-Object { $_.Id }) - foreach ($id in @('task-reverse-mouse-scroll', 'reg-disable-start-web-search', 'reg-disable-lockscreen-spotlight', 'feature-wsl', 'feature-vmplatform')) { - $opinionatedIds | Should -Contain $id -Because 'the opinionated profile enables the personal-taste extras' + $retainedIds = @( + 'reg-disable-start-web-search', + 'reg-clipboard-history-enable', + 'reg-clipboard-no-cross-device', + 'reg-show-hibernate-button', + 'reg-enable-hibernation', + 'reg-power-button-no-action-ac', + 'reg-power-button-no-action-dc', + 'reg-time-dst-automatic', + 'reg-time-sync-automatic', + 'reg-timezone-automatic', + 'reg-timezone-amsterdam', + 'reg-disable-waves-audio-service', + 'reg-disable-openssh-agent', + 'feature-remove-recall', + 'feature-wsl', + 'feature-vmplatform', + 'task-reverse-mouse-scroll' + ) + foreach ($id in $retainedIds) { + $opinionatedIds | Should -Contain $id -Because 'the opinionated profile retains machine-wide/admin extras' $aggressiveIds | Should -Not -Contain $id -Because 'those extras are only in the opinionated profile' } } } + It 'does not include current-user personalization migrated to dotfiles' { + InModuleScope WindowsIsoMaker { + $catalogIds = @(Import-ChangeCatalog | ForEach-Object { $_.Id }) + $migratedIds = @( + 'reg-dark-mode-apps', + 'reg-dark-mode-system', + 'reg-disable-lockscreen-spotlight', + 'reg-disable-task-view', + 'reg-hide-taskbar-search', + 'reg-show-file-extensions', + 'reg-show-hidden-items', + 'reg-spotlight-desktop-background', + 'reg-region-format-nl', + 'reg-keyboard-nl-en-intl', + 'reg-number-format-decimal-us', + 'reg-number-format-thousands-us', + 'reg-number-format-list-us', + 'reg-number-format-us-first-logon' + ) + foreach ($id in $migratedIds) { + $catalogIds | Should -Not -Contain $id + } + } + } + It 'is a strict superset of the aggressive baseline' { InModuleScope WindowsIsoMaker { $catalog = Import-ChangeCatalog @@ -112,7 +156,7 @@ Describe 'Combining profiles (union with gaming veto)' { # Opinionated extras are included. foreach ($id in @('task-reverse-mouse-scroll', 'feature-wsl', 'feature-vmplatform', 'reg-disable-start-web-search')) { - $comboIds | Should -Contain $id -Because 'opinionated in the combination adds its personal-taste extras' + $comboIds | Should -Contain $id -Because 'opinionated in the combination adds its machine-wide/admin extras' } # Aggressive/default non-gaming debloat still applies.