Add branded default provider and enforced enrollment

This commit is contained in:
2026-09-01 11:20:06 -06:00
parent da01343985
commit f3afd62993
22 changed files with 787 additions and 17 deletions
+3
View File
@@ -23,6 +23,8 @@ param(
[string]$DomainNetbios = 'LCI',
[string]$UpnSuffix = 'lci.lasalle.mx',
[string]$RemoteDesktopGroupDn = '',
[ValidateLength(1, 64)]
[string]$DefaultCompany = 'Universidad La Salle',
[ValidateRange(10, 60)]
[int]$NtlmTimeoutSeconds = 15,
[switch]$CreateMissingOus,
@@ -152,6 +154,7 @@ $productionSettings = @{
StudentOuDn = "OU=Alumnos,OU=Usuarios-SGU,$BaseDn"
AdministrativeOuDn = "OU=Administrativos,OU=Usuarios-SGU,$BaseDn"
RemoteDesktopGroupDn = $RemoteDesktopGroupDn
DefaultCompany = $DefaultCompany
CreateMissingOus = [bool]$CreateMissingOus
}
}
+127
View File
@@ -0,0 +1,127 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string]$PublishPath,
[Parameter(Mandatory)]
[ValidatePattern('^https://')]
[string]$BrokerEndpoint,
[Parameter(Mandatory)]
[ValidatePattern('^[0-9A-Fa-f ]{40,59}$')]
[string]$ClientCertificateThumbprint,
[Parameter(Mandatory)]
[ValidatePattern('^[0-9A-Fa-f ]{40,59}$')]
[string]$ServerCertificateThumbprint,
[PSCredential]$DomainCredential,
[string]$DomainName = 'lci.lasalle.mx',
[string]$DomainNetbios = 'LCI',
[string]$NewComputerName,
[string]$NetworkInterfaceAlias = 'Ethernet',
[string[]]$DomainDnsServerAddresses = @('192.168.50.10'),
[string]$RemoteDesktopPrincipal = 'LCI\SG-Laboratorio-Usuarios-RDP',
[string]$DotNetRuntimeInstallerPath,
[switch]$SkipRestart
)
$ErrorActionPreference = 'Stop'
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'Run this script from an elevated Windows PowerShell session.'
}
foreach ($scriptName in @(
'Install-CredentialProvider.ps1',
'Install-SguEnrollmentGuard.ps1',
'Test-SguClientEnrollment.ps1',
'Repair-SguClientEnrollment.ps1',
'Enable-LabRemoteAccess.ps1')) {
if (-not (Test-Path -LiteralPath (Join-Path $PSScriptRoot $scriptName) -PathType Leaf)) {
throw "$scriptName must be beside Enroll-SguDomainClient.ps1."
}
}
$computer = Get-CimInstance Win32_ComputerSystem
if ($computer.PartOfDomain -and $computer.Domain -ne $DomainName) {
throw "The computer is already joined to the unexpected domain $($computer.Domain)."
}
$installParams = @{
PublishPath = $PublishPath
BrokerEndpoint = $BrokerEndpoint
ClientCertificateThumbprint = $ClientCertificateThumbprint
ServerCertificateThumbprint = $ServerCertificateThumbprint
DomainNetbios = $DomainNetbios
TimeoutSeconds = 20
}
if ($DotNetRuntimeInstallerPath) {
$installParams.InstallDotNetRuntime = $true
$installParams.DotNetRuntimeInstallerPath = $DotNetRuntimeInstallerPath
}
$guardParams = @{
PublishPath = $PublishPath
BrokerEndpoint = $BrokerEndpoint
ClientCertificateThumbprint = $ClientCertificateThumbprint
ServerCertificateThumbprint = $ServerCertificateThumbprint
DomainNetbios = $DomainNetbios
TimeoutSeconds = 20
RemoteDesktopPrincipal = $RemoteDesktopPrincipal
DotNetRuntimeInstallerPath = $DotNetRuntimeInstallerPath
}
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Install and verify SGU before joining the domain')) {
& (Join-Path $PSScriptRoot 'Install-CredentialProvider.ps1') @installParams | Out-Null
& (Join-Path $PSScriptRoot 'Install-SguEnrollmentGuard.ps1') @guardParams | Out-Null
$preJoin = & (Join-Path $PSScriptRoot 'Test-SguClientEnrollment.ps1') `
-RequireBrokerHealth
if (-not $preJoin.IsValid) {
throw "Domain join refused because SGU enrollment is invalid: $($preJoin.Issues -join ' ')"
}
if ($computer.PartOfDomain) {
& (Join-Path $PSScriptRoot 'Enable-LabRemoteAccess.ps1') `
-RemoteDesktopPrincipal $RemoteDesktopPrincipal `
-EnableAdministrativeFirewallGroups | Out-Null
return & (Join-Path $PSScriptRoot 'Test-SguClientEnrollment.ps1') `
-RequireDomainJoined `
-RequireRemoteAccess `
-RemoteDesktopPrincipal $RemoteDesktopPrincipal
}
Set-DnsClientServerAddress `
-InterfaceAlias $NetworkInterfaceAlias `
-ServerAddresses $DomainDnsServerAddresses
Resolve-DnsName -Type SRV "_ldap._tcp.dc._msdcs.$DomainName" -ErrorAction Stop | Out-Null
if (-not $DomainCredential) {
$DomainCredential = Get-Credential `
-UserName "$DomainNetbios\Administrator" `
-Message "Credential permitted to join this computer to $DomainName"
}
$joinParams = @{
DomainName = $DomainName
Credential = $DomainCredential
Force = $true
}
if ($NewComputerName) {
$joinParams.NewName = $NewComputerName
}
Add-Computer @joinParams
if (-not $SkipRestart) {
Restart-Computer -Force
}
}
[pscustomobject]@{
ComputerName = if ($NewComputerName) { $NewComputerName } else { $env:COMPUTERNAME }
DomainName = $DomainName
ProviderValidatedBeforeJoin = $true
RestartRequired = [bool]$SkipRestart
}
+25 -1
View File
@@ -20,6 +20,8 @@ param(
[ValidateRange(2, 60)]
[int]$TimeoutSeconds = 20,
[switch]$DoNotSetAsDefaultCredentialProvider,
[switch]$InstallDotNetRuntime,
[string]$DotNetRuntimeInstallerPath
@@ -31,6 +33,7 @@ $installRoot = Join-Path $env:ProgramFiles 'SGU\CredentialProvider'
$settingsPath = Join-Path $env:ProgramData 'SGU\CredentialProvider\settings.json'
$providerRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\$providerClassId"
$classRegistryPath = "HKLM:\SOFTWARE\Classes\CLSID\$providerClassId\InprocServer32"
$defaultProviderPolicyPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System'
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
@@ -175,7 +178,27 @@ if ($PSCmdlet.ShouldProcess($installPath, 'Install and register the SGU Credenti
New-ItemProperty -Path $classRegistryPath -Name ThreadingModel -Value Both -PropertyType String -Force | Out-Null
New-Item -Path $providerRegistryPath -Force | Out-Null
Set-Item -Path $providerRegistryPath -Value 'SGU Institutional Login'
Set-Item -Path $providerRegistryPath -Value 'Universidad La Salle · Acceso SGU'
if (-not $DoNotSetAsDefaultCredentialProvider) {
New-Item -Path $defaultProviderPolicyPath -Force | Out-Null
New-ItemProperty -Path $defaultProviderPolicyPath `
-Name DefaultCredentialProvider `
-Value $providerClassId `
-PropertyType String `
-Force | Out-Null
}
}
$defaultProviderConfigured = $false
try {
$defaultProviderConfigured = (Get-ItemPropertyValue `
-LiteralPath $defaultProviderPolicyPath `
-Name DefaultCredentialProvider `
-ErrorAction Stop) -eq $providerClassId
}
catch {
# An explicitly opted-out installation has no default-provider policy.
}
[pscustomobject]@{
@@ -183,5 +206,6 @@ if ($PSCmdlet.ShouldProcess($installPath, 'Install and register the SGU Credenti
InstallPath = $installPath
SettingsPath = $settingsPath
Registered = Test-Path -LiteralPath $providerRegistryPath
DefaultProviderConfigured = $defaultProviderConfigured
SystemPasswordProviderPreserved = $true
}
+134
View File
@@ -0,0 +1,134 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string]$PublishPath,
[Parameter(Mandatory)]
[ValidatePattern('^https://')]
[string]$BrokerEndpoint,
[Parameter(Mandatory)]
[ValidatePattern('^[0-9A-Fa-f ]{40,59}$')]
[string]$ClientCertificateThumbprint,
[Parameter(Mandatory)]
[ValidatePattern('^[0-9A-Fa-f ]{40,59}$')]
[string]$ServerCertificateThumbprint,
[string]$DomainNetbios = 'LCI',
[ValidateRange(2, 60)]
[int]$TimeoutSeconds = 20,
[string]$RemoteDesktopPrincipal = 'LCI\SG-Laboratorio-Usuarios-RDP',
[string]$DotNetRuntimeInstallerPath
)
$ErrorActionPreference = 'Stop'
$taskName = 'SGU-CredentialProvider-EnrollmentGuard'
$enrollmentRoot = Join-Path $env:ProgramData 'SGU\Enrollment'
$sourceScripts = @(
'Install-CredentialProvider.ps1',
'Enable-LabRemoteAccess.ps1',
'Test-SguClientEnrollment.ps1',
'Repair-SguClientEnrollment.ps1'
)
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'Run this script from an elevated Windows PowerShell session.'
}
foreach ($scriptName in $sourceScripts) {
if (-not (Test-Path -LiteralPath (Join-Path $PSScriptRoot $scriptName) -PathType Leaf)) {
throw "$scriptName must be beside Install-SguEnrollmentGuard.ps1."
}
}
$requiredProviderFile = Join-Path $PublishPath 'SGU.CredentialProvider.comhost.dll'
if (-not (Test-Path -LiteralPath $requiredProviderFile -PathType Leaf)) {
throw 'PublishPath does not contain the Credential Provider package.'
}
if ($DotNetRuntimeInstallerPath -and
-not (Test-Path -LiteralPath $DotNetRuntimeInstallerPath -PathType Leaf)) {
throw 'DotNetRuntimeInstallerPath does not exist.'
}
if ($PSCmdlet.ShouldProcess($enrollmentRoot, 'Install the SGU enrollment repair guard')) {
New-Item -ItemType Directory -Path $enrollmentRoot -Force | Out-Null
$packageId = '{0}-{1}' -f (Get-Date -Format 'yyyyMMddHHmmss'), ([Guid]::NewGuid().ToString('N').Substring(0, 8))
$guardPublishPath = Join-Path $enrollmentRoot "packages\$packageId"
New-Item -ItemType Directory -Path $guardPublishPath -Force | Out-Null
Copy-Item -Path (Join-Path $PublishPath '*') -Destination $guardPublishPath -Recurse -Force
foreach ($scriptName in $sourceScripts) {
Copy-Item -LiteralPath (Join-Path $PSScriptRoot $scriptName) `
-Destination (Join-Path $enrollmentRoot $scriptName) -Force
}
$guardRuntimeInstaller = $null
if ($DotNetRuntimeInstallerPath) {
$runtimeDirectory = Join-Path $enrollmentRoot 'prerequisites'
New-Item -ItemType Directory -Path $runtimeDirectory -Force | Out-Null
$guardRuntimeInstaller = Join-Path $runtimeDirectory (Split-Path $DotNetRuntimeInstallerPath -Leaf)
Copy-Item -LiteralPath $DotNetRuntimeInstallerPath -Destination $guardRuntimeInstaller -Force
}
$guardConfiguration = [ordered]@{
PublishPath = $guardPublishPath
BrokerEndpoint = $BrokerEndpoint
ClientCertificateThumbprint = ($ClientCertificateThumbprint -replace ' ', '')
ServerCertificateThumbprint = ($ServerCertificateThumbprint -replace ' ', '')
DomainNetbios = $DomainNetbios
TimeoutSeconds = $TimeoutSeconds
RemoteDesktopPrincipal = $RemoteDesktopPrincipal
DotNetRuntimeInstallerPath = $guardRuntimeInstaller
}
$configurationPath = Join-Path $enrollmentRoot 'enrollment.json'
[IO.File]::WriteAllText(
$configurationPath,
($guardConfiguration | ConvertTo-Json),
[Text.UTF8Encoding]::new($false))
$acl = Get-Acl -LiteralPath $enrollmentRoot
$acl.SetAccessRuleProtection($true, $false)
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new(
'SYSTEM', 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new(
'BUILTIN\Administrators', 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))
Set-Acl -LiteralPath $enrollmentRoot -AclObject $acl
$repairScript = Join-Path $enrollmentRoot 'Repair-SguClientEnrollment.ps1'
$powerShell = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$action = New-ScheduledTaskAction -Execute $powerShell `
-Argument "-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$repairScript`""
$startupTrigger = New-ScheduledTaskTrigger -AtStartup
# Give domain networking and Group Policy time to initialize before the
# repair script resolves domain principals and validates remote access.
$startupTrigger.Delay = 'PT1M'
$triggers = @(
$startupTrigger,
(New-ScheduledTaskTrigger -Daily -At '3:00 AM')
)
$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName $taskName `
-Action $action `
-Trigger $triggers `
-Settings $settings `
-User 'SYSTEM' `
-RunLevel Highest `
-Force | Out-Null
& $repairScript -ConfigurationPath $configurationPath | Out-Null
}
[pscustomobject]@{
EnrollmentRoot = $enrollmentRoot
TaskName = $taskName
TaskState = (Get-ScheduledTask -TaskName $taskName).State
ConfigurationPath = Join-Path $enrollmentRoot 'enrollment.json'
}
+67
View File
@@ -0,0 +1,67 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string]$CertificatePath,
[string]$BrokerConfigurationPath = 'C:\Program Files\SGU\AuthBroker\appsettings.Production.json'
)
$ErrorActionPreference = 'Stop'
$serviceName = 'SGUAuthBroker'
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'Run this script from an elevated PowerShell session on the broker server.'
}
if (-not (Test-Path -LiteralPath $CertificatePath -PathType Leaf)) {
throw 'CertificatePath does not exist.'
}
if (-not (Test-Path -LiteralPath $BrokerConfigurationPath -PathType Leaf)) {
throw 'The broker production configuration does not exist.'
}
$candidate = [Security.Cryptography.X509Certificates.X509Certificate2]::new($CertificatePath)
if ($candidate.NotBefore -gt (Get-Date) -or $candidate.NotAfter -lt (Get-Date)) {
throw 'The client certificate is outside its validity period.'
}
$clientAuthenticationOid = '1.3.6.1.5.5.7.3.2'
$ekuExtension = $candidate.Extensions |
Where-Object { $_.Oid.Value -eq '2.5.29.37' } |
Select-Object -First 1
if (-not $ekuExtension -or
$ekuExtension.EnhancedKeyUsages.Value -notcontains $clientAuthenticationOid) {
throw 'The certificate is not valid for TLS client authentication.'
}
if ($PSCmdlet.ShouldProcess($candidate.Thumbprint, 'Trust and allow the SGU client certificate')) {
$trustedCertificate = Import-Certificate `
-FilePath $CertificatePath `
-CertStoreLocation Cert:\LocalMachine\Root |
Select-Object -First 1
$configuration = Get-Content -LiteralPath $BrokerConfigurationPath -Raw | ConvertFrom-Json
$allowed = @($configuration.Broker.Tls.AllowedClientThumbprints |
ForEach-Object { $_ -replace ' ', '' })
if ($allowed -notcontains $trustedCertificate.Thumbprint) {
$configuration.Broker.Tls.AllowedClientThumbprints = @($allowed + $trustedCertificate.Thumbprint)
$backupPath = "$BrokerConfigurationPath.before-$($trustedCertificate.Thumbprint.Substring(0, 12)).bak"
Copy-Item -LiteralPath $BrokerConfigurationPath -Destination $backupPath -Force
[IO.File]::WriteAllText(
$BrokerConfigurationPath,
($configuration | ConvertTo-Json -Depth 8),
[Text.UTF8Encoding]::new($false))
}
Restart-Service -Name $serviceName -Force
(Get-Service -Name $serviceName).WaitForStatus(
[System.ServiceProcess.ServiceControllerStatus]::Running,
[TimeSpan]::FromSeconds(20))
}
[pscustomobject]@{
Subject = $candidate.Subject
Thumbprint = $candidate.Thumbprint
NotAfter = $candidate.NotAfter
Allowed = @((Get-Content -LiteralPath $BrokerConfigurationPath -Raw | ConvertFrom-Json).Broker.Tls.AllowedClientThumbprints) -contains $candidate.Thumbprint
ServiceStatus = (Get-Service -Name $serviceName).Status
}
+47
View File
@@ -0,0 +1,47 @@
[CmdletBinding()]
param(
[string]$ConfigurationPath = "$env:ProgramData\SGU\Enrollment\enrollment.json"
)
$ErrorActionPreference = 'Stop'
$configuration = Get-Content -LiteralPath $ConfigurationPath -Raw | ConvertFrom-Json
$enrollmentRoot = Split-Path $ConfigurationPath -Parent
$testScript = Join-Path $enrollmentRoot 'Test-SguClientEnrollment.ps1'
$installScript = Join-Path $enrollmentRoot 'Install-CredentialProvider.ps1'
$remoteAccessScript = Join-Path $enrollmentRoot 'Enable-LabRemoteAccess.ps1'
$before = & $testScript
if (-not $before.IsValid) {
$installParams = @{
PublishPath = [string]$configuration.PublishPath
BrokerEndpoint = [string]$configuration.BrokerEndpoint
ClientCertificateThumbprint = [string]$configuration.ClientCertificateThumbprint
ServerCertificateThumbprint = [string]$configuration.ServerCertificateThumbprint
DomainNetbios = [string]$configuration.DomainNetbios
TimeoutSeconds = [int]$configuration.TimeoutSeconds
}
if ($configuration.DotNetRuntimeInstallerPath) {
$installParams.InstallDotNetRuntime = $true
$installParams.DotNetRuntimeInstallerPath = [string]$configuration.DotNetRuntimeInstallerPath
}
& $installScript @installParams | Out-Null
}
$computer = Get-CimInstance Win32_ComputerSystem
if ($computer.PartOfDomain) {
& $remoteAccessScript `
-RemoteDesktopPrincipal ([string]$configuration.RemoteDesktopPrincipal) `
-EnableAdministrativeFirewallGroups | Out-Null
}
$verificationParams = @{}
if ($computer.PartOfDomain) {
$verificationParams.RequireDomainJoined = $true
$verificationParams.RequireRemoteAccess = $true
$verificationParams.RemoteDesktopPrincipal = [string]$configuration.RemoteDesktopPrincipal
}
$after = & $testScript @verificationParams
$after
if (-not $after.IsValid) {
throw "SGU enrollment repair did not reach a valid state: $($after.Issues -join ' ')"
}
+154
View File
@@ -0,0 +1,154 @@
[CmdletBinding()]
param(
[switch]$RequireDomainJoined,
[switch]$RequireRemoteAccess,
[switch]$RequireBrokerHealth,
[string]$RemoteDesktopPrincipal = 'LCI\SG-Laboratorio-Usuarios-RDP',
[switch]$Enforce
)
$ErrorActionPreference = 'Stop'
$providerClassId = '{D789CFD8-5AD4-489F-9B83-7EB5D9D09335}'
$passwordProviderClassId = '{60B78E88-EAD8-445C-9CFD-0B87F74EA6CD}'
$providerRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\$providerClassId"
$passwordProviderRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\$passwordProviderClassId"
$classRegistryPath = "HKLM:\SOFTWARE\Classes\CLSID\$providerClassId\InprocServer32"
$defaultProviderPolicyPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System'
$settingsPath = Join-Path $env:ProgramData 'SGU\CredentialProvider\settings.json'
$issues = [Collections.Generic.List[string]]::new()
$computer = Get-CimInstance Win32_ComputerSystem
if ($RequireDomainJoined -and -not $computer.PartOfDomain) {
$issues.Add('The computer is not joined to a domain.')
}
$providerRegistered = Test-Path -LiteralPath $providerRegistryPath
if (-not $providerRegistered) {
$issues.Add('The SGU Credential Provider registration is missing.')
}
$registeredDll = $null
if (Test-Path -LiteralPath $classRegistryPath) {
$registeredDll = (Get-Item -LiteralPath $classRegistryPath).GetValue('')
}
$providerBinaryPresent = $registeredDll -and (Test-Path -LiteralPath $registeredDll -PathType Leaf)
if (-not $providerBinaryPresent) {
$issues.Add('The registered SGU COM binary is missing.')
}
$configuredDefault = $null
try {
$configuredDefault = Get-ItemPropertyValue `
-LiteralPath $defaultProviderPolicyPath `
-Name DefaultCredentialProvider `
-ErrorAction Stop
}
catch {
# Report the missing or unreadable policy as a failed enrollment check.
}
$defaultProviderConfigured = $configuredDefault -eq $providerClassId
if (-not $defaultProviderConfigured) {
$issues.Add('The SGU provider is not assigned as the machine default credential provider.')
}
$passwordProviderPreserved = Test-Path -LiteralPath $passwordProviderRegistryPath
if (-not $passwordProviderPreserved) {
$issues.Add('The built-in Microsoft password provider registration is missing.')
}
$settings = $null
try {
$settings = Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json
}
catch {
$issues.Add('The SGU provider settings file is missing or invalid.')
}
$clientCertificatePresent = $false
$serverCertificateTrusted = $false
$brokerHealth = $null
if ($settings) {
$clientCertificate = Get-ChildItem Cert:\LocalMachine\My |
Where-Object Thumbprint -eq $settings.ClientCertificateThumbprint |
Select-Object -First 1
$clientCertificatePresent = $clientCertificate -and $clientCertificate.HasPrivateKey
if (-not $clientCertificatePresent) {
$issues.Add('The client mTLS certificate with private key is missing.')
}
$serverCertificate = Get-ChildItem Cert:\LocalMachine\Root,Cert:\LocalMachine\CA |
Where-Object Thumbprint -eq $settings.ServerCertificateThumbprint |
Select-Object -First 1
$serverCertificateTrusted = [bool]$serverCertificate
if (-not $serverCertificateTrusted) {
$issues.Add('The broker certificate is not trusted by LocalMachine.')
}
if ($RequireBrokerHealth -and $clientCertificatePresent) {
try {
$healthUri = ([Uri]$settings.BrokerEndpoint).GetLeftPart([UriPartial]::Authority) + '/health/live'
$health = Invoke-RestMethod -Uri $healthUri -Certificate $clientCertificate -TimeoutSec 10
$brokerHealth = [string]$health.status
if ($brokerHealth -ne 'ok') {
$issues.Add('The broker health endpoint did not return ok.')
}
}
catch {
$issues.Add("The broker health check failed: $($_.Exception.Message)")
}
}
}
$dotNetRuntimePresent = $false
$dotnetCandidates = @(
(Get-Command dotnet -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -ErrorAction SilentlyContinue),
(Join-Path $env:ProgramFiles 'dotnet\dotnet.exe')
) | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Leaf) } | Select-Object -Unique
foreach ($dotnet in $dotnetCandidates) {
if (& $dotnet --list-runtimes | Select-String '^Microsoft\.NETCore\.App 10\.') {
$dotNetRuntimePresent = $true
break
}
}
if (-not $dotNetRuntimePresent) {
$issues.Add('The Microsoft .NET 10 x64 runtime is missing.')
}
$remoteAccessReady = $null
if ($RequireRemoteAccess) {
$remoteDesktopUsersSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-555')
$remoteDesktopUsersGroup = ($remoteDesktopUsersSid.Translate([Security.Principal.NTAccount]).Value -split '\\', 2)[1]
$rdpMembers = @(Get-LocalGroupMember -Group $remoteDesktopUsersGroup -ErrorAction SilentlyContinue)
$remoteAccessReady =
(Get-Service TermService).Status -eq 'Running' -and
(Get-Service WinRM).Status -eq 'Running' -and
(Get-ItemPropertyValue 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections) -eq 0 -and
$rdpMembers.Name -contains $RemoteDesktopPrincipal
if (-not $remoteAccessReady) {
$issues.Add('RDP/WinRM or the authorized domain group is not fully configured.')
}
}
$result = [pscustomobject]@{
ComputerName = $env:COMPUTERNAME
Domain = $computer.Domain
DomainJoined = [bool]$computer.PartOfDomain
ProviderRegistered = $providerRegistered
ProviderBinary = $registeredDll
ProviderBinaryPresent = [bool]$providerBinaryPresent
DefaultProviderConfigured = $defaultProviderConfigured
PasswordProviderPreserved = $passwordProviderPreserved
SettingsPresent = [bool]$settings
ClientCertificatePresent = [bool]$clientCertificatePresent
ServerCertificateTrusted = $serverCertificateTrusted
DotNetRuntimePresent = $dotNetRuntimePresent
BrokerHealth = $brokerHealth
RemoteAccessReady = $remoteAccessReady
IsValid = $issues.Count -eq 0
Issues = $issues.ToArray()
}
$result
if ($Enforce -and -not $result.IsValid) {
throw "SGU client enrollment is invalid: $($issues -join ' ')"
}
+23
View File
@@ -8,14 +8,37 @@ $providerClassId = '{D789CFD8-5AD4-489F-9B83-7EB5D9D09335}'
$installPath = Join-Path $env:ProgramFiles 'SGU\CredentialProvider'
$providerRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\$providerClassId"
$classRegistryPath = "HKLM:\SOFTWARE\Classes\CLSID\$providerClassId"
$defaultProviderPolicyPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System'
$enrollmentTaskName = 'SGU-CredentialProvider-EnrollmentGuard'
$enrollmentPath = Join-Path $env:ProgramData 'SGU\Enrollment'
if ($PSCmdlet.ShouldProcess($providerClassId, 'Unregister the SGU Credential Provider')) {
if (Get-ScheduledTask -TaskName $enrollmentTaskName -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $enrollmentTaskName -Confirm:$false
}
Remove-Item -LiteralPath $providerRegistryPath -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $classRegistryPath -Recurse -Force -ErrorAction SilentlyContinue
$configuredDefault = $null
try {
$configuredDefault = Get-ItemPropertyValue `
-LiteralPath $defaultProviderPolicyPath `
-Name DefaultCredentialProvider `
-ErrorAction Stop
}
catch {
# Nothing to remove when the policy is absent.
}
if ($configuredDefault -eq $providerClassId) {
Remove-ItemProperty -LiteralPath $defaultProviderPolicyPath `
-Name DefaultCredentialProvider -Force
}
}
if ($RemoveFiles -and $PSCmdlet.ShouldProcess($installPath, 'Remove Credential Provider files')) {
Remove-Item -LiteralPath $installPath -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $enrollmentPath -Recurse -Force -ErrorAction SilentlyContinue
}
Write-Output 'The built-in Windows password Credential Provider was not changed.'