From 7cb37fc87629e1aaa9719494958a93f114daf774 Mon Sep 17 00:00:00 2001 From: Julio Rios <31230801+jsilverdev@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:10:01 -0500 Subject: [PATCH 1/4] Install and validate Windows user fonts --- .github/workflows/validate.yml | 16 +++++++ README.md | 21 ++++++--- install.ps1 | 39 +++++++++++++---- tests/static/validate-architecture.py | 1 + tests/windows/assert-fonts.ps1 | 61 +++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 tests/windows/assert-fonts.ps1 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index da100e0..33209b2 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -117,12 +117,20 @@ jobs: shell: pwsh run: ./tests/windows/assert-migration.ps1 + - name: Assert current-user fonts + shell: pwsh + run: ./tests/windows/assert-fonts.ps1 -RepoRoot $env:GITHUB_WORKSPACE + - shell: pwsh run: ./tests/windows/assert-state.ps1 -RepoRoot $env:GITHUB_WORKSPACE -ExpectedCommit $env:GITHUB_SHA - shell: cmd run: call "%GITHUB_WORKSPACE%\tests\windows\create-update-fixture.cmd" - shell: cmd run: call "%GITHUB_WORKSPACE%\update.cmd" + - name: Reassert current-user fonts + shell: pwsh + run: ./tests/windows/assert-fonts.ps1 -RepoRoot $env:GITHUB_WORKSPACE + - shell: pwsh run: ./tests/windows/assert-state.ps1 -RepoRoot $env:GITHUB_WORKSPACE -UpdateMarker $env:UPDATE_MARKER @@ -155,11 +163,19 @@ jobs: shell: cmd run: call "%GITHUB_WORKSPACE%\scripts\windows\invoke-ps-script.cmd" "%GITHUB_WORKSPACE%\tests\windows\assert-migration.ps1" + - name: Assert current-user fonts under AllSigned + shell: cmd + run: call "%GITHUB_WORKSPACE%\scripts\windows\invoke-ps-script.cmd" "%GITHUB_WORKSPACE%\tests\windows\assert-fonts.ps1" -RepoRoot "%GITHUB_WORKSPACE%" + - shell: cmd run: call "%GITHUB_WORKSPACE%\scripts\windows\invoke-ps-script.cmd" "%GITHUB_WORKSPACE%\tests\windows\assert-allsigned.ps1" -RepoRoot "%GITHUB_WORKSPACE%" -ThumbprintFile "%RUNNER_TEMP%\dotfiles-cert-thumbprint.txt" - shell: cmd run: call "%GITHUB_WORKSPACE%\tests\windows\create-update-fixture.cmd" - shell: cmd run: call "%GITHUB_WORKSPACE%\update.cmd" + - name: Reassert current-user fonts under AllSigned + shell: cmd + run: call "%GITHUB_WORKSPACE%\scripts\windows\invoke-ps-script.cmd" "%GITHUB_WORKSPACE%\tests\windows\assert-fonts.ps1" -RepoRoot "%GITHUB_WORKSPACE%" + - shell: cmd run: call "%GITHUB_WORKSPACE%\scripts\windows\invoke-ps-script.cmd" "%GITHUB_WORKSPACE%\tests\windows\assert-allsigned.ps1" -RepoRoot "%GITHUB_WORKSPACE%" -ThumbprintFile "%RUNNER_TEMP%\dotfiles-cert-thumbprint.txt" -UpdateMarker "%UPDATE_MARKER%" diff --git a/README.md b/README.md index 5ecd1bb..190cc9e 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,19 @@ This repository uses [chezmoi](https://www.chezmoi.io/) to deploy regular dotfil ### Windows -Run this from a normal `cmd.exe` prompt: +Open **Windows PowerShell 5.1** and run: -```cmd -curl.exe -fsSLo "%TEMP%\dotfiles-bootstrap.cmd" https://raw.githubusercontent.com/jsilverdev/dotfiles/main/bootstrap.cmd && call "%TEMP%\dotfiles-bootstrap.cmd" +```powershell +$bootstrap = Join-Path $env:TEMP 'dotfiles-bootstrap.cmd' +Invoke-WebRequest -UseBasicParsing -Uri 'https://raw.githubusercontent.com/jsilverdev/dotfiles/main/bootstrap.cmd' -OutFile $bootstrap +& $env:ComSpec /d /c "call `"$bootstrap`"" ``` +Windows PowerShell 5.1 is only used to download and launch the CMD bootstrap. The bootstrap itself provisions and uses PowerShell 7 where required. + The Windows bootstrap requires WinGet. Before applying chezmoi, it removes only broken links or reparse points that block a currently managed destination path. Parent components under the user profile are checked as well, but valid links are never traversed and unrelated broken links are left untouched. If App Installer exists but WinGet is not registered for the current user, it attempts current-user App Installer registration. If corporate policy disables WinGet, it stops with an error. Bootstrap installs only Git, PowerShell 7, and chezmoi when they are missing; the platform installer owns the remaining application catalog. -Baseline CLI packages are declared in `scripts/windows/managed-apps.csv` and are installed with WinGet in user scope. An interactive run also installs workstation applications, fonts, Windows Terminal customization, and offers optional applications. `DOTFILES_NONINTERACTIVE=1` installs the complete baseline but skips those interactive/workstation customizations. +Baseline CLI packages are declared in `scripts/windows/managed-apps.csv` and are installed with WinGet in user scope. Cascadia Code plus the Cascadia Code and Cascadia Mono Nerd Font variants are installed for the current user as part of the Windows baseline, including non-interactive runs. An interactive run additionally installs workstation applications, configures Windows Terminal, and offers optional applications. `DOTFILES_NONINTERACTIVE=1` installs the complete CLI/font baseline while skipping those interactive/workstation customizations. The bootstrap does not require administrator rights or Developer Mode. It invokes PowerShell 7 with `-NoProfile`; an effective `AllSigned` policy is supported automatically. Each machine reuses or creates its own current-user Code Signing certificate and trusts its public certificate locally. No private key, PFX, or KeePass dependency is used. @@ -51,6 +55,13 @@ For dotfiles plus installer-managed package/application/module updates, use `upd The `Validate dotfiles` workflow exercises Debian, Arch Linux, Windows with its normal execution policy, and Windows with a simulated current-user `AllSigned` policy. GitHub Actions only orchestrates the scenarios; reusable fixture and assertion logic lives under `tests/`. -Integration jobs bootstrap from a temporary local bare Git remote containing the exact commit under test. They run the real non-interactive baseline, validate installed CLI tools, exercise create-only Codex files and update wrappers, check idempotent chezmoi apply, and verify clean source/checkout state. Static validation includes ShellCheck, PowerShell parsing, manifest checks, chezmoi template evaluation, bridge-payload synchronization, and pinned `actionlint` validation of the workflow. +Integration jobs bootstrap from a temporary local bare Git remote containing the exact commit under test. They run the real non-interactive baseline, validate installed CLI tools and current-user fonts, exercise create-only Codex files and update wrappers, check idempotent chezmoi apply, and verify clean source/checkout state. Windows font assertions verify both the files under `%LOCALAPPDATA%\Microsoft\Windows\Fonts` and their matching `HKCU\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts` entries. Static validation includes ShellCheck, PowerShell parsing, manifest checks, chezmoi template evaluation, bridge-payload synchronization, and pinned `actionlint` validation of the workflow. The Windows AllSigned job validates current-user certificate creation and reuse, Authenticode signing, the CMD execution bridge, PowerShell profile startup, and managed module loading. When PowerShell 7 requires signed scripts, the bridge uses inbox Windows PowerShell only for Authenticode signing and executes the resulting signed script with PowerShell 7 under the effective policy. The hosted runner is an administrator with UAC disabled, so CI trusts the test certificate through `LocalMachine\Root` plus `CurrentUser\TrustedPublisher`; runtime helpers also accept `CurrentUser\Root` for the real non-admin path. Corporate GPO/MDM/AppLocker/WDAC policy, enterprise App Installer policy, and a true non-admin corporate Windows 11 token still require validation on a managed machine. + + +## Acknowledgments + +This dotfiles repository is inspired by: +- [Lissy93's dotfiles](https://github.com/Lissy93/dotfiles) +- [KEVINNITRO DOTFILES](https://github.com/KevinNitroG/dotfiles). diff --git a/install.ps1 b/install.ps1 index 50555f7..3f77cc1 100644 --- a/install.ps1 +++ b/install.ps1 @@ -317,14 +317,37 @@ function Install-UserFonts { $sourceDir = Join-Path $RepoRoot "fonts" $userFontsDir = Join-Path $env:LOCALAPPDATA "Microsoft\Windows\Fonts" $fontRegistryKey = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" - if (-not (Test-Path -LiteralPath $sourceDir -PathType Container)) { throw "Font source directory not found: $sourceDir" } + + if (-not (Test-Path -LiteralPath $sourceDir -PathType Container)) { + throw "Font source directory not found: $sourceDir" + } + New-Item -ItemType Directory -Path $userFontsDir -Force | Out-Null + New-Item -Path $fontRegistryKey -Force | Out-Null + $sourceFonts = @(Get-ChildItem -Path $sourceDir -Include *.otc,*.otf,*.ttc,*.ttf -Recurse -File) + if ($sourceFonts.Count -eq 0) { + throw "No font files were downloaded to $sourceDir." + } + foreach ($font in $sourceFonts | Sort-Object Name -Unique) { $destination = Join-Path $userFontsDir $font.Name - if (Test-Path -LiteralPath $destination) { continue } - Copy-Item -LiteralPath $font.FullName -Destination $destination - New-ItemProperty -Path $fontRegistryKey -Name "$($font.Name) (dotfiles)" -Value $destination -PropertyType String -Force | Out-Null + $registryName = "$($font.Name) (dotfiles)" + + if (-not (Test-Path -LiteralPath $destination -PathType Leaf)) { + Copy-Item -LiteralPath $font.FullName -Destination $destination + } + + $registeredPath = $null + try { + $registeredPath = Get-ItemPropertyValue -Path $fontRegistryKey -Name $registryName -ErrorAction Stop + } + catch { + } + + if ($registeredPath -ne $destination) { + New-ItemProperty -Path $fontRegistryKey -Name $registryName -Value $destination -PropertyType String -Force | Out-Null + } } } @@ -357,10 +380,8 @@ function Configure-Wsl { Refresh-Path Check-RequiredApps -if (-not $NonInteractive) { - Download-Fonts - Install-UserFonts -} +Download-Fonts +Install-UserFonts Configure-Git Install-MustHaveApps @@ -370,7 +391,7 @@ if (-not $NonInteractive) { Install-OptionalApps } else { - Write-Host "Skipping fonts, terminal configuration, and optional applications in non-interactive mode." -ForegroundColor Yellow + Write-Host "Skipping terminal configuration and optional applications in non-interactive mode." -ForegroundColor Yellow } Configure-Wsl diff --git a/tests/static/validate-architecture.py b/tests/static/validate-architecture.py index ebfa350..0bca84b 100644 --- a/tests/static/validate-architecture.py +++ b/tests/static/validate-architecture.py @@ -47,6 +47,7 @@ def read_text(path: str) -> str: "scripts/windows/deploy-pwsh.ps1", "home/.chezmoiscripts/run_after_90-deploy-pwsh.cmd.tmpl", "tests/windows/assert-migration.ps1", + "tests/windows/assert-fonts.ps1", ) for relative in required_paths: if not (ROOT / relative).exists(): diff --git a/tests/windows/assert-fonts.ps1 b/tests/windows/assert-fonts.ps1 new file mode 100644 index 0000000..ec71457 --- /dev/null +++ b/tests/windows/assert-fonts.ps1 @@ -0,0 +1,61 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)][string]$RepoRoot +) + +$ErrorActionPreference = "Stop" + +function Fail([string]$Message) { + throw "ASSERTION FAILED: $Message" +} + +$sourceDir = Join-Path $RepoRoot "fonts" +$userFontsDir = Join-Path $env:LOCALAPPDATA "Microsoft\Windows\Fonts" +$fontRegistryKey = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" + +if (-not (Test-Path -LiteralPath $sourceDir -PathType Container)) { + Fail "font source directory is missing: $sourceDir" +} +if (-not (Test-Path -LiteralPath $userFontsDir -PathType Container)) { + Fail "current-user font directory is missing: $userFontsDir" +} +if (-not (Test-Path -LiteralPath $fontRegistryKey)) { + Fail "current-user font registry key is missing: $fontRegistryKey" +} + +$sourceFonts = @(Get-ChildItem -Path $sourceDir -Include *.otc,*.otf,*.ttc,*.ttf -Recurse -File | Sort-Object Name -Unique) +if ($sourceFonts.Count -eq 0) { + Fail "no downloaded fonts were found in $sourceDir" +} + +$requiredPatterns = @( + "CascadiaCode*.ttf", + "CaskaydiaCoveNerdFont*.ttf", + "CaskaydiaMonoNerdFont*.ttf" +) +foreach ($pattern in $requiredPatterns) { + if (@($sourceFonts | Where-Object Name -Like $pattern).Count -eq 0) { + Fail "expected font family is missing from the downloaded source: $pattern" + } +} + +foreach ($font in $sourceFonts) { + $destination = Join-Path $userFontsDir $font.Name + if (-not (Test-Path -LiteralPath $destination -PathType Leaf)) { + Fail "font was not installed for the current user: $destination" + } + + $registryName = "$($font.Name) (dotfiles)" + try { + $registeredPath = Get-ItemPropertyValue -Path $fontRegistryKey -Name $registryName -ErrorAction Stop + } + catch { + Fail "font registry entry is missing: $registryName" + } + + if ([string]$registeredPath -ne $destination) { + Fail "font registry entry '$registryName' points to '$registeredPath' instead of '$destination'" + } +} + +Write-Host "Windows font assertions passed for $($sourceFonts.Count) installed font files." From 28ff05c7c1e682f5dace60a2682b37ec559c17ba Mon Sep 17 00:00:00 2001 From: Julio Rios <31230801+jsilverdev@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:14:23 -0500 Subject: [PATCH 2/4] Harden Windows font provisioning --- install.ps1 | 179 ++++++++++++++++++++++++++------- tests/windows/assert-fonts.ps1 | 60 ++++++----- 2 files changed, 176 insertions(+), 63 deletions(-) diff --git a/install.ps1 b/install.ps1 index 3f77cc1..ad35064 100644 --- a/install.ps1 +++ b/install.ps1 @@ -281,74 +281,179 @@ function Install-OptionalApps { Refresh-Path } +function Invoke-FontDownload { + param( + [Parameter(Mandatory)][string]$Uri, + [Parameter(Mandatory)][string]$Destination + ) + + $curl = Get-Command curl.exe -ErrorAction SilentlyContinue + if ($null -eq $curl) { + throw "curl.exe is required to download fonts." + } + + & $curl.Source --fail --silent --show-error --location $Uri --output $Destination + if ($LASTEXITCODE -ne 0) { + throw "Font download failed from $Uri (exit code $LASTEXITCODE)." + } +} + +function Expand-FontArchive { + param( + [Parameter(Mandatory)][string]$Archive, + [Parameter(Mandatory)][string]$Destination + ) + + $tar = Get-Command tar.exe -ErrorAction SilentlyContinue + if ($null -eq $tar) { + throw "tar.exe is required to extract font archives." + } + + Remove-Item -LiteralPath $Destination -Recurse -Force -ErrorAction SilentlyContinue + New-Item -ItemType Directory -Path $Destination -Force | Out-Null + + & $tar.Source -xf $Archive -C $Destination + if ($LASTEXITCODE -ne 0) { + throw "Font archive extraction failed for $Archive (exit code $LASTEXITCODE)." + } +} + +function Get-LatestReleaseAssetUrl { + param( + [Parameter(Mandatory)][string]$Repository, + [Parameter(Mandatory)][string]$AssetPattern + ) + + $releaseJson = [IO.Path]::GetTempFileName() + try { + Invoke-FontDownload -Uri "https://api.github.com/repos/$Repository/releases/latest" -Destination $releaseJson + $document = [Text.Json.JsonDocument]::Parse([IO.File]::ReadAllText($releaseJson)) + try { + foreach ($asset in $document.RootElement.GetProperty("assets").EnumerateArray()) { + $name = $asset.GetProperty("name").GetString() + if ($name -match $AssetPattern) { + return $asset.GetProperty("browser_download_url").GetString() + } + } + } + finally { + $document.Dispose() + } + } + finally { + Remove-Item -LiteralPath $releaseJson -Force -ErrorAction SilentlyContinue + } + + throw "Unable to locate release asset '$AssetPattern' in $Repository." +} + function Download-Fonts { $fonts = Join-Path $RepoRoot "fonts" New-Item -ItemType Directory -Force -Path $fonts | Out-Null - if (-not (Test-Path (Join-Path $fonts "CascadiaCode.ttf"))) { - $release = Invoke-RestMethod -Uri "https://api.github.com/repos/microsoft/cascadia-code/releases/latest" -Headers @{ "User-Agent" = "PowerShell" } - $asset = @($release.assets | Where-Object name -Match '^CascadiaCode-.*\.zip$' | Select-Object -First 1) - if ($asset.Count -ne 1) { throw "Unable to locate the Cascadia Code ZIP asset in the latest GitHub release." } + if (@(Get-ChildItem -LiteralPath $fonts -Filter "CascadiaCode*.ttf" -File -ErrorAction SilentlyContinue).Count -eq 0) { + $assetUrl = Get-LatestReleaseAssetUrl -Repository "microsoft/cascadia-code" -AssetPattern '^CascadiaCode-.*\.zip$' + $archive = Join-Path $fonts "CascadiaCode.zip" + $extract = Join-Path $fonts ".extract-CascadiaCode" - $zip = Join-Path $fonts "CascadiaCode.zip" - $extract = Join-Path $fonts "CascadiaCode" - Invoke-WebRequest -Uri $asset[0].browser_download_url -OutFile $zip - Expand-Archive $zip -DestinationPath $extract -Force - Remove-Item -Recurse -Force (Join-Path $extract "ttf\static") -ErrorAction SilentlyContinue - Get-ChildItem -Path $extract -Filter *.ttf -Recurse -File | Move-Item -Destination $fonts -Force - Remove-Item -Recurse -Force $zip, $extract + try { + Write-Host "Downloading Cascadia Code..." -ForegroundColor Cyan + Invoke-FontDownload -Uri $assetUrl -Destination $archive + Expand-FontArchive -Archive $archive -Destination $extract + Remove-Item -Recurse -Force (Join-Path $extract "ttf\static") -ErrorAction SilentlyContinue + + $fontFiles = @(Get-ChildItem -LiteralPath $extract -Filter *.ttf -Recurse -File) + if ($fontFiles.Count -eq 0) { + throw "Cascadia Code archive did not contain any TTF files." + } + $fontFiles | Move-Item -Destination $fonts -Force + } + finally { + Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath $extract -Recurse -Force -ErrorAction SilentlyContinue + } } - $nerdRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/ryanoasis/nerd-fonts/releases/latest" -Headers @{ "User-Agent" = "PowerShell" } foreach ($font in @( - @{ folder = (Join-Path $fonts "CaskaydiaCoveNerdFont"); filename = "CascadiaCode" }, - @{ folder = (Join-Path $fonts "CaskaydiaMonoNerdFont"); filename = "CascadiaMono" } + @{ Name = "Caskaydia Cove Nerd Font"; Asset = "CascadiaCode"; Pattern = "CaskaydiaCove*.ttf" }, + @{ Name = "Caskaydia Mono Nerd Font"; Asset = "CascadiaMono"; Pattern = "CaskaydiaMono*.ttf" } )) { - if (Test-Path "$($font.folder)-Regular.ttf") { continue } - $zip = "$($font.folder).zip" - Invoke-WebRequest -Uri "https://github.com/ryanoasis/nerd-fonts/releases/download/$($nerdRelease.tag_name)/$($font.filename).zip" -OutFile $zip - Expand-Archive $zip -DestinationPath $font.folder -Force - Get-ChildItem -Path $font.folder -Filter *.ttf -Recurse -File | Move-Item -Destination $fonts -Force - Remove-Item -Recurse -Force $zip, $font.folder + if (@(Get-ChildItem -LiteralPath $fonts -Filter $font.Pattern -File -ErrorAction SilentlyContinue).Count -gt 0) { + continue + } + + $archive = Join-Path $fonts "$($font.Asset).tar.xz" + $extract = Join-Path $fonts ".extract-$($font.Asset)" + + try { + Write-Host "Downloading $($font.Name)..." -ForegroundColor Cyan + Invoke-FontDownload -Uri "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/$($font.Asset).tar.xz" -Destination $archive + Expand-FontArchive -Archive $archive -Destination $extract + + $fontFiles = @(Get-ChildItem -LiteralPath $extract -Filter *.ttf -Recurse -File) + if ($fontFiles.Count -eq 0) { + throw "$($font.Name) archive did not contain any TTF files." + } + $fontFiles | Move-Item -Destination $fonts -Force + } + finally { + Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath $extract -Recurse -Force -ErrorAction SilentlyContinue + } + } + + foreach ($requiredPattern in @("CascadiaCode*.ttf", "CaskaydiaCove*.ttf", "CaskaydiaMono*.ttf")) { + if (@(Get-ChildItem -LiteralPath $fonts -Filter $requiredPattern -File -ErrorAction SilentlyContinue).Count -eq 0) { + throw "Expected font files are missing after download: $requiredPattern" + } } } function Install-UserFonts { $sourceDir = Join-Path $RepoRoot "fonts" $userFontsDir = Join-Path $env:LOCALAPPDATA "Microsoft\Windows\Fonts" - $fontRegistryKey = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" + $fontRegistrySubKey = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" if (-not (Test-Path -LiteralPath $sourceDir -PathType Container)) { throw "Font source directory not found: $sourceDir" } New-Item -ItemType Directory -Path $userFontsDir -Force | Out-Null - New-Item -Path $fontRegistryKey -Force | Out-Null - $sourceFonts = @(Get-ChildItem -Path $sourceDir -Include *.otc,*.otf,*.ttc,*.ttf -Recurse -File) + $sourceFonts = @(Get-ChildItem -Path $sourceDir -Include *.otc,*.otf,*.ttc,*.ttf -Recurse -File | Sort-Object Name -Unique) if ($sourceFonts.Count -eq 0) { throw "No font files were downloaded to $sourceDir." } - foreach ($font in $sourceFonts | Sort-Object Name -Unique) { - $destination = Join-Path $userFontsDir $font.Name - $registryName = "$($font.Name) (dotfiles)" + $registryKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey($fontRegistrySubKey) + if ($null -eq $registryKey) { + throw "Unable to open the current-user font registry key." + } - if (-not (Test-Path -LiteralPath $destination -PathType Leaf)) { - Copy-Item -LiteralPath $font.FullName -Destination $destination - } + try { + foreach ($font in $sourceFonts) { + $destination = Join-Path $userFontsDir $font.Name + $registryName = "$($font.Name) (dotfiles)" - $registeredPath = $null - try { - $registeredPath = Get-ItemPropertyValue -Path $fontRegistryKey -Name $registryName -ErrorAction Stop - } - catch { - } + if (-not (Test-Path -LiteralPath $destination -PathType Leaf)) { + Copy-Item -LiteralPath $font.FullName -Destination $destination + } - if ($registeredPath -ne $destination) { - New-ItemProperty -Path $fontRegistryKey -Name $registryName -Value $destination -PropertyType String -Force | Out-Null + $registeredPath = $registryKey.GetValue( + $registryName, + $null, + [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames + ) + if ([string]$registeredPath -ne $destination) { + $registryKey.SetValue($registryName, $destination, [Microsoft.Win32.RegistryValueKind]::String) + } } } + finally { + $registryKey.Dispose() + } + + Write-Host "Installed/registered $($sourceFonts.Count) current-user font files." -ForegroundColor Green } function Configure-Git { diff --git a/tests/windows/assert-fonts.ps1 b/tests/windows/assert-fonts.ps1 index ec71457..e370a0c 100644 --- a/tests/windows/assert-fonts.ps1 +++ b/tests/windows/assert-fonts.ps1 @@ -11,7 +11,7 @@ function Fail([string]$Message) { $sourceDir = Join-Path $RepoRoot "fonts" $userFontsDir = Join-Path $env:LOCALAPPDATA "Microsoft\Windows\Fonts" -$fontRegistryKey = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" +$fontRegistrySubKey = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" if (-not (Test-Path -LiteralPath $sourceDir -PathType Container)) { Fail "font source directory is missing: $sourceDir" @@ -19,43 +19,51 @@ if (-not (Test-Path -LiteralPath $sourceDir -PathType Container)) { if (-not (Test-Path -LiteralPath $userFontsDir -PathType Container)) { Fail "current-user font directory is missing: $userFontsDir" } -if (-not (Test-Path -LiteralPath $fontRegistryKey)) { - Fail "current-user font registry key is missing: $fontRegistryKey" -} $sourceFonts = @(Get-ChildItem -Path $sourceDir -Include *.otc,*.otf,*.ttc,*.ttf -Recurse -File | Sort-Object Name -Unique) if ($sourceFonts.Count -eq 0) { Fail "no downloaded fonts were found in $sourceDir" } -$requiredPatterns = @( - "CascadiaCode*.ttf", - "CaskaydiaCoveNerdFont*.ttf", - "CaskaydiaMonoNerdFont*.ttf" -) -foreach ($pattern in $requiredPatterns) { - if (@($sourceFonts | Where-Object Name -Like $pattern).Count -eq 0) { - Fail "expected font family is missing from the downloaded source: $pattern" +foreach ($requiredPattern in @("CascadiaCode*.ttf", "CaskaydiaCove*.ttf", "CaskaydiaMono*.ttf")) { + if (@($sourceFonts | Where-Object Name -Like $requiredPattern).Count -eq 0) { + Fail "expected font family is missing from the downloaded source: $requiredPattern" } } -foreach ($font in $sourceFonts) { - $destination = Join-Path $userFontsDir $font.Name - if (-not (Test-Path -LiteralPath $destination -PathType Leaf)) { - Fail "font was not installed for the current user: $destination" - } +$registryKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($fontRegistrySubKey, $false) +if ($null -eq $registryKey) { + Fail "current-user font registry key is missing: HKCU\$fontRegistrySubKey" +} - $registryName = "$($font.Name) (dotfiles)" - try { - $registeredPath = Get-ItemPropertyValue -Path $fontRegistryKey -Name $registryName -ErrorAction Stop - } - catch { - Fail "font registry entry is missing: $registryName" - } +try { + foreach ($font in $sourceFonts) { + $destination = Join-Path $userFontsDir $font.Name + if (-not (Test-Path -LiteralPath $destination -PathType Leaf)) { + Fail "font was not installed for the current user: $destination" + } - if ([string]$registeredPath -ne $destination) { - Fail "font registry entry '$registryName' points to '$registeredPath' instead of '$destination'" + $registryName = "$($font.Name) (dotfiles)" + $registeredPath = $registryKey.GetValue( + $registryName, + $null, + [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames + ) + if ($null -eq $registeredPath) { + Fail "font registry entry is missing: $registryName" + } + + if (-not [string]::Equals( + [IO.Path]::GetFullPath([string]$registeredPath), + [IO.Path]::GetFullPath($destination), + [StringComparison]::OrdinalIgnoreCase + )) { + Fail "font registry entry '$registryName' points to '$registeredPath' instead of '$destination'" + } } } +finally { + $registryKey.Dispose() +} Write-Host "Windows font assertions passed for $($sourceFonts.Count) installed font files." From 10a53f1c18179e8cb5911dca6c2e1125abd77ed1 Mon Sep 17 00:00:00 2001 From: Julio Rios <31230801+jsilverdev@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:15:29 -0500 Subject: [PATCH 3/4] Trigger Windows font validation From 98fb4309e3dd5363a860a7b76fb69c952fb2ab28 Mon Sep 17 00:00:00 2001 From: Julio Rios <31230801+jsilverdev@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:16:08 -0500 Subject: [PATCH 4/4] Document Windows integration assertions --- tests/windows/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/windows/README.md diff --git a/tests/windows/README.md b/tests/windows/README.md new file mode 100644 index 0000000..5fc1824 --- /dev/null +++ b/tests/windows/README.md @@ -0,0 +1,10 @@ +# Windows integration tests + +The Windows jobs exercise the real non-interactive bootstrap and update paths under both the normal execution policy and `AllSigned`. + +- `assert-migration.ps1` verifies recovery from broken managed links without modifying unrelated broken links. +- `assert-fonts.ps1` verifies that every downloaded baseline font exists in the current-user Fonts directory and has a matching HKCU font registration. +- `assert-state.ps1` validates the normal-policy runtime state. +- `assert-allsigned.ps1` validates signatures, trusted certificate state, modules, and profile startup under `AllSigned`. + +Scripts executed after `AllSigned` becomes effective must run through the repository CMD signing bridge.