Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2a40f051b | ||
|
|
17c8960cdd | ||
|
|
b5f526e244 | ||
|
|
b3e40fabb5 | ||
|
|
a850b56a02 |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
@@ -85,7 +85,10 @@ The generic SGU credential is rendered as a dedicated branded tile instead of
|
||||
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. It enumerates one
|
||||
provider and its **Other user** recovery path. The computer GPO also applies
|
||||
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**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -61,15 +61,29 @@ elseif (-not $existingLinkEnabled -and
|
||||
|
||||
$dataCollectionKey = 'HKLM\Software\Policies\Microsoft\Windows\DataCollection'
|
||||
$powerPolicyRoot = 'HKLM\Software\Policies\Microsoft\Power\PowerSettings'
|
||||
$credentialProviderPolicyKey = 'HKLM\Software\Policies\Microsoft\Windows\System'
|
||||
$interactiveLogonPolicyKey = 'HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System'
|
||||
$accountPicturePolicyKey = 'HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer'
|
||||
$providerClassId = '{D789CFD8-5AD4-489F-9B83-7EB5D9D09335}'
|
||||
$policies = @(
|
||||
@{ Key = $dataCollectionKey; Name = 'AllowTelemetry'; Value = 0 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableTelemetryOptInSettingsUx'; Value = 1 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableTelemetryOptInChangeNotification'; Value = 1 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableDiagnosticDataViewer'; Value = 1 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\OOBE'; Name = 'DisablePrivacyExperience'; Value = 1 },
|
||||
@{ Key = 'HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System'; Name = 'EnableFirstLogonAnimation'; Value = 0 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\LocationAndSensors'; Name = 'DisableLocation'; Value = 1 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\AppPrivacy'; Name = 'LetAppsAccessLocation'; Value = 2 }
|
||||
@{ Key = $dataCollectionKey; Name = 'AllowTelemetry'; Type = 'DWord'; Value = 0 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableTelemetryOptInSettingsUx'; Type = 'DWord'; Value = 1 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableTelemetryOptInChangeNotification'; Type = 'DWord'; Value = 1 },
|
||||
@{ Key = $dataCollectionKey; Name = 'DisableDiagnosticDataViewer'; Type = 'DWord'; Value = 1 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\OOBE'; Name = 'DisablePrivacyExperience'; Type = 'DWord'; Value = 1 },
|
||||
@{ Key = $interactiveLogonPolicyKey; Name = 'EnableFirstLogonAnimation'; Type = 'DWord'; Value = 0 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\LocationAndSensors'; Name = 'DisableLocation'; Type = 'DWord'; Value = 1 },
|
||||
@{ Key = 'HKLM\Software\Policies\Microsoft\Windows\AppPrivacy'; Name = 'LetAppsAccessLocation'; Type = 'DWord'; Value = 2 },
|
||||
|
||||
# Enrollment selects the provider before domain join; this computer GPO
|
||||
# becomes the authoritative, self-healing configuration afterwards.
|
||||
@{ Key = $credentialProviderPolicyKey; Name = 'DefaultCredentialProvider'; Type = 'String'; Value = $providerClassId },
|
||||
@{ Key = $credentialProviderPolicyKey; Name = 'EnumerateLocalUsers'; Type = 'DWord'; Value = 0 },
|
||||
@{ Key = $interactiveLogonPolicyKey; Name = 'DontDisplayLastUserName'; Type = 'DWord'; Value = 1 },
|
||||
|
||||
# Use Windows' native default account image for named user tiles. LogonUI
|
||||
# retains ownership of the anonymous Other user tile and its circular mask.
|
||||
@{ Key = $accountPicturePolicyKey; Name = 'UseDefaultTile'; Type = 'DWord'; Value = 1 }
|
||||
)
|
||||
|
||||
$powerSettingIds = @(
|
||||
@@ -80,8 +94,8 @@ $powerSettingIds = @(
|
||||
)
|
||||
foreach ($settingId in $powerSettingIds) {
|
||||
$settingKey = "$powerPolicyRoot\$settingId"
|
||||
$policies += @{ Key = $settingKey; Name = 'ACSettingIndex'; Value = 0 }
|
||||
$policies += @{ Key = $settingKey; Name = 'DCSettingIndex'; Value = 0 }
|
||||
$policies += @{ Key = $settingKey; Name = 'ACSettingIndex'; Type = 'DWord'; Value = 0 }
|
||||
$policies += @{ Key = $settingKey; Name = 'DCSettingIndex'; Type = 'DWord'; Value = 0 }
|
||||
}
|
||||
|
||||
foreach ($policy in $policies) {
|
||||
@@ -92,7 +106,7 @@ foreach ($policy in $policies) {
|
||||
-Server $DomainController `
|
||||
-Key $policy.Key `
|
||||
-ValueName $policy.Name `
|
||||
-Type DWord `
|
||||
-Type $policy.Type `
|
||||
-Value $policy.Value | Out-Null
|
||||
}
|
||||
}
|
||||
@@ -105,7 +119,7 @@ foreach ($policy in $policies) {
|
||||
-Server $DomainController `
|
||||
-Key $policy.Key `
|
||||
-ValueName $policy.Name
|
||||
$configuredPolicies[$policy.Name + '@' + $policy.Key] = [int]$configured.Value
|
||||
$configuredPolicies[$policy.Name + '@' + $policy.Key] = $configured.Value
|
||||
}
|
||||
$link = @(Get-GPInheritance -Target $TargetOuDn -Domain $domainName -Server $DomainController).GpoLinks |
|
||||
Where-Object DisplayName -eq $GpoName |
|
||||
|
||||
@@ -1,35 +1,32 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
namespace SGU.CredentialProvider;
|
||||
|
||||
internal static class ProviderTileIcon
|
||||
{
|
||||
public const int Size = 72;
|
||||
// LogonUI enlarges the dedicated-tile artwork. Supply a dense source image
|
||||
// so the mascot remains crisp at the large sign-in surface.
|
||||
public const int Size = 256;
|
||||
private const string MascotResourceName = "SGU.CredentialProvider.Branding.LaSalleMascot.png";
|
||||
|
||||
public static Bitmap Create()
|
||||
{
|
||||
Bitmap bitmap = new(Size, Size, PixelFormat.Format32bppArgb);
|
||||
using Graphics graphics = Graphics.FromImage(bitmap);
|
||||
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
graphics.Clear(Color.Transparent);
|
||||
|
||||
using SolidBrush background = new(Color.FromArgb(0, 83, 155));
|
||||
graphics.FillEllipse(background, 1, 1, Size - 2, Size - 2);
|
||||
|
||||
using Pen key = new(Color.White, 5.5f)
|
||||
{
|
||||
StartCap = LineCap.Round,
|
||||
EndCap = LineCap.Round,
|
||||
LineJoin = LineJoin.Round
|
||||
};
|
||||
|
||||
graphics.DrawEllipse(key, 14, 14, 25, 25);
|
||||
graphics.DrawLine(key, 35, 35, 57, 57);
|
||||
graphics.DrawLine(key, 47, 47, 55, 39);
|
||||
graphics.DrawLine(key, 53, 53, 61, 45);
|
||||
using Stream sourceStream = typeof(ProviderTileIcon).Assembly.GetManifestResourceStream(MascotResourceName)
|
||||
?? throw new InvalidOperationException($"The branded Credential Provider logo '{MascotResourceName}' is unavailable.");
|
||||
using Bitmap mascot = new(sourceStream);
|
||||
using GraphicsPath circularMask = new();
|
||||
circularMask.AddEllipse(0, 0, Size, Size);
|
||||
graphics.SetClip(circularMask);
|
||||
graphics.DrawImage(mascot, new Rectangle(0, 0, Size, Size));
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
<ProjectReference Include="..\SGU.AuthBroker.Core\SGU.AuthBroker.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\..\assets\branding\lasalle-mexico-provider.png"
|
||||
LogicalName="SGU.CredentialProvider.Branding.LaSalleMascot.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||
<_Parameter1>SGU.CredentialProvider.Tests</_Parameter1>
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace SGU.CredentialProvider.Tests;
|
||||
public sealed class ProviderTileIconTests
|
||||
{
|
||||
[Fact]
|
||||
public void ProviderPublishesASeventyTwoPixelLogoForSignInOptions()
|
||||
public void ProviderPublishesAHighResolutionLogoForSignInOptions()
|
||||
{
|
||||
SguCredentialProvider provider = new();
|
||||
|
||||
@@ -20,22 +20,27 @@ public sealed class ProviderTileIconTests
|
||||
Assert.Equal(0, logo.Bitmap.GetPixel(ProviderTileIcon.Size - 1, 0).A);
|
||||
Assert.Equal(0, logo.Bitmap.GetPixel(0, ProviderTileIcon.Size - 1).A);
|
||||
Assert.Equal(0, logo.Bitmap.GetPixel(ProviderTileIcon.Size - 1, ProviderTileIcon.Size - 1).A);
|
||||
Assert.Equal(
|
||||
Color.FromArgb(0, 83, 155).ToArgb(),
|
||||
logo.Bitmap.GetPixel(6, ProviderTileIcon.Size / 2).ToArgb());
|
||||
int lightPixels = 0;
|
||||
int redPixels = 0;
|
||||
int navyPixels = 0;
|
||||
for (int x = 0; x < logo.Bitmap.Width; x++)
|
||||
{
|
||||
for (int y = 0; y < logo.Bitmap.Height; y++)
|
||||
{
|
||||
if (logo.Bitmap.GetPixel(x, y).GetBrightness() > 0.7f)
|
||||
Color pixel = logo.Bitmap.GetPixel(x, y);
|
||||
if (pixel.R > 160 && pixel.G < 100 && pixel.B < 100)
|
||||
{
|
||||
lightPixels++;
|
||||
redPixels++;
|
||||
}
|
||||
|
||||
if (pixel.B > pixel.R && pixel.B > pixel.G && pixel.R < 70)
|
||||
{
|
||||
navyPixels++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.InRange(lightPixels, 200, 2_000);
|
||||
Assert.InRange(redPixels, 1_000, 30_000);
|
||||
Assert.InRange(navyPixels, 1_000, 50_000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user