Private/Get-AGEnrollmentService.ps1
|
function Get-AGEnrollmentService { <# .SYNOPSIS Resolves the single selected enterprise CA for the ADCSGoat deploy spine. .DESCRIPTION Implements the selected-CA contract settled in ADCSGoat issue #24. - When -CAName is supplied, the CA with that exact name under CN=Enrollment Services,CN=Public Key Services,CN=Services,<ConfigNC> is resolved. An unknown name fails with error id 'CANotFound' listing the CAs that do exist. - When -CAName is omitted and the forest holds exactly one enterprise CA, that CA is autodetected. - When -CAName is omitted and the forest holds multiple enterprise CAs, the function fails with error id 'MultipleCAsFound' listing every CA and demanding -CAName. Zero CAs fails with 'NoEnterpriseCAFound'. Every failure happens before any AD write. The CA's identity is its pKIEnrollmentService object, the object whose certificateTemplates attribute lists the templates the CA issues. The returned object identifies the CA as '<host FQDN>\<CA name>' via FullName, plus its Name, HostFqdn, and DistinguishedName. .PARAMETER CAName The cn of the enterprise CA's pKIEnrollmentService object (e.g. 'LabRootCA1'). Optional; autodetected when the forest has exactly one enterprise CA. .PARAMETER Server The domain controller to query. Defaults to the logon server. .OUTPUTS System.Management.Automation.PSCustomObject with Name, HostFqdn, FullName ('<host FQDN>\<CA name>'), and DistinguishedName of the selected CA's pKIEnrollmentService object. .EXAMPLE Get-AGEnrollmentService Autodetects the forest's single enterprise CA. .EXAMPLE Get-AGEnrollmentService -CAName 'LabRootCA1' Selects the named CA, failing if it does not exist. #> [CmdletBinding()] param ( [Parameter()] [ValidateNotNullOrEmpty()] [string]$CAName, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Server ) begin { Add-Type -AssemblyName System.DirectoryServices if ([string]::IsNullOrEmpty($Server)) { $Server = [System.Net.Dns]::GetHostEntry($env:LOGONSERVER.TrimStart('\')).HostName } $rootDSE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Server/RootDSE") $configurationPartition = $rootDSE.configurationNamingContext $enrollmentServicesDN = "CN=Enrollment Services,CN=Public Key Services,CN=Services,$configurationPartition" } process { $container = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Server/$enrollmentServicesDN") # Loop variables are deliberately not named $caName: PowerShell # variable names are case-insensitive, so $caName would silently # overwrite the $CAName parameter. $candidates = @( foreach ($child in $container.Children) { $childName = "$($child.Properties['cn'].Value)" $childHostFqdn = "$($child.Properties['dNSHostName'].Value)" [pscustomobject]@{ Name = $childName HostFqdn = $childHostFqdn FullName = "$childHostFqdn\$childName" DistinguishedName = "$($child.Properties['distinguishedName'].Value)" } $child.Dispose() } ) $container.Dispose() if ($PSBoundParameters.ContainsKey('CAName')) { $match = @($candidates | Where-Object { $_.Name -eq $CAName }) if ($match.Count -eq 0) { $available = ($candidates | ForEach-Object { $_.Name }) -join ', ' $exception = New-Object System.InvalidOperationException("CA '$CAName' was not found under '$enrollmentServicesDN'. Available enterprise CAs: $available. No AD changes were made.") $errorRecord = New-Object System.Management.Automation.ErrorRecord($exception, 'CANotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $CAName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Write-Output $match[0] return } if ($candidates.Count -eq 0) { $exception = New-Object System.InvalidOperationException("No enterprise CA found under '$enrollmentServicesDN'. Is AD CS installed in this forest? No AD changes were made.") $errorRecord = New-Object System.Management.Automation.ErrorRecord($exception, 'NoEnterpriseCAFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $enrollmentServicesDN) $PSCmdlet.ThrowTerminatingError($errorRecord) } if ($candidates.Count -gt 1) { $available = ($candidates | ForEach-Object { $_.Name }) -join ', ' $exception = New-Object System.InvalidOperationException("Multiple enterprise CAs found: $available. Rerun with -CAName to select one. No AD changes were made.") $errorRecord = New-Object System.Management.Automation.ErrorRecord($exception, 'MultipleCAsFound', [System.Management.Automation.ErrorCategory]::InvalidOperation, $enrollmentServicesDN) $PSCmdlet.ThrowTerminatingError($errorRecord) } Write-Output $candidates[0] } } function Resolve-AGEnrollmentServiceSelection { <# .SYNOPSIS Pure resolution of the selected-CA contract against a supplied candidate list. Internal seam; LDAP discovery lives in Get-AGEnrollmentService. Exists so the multi-CA, zero-CA, and invalid-name failures are unit-testable without touching a forest. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [AllowEmptyCollection()] [pscustomobject[]]$Candidates, [Parameter()] [AllowNull()] [AllowEmptyString()] [string]$CAName ) process { if (-not [string]::IsNullOrEmpty($CAName)) { $match = @($Candidates | Where-Object { $_.Name -eq $CAName }) if ($match.Count -eq 0) { $available = ($Candidates | ForEach-Object { $_.Name }) -join ', ' $exception = New-Object System.InvalidOperationException("CA '$CAName' was not found. Available enterprise CAs: $available. No AD changes were made.") $errorRecord = New-Object System.Management.Automation.ErrorRecord($exception, 'CANotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $CAName) $PSCmdlet.ThrowTerminatingError($errorRecord) } Write-Output $match[0] return } if ($Candidates.Count -eq 0) { $exception = New-Object System.InvalidOperationException('No enterprise CA found in the forest. No AD changes were made.') $errorRecord = New-Object System.Management.Automation.ErrorRecord($exception, 'NoEnterpriseCAFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $null) $PSCmdlet.ThrowTerminatingError($errorRecord) } if ($Candidates.Count -gt 1) { $available = ($Candidates | ForEach-Object { $_.Name }) -join ', ' $exception = New-Object System.InvalidOperationException("Multiple enterprise CAs found: $available. Rerun with -CAName to select one. No AD changes were made.") $errorRecord = New-Object System.Management.Automation.ErrorRecord($exception, 'MultipleCAsFound', [System.Management.Automation.ErrorCategory]::InvalidOperation, $null) $PSCmdlet.ThrowTerminatingError($errorRecord) } Write-Output $Candidates[0] } } |