Install-NdesServer.ps1
|
<#PSScriptInfo .VERSION 4.2 .GUID a52391cf-9c38-4304-8c9b-89f151461f3c .AUTHOR Richard Hicks .COMPANYNAME Richard M. Hicks Consulting, Inc. .COPYRIGHT Copyright (C) 2025-2026 Richard M. Hicks Consulting, Inc. All Rights Reserved. .LICENSE Licensed under the MIT License. See LICENSE file in the project root for full license information. .LICENSEURI https://github.com/richardhicks/ndes/blob/main/LICENSE .PROJECTURI https://github.com/richardhicks/ndes/ .TAGS NDES, SCEP, Intune, PKI, ADCS, Certificate, Microsoft, Windows #> <# .SYNOPSIS Installs and configures the Network Device Enrollment Service (NDES) role on Windows Server to support the Microsoft Intune Certificate Connector. .PARAMETER RaName The name of the NDES registration authority (RA). .PARAMETER CaConfig The configuration of the CA to use for NDES. The syntax is 'CA server FQDN\CA common name'. Use certutil.exe -dump to find the CA configuration. .PARAMETER EnrollmentTemplate The name of the NDES certificate template. This is different than the display name of the template shown in the Certification Authority (CA) management console. .PARAMETER Thumbprint The thumbprint of the TLS certificate to use for the NDES service. .PARAMETER ServiceAccount The service account to use for the NDES service. Use the format domain\username. If using a Group Managed Service Account (gMSA), use the format domain\username$ and include the GroupManagedServiceAccount parameter. For standard domain service accounts, the script prompts for the account password and validates it against the domain. .PARAMETER GroupManagedServiceAccount This parameter is optional. If specified, the NDES service will be configured to use a Group Managed Service Account (gMSA) for the SCEP IIS application pool. If RemoveLegacyCertificates is not specified, the gMSA is also granted read access to the RA certificate private keys. If RemoveLegacyCertificates is specified, ensure replacement RA certificates are enrolled from certificate templates that grant the gMSA read access to the private key. .PARAMETER Fqdn This parameter is optional. It is the custom fully qualified domain name (FQDN) for the NDES service when configured behind a load balancer. .PARAMETER RemoveLegacyCertificates This parameter is optional. If specified, any legacy certificates issued to the NDES server will be removed. .PARAMETER RemoveDefaultTemplates This parameter is optional. If specified, the default NDES certificate templates (CEPEncryption, EnrollmentAgentOffline, and IPSECIntermediateOffline) will be unpublished from the CA. .PARAMETER AutoEnrollment This parameter is optional. If specified, a scheduled task will be created to restart the SCEP IIS application pool on certificate renewal events. .PARAMETER Restart This parameter is optional. If specified, the server will be restarted after the NDES role is installed and configured. .EXAMPLE .\Install-NdesServer.ps1 -RaName 'Richard M. Hicks Consulting NDES RA' -CaConfig 'ca1.corp.example.net\Richard M. Hicks Consulting Issuing CA' -Template 'IntuneSCEPEnrollment' -Thumbprint 'B9413E2A1B2F5BFA0AD8A16118198ACC256D0CF9' -ServiceAccount 'corp\svc_ndes' -Restart This example installs and configures the NDES role on the local server using the specified parameters. The server will be restarted after the NDES role is installed and configured. .EXAMPLE .\Install-NdesServer.ps1 -RaName 'Richard M. Hicks Consulting NDES RA' -CaConfig 'ca1.corp.example.net\Richard M. Hicks Consulting Issuing CA' -Template 'IntuneSCEPEnrollment' -Thumbprint 'B9413E2A1B2F5BFA0AD8A16118198ACC256D0CF9' -ServiceAccount 'corp\gmsa_ndes$' -GroupManagedServiceAccount This example installs and configures the NDES role on the local server using a Group Managed Service Account (gMSA) for the SCEP IIS application pool. .EXAMPLE .\Install-NdesServer.ps1 -RaName 'Richard M. Hicks Consulting NDES RA' -CaConfig 'ca1.corp.example.net\Richard M. Hicks Consulting Issuing CA' -Template 'IntuneSCEPEnrollment' -Thumbprint 'B9413E2A1B2F5BFA0AD8A16118198ACC256D0CF9' -ServiceAccount 'corp\gmsa_ndes$' -GroupManagedServiceAccount -AutoEnrollment This example installs and configures the NDES role on the local server using a Group Managed Service Account (gMSA) for the SCEP IIS application pool and creates a scheduled task to restart the SCEP IIS application pool on certificate renewal events. .EXAMPLE .\Install-NdesServer.ps1 -RaName 'Richard M. Hicks Consulting NDES RA' -CaConfig 'ca1.corp.example.net\Richard M. Hicks Consulting Issuing CA' -Template 'IntuneSCEPEnrollment' -Thumbprint 'B9413E2A1B2F5BFA0AD8A16118198ACC256D0CF9' -ServiceAccount 'corp\svc_ndes' -Fqdn 'ndes.corp.example.net' This example installs and configures the NDES role on the local server using the specified parameters and a custom FQDN for the NDES service. .EXAMPLE .\Install-NdesServer.ps1 -RaName 'Richard M. Hicks Consulting NDES RA' -CaConfig 'ca1.corp.example.net\Richard M. Hicks Consulting Issuing CA' -Template 'IntuneSCEPEnrollment' -Thumbprint 'B9413E2A1B2F5BFA0AD8A16118198ACC256D0CF9' -ServiceAccount 'corp\svc_ndes' -RemoveLegacyCertificates This example installs and configures the NDES role on the local server using the specified parameters and removes any legacy certificates issued to the NDES server. .DESCRIPTION This script installs and configures the Network Device Enrollment Service (NDES) role on Windows Server to support the Microsoft Intune Certificate Connector. It also configures the server to use a specific TLS certificate and enrollment certificate template for NDES. In addition, this script performs several post-installation tasks to ensure the NDES service is properly configured, optimized, and secured. As part of post-installation hardening, permissions on the HKLM\SOFTWARE\Microsoft\Cryptography\MSCEP registry key are restricted to SYSTEM, Administrators, and the NDES service account, and auditing of configuration changes to this key is enabled (Security log events require the 'Audit Registry' subcategory to be enabled via Group Policy). If the Microsoft Intune Certificate Connector service runs as a custom account instead of SYSTEM, grant that account read access to this registry key manually. Note that changing the SCEP application pool identity after installation will cause NDES to fail until the registry key permissions are updated. IMPORTANT NOTE: This script is for configuring NDES to support the Microsoft Intune Certificate Connector. Settings configured by this script will not work with other deployment scenarios. .LINK https://github.com/richardhicks/ndes/blob/main/Install-NdesServer.ps1 .LINK https://www.richardhicks.com/ .NOTES Version: 4.2 Creation Date: November 29, 2023 Last Updated: August 12, 2026 Author: Richard Hicks Organization: Richard M. Hicks Consulting, Inc. Contact: rich@richardhicks.com Website: https://www.richardhicks.com/ #> #Requires -Version 5.1 #Requires -RunAsAdministrator [CmdletBinding(SupportsShouldProcess)] Param ( [Parameter(Mandatory, HelpMessage = 'Enter a name for the NDES registration authority (RA)')] [ValidateNotNullOrEmpty()] [string]$RaName, [Parameter(Mandatory, HelpMessage = 'Enter the configuration of the certification authority (CA) to use for NDES. The syntax is `[CA server FQDN`]\`[CA common name`]. Use certutil.exe -dump to find the CA configuration')] [ValidateNotNullOrEmpty()] [string]$CaConfig, [Parameter(Mandatory, HelpMessage = 'Enter the name of the NDES certificate enrollment template')] [ValidateNotNullOrEmpty()] [Alias('Template')] [string]$EnrollmentTemplate, [Parameter(Mandatory, HelpMessage = 'Enter the thumbprint of the TLS certificate to use for the NDES service')] [ValidateNotNullOrEmpty()] # Ensure the thumbprint is 40 characters in length and contains only hexadecimal characters [ValidatePattern('^[0-9A-Fa-f]{40}$')] [string]$Thumbprint, [Parameter(Mandatory, HelpMessage = 'Enter the name of the service account to use for the NDES service. Use the format domain\username. If using a Group Managed Service Account (gMSA), use the format domain\username$')] [ValidateNotNullOrEmpty()] [string]$ServiceAccount, [switch]$GroupManagedServiceAccount, [string]$Fqdn, [switch]$RemoveLegacyCertificates, [switch]$RemoveDefaultTemplates, [switch]$AutoEnrollment, [switch]$Restart ) # Ensure verbose output is always displayed and captured in the transcript $VerbosePreference = 'Continue' # Create log directory if it doesn't exist Write-Verbose 'Starting transcript...' $LogPath = "$env:ProgramData\RMHCI\PowerShell" If (-not (Test-Path -Path $LogPath)) { # Logging is exempt from -WhatIf so the transcript can always be created [void](New-Item -Path $LogPath -ItemType Directory -Force -WhatIf:$False) } # Start transcript. Exempt from -WhatIf so -WhatIf runs are always logged and Stop-Transcript in the Finally block always succeeds Start-Transcript -Path "$LogPath\Install-NdesServer_$(Get-Date -Format 'yyyyMMdd-HHmmss').log" -WhatIf:$False # Record the invocation and bound parameters in the transcript for troubleshooting Write-Verbose "Invocation: $($MyInvocation.Line.Trim())" Write-Verbose 'Bound parameters:' ForEach ($Parameter in $PSBoundParameters.GetEnumerator()) { Write-Verbose " -$($Parameter.Key): $($Parameter.Value)" } # Begin configuration Try { # Honor -WhatIf and -Confirm for the script as a whole If (-not $PSCmdlet.ShouldProcess($env:ComputerName, 'Install and configure the NDES role')) { Return } # Validate the service account If ($GroupManagedServiceAccount) { # Validate the gMSA account format using a regular expression. The expected format is 'domain\username$' $Pattern = '^[^\\]+\\[^\\]+\$$' If ($ServiceAccount -match $Pattern) { Write-Verbose "Group Managed Service Account (gMSA) $ServiceAccount format is valid." } Else { # Display a warning and exit if the gMSA account isn't formatted correctly Write-Warning "The gMSA account $ServiceAccount is not formatted correctly. The correct format is <domain>\<user>$." Return } } Else { # A trailing $ indicates a gMSA. Exit rather than prompt for a password a gMSA doesn't have If ($ServiceAccount -match '\$$') { Write-Warning "The service account $ServiceAccount appears to be a Group Managed Service Account (gMSA). Run the script again with the GroupManagedServiceAccount parameter." Return } # Validate the service account format using a regular expression. The expected format is 'domain\username' If ($ServiceAccount -notmatch '^[^\\]+\\[^\\]+$') { # Display a warning and exit if the service account isn't formatted correctly Write-Warning "The service account $ServiceAccount is not formatted correctly. The correct format is <domain>\<user>." Return } # Prompt for the NDES service account password $Credential = Get-Credential -UserName $ServiceAccount -Message 'Enter the password for the NDES service account' If ($Null -eq $Credential) { # Display a warning and exit if the credential prompt was cancelled Write-Warning 'No password supplied for the NDES service account. Run the script again and enter the password when prompted.' Return } # Validate the NDES service account credentials against the domain Write-Verbose "Validating credentials for NDES service account $ServiceAccount..." Add-Type -AssemblyName System.DirectoryServices.AccountManagement $PrincipalContext = $Null Try { # Validate using the account name from the ServiceAccount parameter so it remains authoritative even if the username was altered at the credential prompt # GetNetworkCredential exposes the password as plain text in process memory only for the duration of validation $SamAccountName = ($ServiceAccount -split '\\')[1] $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain') $CredentialsValid = $PrincipalContext.ValidateCredentials($SamAccountName, $Credential.GetNetworkCredential().Password, 'Negotiate') } Catch { Throw "Unable to validate credentials for '$ServiceAccount'. Verify the server is joined to the domain and a domain controller is reachable. Error: $_" } Finally { # Dispose of the principal context to release the underlying directory connection If ($PrincipalContext) { $PrincipalContext.Dispose() } } If (-not $CredentialsValid) { # Display a warning and exit if credential validation fails Write-Warning "The credentials for $ServiceAccount are invalid. Verify the username and password and run the script again." Return } Write-Verbose "Credentials for NDES service account $ServiceAccount validated successfully." # Extract the password as a secure string for NDES service configuration $Password = $Credential.Password } # Resolve the service account to a SID. This confirms the account exists in the domain (the only # existence check for gMSAs at this point) and is required later to grant standard accounts the # 'Log on as a service' right. Write-Verbose "Validating service account '$ServiceAccount'..." Try { $NtAccount = New-Object System.Security.Principal.NTAccount($ServiceAccount) $ServiceAccountSid = $NtAccount.Translate([System.Security.Principal.SecurityIdentifier]) } Catch { Throw "Could not resolve account '$ServiceAccount' to a SID. Verify the account exists and the format is 'domain\user'. Error: $_" } # Validate TLS certificate Write-Verbose "Validating TLS certificate with thumbprint $Thumbprint..." $Certificate = Get-ChildItem -Path cert:\LocalMachine\My\$Thumbprint -ErrorAction SilentlyContinue If ($Null -eq $Certificate) { # Display a warning and exit if the certificate isn't found Write-Warning "Unable to find certificate with thumbprint $Thumbprint." Return } Write-Verbose "Certificate with thumbprint $Thumbprint found." # Verify the certificate has an associated private key If (-not $Certificate.HasPrivateKey) { Write-Warning "Certificate with thumbprint $Thumbprint does not have an associated private key." Return } # Verify the certificate is time valid $Now = Get-Date If (($Certificate.NotBefore -gt $Now) -or ($Certificate.NotAfter -lt $Now)) { Write-Warning "Certificate with thumbprint $Thumbprint is expired or not yet valid (valid from $($Certificate.NotBefore) to $($Certificate.NotAfter))." Return } # Verify the certificate supports server authentication. A certificate without an EKU extension is valid for all purposes $ServerAuthOid = '1.3.6.1.5.5.7.3.1' $Eku = $Certificate.Extensions | Where-Object { $_ -is [System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension] } If ($Eku -and -not ($Eku.EnhancedKeyUsages | Where-Object { $_.Value -eq $ServerAuthOid })) { Write-Warning "Certificate with thumbprint $Thumbprint does not include the Server Authentication enhanced key usage." Return } # Grant the service account the "Log on as a service" right (not required for Group Managed Service Accounts (gMSA)) If (-not $GroupManagedServiceAccount) { Write-Verbose "Granting 'Log on as a service' right to NDES service account $ServiceAccount..." If (-not ([System.Management.Automation.PSTypeName]'LsaApi').Type) { Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; public class LsaApi { [StructLayout(LayoutKind.Sequential)] public struct LSA_UNICODE_STRING { public ushort Length; public ushort MaximumLength; [MarshalAs(UnmanagedType.LPWStr)] public string Buffer; } [StructLayout(LayoutKind.Sequential)] public struct LSA_OBJECT_ATTRIBUTES { public int Length; public IntPtr RootDirectory; public LSA_UNICODE_STRING ObjectName; public uint Attributes; public IntPtr SecurityDescriptor; public IntPtr SecurityQualityOfService; } [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] public static extern uint LsaOpenPolicy( ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, uint DesiredAccess, out IntPtr PolicyHandle); [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] public static extern uint LsaAddAccountRights( IntPtr PolicyHandle, IntPtr AccountSid, LSA_UNICODE_STRING[] UserRights, uint CountOfRights); [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] public static extern uint LsaClose(IntPtr ObjectHandle); [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] public static extern uint LsaNtStatusToWinError(uint Status); } '@ } Function Grant-LogOnAsService { Param ( [System.Security.Principal.SecurityIdentifier]$Sid ) # Marshal the SID to unmanaged memory $SidBytes = New-Object byte[] $Sid.BinaryLength $Sid.GetBinaryForm($SidBytes, 0) $SidPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($SidBytes.Length) [System.Runtime.InteropServices.Marshal]::Copy($SidBytes, 0, $SidPtr, $SidBytes.Length) Try { $objAttr = New-Object LsaApi+LSA_OBJECT_ATTRIBUTES $objAttr.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($objAttr) $EmptyName = New-Object LsaApi+LSA_UNICODE_STRING $PolicyHandle = [IntPtr]::Zero # POLICY_CREATE_ACCOUNT | POLICY_LOOKUP_NAMES = 0x00000010 | 0x00000800 $Status = [LsaApi]::LsaOpenPolicy([ref]$EmptyName, [ref]$objAttr, 0x00000810, [ref]$PolicyHandle) If ($Status -ne 0) { $WinErr = [LsaApi]::LsaNtStatusToWinError($Status) Throw "LsaOpenPolicy failed. Win32 error: $WinErr" } Try { $Right = New-Object LsaApi+LSA_UNICODE_STRING $Right.Buffer = 'SeServiceLogonRight' $Right.Length = [uint16]($Right.Buffer.Length * 2) $Right.MaximumLength = [uint16]($Right.Buffer.Length * 2 + 2) $Status = [LsaApi]::LsaAddAccountRights($PolicyHandle, $SidPtr, @($Right), 1) If ($Status -ne 0) { $WinErr = [LsaApi]::LsaNtStatusToWinError($Status) Throw "LsaAddAccountRights failed. Win32 error: $WinErr" } Write-Verbose "Successfully granted 'Log on as a service' to '$ServiceAccount' (SID: $($Sid.Value))." } Finally { [void]([LsaApi]::LsaClose($PolicyHandle)) } } Finally { [System.Runtime.InteropServices.Marshal]::FreeHGlobal($SidPtr) } } Grant-LogOnAsService -Sid $ServiceAccountSid } # Install NDES role and supporting features Try { Write-Verbose 'Installing NDES role and supporting features...' $Install = Install-WindowsFeature -Name @('ADCS-Device-Enrollment', 'Web-Filtering', 'Web-ASP-Net', 'Web-ASP-Net45', 'Web-WMI', 'NET-HTTP-Activation', 'NET-WCF-HTTP-Activation45', 'RSAT-AD-PowerShell') -IncludeManagementTools -ErrorAction Stop } Catch { Write-Warning $_.Exception.Message Write-Warning 'An error occurred while installing the NDES role and supporting features. Correct the issue and run the script again.' Return } # Check if the installation was successful (catches silent failures not thrown as exceptions) If (-not $Install.Success) { Write-Warning 'NDES role or supporting features installation failed. Review the transcript, correct the issue, and run the script again.' Return } # Check if the installation requires a restart If ($Install.RestartNeeded -ne 'No') { Write-Warning 'A restart is required to complete the NDES role installation. Restart the server and run the script again.' Return } # Backup IIS configuration Write-Verbose 'Backing up IIS configuration...' $BackupName = "NDES_Install_$((Get-Date).ToString('yyyyMMdd-HHmmss'))" [void](& "$env:SystemRoot\System32\inetsrv\appcmd.exe" add backup $BackupName 2>&1) If ($LASTEXITCODE -ne 0) { Write-Warning "IIS configuration backup failed (exit code $LASTEXITCODE). Proceeding, but a pre-change backup may not exist." } Else { Write-Verbose "IIS configuration backup '$BackupName' created." } # Install gMSA on local computer and verify functionality (requires RSAT-AD-PowerShell, installed above) If ($GroupManagedServiceAccount) { # Extract the gMSA SAM account name (strip domain prefix) $AccountName = ($ServiceAccount -split '\\')[1] # Install the gMSA on the local computer to enable managed password retrieval Write-Verbose "Installing gMSA $ServiceAccount on local computer..." Try { Install-ADServiceAccount -Identity $AccountName -ErrorAction Stop Write-Verbose "gMSA $ServiceAccount successfully installed on local computer." } Catch { Throw "Failed to install gMSA '$ServiceAccount' on local computer. Verify the account exists and this computer is authorized to retrieve its managed password. Error: $_" } # Test gMSA functionality Write-Verbose "Testing gMSA $ServiceAccount functionality..." $ServiceAccountTest = Test-ADServiceAccount -Identity $AccountName If ($ServiceAccountTest) { Write-Verbose "gMSA $ServiceAccount is correctly configured and functional." } Else { Write-Warning "gMSA $ServiceAccount is not correctly configured. Verify this computer is a member of the gMSA's PrincipalsAllowedToRetrieveManagedPassword group and that the KDS Root Key has been created." } } # Add NDES service account to local IIS_IUSRS group if required. Adding unconditionally and handling the # 'member exists' error avoids Get-LocalGroupMember, which fails if the group contains orphaned SIDs Write-Verbose "Adding NDES service account $ServiceAccount to local IIS_IUSRS group..." Try { Add-LocalGroupMember -Group IIS_IUSRS -Member $ServiceAccount -ErrorAction Stop } Catch { If ($_.FullyQualifiedErrorId -like 'MemberExists,*') { Write-Verbose "NDES service account $ServiceAccount is already a member of the local IIS_IUSRS group." } Else { Throw } } # Configure NDES Write-Verbose 'Configuring NDES...' If ($GroupManagedServiceAccount) { # Define configuration parameters when using a Group Managed Service Account (gMSA) $Params = @{ ApplicationPoolIdentity = $True RaName = $RaName SigningProviderName = 'Microsoft Strong Cryptographic Provider' SigningKeyLength = 2048 EncryptionProviderName = 'Microsoft Strong Cryptographic Provider' EncryptionKeyLength = 2048 CaConfig = $CaConfig Force = $True ErrorAction = 'Stop' } } Else { # Define configuration parameters when using a standard domain service account $Params = @{ ServiceAccountName = $ServiceAccount ServiceAccountPassword = $Password RaName = $RaName SigningProviderName = 'Microsoft Strong Cryptographic Provider' SigningKeyLength = 2048 EncryptionProviderName = 'Microsoft Strong Cryptographic Provider' EncryptionKeyLength = 2048 CaConfig = $CaConfig Force = $True ErrorAction = 'Stop' } } Try { # Install NDES [void](Install-AdcsNetworkDeviceEnrollmentService @Params) } Catch { # If an error occurs, display a warning and exit the script Write-Warning -Message $_.Exception.Message Write-Warning 'An error occurred while installing the NDES role. Remove the configuration using the following PowerShell command and run the script again: Uninstall-AdcsNetworkDeviceEnrollmentService -Force' If ($_.Exception.HResult -eq -2147024893) { Write-Warning "IMPORTANT: The IIS configuration file may be corrupt. Be sure to run the following command before running the script again: & $env:SystemDrive\Windows\System32\inetsrv\appcmd.exe restore backup $BackupName." } Return } # Set service principal names (SPNs) for the FQDN and short name. Only required when using a custom FQDN for the NDES service If ($Fqdn) { ForEach ($Spn in @("http/$Fqdn", "http/$($Fqdn -Replace '(\w+)\..+', '$1')")) { Write-Verbose "Registering service principal name (SPN) $Spn for $ServiceAccount..." $SpnResult = & setspn.exe -s $Spn $ServiceAccount 2>&1 If ($LASTEXITCODE -ne 0) { Write-Warning "Failed to register SPN $Spn for $ServiceAccount. Resolve the issue or register the SPN manually. Error: $SpnResult" } Else { Write-Verbose "SPN $Spn registered successfully." } } } # Disable IE enhanced security. This is required to install the Intune Certificate Connector Write-Verbose 'Disabling IE enhanced security...' Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}' -Name 'IsInstalled' -Type DWORD -Value '0' Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}' -Name 'IsInstalled' -Type DWORD -Value '0' # Define NDES certificate templates using the same enrollment template for all three purposes (Encryption, GeneralPurpose, and Signature) for simplicity # Note that it is possible (and sometimes preferable) to specify different templates for each purpose if more granular control is required Write-Verbose 'Defining NDES certificate template...' Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name EncryptionTemplate -Value $EnrollmentTemplate -Force Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name GeneralPurposeTemplate -Value $EnrollmentTemplate -Force Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name SignatureTemplate -Value $EnrollmentTemplate -Force # Enable NDES long URL support Write-Verbose 'Enabling IIS long URL support...' [void](New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\' -Name MaxFieldLength -Type DWORD -Value 65534 -Force) [void](New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\' -Name MaxRequestBytes -Type DWORD -Value 65534 -Force) # Update NDES max URL length and max query string values in IIS request filtering Write-Verbose 'Setting URL length and max query string values...' Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.webServer/security/requestFiltering/requestLimits' -Name 'maxUrl' -Value 65534 Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.webServer/security/requestFiltering/requestLimits' -Name 'maxQueryString' -Value 65534 # Remove HTTP site binding if present. The binding may already be removed if the script has run previously. # Get-WebBinding parameter filtering is unreliable, so filter on the protocol property and pipe the results # to Remove-WebBinding to ensure the removal targets exactly the bindings found $HttpBindings = Get-WebBinding -Name 'Default Web Site' | Where-Object { $_.protocol -eq 'http' } If ($HttpBindings) { Write-Verbose 'Removing HTTP site binding in IIS...' [void]($HttpBindings | Remove-WebBinding -Confirm:$false) } Else { Write-Verbose 'HTTP site binding not found. Skipping removal.' } # Disable IIS default document Write-Verbose 'Disabling IIS default document...' [void](Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.webServer/defaultDocument' -Name 'Enabled' -Value 'False') # Remove default IIS files Write-Verbose 'Removing default IIS files...' [void](Remove-Item -Path $env:systemdrive\Inetpub\wwwroot\iisstart.* -ErrorAction SilentlyContinue) # Remove NDES administration page IIS application if present. The application may already be removed if the script has run previously If (Get-WebApplication -Site 'Default Web Site' -Name 'CertSrv/mscep_admin') { Write-Verbose 'Removing NDES administration page IIS application...' Remove-WebApplication -Site 'Default Web Site' -Name 'CertSrv/mscep_admin' -Confirm:$false } Else { Write-Verbose 'NDES administration page IIS application not found. Skipping removal.' } # Check for existing certificate binding in IIS. Filter on the protocol property and pipe the results # to Remove-WebBinding for the same reasons noted for the HTTP binding removal above $HttpsBindings = Get-WebBinding -Name 'Default Web Site' | Where-Object { $_.protocol -eq 'https' } If ($HttpsBindings) { # Remove existing web binding Write-Verbose 'Removing existing HTTPS binding...' [void]($HttpsBindings | Remove-WebBinding -Confirm:$false) } # Configure TLS certificate binding in IIS [void](New-WebBinding -Name 'Default Web Site' -Ipaddress '*' -Port 443 -Protocol 'HTTPS' -SslFlags 0) (Get-WebBinding -Name 'Default Web Site').AddSslCertificate($Thumbprint, 'My') # Configure IIS SCEP application pool to use a Group Managed Service Account (gMSA) If ($GroupManagedServiceAccount) { Write-Verbose 'Configuring IIS SCEP application pool to use a Group Managed Service Account (gMSA)...' [void](Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.applicationHost/applicationPools/add[@name="SCEP"]/processModel' -Name 'identityType' -Value 'SpecificUser') [void](Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.applicationHost/applicationPools/add[@name="SCEP"]/processModel' -Name 'userName' -Value $ServiceAccount) } # Restrict permissions on the MSCEP registry key to SYSTEM, Administrators, and the NDES service # account. For gMSA deployments, the initial NDES configuration granted the SCEP application pool # virtual account (IIS AppPool\SCEP) permissions on this key and the application pool now runs as # the gMSA, so those permissions are mirrored to the gMSA and the legacy application pool entries # removed. For standard domain service account deployments, the application pool already runs as # the service account Write-Verbose "Restricting MSCEP registry key permissions to $ServiceAccount..." $MscepRegistryKey = 'HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP' # Read the DACL and SACL together so both are updated in a single operation $Acl = Get-Acl -Path $MscepRegistryKey -Audit # Disable inheritance and discard inherited entries when the ACL is written. PurgeAccessRules # cannot remove inherited entries (preserved copies are only created when the ACL is written), # so preserving inherited entries would leave the broad-read grants in place. Explicit entries # for SYSTEM and Administrators are added below to replace their inherited grants $Acl.SetAccessRuleProtection($True, $False) # Replace the inherited SYSTEM (S-1-5-18) and Administrators (S-1-5-32-544) grants with # explicit entries $Acl.AddAccessRule((New-Object System.Security.AccessControl.RegistryAccessRule((New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')), 'FullControl', 'ContainerInherit', 'None', 'Allow'))) $Acl.AddAccessRule((New-Object System.Security.AccessControl.RegistryAccessRule((New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')), 'FullControl', 'ContainerInherit', 'None', 'Allow'))) # Mirror explicit application pool identity entries to the service account (present in gMSA # deployments), preserving rights, inheritance, and propagation so MSCEP subkeys remain covered $AppPoolAces = @($Acl.Access | Where-Object { -not $_.IsInherited -and $_.IdentityReference.Value -eq 'IIS AppPool\SCEP' }) ForEach ($Ace in $AppPoolAces) { $ServiceAccountRule = New-Object System.Security.AccessControl.RegistryAccessRule($ServiceAccountSid, $Ace.RegistryRights, $Ace.InheritanceFlags, $Ace.PropagationFlags, $Ace.AccessControlType) $Acl.AddAccessRule($ServiceAccountRule) } # Ensure the service account retains access when no application pool entries exist (standard # domain service account deployments, or reruns where the entries were already removed by a # previous run of this script) $SidRules = $Acl.GetAccessRules($True, $False, [System.Security.Principal.SecurityIdentifier]) If (-not ($SidRules | Where-Object { $_.IdentityReference -eq $ServiceAccountSid })) { $ServiceAccountRule = New-Object System.Security.AccessControl.RegistryAccessRule($ServiceAccountSid, 'FullControl', 'ContainerInherit', 'None', 'Allow') $Acl.AddAccessRule($ServiceAccountRule) } # Remove every explicit entry not belonging to SYSTEM, Administrators, or the service account. # Sweeping against an identity allowlist (rather than purging a fixed list of SIDs) also removes # the application pool entries, capability SIDs, CREATOR OWNER, and any leftover entries from # previous runs of this script $AllowedSids = @('S-1-5-18', 'S-1-5-32-544', $ServiceAccountSid.Value) ForEach ($Identity in ($Acl.Access | Where-Object { -not $_.IsInherited } | Select-Object -ExpandProperty IdentityReference -Unique)) { If ($Identity -is [System.Security.Principal.SecurityIdentifier]) { $Sid = $Identity } Else { $Sid = $Identity.Translate([System.Security.Principal.SecurityIdentifier]) } If ($AllowedSids -notcontains $Sid.Value) { $Acl.PurgeAccessRules($Sid) } } # Add an audit rule (SACL) to record successful and failed attempts by any identity to modify # the NDES template configuration (value changes, subkey create/delete, permission or owner # changes). Read operations are not audited to avoid Security log noise $EveryoneSid = New-Object System.Security.Principal.SecurityIdentifier('S-1-1-0') $AuditRule = New-Object System.Security.AccessControl.RegistryAuditRule($EveryoneSid, 'SetValue, CreateSubKey, Delete, ChangePermissions, TakeOwnership', 'ContainerInherit', 'None', 'Success, Failure') $Acl.AddAuditRule($AuditRule) Set-Acl -Path $MscepRegistryKey -AclObject $Acl Write-Verbose 'MSCEP registry key permissions restricted and change auditing enabled.' # The audit rule is only effective if the Object Access > Registry audit subcategory is enabled. # The subcategory GUID is used for locale independence. Enabling audit policy is deliberately # left to the administrator (typically via Group Policy) rather than changed by this script $AuditPolicy = & auditpol.exe /get /subcategory:"{0CCE921E-69AE-11D9-BED3-505054503030}" /r 2>&1 | ConvertFrom-Csv If ($AuditPolicy.'Inclusion Setting' -notmatch 'Success') { Write-Warning "The 'Audit Registry' audit subcategory is not enabled. The MSCEP audit rule will not generate Security log events until it is enabled via Group Policy (Advanced Audit Policy Configuration > Object Access > Audit Registry)." } # Update RA certificate private key permissions for gMSA deployments. The initial NDES # configuration granted key access to the SCEP application pool identity; the application pool # now runs as the gMSA. Skip this update when RemoveLegacyCertificates is specified, as those # certificates will be removed and their replacements enrolled from certificate templates that # define gMSA key permissions If ($GroupManagedServiceAccount) { If (-not $RemoveLegacyCertificates) { Write-Verbose "Updating RA certificate private key permissions for $ServiceAccount..." $AppPoolSid = (New-Object System.Security.Principal.NTAccount('IIS AppPool\SCEP')).Translate([System.Security.Principal.SecurityIdentifier]) $RaCertificates = @(Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -match [regex]::Escape($RaName) -and $_.HasPrivateKey }) If ($RaCertificates.Count -eq 0) { Write-Warning "No RA certificates matching '$RaName' with private keys were found. Verify NDES enrolled its RA certificates successfully." } ForEach ($RaCertificate in $RaCertificates) { $PrivateKey = $RaCertificate.PrivateKey If ($Null -eq $PrivateKey) { Write-Warning "Unable to access the private key for RA certificate $($RaCertificate.Thumbprint). Grant $ServiceAccount read access to the private key manually." Continue } # Grant the gMSA read access and remove the legacy application pool identity entry so key # access does not depend on the application pool SID injected into the worker process $KeyFile = Join-Path -Path "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys" -ChildPath $PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName $KeyAcl = Get-Acl -Path $KeyFile $KeyAcl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($ServiceAccountSid, 'Read', 'Allow'))) $KeyAcl.PurgeAccessRules($AppPoolSid) Set-Acl -Path $KeyFile -AclObject $KeyAcl Write-Verbose "Updated private key permissions for RA certificate $($RaCertificate.Thumbprint)." } } Else { Write-Verbose 'Skipping RA certificate private key permission update. Legacy RA certificates will be removed and replacement certificates enrolled from templates that define gMSA key permissions.' } } # Restart IIS Write-Verbose 'Restarting IIS...' [void](Restart-Service -Name W3SVC -Force) # Configure the SHA256 hash algorithm for certificate requests [void](New-Item -Path 'HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\HashAlgorithm\' -Force) [void](New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\HashAlgorithm\' -PropertyType String -Name HashAlgorithm -Value SHA256 -Force) # Advertise only strong algorithms (SHA-512, SHA-256, and AES) in the SCEP GetCACaps response. This change is # not critical because the SCEP exchange is protected with TLS, but it is made to align with security best practices Write-Verbose 'Configuring SCEP CA capabilities algorithms...' $Path = 'HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\CACapabilitiesAlgorithms' If (-Not (Test-Path -Path $Path)) { [void](New-Item -Path $Path -Force) } $Params = @{ Path = $Path Name = 'CACapabilitiesAlgorithms' Value = @('SHA-512', 'SHA-256', 'AES') PropertyType = 'MultiString' Force = $true } [void](New-ItemProperty @Params) If ($AutoEnrollment) { # Enable verbose logging for certificate enrollment events Write-Verbose 'Enabling verbose logging for certificate enrollment events...' [void](Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography\AutoEnrollment\ -Name AEEventLogLevel -Value 0) # Disable overlapped recycling so the old worker process releases the RA certificates before the new one starts Write-Verbose 'Disabling overlapped recycling for the SCEP application pool...' [void](Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.applicationHost/applicationPools/add[@name="SCEP"]/recycling' -Name 'disallowOverlappingRotation' -Value $True) # Create scheduled task to restart the SCEP IIS application pool on certificate renewal events Write-Verbose 'Creating scheduled task to restart SCEP IIS application pool on certificate renewal events...' $User = 'NT AUTHORITY\SYSTEM' $Action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NonInteractive -NoLogo -NoProfile Restart-WebAppPool -Name SCEP' $CIMTriggerClass = Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskEventTrigger $Trigger = New-CimInstance -CimClass $CIMTriggerClass -ClientOnly $Trigger.Subscription = @' <QueryList><Query Id="0" Path="Application"><Select Path="Application">*[System[Provider[@Name='Microsoft-Windows-CertificateServicesClient-CertEnroll'] and EventID=20]]</Select></Query></QueryList> '@ $Trigger.Enabled = $True # Register scheduled task Write-Verbose 'Registering scheduled task...' [void](Register-ScheduledTask -TaskName 'Restart SCEP IIS Application Pool on Certificate Enrollment' -User $User -Action $Action -Trigger $Trigger -RunLevel Highest -Force) } # Remove legacy CEP Encryption and Exchange Enrollment Agent (Offline request) certificates If ($RemoveLegacyCertificates) { Write-Verbose 'Removing legacy certificates...' $LegacyCertificates = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -match $RaName } ForEach ($LegacyCertificate in $LegacyCertificates) { Write-Verbose "Removing legacy certificate $($LegacyCertificate.Thumbprint)..." Remove-Item -Path Cert:\LocalMachine\My\$($LegacyCertificate.Thumbprint) -Force } Write-Warning 'Legacy certificates have been removed. Ensure the server has enrolled for new certificates.' } # Unpublish default NDES certificate templates If ($RemoveDefaultTemplates) { Write-Verbose 'Unpublishing default NDES certificate templates from the CA...' $Result = & certutil.exe -config "$CaConfig" -setcatemplates '-CEPEncryption,EnrollmentAgentOffline,IPSECIntermediateOffline' 2>&1 If ($LASTEXITCODE -ne 0) { Write-Warning "Failed to remove default NDES certificate templates. Verify the NDES service account has CA administrator permissions or remove them manually. Error: $Result" } Else { Write-Verbose 'Default NDES certificate templates (CEPEncryption, EnrollmentAgentOffline, IPSECIntermediateOffline) successfully unpublished from the CA.' } } # Record that installation and configuration completed so post-transcript steps run only on success $InstallComplete = $True } Finally { # Stop transcript Write-Verbose 'Stopping transcript...' Stop-Transcript } # Restart the server or display post-installation instructions (skipped if the script exited early above) If ($InstallComplete) { If ($Restart) { Write-Verbose 'Restarting server...' Restart-Computer -Force } Else { Write-Warning 'A restart is required to complete the installation and configuration of the NDES role.' } } # SIG # Begin signature block # MIIk7AYJKoZIhvcNAQcCoIIk3TCCJNkCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBHmVtA0XJdt1Ws # f10cYMQ+LRh7e2K9piKeW4TkiJa7v6CCH6YwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggW0MIIDnKADAgECAhAOxitIKuZQm69NGxw+uiH/MA0GCSqG # SIb3DQEBDAUAMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j # LjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNB # NDA5NiBTSEEzODQgMjAyMSBDQTEwHhcNMjYwNTE2MDAwMDAwWhcNMjcwODE3MjM1 # OTU5WjCBhjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV # BAcTDU1pc3Npb24gVmllam8xJDAiBgNVBAoTG1JpY2hhcmQgTS4gSGlja3MgQ29u # c3VsdGluZzEkMCIGA1UEAxMbUmljaGFyZCBNLiBIaWNrcyBDb25zdWx0aW5nMFkw # EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOooTPiege6mCA4AriPO+Xh3mymiiZ+3k # kn31uJifB2ojzzfY7VkAVKhgj+rcVBnofnj2b8OhvAJ4YaQ2Iwuc6aOCAgMwggH/ # MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBQJvGhl # Ahwi6UKROatrFKBmPLmd5TA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUF # BwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgeA # MBMGA1UdJQQMMAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6 # Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5n # UlNBNDA5NlNIQTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdp # Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz # ODQyMDIxQ0ExLmNybDCBlAYIKwYBBQUHAQEEgYcwgYQwJAYIKwYBBQUHMAGGGGh0 # dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBcBggrBgEFBQcwAoZQaHR0cDovL2NhY2Vy # dHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0 # MDk2U0hBMzg0MjAyMUNBMS5jcnQwCQYDVR0TBAIwADANBgkqhkiG9w0BAQwFAAOC # AgEAbaKnnRcJAMHjuWSc2PG/QhJ0jj4hQVwJIbddYDJNxPmD0cxuuorSiR9gX2nl # ajqNI9N7Kl+FB3oheRTGh/wp4JgZMpCq0qS0zGJ/N6Js+HmVtbkFaPyYxJMXbIWq # p9zKkoXtSXkpR6nGZnzYkn3EBcRlu4R6hIJHzM/C2PUztH/Hd4fGIryyD69iHvKx # zotYdlHHY6+X1ACaQnuCz3TLxs3/CDKhPUXesKcISnXHmm4uCwyVdtGyl7wPuZVk # +rfCIOeWn+XG5J7L8xwhXCPSJ5fKJ5m8/H5cICLR0I7hI4SUiybE1nG5CZ1hKhbW # abSfNer1dHH/vSYi80YGXCej/88vZeCGQ9/rrjugsg0yN7WCPqNKjEMTYGWkrt37 # lp4cJqULS+alUbL6x1HBdoBStDE2CFmPivL7cCCtnudqCA6b3XB416/FlRo8t4Lw # Dc2ty+RDKirWM84Zj3ANTVs5fi43rxClBQwngGdqi5TjriKHGTkEKYRIFTViy6Ie # JDIboOkCFJU5vM7Curvh4rQnw+aM4CyjwnDwnzwcKQVZC3Iy1T4h/FvmpSgu5ouM # wjdzaR3cSh4OPDRrfBl1YIOoZEOHcshCaHDC46t8+UyAf70BMlrB7Nj84ORTuKTi # IlU062VzGeREc1KHJqp/S3/NtArpVUVQEgibRxQ99KJCOV8wggawMIIEmKADAgEC # AhAIrUCyYNKcTJ9ezam9k67ZMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVT # MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j # b20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0Mjkw # MDAwMDBaFw0zNjA0MjgyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5E # aWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2Rl # IFNpZ25pbmcgUlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDVtC9C0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYw # n6SOaNhc9es0JAfhS0/TeEP0F9ce2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43i # CH00fUyAVxJrQ5qZ8sU7H/Lvy0daE6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1 # hz1RGeiQIXhFLqGfLOEYwhrMxe6TSXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd # 6BgTZcV/sk+FLEikVoQ11vkunKoAFdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObar # YBLj6Na59zHh3K3kGKDYwSNHR7OhD26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18eb # MlrC/2pgVItJwZPt4bRc4G/rJvmM1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYo # X7BzzosmJQayg9Rc9hUZTO1i4F4z8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDz # d5Ea/ttQokbIYViY9XwCFjyDKK05huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8S # kXbev1jLchApQfDVxW0mdmgRQRNYmtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZ # YIpkVMHMIRroOBl8ZhzNeDhFMJlP/2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxW # EQIDAQABo4IBWTCCAVUwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg # 67Y7+F8Rhvv+YXsIiGX0TkIwHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4c # D08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUF # BwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEG # CCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRU # cnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5k # aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTAT # MAcGBWeBDAEDMAgGBmeBDAEEATANBgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6P # vDqZ01bgAhql+Eg08yy25nRm95RysQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V # 1T9J9Ce7FoFFUP2cvbaF4HZ+N3HLIvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+ # 3NiAGhEZGM1hmYFW9snjdufE5BtfQ/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcn # P/2Q0XaG3RywYFzzDaju4ImhvTnhOE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgU # kpn13c5UbdldAhQfQDN8A+KVssIhdXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6Q # B7BDf5WIIIJw8MzK7/0pNVwfiThV9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3 # kuZOX956rEnPLqR0kq3bPKSchh/jwVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKL # QcBIhEuWTatEQOON8BUozu3xGFYHKi8QxAwIZDwzj64ojDzLj4gLDb879M4ee47v # tevLt/B3E+bnKD+sEq6lLyJsQfmCXBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0 # qFEgu60bhQjiWQ1tygVQK+pKHJ6l/aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0 # YW6/aOImYIbqyK+p/pQd52MbOoZWeE4wgga0MIIEnKADAgECAhANx6xXBf8hmS5A # QyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMT # GERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAx # MTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j # LjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNB # NDA5NiBTSEEyNTYgMjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK # AoICAQC0eDHTCphBcr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcy # bEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzT # qpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftB # dsOECS1UkxBvMgEdgkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3 # mmdglTcaarps0wjUjsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6z # MUjZa/zbCclF83bRVFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS # 5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBB # BnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqL # XvJWnY0v5ydPpOjL6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7ps # NOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeE # WvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCC # AVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv # 1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/ # BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVo # dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0 # LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v # RGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjAL # BglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvI # tTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/m # S83afsl3YTj+IQhQE7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgX # f9r5nWMQwr8Myb9rEVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liy # rukZ2iA/wdG2th9y1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+ # Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2 # ykgcGV00TYr2Lr3ty9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipD # oq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6Ax # nJll38F0cuJG7uEBYTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAl # Z66RzIg9sC+NJpud/v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1 # MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZs # q8WhbaM2tszWkPZPubdcMIIG7TCCBNWgAwIBAgIQCoDvGEuN8QWC0cR2p5V0aDAN # BgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs # IEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5n # IFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkw # MzIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MTswOQYDVQQDEzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVz # cG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANBG # rC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwB # SOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/ # 4QhguSssp3qome7MrxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3 # K3E0zz09ldQ//nBZZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmncOOMA3CoB/iUSROU # INDT98oksouTMYFOnHoRh6+86Ltc5zjPKHW5KqCvpSduSwhwUmotuQhcg9tw2YD3 # w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL4Q1OpbybpMe46Yce # NA0LfNsnqcnpJeItK/DhKbPxTTuGoX7wJNdoRORVbPR1VVnDuSeHVZlc4seAO+6d # 2sC26/PQPdP51ho1zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCyFG1roSrgHjSHlq8x # ymLnjCbSLZ49kPmk8iyyizNDIXj//cOgrY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+ # AliL7ojTdS5PWPsWeupWs7NpChUk555K096V1hE0yZIXe+giAwW00aHzrDchIc2b # Qhpp0IoKRR7YufAkprxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGjggGVMIIBkTAMBgNV # HRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zyMe39/dfzkXFjGVBDz2GM6DAfBgNVHSME # GDAWgBTvb1NK6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0l # AQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGlu # Z1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQhk5odHRw # Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBp # bmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIw # CwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3xHCcEua5gQezRCESe # Y0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FU # FqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7Y # MTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu+WUqW4daIqToXFE/JQ/EABgfZXLWU0zi # TN6R3ygQBHMUBaB5bdrPbF6MRYs03h4obEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/ # QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq8/gVutDojBIFeRlq # AcuEVT0cKsb+zJNEsuEB7O7/cuvTQasnM9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3 # Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol/DJgddJ35XTxfUlQ+8Hggt8l2Yv7roan # cJIFcbojBcxlRcGG0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ # ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3ocCVccAvlKV9jEnstrniLvUxxVZE/rptb7 # IRE2lskKPIJgbaP5t2nGj/ULLi49xTcBZU8atufk+EMF/cWuiC7POGT75qaL6vdC # vHlshtjdNXOCIUjsarfNZzGCBJwwggSYAgEBMH0waTELMAkGA1UEBhMCVVMxFzAV # BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVk # IEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQDsYrSCrm # UJuvTRscProh/zANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKAC # gAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsx # DjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCDpdhoFq+fSGvlM1lcDmju3 # c+yBoeAzuBpVIrxkQrKcZzALBgcqhkjOPQIBBQAERzBFAiA9u3098RAqovk5q38/ # kycj81y65PKDaWOJsc+Xr4A//QIhAP8Z86fDX7fhcjuVwsBRiyQovCHwAKFp9Zma # XZoZIpY7oYIDJjCCAyIGCSqGSIb3DQEJBjGCAxMwggMPAgEBMH0waTELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy # dCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENB # MQIQCoDvGEuN8QWC0cR2p5V0aDANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJ # AzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTI2MDgxMjIwMzQxNFowLwYJ # KoZIhvcNAQkEMSIEIIfFwbtIbzkL2Uwhk39kgg2q+PqbMN4JtfHMODlf2qJNMA0G # CSqGSIb3DQEBAQUABIICADNgQpORPmoWKKIXJj7icC5V30EMWNS8NBll7qyArxN5 # c7jQ4zLWvbYQSirtn4VN9zekzSBzPzvJCM64k5JAxFVF/QCOqDKwP/oylraPmtp5 # JTuQjN+IfJpxkNq98ZfqtTziq8je7R7Kpm+fLh/k7+AlHBaWsMCVb78yA/mQF3aB # CSSrEqXITo4ENc2rYvlzhSeQ5Csx1AxQBKbLLQLkiO+jiwdk6OBvxVouR8ijZtPJ # LSq+UZDS4bIOrj31R2JrRSLZuhvHpAgySLhFJWQ5epvbLJ7onK7FJKpdyCQmtpJ6 # vUyvqR3gjuM4VT4qICM4K/aGZ+WyYToNXOxcwuybUNSTJjBhgpjSnRYY0v+c0mhR # VSBBDRRHM83OL1bb24NidUP1sHeHGcj0AYePhFnKCGSTc3LZDbeMXSAj3JVLJPu5 # VVpiYtE15Gngtmg1wWb8qfYo1GDvhgDivPDjdR0RIjH3qeiUjtlkqhGpLQztzKp+ # lo+GPsVG+Y3moXlPOYbh94STth3iBZEwJsG2+Gf5gzCXNfmC9IwdKyMckX7KmLxJ # c9cdHdzswU+VH0dvYtM5IUKr/7OzFgTLO2sh1oVhkEfIZqF40l04bduyFMgQYTGH # 0kA94CAGJGUl2WgNbewk1oKAqxaPOudFkwFK3+4yANoZofimwS9XpCIFWGTH3yGT # SIG # End signature block |