Add branded default provider and enforced enrollment
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 ' ')"
|
||||
}
|
||||
@@ -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 ' ')"
|
||||
}
|
||||
@@ -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.'
|
||||
|
||||
Reference in New Issue
Block a user