diff --git a/assets/branding/lasalle-mascot-account.png b/assets/branding/lasalle-mascot-account.png new file mode 100644 index 0000000..8aa46a5 Binary files /dev/null and b/assets/branding/lasalle-mascot-account.png differ diff --git a/docs/architecture.md b/docs/architecture.md index 11c685c..a4bf07c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -86,7 +86,9 @@ being grouped below the anonymous **Other user** tile. Machine policy assigns the SGU CLSID as the default provider, hides the last signed-in identity, and disables local-user enumeration while retaining the built-in Microsoft password provider and its **Other user** recovery path. The computer GPO also applies -Windows' native default account picture to named Windows accounts. It enumerates one +Windows' native default account picture to named Windows accounts; client +enrollment installs the La Salle mascot bitmap in Windows' standard account-picture +location before that GPO takes effect. It enumerates one `CPFT_TILE_IMAGE` and places the `CPFT_LARGE_TEXT` heading immediately after it with `CPFS_DISPLAY_IN_SELECTED_TILE`. LogonUI owns field typography and vertical tile order: on Windows 10 and 11, the account-name title used by **Other user** diff --git a/scripts/Install-CredentialProvider.ps1 b/scripts/Install-CredentialProvider.ps1 index 9712f84..39ab8b2 100644 --- a/scripts/Install-CredentialProvider.ps1 +++ b/scripts/Install-CredentialProvider.ps1 @@ -35,6 +35,8 @@ $providerRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authent $classRegistryPath = "HKLM:\SOFTWARE\Classes\CLSID\$providerClassId\InprocServer32" $defaultProviderPolicyPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System' $interactiveLogonPolicyPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' +$accountPictureSourcePath = Join-Path $PublishPath 'branding\user.png' +$accountPictureDirectory = Join-Path $env:ProgramData 'Microsoft\User Account Pictures' $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) @@ -56,6 +58,70 @@ function Test-DotNet10Runtime { return $false } +function Install-DefaultAccountPicture { + param([Parameter(Mandatory)][string]$SourcePath) + + if (-not (Test-Path -LiteralPath $SourcePath -PathType Leaf)) { + return $false + } + + Add-Type -AssemblyName System.Drawing + New-Item -ItemType Directory -Path $accountPictureDirectory -Force | Out-Null + + function Save-AccountPicture { + param( + [Parameter(Mandatory)][Drawing.Image]$Image, + [Parameter(Mandatory)][string]$Path, + [Parameter(Mandatory)][Drawing.Imaging.ImageFormat]$Format + ) + + $stream = [IO.MemoryStream]::new() + try { + $Image.Save($stream, $Format) + [IO.File]::WriteAllBytes($Path, $stream.ToArray()) + } + finally { + $stream.Dispose() + } + } + + $source = [Drawing.Image]::FromFile($SourcePath) + try { + foreach ($size in @(192, 48, 40, 32)) { + $bitmap = [Drawing.Bitmap]::new($size, $size) + try { + $graphics = [Drawing.Graphics]::FromImage($bitmap) + try { + $graphics.Clear([Drawing.Color]::Transparent) + $graphics.InterpolationMode = [Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $graphics.DrawImage($source, [Drawing.Rectangle]::new(0, 0, $size, $size)) + Save-AccountPicture -Image $bitmap ` + -Path (Join-Path $accountPictureDirectory "user-$size.png") ` + -Format ([Drawing.Imaging.ImageFormat]::Png) + } + finally { + $graphics.Dispose() + } + } + finally { + $bitmap.Dispose() + } + } + + Save-AccountPicture -Image $source ` + -Path (Join-Path $accountPictureDirectory 'user.png') ` + -Format ([Drawing.Imaging.ImageFormat]::Png) + Save-AccountPicture -Image $source ` + -Path (Join-Path $accountPictureDirectory 'user.bmp') ` + -Format ([Drawing.Imaging.ImageFormat]::Bmp) + } + finally { + $source.Dispose() + } + + return $true +} + if (-not (Test-DotNet10Runtime)) { if (-not $InstallDotNetRuntime) { throw 'Microsoft .NET 10 x64 runtime is required. Re-run with -InstallDotNetRuntime or install it first.' @@ -166,6 +232,10 @@ if ($PSCmdlet.ShouldProcess($installPath, 'Install and register the SGU Credenti [IO.File]::WriteAllText($completeMarker, $packageHash, [Text.UTF8Encoding]::new($false)) } + # The domain GPO selects the Windows default account picture. Install its + # branded bitmap during enrollment so no per-machine manual setup is needed. + Install-DefaultAccountPicture -SourcePath $accountPictureSourcePath | Out-Null + New-Item -ItemType Directory -Path (Split-Path $settingsPath -Parent) -Force | Out-Null $settingsJson = @{ BrokerEndpoint = $BrokerEndpoint diff --git a/scripts/New-SguBootstrapPackages.ps1 b/scripts/New-SguBootstrapPackages.ps1 index 52f1274..abb9b3e 100644 --- a/scripts/New-SguBootstrapPackages.ps1 +++ b/scripts/New-SguBootstrapPackages.ps1 @@ -111,6 +111,8 @@ Copy-Item -Path (Join-Path $providerOutput '*') ` -Destination (New-Item -ItemType Directory ` -Path (Join-Path $clientRoot 'payload\credential-provider') -Force).FullName ` -Recurse -Force +Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\lasalle-mascot-account.png') ` + -Destination (Join-Path $clientRoot 'payload\credential-provider\branding\user.png') Copy-RequiredFile -Source $runtimeInstaller.FullName ` -Destination (Join-Path $clientRoot "payload\prerequisites\$($runtimeInstaller.Name)") Write-PackageManifest -PackageRoot $clientRoot -PackageVersion $Version -PackageKind Client