DefenderForIdentity.psm1
<#
Copyright (c) Microsoft Corporation. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #> #requires -Version 4.0 #requires -Modules ActiveDirectory, GroupPolicy #region General settings $script:settings = @{ gpoNamePrefix = 'Microsoft Defender for Identity' gpoExtensions = @{ 'Core GPO Engine' = '00000000-0000-0000-0000-000000000000' 'Tool Extension GUID (Computer Policy Settings)' = '0F6B957D-509E-11D1-A7CC-0000F87571E3' 'Security' = '827D319E-6EAC-11D2-A4EA-00C04F79F83A' 'Computer Restricted Groups' = '803E14A0-B4FB-11D0-A0D0-00A0C90F574B' 'Preference Tool CSE GUID Registry' = 'BEE07A6A-EC9F-4659-B8C9-0B1937907C83' 'Preference CSE GUID Registry' = 'B087BE9D-ED37-454F-AF9C-04291E351182' 'Audit Configuration Extension' = '0F3F3735-573D-9804-99E4-AB2A69BA5FD4' 'Audit Policy Configuration' = 'F3CCC681-B74C-4060-9F26-CD84525DCA2A' } ProcessorPerformance = @{ GpoName = '{0} - Processor Performance' SchemeGuid = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' Key = 'HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Power\PowerSettings' ValueName = 'ActivePowerScheme' } NTLMAuditing = @{ GpoName = '{0} - NTLM Auditing for DCs' RegistrySet = @{ 'System\CurrentControlSet\Control\Lsa\MSV1_0\AuditReceivingNTLMTraffic' = '2' 'System\CurrentControlSet\Control\Lsa\MSV1_0\RestrictSendingNTLMTraffic' = '1|2' 'System\CurrentControlSet\Services\Netlogon\Parameters\AuditNTLMInDomain' = '7' } } CAAuditing = @{ GpoName = '{0} - Auditing for CAs' GpoVal = @{ 'AuditFilter' = 127 } GpoReg = 'System\CurrentControlSet\Services\CertSvc\Configuration\%DomainName%-%ComputerName%-CA' RegPathActive = 'System\CurrentControlSet\Services\CertSvc\Configuration\Active' RegistrySet = @{ 'System\CurrentControlSet\Services\CertSvc\Configuration\{0}\AuditFilter' = 127 } GPPermissions = [ordered]@{ 'Cert Publishers' = 'GpoApply' 'Domain Controllers' = 'GpoRead' 'Authenticated Users' = 'GpoRead' } } AdvancedAuditPolicyDCs = @{ GpoName = '{0} - Advanced Audit Policy for DCs' PolicySettings = @' Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value ,System,Security System Extension,{0CCE9211-69AE-11D9-BED3-505054503030},Success and Failure,,3 ,System,Distribution Group Management,{0CCE9238-69AE-11D9-BED3-505054503030},Success and Failure,,3 ,System,Security Group Management,{0CCE9237-69AE-11D9-BED3-505054503030},Success and Failure,,3 ,System,Computer Account Management,{0CCE9236-69AE-11D9-BED3-505054503030},Success and Failure,,3 ,System,User Account Management,{0CCE9235-69AE-11D9-BED3-505054503030},Success and Failure,,3 ,System,Directory Service Access,{0CCE923B-69AE-11D9-BED3-505054503030},Success and Failure,,3 ,System,Directory Service Changes,{0CCE923C-69AE-11D9-BED3-505054503030},Success and Failure,,3 ,System,Credential Validation,{0CCE923F-69AE-11D9-BED3-505054503030},Success and Failure,,3 '@ } AdvancedAuditPolicyCAs = @{ GpoName = '{0} - Advanced Audit Policy for CAs' PolicySettings = @' Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value ,System,Audit Certification Services,{0cce9221-69ae-11d9-bed3-505054503030},Success and Failure,,3 '@ GPPermissions = [ordered]@{ 'Cert Publishers' = 'GpoApply' 'Domain Controllers' = 'GpoRead' 'Authenticated Users' = 'GpoRead' } } ObjectAuditing = @{ Path = 'AD:\{0}' Auditing = @' SecurityIdentifier,AccessMask,AuditFlagsValue,InheritedObjectAceType,Description,InheritanceType,PropagationFlags S-1-1-0,852331,1,bf967aba-0de6-11d0-a285-00aa003049e2,Descendant User Objects,2,2 S-1-1-0,852331,1,bf967a9c-0de6-11d0-a285-00aa003049e2,Descendant Group Objects,2,2 S-1-1-0,852331,1,bf967a86-0de6-11d0-a285-00aa003049e2,Descendant Computer Objects,2,2 S-1-1-0,852331,1,ce206244-5827-4a86-ba1c-1c0c386c1b64,Descendant msDS-ManagedServiceAccount Objects,2,2 S-1-1-0,852075,1,7b8b558a-93a5-4af7-adca-c017e67f1057,Descendant msDS-GroupManagedServiceAccount Objects,2,2 '@ | ConvertFrom-Csv } ConfigurationContainerAuditing = @{ Validate = 'LDAP://CN=Microsoft Exchange,CN=Services,CN=Configuration,{0}' Path = 'AD:\CN=Configuration,{0}' Auditing = @' SecurityIdentifier,AccessMask,AuditFlagsValue,AceFlagsValue,InheritedObjectAceType,InheritanceType,PropagationFlags S-1-1-0,32,3,194,00000000-0000-0000-0000-000000000000,1,0 '@ | ConvertFrom-Csv } AdfsAuditing = @{ Validate = 'LDAP://CN=ADFS,CN=Microsoft,CN=Program Data,{0}' Path = 'AD:\CN=ADFS,CN=Microsoft,CN=Program Data,{0}' Auditing = @' SecurityIdentifier,AccessMask,AuditFlagsValue,AceFlagsValue,InheritedObjectAceType,InheritanceType,PropagationFlags S-1-1-0,48,3,194,00000000-0000-0000-0000-000000000000,1,0 '@ | ConvertFrom-Csv } SensitiveGroups = @{ 'Administrators' = 'S-1-5-32-544' 'Account Operators' = 'S-1-5-32-548' 'Backup Operators' = 'S-1-5-32-551' 'Domain Admins' = '{0}-512' 'Domain Controllers' = '{0}-516' 'Enterprise Admins' = '{0}-519' 'Group Policy Creator Owners' = '{0}-520' 'Print Operators' = 'S-1-5-32-550' 'Replicators' = 'S-1-5-32-552' 'Schema Admins' = '{0}-518' 'Server Operators' = 'S-1-5-32-549' 'Cert Publishers' = '{0}-517' } pdcE = [string]((Get-ADDomain -ErrorAction SilentlyContinue).PDCEmulator) } if (Test-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath ($PSUICulture))) { Import-LocalizedData -BindingVariable strings } else { Import-LocalizedData -BindingVariable strings -UICulture en-US } #endregion #region General helper functions function Get-MDIValidationMessage { param($Result) if ($Result) { $strings['Validation_Passed'] } else { $strings['Validation_Failed'] } } function Resolve-MDIPath { param( [parameter(Mandatory)] $Path ) $return = Resolve-Path -Path $Path -ErrorAction SilentlyContinue -ErrorVariable resolveError if ($return.Path) { $return.Path } else { $resolveError[0].TargetObject } } function Format-Json { param( [Parameter(Mandatory, ValueFromPipeline)] [String] $json ) $indent = 0; ($json -Split '\n' | ForEach-Object { if ($_ -match '[\}\]]') { $indent-- } $line = (' ' * $indent * 2) + $_.TrimStart().Replace(': ', ': ') if ($_ -match '[\{\[]') { $indent++ } $line }) -join "`n" } function Test-MDICAServer { [CmdletBinding()] param() [bool](Get-Service CertSvc -ErrorAction SilentlyContinue) } function New-MDIPassword { $guid = (New-Guid).guid.split('-') $guid[0] += [char](Get-Random -min 65 -max 90) $guid[1] += [char](Get-Random -min 65 -max 90) ConvertTo-SecureString ($guid -join '-') -AsPlainText -Force } #endregion #region Sensor service helper functions function Get-MDISensorBinPath { [CmdletBinding()] param() $wmiParams = @{ Namespace = 'root\cimv2' ClassName = 'Win32_Service' Property = 'PathName' Filter = 'Name="AATPSensor"' ErrorAction = 'Stop' } Write-Verbose -Message $strings['Sensor_LocateConfigurationFile'] try { $return = (Get-CimInstance @wmiParams | Select-Object -ExpandProperty PathName) -replace '"|Microsoft\.Tri\.Sensor\.exe', '' } catch { $return = $null } if ([string]::IsNullOrEmpty($return)) { Write-Warning $strings['Sensor_ServiceNotFound'] } $return } function Stop-MDISensor { [CmdletBinding()] param() Stop-Service -Name AATPSensorUpdater -Force } function Start-MDISensor { [CmdletBinding()] param() Start-Service -Name AATPSensorUpdater } #endregion #region Service account configuration functions function New-MDIDSA { [CmdletBinding(DefaultParameterSetName = "gmsaAccount")] Param( [parameter(Mandatory = $true, ParameterSetName = "gmsaAccount", Position = 1)] [parameter(Mandatory = $true, ParameterSetName = "standardAccount", Position = 1)] [ValidateLength(1, 16)] [string]$Identity, [parameter(Mandatory = $true, ParameterSetName = "gmsaAccount")] [ValidateLength(1, 28)] [string]$GmsaGroupName, [parameter(Mandatory = $false, ParameterSetName = "gmsaAccount")] [parameter(Mandatory = $false, ParameterSetName = "standardAccount")] [string]$BaseDn, [parameter(Mandatory = $false, ParameterSetName = "standardAccount")] [switch]$ForceStandardAccount, [parameter(Mandatory = $false, ParameterSetName = "gmsaAccount")] [parameter(Mandatory = $false, ParameterSetName = "standardAccount")] [string]$Server ) $Server = Get-MDIDC -Server $Server $domain = Get-ADDomain -Server $Server $returnVal = $false if ($Identity -match '.*\$') { $Identity = $Identity.replace('$', '') } try { $adObjectParams = @{ LDAPFilter = "(objectSid=$(($domain).domainSid)-519)" }; if (-not [string]::IsNullOrEmpty($server)) { $adObjectParams.Add("Server",$server) } $id = [Security.Principal.WindowsIdentity]::GetCurrent() $groups = $id.Groups | ForEach-Object { $_.Translate([Security.Principal.NTAccount]) } if ([bool]$(($domain).parentdomain)) { $forestSid = (Get-ADDomain -Server $($domain.parentdomain)).domainsid.value $eaGroupName = '{0}\{1}' -f (Get-ADDomain -Server $($domain.parentdomain)).netbiosname, (Get-ADObject -LDAPFilter "(objectSid=$forestSid-519)" -Server $($domain.parentdomain)).name } else { $eaGroupName = '{0}\{1}' -f ($domain.netbiosname), (Get-ADObject @adObjectParams).name } } catch { Write-Warning -Message $strings['DSA_EnterpriseAdminGroupNotFound'] } if ([string]::IsNullOrEmpty($baseDn)) { $baseDn = "{0},{1}" -f "CN=Users", $($domain.DistinguishedName) $adObjectParams.LDAPFilter = "(distinguishedName=$baseDn)" try { $bdnCheck = Get-ADObject @adObjectParams } catch { $baseDn = $($domain.distinguishedname) } } if ($forceStandardAccount) { $adUserParams = @{ Identity = $Identity }; if (-not [string]::IsNullOrEmpty($server)) { $adUserParams.Add("Server",$server) } try { Get-Aduser @adUserParams -ErrorAction silentlycontinue } catch { try { $securePassword = New-MDIPassword $adUserParams.Add("Name", $Identity) $adUserParams.Add("AccountPassword", $securePassword) $adUserParams.Add("SamAccountName", $Identity) $adUserParams.Add("Path", $baseDn) $adUserParams.Add("Description", "This account runs the MDI service") $adUserParams.Add("Enabled", $true) $adUserParams.Remove("Identity") $serviceAccount = New-ADUser @adUserParams -PassThru $returnVal = $true } catch { Write-Error $strings['DSA_CannotCreateAccount'] } } } else { if ($eaGroupName) { if ($eaGroupName -in $groups) { if (-not (Get-KdsRootKey)) { try { Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10)) $strings['DSA_CreatedKDSRootKey'] } catch { throw $strings['DSA_CannotCreateKDSRootKey'] } } else { Write-Verbose $strings['DSA_FoundKDSRootKey'] } } } $domainDCs = "$(($domain.domainsid).value)-516" try { $adObjectParams.LDAPFilter = "(objectSid=$domainDCs)" $domainDCsGroupName = (Get-ADGroup @adObjectParams).Name } catch { Write-Warning $strings['DSA_CannotFindDomainControllersGroup'] } $gmsaGroupParams = @{ Name = $GmsaGroupName SamAccountName = $GmsaGroupName Path = $baseDn GroupScope = "Universal" Description = $strings['DSA_GroupDescription'] -f $Identity }; if (-not [string]::IsNullOrEmpty($server)) { $gmsaGroupParams.Add("Server",$server) } try { $groupExists = [bool](Get-ADGroup -Identity $GmsaGroupName -ErrorAction SilentlyContinue) } catch { $groupExists = $false } if (-not $groupExists) { try { New-ADGroup @gmsaGroupParams Write-Verbose $strings['DSA_CreatedGMSAGroup'] } catch { Write-Error $strings['DSA_CannotCreateGMSAGroup'] } } $newAdServiceAccountParams = @{ Name = $Identity DNSHostName = $($domain.DNSRoot) PrincipalsAllowedToRetrieveManagedPassword = [string[]]$GmsaGroupName SamAccountName = $gmsaAccountName }; if (-not [string]::IsNullOrEmpty($server)) { $newAdServiceAccountParams.Add("Server",$server) } if (-not [string]::IsNullOrEmpty($domainDCsGroupName)) { $newAdServiceAccountParams.PrincipalsAllowedToRetrieveManagedPassword += $domainDCsGroupName } try { $serviceAccount = New-ADServiceAccount @newAdServiceAccountParams -PassThru $returnVal = $true } catch { try { $newAdServiceAccountParams.Add("Path",$baseDn) $serviceAccount = New-ADServiceAccount @newAdServiceAccountParams -PassThru $returnVal = $true } catch { throw } write-error $strings['DSA_CannotCreateGMSAAccount'] } } Set-MDIDeletedObjectsContainerPermission -Identity ("{0}\{1}" -f $domain.netbiosname, $serviceAccount.SamAccountName) -Server $Server | out-null return $returnVal } #endregion #region Sensor configuration helper functions function Get-MDISensorConfiguration { [CmdletBinding()] param() $sensorBinPath = Get-MDISensorBinPath if ($null -eq $sensorBinPath) { $sensorConfiguration = $null } else { Write-Verbose -Message $strings['Sensor_ReadConfigurationFile'] $sensorConfigurationPath = Join-Path -Path $sensorBinPath -ChildPath 'SensorConfiguration.json' $sensorConfiguration = Get-Content -Path $sensorConfigurationPath -Raw | ConvertFrom-Json } if ($null -ne $sensorConfiguration.SensorProxyConfiguration) { $SensorProxyConfiguration = [PSCustomObject]@{ IsProxyEnabled = -not [string]::IsNullOrEmpty($sensorConfiguration.SensorProxyConfiguration.Url) IsAuthenticationProxyEnabled = -not [string]::IsNullOrEmpty($sensorConfiguration.SensorProxyConfiguration.UserName) Url = $sensorConfiguration.SensorProxyConfiguration.Url UserName = $sensorConfiguration.SensorProxyConfiguration.UserName EncryptedUserPasswordData = $sensorConfiguration.SensorProxyConfiguration.EncryptedUserPasswordData.EncryptedBytes CertificateThumbprint = $sensorConfiguration.SensorProxyConfiguration.EncryptedUserPasswordData.CertificateThumbprint } $sensorConfiguration.SensorProxyConfiguration = $SensorProxyConfiguration } $sensorConfiguration } function Get-MDIEncryptedPassword { param( [Parameter(Mandatory = $true)] [string] $CertificateThumbprint, [Parameter(Mandatory = $true)] [PSCredential] $Credential ) $store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList @( [System.Security.Cryptography.X509Certificates.StoreName]::My, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine ) $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2] $store.Certificates.Find( [System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $CertificateThumbprint, $false)[0] $rsaPublicKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert) $bytes = [System.Text.Encoding]::Unicode.GetBytes( $Credential.GetNetworkCredential().Password ) $encrypted = $rsaPublicKey.Encrypt($bytes, [System.Security.Cryptography.RSAEncryptionPadding]::OaepSHA256) $encryptedPassword = [System.Convert]::ToBase64String($encrypted) $store.Close() $encryptedPassword } function Get-MDIDecryptedPassword { param( [Parameter(Mandatory = $true)] [string] $CertificateThumbprint, [Parameter(Mandatory = $true)] [string] $EncryptedString ) $store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList @( [System.Security.Cryptography.X509Certificates.StoreName]::My, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine ) $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2] $store.Certificates.Find( [System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $CertificateThumbprint, $false)[0] $rsaPublicKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert) $encrypted = [System.Convert]::FromBase64String($EncryptedString) $bytes = $rsaPublicKey.Decrypt($encrypted, [System.Security.Cryptography.RSAEncryptionPadding]::OaepSHA256) $decryptedPassword = [System.Text.Encoding]::Unicode.GetString($bytes) $store.Close() $decryptedPassword } function Get-MDISensorProxyConfiguration { [CmdletBinding()] param() $sensorConfiguration = Get-MDISensorConfiguration if ($null -eq $sensorConfiguration) { $proxyConfiguration = $null } else { $proxyConfiguration = $sensorConfiguration.SensorProxyConfiguration } $proxyConfiguration } function Set-MDISensorProxyConfiguration { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $false)] [string] $ProxyUrl, [Parameter(Mandatory = $false)] [PSCredential] $ProxyCredential ) $operation = if ([string]::IsNullOrEmpty($ProxyUrl)) { 'Clear' } else { 'Set' } if ($PSCmdlet.ShouldProcess($strings['Sensor_ProxyConfigurationAction'], $operation)) { $sensorConfiguration = Get-MDISensorConfiguration if ($null -eq $sensorConfiguration) { Write-Error $strings['Sensor_ErrorReadingSensorConfiguration'] -ErrorAction Stop } if ([string]::IsNullOrEmpty($ProxyUrl)) { $sensorConfiguration.SensorProxyConfiguration = $null } else { if ($ProxyCredential) { $thumbprint = $sensorConfiguration.SecretManagerConfigurationCertificateThumbprint $sensorConfiguration.SensorProxyConfiguration = [PSCustomObject]@{ '$type' = 'SensorProxyConfiguration' Url = $ProxyUrl UserName = $ProxyCredential.UserName EncryptedUserPasswordData = [PSCustomObject]@{ '$type' = 'EncryptedData' EncryptedBytes = Get-MDIEncryptedPassword -CertificateThumbprint $thumbprint -Credential $ProxyCredential SecretVersion = $null CertificateThumbprint = $sensorConfiguration.SecretManagerConfigurationCertificateThumbprint } } } else { $sensorConfiguration.SensorProxyConfiguration = [PSCustomObject]@{ '$type' = 'SensorProxyConfiguration' Url = $ProxyUrl } } } Stop-MDISensor Write-Verbose -Message $strings['Sensor_WriteSensorConfigurationFile'] $sensorConfiguration | ConvertTo-Json | Format-Json | Set-Content -Path (Join-Path -Path (Get-MDISensorBinPath) -ChildPath 'SensorConfiguration.json') Start-MDISensor } } function Clear-MDISensorProxyConfiguration { [CmdletBinding(SupportsShouldProcess = $true)] param() if ($PSCmdlet.ShouldProcess($strings['Sensor_ProxyConfigurationAction'], 'Clear')) { Set-MDISensorProxyConfiguration -ProxyUrl $null } } #endregion #region GPO helper functions function Get-MDIGPOName { param( [Parameter(Mandatory)] [string] $Name, [Parameter(Mandatory = $false)] [string] $GpoNamePrefix ) if ([string]::IsNullOrEmpty($GpoNamePrefix)) { $Name -f $script:settings['gpoNamePrefix'] } else { $Name -f $GpoNamePrefix } } function New-MDIGPO { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Name, [Parameter(Mandatory = $false)] [switch] $CreateGpoDisabled, [Parameter(Mandatory = $false)] [string] $Server ) $returnVal = $null Write-Verbose -Message ($strings['GPO_Create'] -f $Name) $Server = Get-MDIDC -Server $Server $maxWaitTime = (Get-Date).AddSeconds(3) $successGpo = $false $gpoParams = @{ Name = $Name }; if (-not [string]::IsNullOrEmpty($server)) { $gpoParams.Add("Server",$server) } try { $gpoCreate = New-GPO @gpoParams -ErrorAction silentlycontinue $successGpo = $true } catch { $successGpo = $false } if ($successGpo) { do { Start-Sleep -Milliseconds 500 $gpo = Get-MDIGPO @gpoParams } while (-not (($gpo) -or ($maxWaitTime -lt (Get-Date)))) if ($gpo) { $gPCFileSysPath = $gpo.gPCFileSysPath do { Start-Sleep -Milliseconds 500 } while (-not ((Test-Path -Path $gPCFileSysPath) -or ($maxWaitTime -lt (Get-Date)))) if ($CreateGpoDisabled) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::AllSettingsDisabled } else { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled } $returnVal = $gpo | Add-Member -MemberType NoteProperty -Name gPCFileSysPath -Value $gPCFileSysPath -PassThru -Force } else { $returnVal = $null } } else { $returnVal = $null } return $returnVal } function Get-MDIGPO { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Name, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $maxWaitTime = (Get-Date).AddSeconds(3) $gpoParams = @{ Name = $Name }; if (-not [string]::IsNullOrEmpty($server)) { $gpoParams.Add("Server",$server) } try { $gpo = Get-Gpo @gpoParams -ErrorAction SilentlyContinue } catch { $gpo = $null } if ($null -eq $gpo) { Write-Verbose -Message ("'{0}' - {1}" -f $Name, $strings['GPO_NotFound']) } else { Start-Sleep -Milliseconds 500 if ($Server) { try { $gPCFileSysPath = (Get-ADObject -Identity $gpo.Path -Properties gPCFileSysPath -Server $Server).gPCFileSysPath } catch { do { try { Start-Sleep -Milliseconds 500 $gPCFileSysPath = (Get-ADObject -Identity $gpo.Path -Properties gPCFileSysPath -Server $Server).gPCFileSysPath } catch { } } while (!(($gPCFileSysPath) -or ($maxWaitTime -lt (Get-Date)))) } $gPCArray = $gPCFileSysPath.split('\') $gPCArray[2] = $Server $gPCFileSysPath = $gPCArray -join '\' } else { $gPCFileSysPath = (Get-ADObject -Identity $gpo.Path -Properties gPCFileSysPath).gPCFileSysPath } do { Start-Sleep -Milliseconds 500 } while (-not ((Test-Path -Path $gPCFileSysPath) -or ($maxWaitTime -lt (Get-Date)))) $gpo | Add-Member -MemberType NoteProperty -Name gPCFileSysPath -Value $gPCFileSysPath -Force } return $gpo } function Get-MDIGPOLink { param( [guid] $Guid, [Parameter(Mandatory = $false)] [string] $Server ) Write-Verbose -Message $strings['GPO_GetLinks'] $Server = Get-MDIDC -Server $Server $gpoReportParams = @{ Guid = $Guid ReportType = "Xml" }; if (-not [string]::IsNullOrEmpty($server)) { $gpoReportParams.Add("Server",$server) } $xml = [xml](Get-GPOReport @gpoReportParams) @($xml.GPO.LinksTo) } function Test-MDIGPOLink { [CmdletBinding()] param( [guid] $Guid, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $return = $false $gpoParams = @{ Guid = $Guid }; if (-not [string]::IsNullOrEmpty($server)) { $gpoParams.Add("Server",$server) } $enabledLinks = @(Get-MDIGPOLink @gpoParams | Where-Object { $_.Enabled -eq 'true' }) if ($enabledLinks.Count -lt 1) { Write-Verbose -Message ($strings['GPO_NotLinkedOrEnabled']) } else { $return = $true $enabledLinks | ForEach-Object { Write-Verbose -Message ($strings['GPO_LinkedAndEnabled'] -f $_.SOMPath) } } $return } function Set-MDIGPOLink { param( [Parameter(Mandatory)] [guid] $Guid, [Parameter(Mandatory)] [string] $Target, [Microsoft.GroupPolicy.EnableLink] $LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes, [Microsoft.GroupPolicy.EnforceLink] $Enforced = [Microsoft.GroupPolicy.EnforceLink]::Yes, [Parameter(Mandatory = $false)] [string] $Server ) Write-Verbose -Message $strings['GPO_SetLink'] $gpLink = @{ Guid = $Guid LinkEnabled = $LinkEnabled Enforced = $Enforced Target = $Target }; if (-not [string]::IsNullOrEmpty($server)) { $gpLink.Add("Server",$server) } $link = New-GPLink @gpLink -ErrorAction SilentlyContinue if ($null -eq $link) { $link = Set-GPLink @gpLink -ErrorAction SilentlyContinue } if ($null -eq $link) { throw $strings['GPO_UnableToUpdateLink'] } } function Get-MDIGPOMachineVersion { [CmdletBinding()] param( [Parameter(Mandatory)] [guid] $Guid, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoParams = @{ Guid = $Guid }; if (-not [string]::IsNullOrEmpty($server)) { $gpoParams.Add("Server",$server) } (Get-GPO @gpoParams).Computer | Select-Object -Property *Version } function Set-MDIGPOMachineVersion { [CmdletBinding()] param( [Parameter(Mandatory)] [guid] $Guid, [Parameter(Mandatory)] [int] $Version, [Parameter(Mandatory = $false)] [ValidateSet('Sysvol', 'DS', 'All')] [string] $Mode = 'Sysvol', [Parameter(Mandatory = $false)] [string] $Server ) Write-Verbose -Message $strings['GPO_UpdateVersion'] $Server = Get-MDIDC -Server $Server if ($Mode -match 'ALL|DS') { $Replace = @{versionNumber = $Version } $gpoAdObjectPath = 'CN={0},{1}' -f "{$Guid}", (Get-MDIAdPath 'CN=Policies,CN=System,{0}') $adObjectParams = @{ Identity = $gpoAdObjectPath Replace = $Replace }; if (-not [string]::IsNullOrEmpty($server)) { $adObjectParams.Add("Server",$server) } Set-ADObject @adObjectParams | Out-Null } if ($Mode -match 'ALL|Sysvol') { if ($Server) { $filePath = '\\{0}\SYSVOL\{1}\Policies\{2}\GPT.INI' -f $Server, $env:USERDNSDOMAIN, "{$guid}" } else { $filePath = '\\{0}\SYSVOL\{0}\Policies\{1}\GPT.INI' -f $env:USERDNSDOMAIN, "{$guid}" } $newContent = (([system.io.file]::ReadAllLines($filePath)) -join [environment]::NewLine) -replace 'Version=\d+', ('Version={0}' -f $version) [System.io.file]::WriteAllLines($filePath, $newContent, (New-Object System.Text.ASCIIEncoding)) } } function Get-MDIGPOMachineExtension { [CmdletBinding()] param( [Parameter(Mandatory)] [guid] $Guid, [Parameter(Mandatory = $false)] [string] $Server ) Write-Verbose -Message $strings['GPO_GetExtension'] $Server = Get-MDIDC -Server $Server $gpoAdObjectPath = 'CN={0},{1}' -f "{$Guid}", (Get-MDIAdPath 'CN=Policies,CN=System,{0}') $adObjectParams = @{ Identity = $gpoAdObjectPath Properties = @("gPCMachineExtensionNames", "VersionNumber") }; if (-not [string]::IsNullOrEmpty($server)) { $adObjectParams.Add("Server",$server) } Get-ADObject @adObjectParams } function Set-MDIGPOMachineExtension { [CmdletBinding()] param( [Parameter(Mandatory)] [guid] $Guid, [Parameter(Mandatory)] [string[]] $Extension, [Parameter(Mandatory = $false)] [string] $Server ) Write-Verbose -Message $strings['GPO_SetExtension'] $return = $null $Server = Get-MDIDC -Server $Server $extensions = $Extension | ForEach-Object { "{$_}" } $extensionGuids = '[{0}]' -f [string]::Join('', $extensions, 0, 2) $Replace = @{gPCMachineExtensionNames = $extensionGuids } $gpoAdObjectPath = 'CN={0},{1}' -f "{$Guid}", (Get-MDIAdPath 'CN=Policies,CN=System,{0}') $adObjectParams = @{ Identity = $gpoAdObjectPath Replace = $Replace }; if (-not [string]::IsNullOrEmpty($server)) { $adObjectParams.Add("Server",$server) } try { $gpoUpdated = Set-ADObject @adObjectParams -PassThru } catch { Write-Verbose -Message $strings['GPO_UnableToSetExtension'] } if ($gpoUpdated) { try { $gpoVersionParams = @{ Guid = $Guid }; if (-not [string]::IsNullOrEmpty($server)) { $gpoVersionParams.Add("Server",$server) } $gpoComputerDSVersion = (Get-MDIGPOMachineVersion @gpoVersionParams).DSVersion if ($gpoComputerDSVersion -lt 2) { $gpoComputerDSVersion = 3 } else { $gpoComputerDSVersion++ } $setGpoMachineVersionParams = @{ Guid = $Guid Version = $gpoComputerDSVersion Mode = "All" }; if (-not [string]::IsNullOrEmpty($server)) { $setGpoMachineVersionParams.Add("Server",$server) } Set-MDIGPOMachineVersion @setGpoMachineVersionParams $return = $gpoComputerDSVersion } catch { Write-Verbose -Message $strings['GPO_UnableToSetExtension'] } } $return } function Test-MDIGPOEnabledAndLink { [CmdletBinding()] param( [Parameter(Mandatory)] $GPO, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $state = $false $testMdiGpoLinkParams = @{ Guid = $GPO.Id.Guid }; if (-not [string]::IsNullOrEmpty($server)) { $testMdiGpoLinkParams.Add("Server",$Server) } if (-not ($GPO.GpoStatus -ne [Microsoft.GroupPolicy.GpoStatus]::AllSettingsDisabled)) { Write-Verbose -Message $strings['GPO_SettingsDisabled'] } else { if (-not (Test-MDIGPOLink @testMdiGpoLinkParams)) { Write-Verbose -Message $strings['GPO_LinkNotFound'] } else { $state = $true } } $state } #endregion #region Processor Performance helper functions function Get-MDIProcessorPerformance { & "$($env:SystemRoot)\system32\powercfg.exe" @('/GETACTIVESCHEME') } function Test-MDIProcessorPerformance { [CmdletBinding()] param( [switch] $Detailed ) Write-Verbose -Message $strings['ProcessorPerformance_Validate'] $result = $false $activeScheme = Get-MDIProcessorPerformance if ($activeScheme -match ':\s+(?<guid>[a-fA-F0-9]{8}[-]?([a-fA-F0-9]{4}[-]?){3}[a-fA-F0-9]{12})\s+\((?<name>.*)\)') { $result = $Matches.guid -eq '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } Write-Verbose -Message (Get-MDIValidationMessage $result) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $result Details = $activeScheme }) } else { $result } } function Set-MDIProcessorPerformance { & "$($env:SystemRoot)\system32\powercfg.exe" @('/SETACTIVE', '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c') } function Get-MDIProcessorPerformanceGPO { [CmdletBinding()] param( [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.ProcessorPerformance.GpoName -GpoNamePrefix $GpoNamePrefix $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams $gpRegValParams = @{ Guid = $gpo.Id.Guid Key = $settings.ProcessorPerformance.Key }; if (-not [string]::IsNullOrEmpty($server)) { $gpRegValParams.Add("Server",$Server) } if ($gpo) { $gpo | Select-Object -Property *, @{N = 'GPRegistryValue'; E = { Get-GPRegistryValue @gpRegValParams } } } } function Test-MDIProcessorPerformanceGPO { [CmdletBinding()] param( [switch] $Detailed, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.ProcessorPerformance.GpoName -GpoNamePrefix $GpoNamePrefix Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName) $processorPerfGpoParams = @{ GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $processorPerfGpoParams.Add("Server",$Server) } $state = $false $gpo = Get-MDIProcessorPerformanceGPO @processorPerfGpoParams if ($gpo) { $gpSetOk = $gpo.GPRegistryValue.ValueName -eq $settings.ProcessorPerformance.ValueName -and $gpo.GPRegistryValue.Value -eq $settings.ProcessorPerformance.SchemeGuid -and $gpo.GPRegistryValue.PolicyState -eq [Microsoft.GroupPolicy.PolicyState]::Set $mdiGpoTestLinkParams = @{ GPO = $gpo }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoTestLinkParams.Add("Server",$Server) } if ($gpSetOk) { $state = Test-MDIGPOEnabledAndLink @mdiGpoTestLinkParams } else { Write-Verbose -Message $strings['GPO_SettingsMismatch'] } } Write-Verbose -Message (Get-MDIValidationMessage $state) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $state Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, GPRegistryValue } else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] } }) } else { $state } } function Set-MDIProcessorPerformanceGPO { [CmdletBinding()] param( [switch] $SkipGpoLink, [switch] $CreateGpoDisabled, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.ProcessorPerformance.GpoName -GpoNamePrefix $GpoNamePrefix $gpoParams = @{ GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $gpoParams.Add("Server",$Server) } $gpo = Get-MDIProcessorPerformanceGPO @gpoParams if ($null -eq $gpo) { $gpoParams.Add("CreateGpoDisabled",$CreateGpoDisabled) $gpoParams.Add("Name",$gpoName) $gpoParams.Remove("GpoNamePrefix") $gpo = New-MDIGPO -Name $gpoName -CreateGpoDisabled:$CreateGpoDisabled } if ($gpo) { $gppParams = @{ Guid = $gpo.Id.Guid Type = 'String' Key = $settings.ProcessorPerformance.Key ValueName = $settings.ProcessorPerformance.ValueName Value = $settings.ProcessorPerformance.SchemeGuid }; if (-not [string]::IsNullOrEmpty($server)) { $gppParams.Add("Server",$Server) } $gpoUpdated = Set-GPRegistryValue @gppParams if (-not ($CreateGpoDisabled)) { $gpoUpdated.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled } Start-Sleep -Milliseconds 500 $gpoUpdated.MakeAclConsistent() if (-not $SkipGpoLink) { $gpLinkParams = @{ Guid = $gpo.Id.Guid LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes Enforced = [Microsoft.GroupPolicy.EnforceLink]::Yes Target = 'OU=Domain Controllers,{0}' -f ([adsi]'').distinguishedName.Value }; if (-not [string]::IsNullOrEmpty($server)) { $gpLinkParams.Add("Server",$Server) } Set-MDIGPOLink @gpLinkParams } } else { throw $strings['GPO_UnableToUpdate'] } } #endregion #region Directory Services Auditing helper functions function Get-MDIAdPath { param( [Parameter(Mandatory)] $Path ) $DefaultNamingContext = ([adsi]('LDAP://{0}/RootDSE' -f $env:USERDNSDOMAIN)).defaultNamingContext.Value $Path -f $DefaultNamingContext } function Get-MDISAcl { param( [Parameter(Mandatory)] $Path ) $acls = Get-Acl -Path $Path -Audit -ErrorAction Stop if ($acls) { foreach ($acl in $acls.Audit) { [PSCustomObject]@{ Account = $acl.IdentityReference.Value SecurityIdentifier = $acl.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value AccessMask = [int]$acl.ActiveDirectoryRights AccessMaskDetails = $acl.ActiveDirectoryRights AuditFlags = $acl.AuditFlags AuditFlagsValue = [int]$acl.AuditFlags InheritedObjectAceType = $acl.InheritedObjectType InheritanceType = [int]$acl.InheritanceType PropagationFlags = [int]$acl.PropagationFlags } } } } function Set-MDISAcl { param( [Parameter(Mandatory)] $Auditing ) $Path = Get-MDIAdPath -Path $Auditing.Path $acls = Get-Acl -Path $Path -Audit -ErrorAction SilentlyContinue if ($acls) { Write-Verbose -Message ('Setting System Access Control Lists') foreach ($audit in $Auditing.Auditing) { $account = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @( $audit.SecurityIdentifier)).Translate([System.Security.Principal.NTAccount]).Value $argumentList = @( [Security.Principal.NTAccount] $account, [System.DirectoryServices.ActiveDirectoryRights] $audit.AccessMask, [System.Security.AccessControl.AuditFlags] $audit.AuditFlagsValue, [guid]::Empty.Guid.ToString(), [System.DirectoryServices.ActiveDirectorySecurityInheritance] $audit.InheritanceType, [guid] $audit.InheritedObjectAceType ) $rule = New-Object -TypeName System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $argumentList $acls.AddAuditRule($rule) } Set-Acl -Path $Path -AclObject $acls } } function Get-MDIDomainObjectAuditing { try { Get-MDISAcl -Path (Get-MDIAdPath -Path $settings.ObjectAuditing.Path) } catch [System.Management.Automation.ActionPreferenceStopException] { if ('ObjectNotFound' -eq $_.Exception.ErrorRecord.CategoryInfo.Category) { Write-Warning $_.Exception.Message } else { throw $_ } } } function Get-MDIAdfsAuditing { try { Get-MDISAcl -Path (Get-MDIAdPath -Path $settings.AdfsAuditing.Path) } catch [System.Management.Automation.ActionPreferenceStopException] { if ('ObjectNotFound' -eq $_.Exception.ErrorRecord.CategoryInfo.Category) { Write-Warning $_.Exception.Message } else { throw $_ } } } function Get-MDIConfigurationContainerAuditing { try { Get-MDISAcl -Path (Get-MDIAdPath -Path $settings.ConfigurationContainerAuditing.Path) } catch [System.Management.Automation.ActionPreferenceStopException] { if ('ObjectNotFound' -eq $_.Exception.ErrorRecord.CategoryInfo.Category) { Write-Warning $_.Exception.Message } else { throw $_ } } } function Test-MDIAuditing { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory)] [object[]] $ExpectedAuditing, [switch] $Detailed ) try { $AppliedAuditing = Get-MDISAcl -Path (Get-MDIAdPath -Path $Path) $isAuditingOk = @(foreach ($applied in $AppliedAuditing) { $ExpectedAuditing | Where-Object { ($_.SecurityIdentifier -eq $applied.SecurityIdentifier) -and ($_.AuditFlagsValue -eq $applied.AuditFlagsValue) -and ($_.InheritedObjectAceType -eq $applied.InheritedObjectAceType) -and ($_.InheritanceType -eq $applied.InheritanceType) -and ($_.PropagationFlags -eq $applied.PropagationFlags) -and (([System.DirectoryServices.ActiveDirectoryRights]$applied.AccessMask).HasFlag(([System.DirectoryServices.ActiveDirectoryRights]($_.AccessMask)))) } }).Count -ge $ExpectedAuditing.Count } catch [System.Management.Automation.ActionPreferenceStopException] { if ('ObjectNotFound' -eq $_.Exception.ErrorRecord.CategoryInfo.Category) { $isAuditingOk = $true } else { $isAuditingOk = $false } } if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $isAuditingOk Details = $AppliedAuditing }) } else { $isAuditingOk } } function Test-MDIDomainObjectAuditing { [CmdletBinding()] param( [switch] $Detailed ) Write-Verbose -Message $strings['DomainObject_ValidateAuditing'] $result = Test-MDIAuditing -Path $settings.ObjectAuditing.Path -ExpectedAuditing $settings.ObjectAuditing.Auditing -Detailed:$Detailed if ($Detailed) { Write-Verbose -Message (Get-MDIValidationMessage $result.Status) } else { Write-Verbose -Message (Get-MDIValidationMessage $result) } $result } function Test-MDIAdfsAuditing { [CmdletBinding()] param( [switch] $Detailed ) $result = [PSCustomObject]@{ Status = $true Details = $strings['ADFS_ContainerNotFound'] } Write-Verbose -Message $strings['ADFS_ValidateAuditing'] if ([System.DirectoryServices.DirectoryEntry]::Exists((Get-MDIAdPath -Path $settings.AdfsAuditing.Validate))) { $result = Test-MDIAuditing -Path $settings.AdfsAuditing.Path -ExpectedAuditing $settings.AdfsAuditing.Auditing -Detailed:$Detailed } elseif (-not $Detailed) { $result = $true } if ($Detailed) { Write-Verbose -Message (Get-MDIValidationMessage $result.Status) } else { Write-Verbose -Message (Get-MDIValidationMessage $result) } $result } function Test-MDIConfigurationContainerAuditing { [CmdletBinding()] param( [switch] $Detailed ) $result = [PSCustomObject]@{ Status = $true Details = $strings['Exchange_ContainerNotFound'] } Write-Verbose -Message $strings['Exchange_ValidateAuditing'] if ([System.DirectoryServices.DirectoryEntry]::Exists((Get-MDIAdPath -Path $settings.ConfigurationContainerAuditing.Validate))) { $result = Test-MDIAuditing -Path $settings.ConfigurationContainerAuditing.Path -ExpectedAuditing $settings.ConfigurationContainerAuditing.Auditing -Detailed:$Detailed } elseif (-not $Detailed) { $result = $true } if ($Detailed) { Write-Verbose -Message (Get-MDIValidationMessage $result.Status) } else { Write-Verbose -Message (Get-MDIValidationMessage $result) } $result } function Set-MDIDomainObjectAuditing { Set-MDISAcl -Auditing $settings.ObjectAuditing } function Set-MDIAdfsAuditing { if ([System.DirectoryServices.DirectoryEntry]::Exists((Get-MDIAdPath -Path $settings.AdfsAuditing.Validate))) { Set-MDISAcl -Auditing $settings.AdfsAuditing } else { Write-Warning $strings['ADFS_ContainerNotFound'] } } function Set-MDIConfigurationContainerAuditing { [CmdletBinding()] param( [switch] $Force ) if ($Force -or [System.DirectoryServices.DirectoryEntry]::Exists((Get-MDIAdPath -Path $settings.ConfigurationContainerAuditing.Validate))) { Set-MDISAcl -Auditing $settings.ConfigurationContainerAuditing } else { Write-Warning $strings['Exchange_ContainerNotFound'] } } function Set-MDIDeletedObjectsContainerPermission { [CmdletBinding()] Param( [parameter(Mandatory = $True, Position = 1)] [string]$Identity, [Parameter(Mandatory = $false)] [string] $Server ) $returnVal = $false $Server = Get-MDIDC -Server $Server try { $domain = (Get-ADDomain) } catch { throw } try { $parameters = @{ ScriptBlock = { Param ($param1, $param2) $deletedObjectsDN = "\\$server\CN=Deleted Objects,{0}" -f $param1 $params = @("$deletedObjectsDN", '/takeOwnership') C:\Windows\System32\dsacls.exe $params $params = @("$deletedObjectsDN", '/G', "$($param2):LCRP") C:\Windows\System32\dsacls.exe $params } ArgumentList = $($domain.distinguishedName), $Identity } $command = "Invoke-command @parameters" $dsaclCheck = Invoke-Expression $command $returnVal = $true } catch { Write-Error $strings['DeletedObjectsPermissions_StatusFail'] } return $returnVal } #endregion #region NTLM Auditing helper functions function Get-MDINTLMAuditing { [CmdletBinding()] param() $settings.NTLMAuditing.RegistrySet.GetEnumerator() | ForEach-Object { $name = ($_.Name -split '\\')[-1] $path = 'HKLM:\{0}' -f ($_.Name -replace $name) $value = Get-ItemProperty -Path $path -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name $expected = $_.Value [PSCustomObject]@{ Path = $path Name = $name ActualValue = $value ExpectedValue = $expected } } } function Test-MDINTLMAuditing { [CmdletBinding()] param( [switch] $Detailed ) Write-Verbose -Message $strings['NTLM_ValidateAuditing'] $ntlmAuditing = Get-MDINTLMAuditing $status = @($ntlmAuditing | Where-Object { $_.ActualValue -match $_.ExpectedValue }).Count -eq $settings.NTLMAuditing.RegistrySet.Count Write-Verbose (Get-MDIValidationMessage $status) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $status Details = $ntlmAuditing }) } else { $status } } function Set-MDINTLMAuditing { [CmdletBinding()] param() $settings.NTLMAuditing.RegistrySet.GetEnumerator() | ForEach-Object { $name = ($_.Name -split '\\')[-1] $path = 'HKLM:\{0}' -f ($_.Name -replace $name) $value = ($_.Value -split '\|')[0] Set-ItemProperty -Path $path -Name $name -Value $value -ErrorAction Stop } } function Get-MDINTLMAuditingGPO { [CmdletBinding()] param( [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.NTLMAuditing.GpoName -GpoNamePrefix $GpoNamePrefix $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams if ($gpo) { $gpoReportParams = @{ Guid = $gpo.Id ReportType = "Xml" }; if (-not [string]::IsNullOrEmpty($server)) { $gpoReportParams.Add("Server",$Server) } $report = [xml](Get-GPOReport @gpoReportParams) $options = $report.GPO.Computer.ExtensionData.Extension.SecurityOptions | Where-Object { $_.KeyName -Match 'AuditReceivingNTLMTraffic|RestrictSendingNTLMTraffic|AuditNTLMInDomain' } $RegistryValue = foreach ($opt in $options) { $valueName = ($opt.KeyName -split '\\')[-1] $path = $opt.KeyName -replace '(.*)\\(\w+)', '$1' [PSCustomObject]@{ KeyName = $path valueName = $valueName Value = $opt.SettingNumber valueDisplay = $opt.Display.DisplayString ExpectedValue = ($settings.NTLMAuditing.RegistrySet.GetEnumerator() | Where-Object { ('MACHINE\{0}' -f $_.Name) -eq (Join-Path -Path $path -ChildPath $valueName) }).Value } } $gpo | Select-Object -Property *, @{N = 'RegistryValue'; E = { $RegistryValue } } } } function Test-MDINTLMAuditingGPO { [CmdletBinding()] param( [switch] $Detailed, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.NTLMAuditing.GpoName -GpoNamePrefix $GpoNamePrefix Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName) $mdiGpoParams = @{ GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $state = $false $gpo = Get-MDINTLMAuditingGPO @mdiGpoParams if ($gpo) { $gpSetOk = @($gpo.RegistryValue | Where-Object { $_.Value -match $_.ExpectedValue }).Count -eq $settings.NTLMAuditing.RegistrySet.Count if ($gpSetOk) { $mdiGpoCheckParams = @{ GPO = $gpo }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoCheckParams.Add("Server",$Server) } $state = Test-MDIGPOEnabledAndLink @mdiGpoCheckParams } else { Write-Verbose -Message $strings['GPO_SettingsMismatch'] } } Write-Verbose -Message (Get-MDIValidationMessage $state) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $state Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, RegistryValue } else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] } }) } else { $state } } function Set-MDINTLMAuditingGPO { [CmdletBinding()] param( [switch] $SkipGpoLink, [switch] $CreateGpoDisabled, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.NTLMAuditing.GpoName -GpoNamePrefix $GpoNamePrefix $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams if ($null -eq $gpo) { $mdiGpoParams.Add("CreateGpoDisabled",$CreateGpoDisabled) $gpo = New-MDIGPO @mdiGpoParams } $filePath = '{0}\Machine\Microsoft\Windows NT\SecEdit' -f $gpo.gPCFileSysPath try { New-Item -Path $filePath -ItemType Directory -Force | Out-Null } catch {} $fileContent = @' [Unicode] Unicode=yes [Version] signature="$CHICAGO$" Revision=1 [Registry Values] '@ $settings.NTLMAuditing.RegistrySet.GetEnumerator() | ForEach-Object { $value = ($_.Value -split '\|')[0] $fileContent += '{2}MACHINE\{0}=4,{1}' -f $_.Name, $Value, [System.Environment]::NewLine } [System.Io.File]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'GptTmpl.inf'), $fileContent, (New-Object System.Text.UnicodeEncoding)) if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled } $gpo.MakeAclConsistent() $mdiGpoMachineExtensionParams = @{ Guid = $gpo.Id.Guid Extension = @($settings.gpoExtensions['Security'], $settings.gpoExtensions['Computer Restricted Groups']) }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoMachineExtensionParams.Add("Server",$Server) } $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams if ($null -ne $gpoUpdated) { if (-not $SkipGpoLink) { $gpLinkParams = @{ Guid = $gpo.Id.Guid LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes Enforced = [Microsoft.GroupPolicy.EnforceLink]::Yes Target = 'OU=Domain Controllers,{0}' -f ([adsi]'').distinguishedName.Value } if ($Server) { Start-Sleep -Milliseconds 500 $gpLinkParams.Add("Server", $Server) } Set-MDIGPOLink @gpLinkParams } } else { Write-Warning $strings['GPO_UnableToSetExtension'] } } #endregion #region Advanced Auditing Policy helper functions function Get-MDIAdvAuditPolicy { [CmdletBinding()] param() & "$($env:SystemRoot)\system32\auditpol.exe" @('/get', '/category:*', '/r') | ConvertFrom-Csv | Select-Object *, @{N = 'Setting Value'; E = { $setting = 0 if ($_.'Inclusion Setting' -match 'Success') { $setting += 1 } if ($_.'Inclusion Setting' -match 'Failure') { $setting += 2 } if ($_.'Exclusion Setting' -match 'Success') { $setting += 4 } if ($_.'Exclusion Setting' -match 'Failure') { $setting += 8 } $setting } } } function Test-MDIAdvAuditPolicy { [CmdletBinding()] param( [Parameter(Mandatory)] [object[]] $ExpectedAuditing, [switch] $Detailed ) $AppliedAuditing = Get-MDIAdvAuditPolicy $status = @(foreach ($applied in $AppliedAuditing) { $ExpectedAuditing | Where-Object { ($applied.'Policy Target') -eq ($_.'Policy Target') -and ($applied.'Subcategory GUID').ToUpper() -eq ($_.'Subcategory Guid').ToUpper() -and ($applied.'Setting Value') -eq ($_.'Setting Value') } }).Count -ge $ExpectedAuditing.Count if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $status Details = $AppliedAuditing }) } else { $status } } function Set-MDIAdvAuditPolicy { [CmdletBinding()] param( [Parameter(Mandatory)] $SubcategoryGUID, [string] $InclusionSetting ) if ($SubcategoryGUID -notmatch '^{.*}$') { $SubcategoryGUID = "{$SubcategoryGUID}" } $success = if ($InclusionSetting -match 'Success') { 'enable' } else { 'disable' } $failure = if ($InclusionSetting -match 'Failure') { 'enable' } else { 'disable' } $null = & "$($env:SystemRoot)\system32\auditpol.exe" @('/set', "/subcategory:$SubcategoryGUID", "/success:$success", "/failure:$failure") } #endregion #region Advanced Auditing Policy for DCs Settings function Get-MDIAdvancedAuditPolicyDCsGPO { [CmdletBinding()] param( [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyDCs.GpoName -GpoNamePrefix $GpoNamePrefix $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams if ($gpo) { $gpoReportParams = @{ Guid = $gpo.Id ReportType = "Xml" }; if (-not [string]::IsNullOrEmpty($server)) { $gpoReportParams.Add("Server",$Server) } $report = [xml](Get-GPOReport @gpoReportParams) $currentSettings = $report.GPO.Computer.ExtensionData.Extension.AuditSetting $expectedSettings = $settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv $AuditSettings = foreach ($audit in $expectedSettings) { [PSCustomObject]@{ PolicyTarget = $audit.'Policy Target' SubcategoryName = $audit.'Subcategory' SubcategoryGuid = $audit.'Subcategory GUID' SettingValue = $audit.'Setting Value' ExpectedValue = ($currentSettings | Where-Object { -not [string]::IsNullOrEmpty($_) } | Where-Object { ($_.SubcategoryGuid).ToUpper() -eq ($audit.'Subcategory GUID').ToUpper() -and $_.PolicyTarget -eq $audit.'Policy Target' }).SettingValue } } $gpo | Select-Object -Property *, @{N = 'AuditSettings'; E = { $AuditSettings } } } } function Test-MDIAdvancedAuditPolicyDCsGPO { [CmdletBinding()] param( [switch] $Detailed, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyDCs.GpoName -GpoNamePrefix $GpoNamePrefix Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName) $state = $false $mdiGpoParams = @{ GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIAdvancedAuditPolicyDCsGPO @mdiGpoParams if ($gpo) { $expectedSettings = @($settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv) $gpSetOk = @($gpo.AuditSettings | Where-Object { $_.SettingValue -match $_.ExpectedValue }).Count -eq $expectedSettings.Count if ($gpSetOk) { $mdiGpoCheckParams = @{ GPO = $gpo }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoCheckParams.Add("Server",$Server) } $state = Test-MDIGPOEnabledAndLink @mdiGpoCheckParams } else { Write-Verbose -Message $strings['GPO_SettingsMismatch'] } } Write-Verbose -Message (Get-MDIValidationMessage $state) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $state Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, AuditSettings } else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] } }) } else { $state } } function Set-MDIAdvancedAuditPolicyDCsGPO { [CmdletBinding()] param( [switch] $SkipGpoLink, [switch] $CreateGpoDisabled, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyDCs.GpoName -GpoNamePrefix $GpoNamePrefix $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams if ($null -eq $gpo) { $mdiGpoParams.Add("CreateGpoDisabled",$CreateGpoDisabled) $gpo = New-MDIGPO @mdiGpoParams } $filePath = '{0}\Machine\Microsoft\Windows NT\Audit' -f $gpo.gPCFileSysPath try { New-Item -Path $filePath -ItemType Directory -Force | Out-Null } catch {} [System.io.file]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'audit.csv'), $settings.AdvancedAuditPolicyDCs.PolicySettings, (New-Object System.Text.ASCIIEncoding)) if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled } $gpo.MakeAclConsistent() $mdiGpoMachineExtensionParams = @{ Guid = $gpo.Id.Guid Extension = @($settings.gpoExtensions['Audit Policy Configuration'], $settings.gpoExtensions['Audit Configuration Extension']) }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoMachineExtensionParams.Add("Server",$Server) } $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams if ($null -ne $gpoUpdated) { if (-not $SkipGpoLink) { $gpLinkParams = @{ Guid = $gpo.Id.Guid LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes Enforced = [Microsoft.GroupPolicy.EnforceLink]::Yes Target = 'OU=Domain Controllers,{0}' -f ([adsi]'').distinguishedName.Value } if ($Server) { Start-Sleep -Milliseconds 500 $gpLinkParams.Add("Server", $Server) } Set-MDIGPOLink @gpLinkParams } } else { Write-Warning $strings['GPO_UnableToSetExtension'] } } function Get-MDIAdvancedAuditPolicyDCs { [CmdletBinding()] param() $relevantGUIDs = @($settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv) | Select-Object -ExpandProperty 'Subcategory GUID' -Unique Get-MDIAdvAuditPolicy | Where-Object { $_.'Subcategory GUID' -in $relevantGUIDs } } function Test-MDIAdvancedAuditPolicyDCs { [CmdletBinding()] param( [switch] $Detailed ) Write-Verbose -Message $strings['AdvancedPolicyDCs_Validate'] $result = Test-MDIAdvAuditPolicy -ExpectedAuditing @($settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv) -Detailed:$Detailed if ($Detailed) { Write-Verbose -Message (Get-MDIValidationMessage $result.Status) } else { Write-Verbose -Message (Get-MDIValidationMessage $result) } $result } function Set-MDIAdvancedAuditPolicyDCs { Write-Verbose -Message $strings['AdvancedPolicyDCs_Set'] $settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv | ForEach-Object { $param = @{ SubcategoryGUID = $_.'Subcategory GUID' InclusionSetting = $_.'Inclusion Setting' } Set-MDIAdvAuditPolicy @param } } #endregion #region Advanced Auditing Policy for CAs Settings function Get-MDIAdvancedAuditPolicyCAsGPO { [CmdletBinding()] param( [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyCAs.GpoName -GpoNamePrefix $GpoNamePrefix $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams if ($gpo) { $gpoReportParams = @{ Guid = $gpo.Id ReportType = "Xml" }; if (-not [string]::IsNullOrEmpty($server)) { $gpoReportParams.Add("Server",$Server) } $report = [xml](Get-GPOReport @gpoReportParams) $currentSettings = $report.GPO.Computer.ExtensionData.Extension.AuditSetting $expectedSettings = $settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv $AuditSettings = foreach ($audit in $expectedSettings) { [PSCustomObject]@{ PolicyTarget = $audit.'Policy Target' SubcategoryName = $audit.'Subcategory' SubcategoryGuid = $audit.'Subcategory GUID' SettingValue = $audit.'Setting Value' ExpectedValue = ($currentSettings | Where-Object { $_.SubcategoryName -eq ($audit.Subcategory) -and ($_.SubcategoryGuid).ToUpper() -eq ($audit.'Subcategory GUID').ToUpper() -and $_.PolicyTarget -eq $audit.'Policy Target' }).SettingValue } } $delegation = $settings.AdvancedAuditPolicyCAs.GPPermissions.GetEnumerator() | ForEach-Object { $mdiGpPermissionParams = @{ Guid = $gpo.Id.Guid TargetType = "Group" TargetName = $_.Key }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpPermissionParams.Add("Server",$Server) } Get-GPPermission @mdiGpPermissionParams } $gpo = $gpo | Select-Object -Property *, @{N = 'AuditSettings'; E = { $AuditSettings } }, @{N = 'Delegation'; E = { $delegation } } } $gpo } function Test-MDIAdvancedAuditPolicyCAsGPO { [CmdletBinding()] param( [switch] $Detailed, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyCAs.GpoName -GpoNamePrefix $GpoNamePrefix Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName) $mdiGpoParams = @{ GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $state = $false $gpo = Get-MDIAdvancedAuditPolicyCAsGPO @mdiGpoParams if ($gpo) { $expectedSettings = @($settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv) $gpSetOk = @($gpo.AuditSettings | Where-Object { $_.SettingValue -match $_.ExpectedValue }).Count -eq $expectedSettings.Count if ($gpSetOk) { $gpDelegationOk = @($gpo.Delegation | Where-Object { $settings.AdvancedAuditPolicyCAs.GPPermissions[$_.Trustee.Name] -eq $_.Permission }).Count -eq $settings.AdvancedAuditPolicyCAs.GPPermissions.Count if (-not $gpDelegationOk) { Write-Verbose -Message $strings['GPO_DelegationMismatch'] } else { $mdiGpoCheckParams = @{ GPO = $gpo }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoCheckParams.Add("Server",$Server) } $state = Test-MDIGPOEnabledAndLink @mdiGpoCheckParams } } else { Write-Verbose -Message $strings['GPO_SettingsMismatch'] } } Write-Verbose -Message (Get-MDIValidationMessage $state) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $state Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, AuditSettings } else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] } }) } else { $state } } function Set-MDIAdvancedAuditPolicyCAsGPO { [CmdletBinding()] param( [switch] $SkipGpoLink, [switch] $CreateGpoDisabled, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyCAs.GpoName -GpoNamePrefix $GpoNamePrefix $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams if ($null -eq $gpo) { $mdiGpoParams.Add("CreateGpoDisabled",$CreateGpoDisabled) $gpo = New-MDIGPO @mdiGpoParams } if ($gpo) { $filePath = '{0}\Machine\Microsoft\Windows NT\Audit' -f $gpo.gPCFileSysPath try { Test-Path $filePath | Out-Null } catch { Start-Sleep 3 } try { New-Item -Path $filePath -ItemType Directory -Force | Out-Null Start-Sleep -Milliseconds 500 } catch {} [System.io.file]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'audit.csv'), $settings.AdvancedAuditPolicyCAs.PolicySettings, (New-Object System.Text.ASCIIEncoding)) if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled } $gpo.MakeAclConsistent() $mdiGpoMachineExtensionParams = @{ Guid = $gpo.Id.Guid Extension = @($settings.gpoExtensions['Audit Policy Configuration'], $settings.gpoExtensions['Audit Configuration Extension']) }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoMachineExtensionParams.Add("Server",$Server) } $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams Write-Verbose -Message $strings['GPO_SetDelegation'] $settings.AdvancedAuditPolicyCAs.GPPermissions.GetEnumerator() | ForEach-Object { $TargetName = $($_.Key) $PermissionLevel = $($_.Value) $mdiGpPermissionParams = @{ Guid = $gpo.Id.Guid TargetType = "Group" TargetName = "$TargetName" PermissionLevel = "$PermissionLevel" Replace = $true }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpPermissionParams.Add("Server",$Server) } Start-Sleep -Milliseconds 500 Set-GPPermission @mdiGpPermissionParams | Out-Null } if ($null -ne $gpoUpdated) { if (-not $SkipGpoLink) { $gpLinkParams = @{ Guid = $gpo.Id.Guid LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes Enforced = [Microsoft.GroupPolicy.EnforceLink]::Yes Target = ([adsi]'').distinguishedName.Value } if ($Server) { Start-Sleep -Milliseconds 500 $gpLinkParams.Add("Server", $Server) } Set-MDIGPOLink @gpLinkParams } } else { Write-Warning $strings['GPO_UnableToSetExtension'] } } } function Get-MDIAdvancedAuditPolicyCAs { [CmdletBinding()] param() $relevantGUIDs = @($settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv) | Select-Object -ExpandProperty 'Subcategory GUID' -Unique Get-MDIAdvAuditPolicy | Where-Object { $_.'Subcategory GUID' -in $relevantGUIDs } } function Test-MDIAdvancedAuditPolicyCAs { [CmdletBinding()] param( [switch] $Detailed ) Write-Verbose -Message $strings['AdvancedPolicyCAs_Validate'] if (Test-MDICAServer) { $result = Test-MDIAdvAuditPolicy -ExpectedAuditing @($settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv) -Detailed:$Detailed } else { Write-Verbose -Message $strings['CAAuditing_NotCAServer'] $result = [PSCustomObject]([ordered]@{ Status = $true Details = $strings['CAAuditing_NotCAServer'] }) } if ($Detailed) { Write-Verbose -Message (Get-MDIValidationMessage $result.Status) } else { Write-Verbose -Message (Get-MDIValidationMessage $result) } $result } function Set-MDIAdvancedAuditPolicyCAs { Write-Verbose -Message $strings['AdvancedPolicyCAs_Set'] $settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv | ForEach-Object { $param = @{ SubcategoryGUID = $_.'Subcategory GUID' InclusionSetting = $_.'Inclusion Setting' } Set-MDIAdvAuditPolicy @param } } #endregion #region CA Audit configuration helper functions function Get-MDICAAuditing { [CmdletBinding()] param() $certSvcConfigPath = $settings.CAAuditing.RegPathActive $name = ($certSvcConfigPath -split '\\')[-1] $activePath = 'HKLM:\{0}' -f ($certSvcConfigPath -replace $name) $activeValue = Get-ItemProperty -Path $activePath -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name $settings.CAAuditing.RegistrySet.GetEnumerator() | ForEach-Object { $name = ($_.Name -split '\\')[-1] $path = 'HKLM:\{0}' -f (($_.Name -replace $name) -f $activeValue) $value = Get-ItemProperty -Path $path -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name $expected = $_.Value [PSCustomObject]@{ Path = $path Name = $name ActualValue = $value ExpectedValue = $expected } } } function Test-MDICAAuditing { [CmdletBinding()] param( [switch] $Detailed ) Write-Verbose -Message $strings['CAAuditing_Validate'] if (Test-MDICAServer) { $caAuditing = Get-MDICAAuditing $caAuditingOk = @($caAuditing | Where-Object { $_.ActualValue -match $_.ExpectedValue }).Count -eq $settings.CAAuditing.RegistrySet.Count } else { Write-Verbose -Message $strings['CAAuditing_NotCAServer'] $caAuditing = $strings['CAAuditing_NotCAServer'] $caAuditingOk = $true } Write-Verbose -Message (Get-MDIValidationMessage $caAuditingOk) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $caAuditingOk Details = $caAuditing }) } else { $caAuditingOk } } function Set-MDICAAuditing { [CmdletBinding()] param( [switch] $SkipServiceRestart ) if (Get-Service CertSvc -ErrorAction SilentlyContinue) { $certSvcConfigPath = $settings.CAAuditing.RegPathActive $name = ($certSvcConfigPath -split '\\')[-1] $activePath = 'HKLM:\{0}' -f ($certSvcConfigPath -replace $name) $activeValue = Get-ItemProperty -Path $activePath -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name $settings.CAAuditing.RegistrySet.GetEnumerator() | ForEach-Object { $name = ($_.Name -split '\\')[-1] $path = 'HKLM:\{0}' -f (($_.Name -replace $name) -f $activeValue) $value = ($_.Value -split '\|')[0] Write-Verbose -Message ('Setting {0}{1} to {2}' -f $path, $name, $value) Set-ItemProperty -Path $path -Name $name -Value $value -ErrorAction Stop } if (-not $SkipServiceRestart) { Restart-Service -Name CertSvc -Force -Verbose:$VerbosePreference } } else { Write-Warning $strings['CAAuditing_NotCAServer'] } } function Get-MDICAAuditingGPO { [CmdletBinding()] param( [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.CAAuditing.GpoName -GpoNamePrefix $GpoNamePrefix $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams if ($gpo) { $params = @{ Guid = $gpo.Id Context = 'Computer' Key = 'HKEY_LOCAL_MACHINE\{0}' -f $settings.CAAuditing.GpoReg ValueName = ($settings.CAAuditing.GpoVal).Keys[0] }; if ($Server) { $params.Add("Server", $Server) } $GPPrefRegistryValue = Get-GPPrefRegistryValue @params -ErrorAction SilentlyContinue $delegation = $settings.CAAuditing.GPPermissions.GetEnumerator() | ForEach-Object { $mdiGpPermissionParams = @{ Guid = $gpo.Id.Guid TargetType = "Group" TargetName = $_.Key }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpPermissionParams.Add("Server",$Server) } Get-GPPermission @mdiGpPermissionParams } $gpo = $gpo | Select-Object -Property *, @{N = 'GPPrefRegistryValue'; E = { $GPPrefRegistryValue } }, @{N = 'Delegation'; E = { $delegation } } } $gpo } function Test-MDICAAuditingGPO { [CmdletBinding()] param( [switch] $Detailed, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.CAAuditing.GpoName -GpoNamePrefix $GpoNamePrefix Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName) $state = $false $mdiGpoParams = @{ GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDICAAuditingGPO @mdiGpoParams $gpSetOk = @() if ($gpo -and $gpo.GPPrefRegistryValue) { $settings.CAAuditing.GpoVal.GetEnumerator() | ForEach-Object { $expected = [PSCustomObject]@{ DisabledDirectly = $false Type = 'DWord' Action = 'Update' Hive = 'LocalMachine' FullKeyPath = 'HKEY_LOCAL_MACHINE\{0}' -f $settings.CAAuditing.GpoReg ValueName = $_.Key Value = $_.Value } $properties = $expected | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name $applied = $gpo.GPPrefRegistryValue | Select-Object -Property $properties $gpSetOk += ($null -ne (Compare-Object -ReferenceObject $applied -DifferenceObject $expected -Property $properties -IncludeEqual -ExcludeDifferent)) } if (($gpSetOk -eq $false).Count -eq 0) { $gpDelegationOk = @($gpo.Delegation | Where-Object { $settings.CAAuditing.GPPermissions[$_.Trustee.Name] -eq $_.Permission }).Count -eq $settings.CAAuditing.GPPermissions.Count if (-not $gpDelegationOk) { Write-Verbose -Message $strings['GPO_DelegationMismatch'] } else { $mdiGpoParams.Add("GPO", $gpo) $mdiGpoParams.Remove("GpoNamePrefix") $state = Test-MDIGPOEnabledAndLink @mdiGpoParams } } else { Write-Verbose -Message $strings['GPO_SettingsMismatch'] } } Write-Verbose -Message (Get-MDIValidationMessage $state) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $state Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, GPPrefRegistryValue } else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] } }) } else { $state } } function Set-MDICAAuditingGPO { [CmdletBinding()] param( [switch] $SkipGpoLink, [switch] $CreateGpoDisabled, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $gpoName = Get-MDIGPOName -Name $settings.CAAuditing.GpoName -GpoNamePrefix $GpoNamePrefix $Server = Get-MDIDC -Server $Server $mdiGpoParams = @{ Name = $gpoName }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server",$Server) } $gpo = Get-MDIGPO @mdiGpoParams if ($null -eq $gpo) { $mdiGpoParams.Add("CreateGpoDisabled",$CreateGpoDisabled) $gpo = New-MDIGPO @mdiGpoParams } $settings.CAAuditing.GpoVal.GetEnumerator() | ForEach-Object { $params = @{ Guid = $gpo.Id Context = 'Computer' Key = 'HKEY_LOCAL_MACHINE\{0}' -f $settings.CAAuditing.GpoReg ValueName = $_.Name Order = -1 }; if ($Server) { Start-Sleep -Milliseconds 500 $params.Add("Server", $Server) } if (Get-GPPrefRegistryValue @params -ErrorAction SilentlyContinue) { $gpo = Remove-GPPrefRegistryValue @params } $params += @{ Value = [int]$_.Value Type = 'DWord' Action = 'Update' } Set-GPPrefRegistryValue @params | Out-Null } if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled } $gpo.MakeAclConsistent() $gpoUpdated = Set-MDIGPOMachineExtension -Guid $gpo.Id.Guid -Extension @( $settings.gpoExtensions['Preference CSE GUID Registry'], $settings.gpoExtensions['Preference Tool CSE GUID Registry']) Write-Verbose -Message $strings['GPO_SetDelegation'] $settings.CAAuditing.GPPermissions.GetEnumerator() | ForEach-Object { $TargetName = $($_.Key) $PermissionLevel = $($_.Value) $mdiGpPermissionParams = @{ Guid = $gpo.Id.Guid TargetType = "Group" TargetName = "$TargetName" PermissionLevel = "$PermissionLevel" Replace = $true }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpPermissionParams.Add("Server",$Server) } Start-Sleep -Milliseconds 500 Set-GPPermission @mdiGpPermissionParams | Out-Null } if ($null -ne $gpoUpdated) { if (-not $SkipGpoLink) { $gpLinkParams = @{ Guid = $gpo.Id.Guid LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes Enforced = [Microsoft.GroupPolicy.EnforceLink]::Yes Target = ([adsi]'').distinguishedName.Value } if ($Server) { Start-Sleep -Milliseconds 500 $gpLinkParams.Add("Server", $Server) } Set-MDIGPOLink @gpLinkParams } } else { Write-Warning $strings['GPO_UnableToSetExtension'] } } #endregion #region Domain helper functions function Get-MDIDC { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $Server ) & { $VerbosePreference = 'SilentlyContinue' $returnVal = $null if ($Server) { try { $returnVal = ((Resolve-DnsName $Server -Verbose:$false -ErrorAction Silentlycontinue) | Where-Object { $_.type -eq 'A' })[0].Name } catch { $returnVal = $Server } try { $socket = New-Object -TypeName System.Net.Sockets.TcpClient -ArgumentList ($returnVal, 9389) if (-not $socket.Connected) { throw } $socket.Close() } catch { $returnVal = $settings.pdcE } } else { if (-not [string]::IsNullOrEmpty($settings.pdcE)) { $returnVal = $settings.pdcE } } return $returnVal } } function Get-MDIDomainSchemaVersion { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $Domain = $env:USERDNSDOMAIN ) $schemaVersions = @{ 13 = 'Windows 2000 Server' 30 = 'Windows Server 2003' 31 = 'Windows Server 2003 R2' 44 = 'Windows Server 2008' 47 = 'Windows Server 2008 R2' 56 = 'Windows Server 2012' 69 = 'Windows Server 2012 R2' 87 = 'Windows Server 2016' 88 = 'Windows Server 2019 / 2022' 90 = 'Windows Server vNext' } Write-Verbose -Message 'Getting AD Schema Version' $schema = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList ( 'LDAP://{0}' -f ([adsi]'LDAP://rootDSE').Properties['schemaNamingContext'].Value ) $schemaVersion = $schema.Properties['objectVersion'].Value $return = @{ schemaVersion = $schemaVersion details = $schemaVersions[$schemaVersion] } $return } #endregion #region DSA helper functions function Get-MDIDeletedObjectsContainerPermission { [CmdletBinding()] param () $deletedObjectsDN = 'CN=Deleted Objects,{0}' -f ([adsi]'').distinguishedName.Value $output = & "$($env:SystemRoot)\system32\dsacls.exe" @($deletedObjectsDN) ($output -join [System.Environment]::NewLine) -split '(?=Allow\s)' | Where-Object { $_ -match 'Allow' } | ForEach-Object { if ($_ -match 'Allow\s(?<Identity>(NT AUTHORITY\\\w+)|([^\s]+))\s+(?<Permissions>.*(?:\n\s+.*)*)') { [PSCustomObject]@{ Identity = $Matches.Identity Permissions = $Matches.Permissions -split '\s{2,}' | ForEach-Object { $_.Trim() } } } } } function Test-MDIDSA { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $Identity, [switch] $Detailed, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $return = @() $adAccountParams = @{ Identity = $Identity Properties = "msDS-PrincipalName" }; if (-not [string]::IsNullOrEmpty($server)) { $adAccountParams.Add("Server",$Server) } $account = try { Get-ADUser @adAccountParams } catch { try { Get-ADServiceAccount @adAccountParams } catch { $null } } if ($null -eq $account) { $return += [PSCustomObject][ordered]@{ Test = 'AccountExists' Status = $false Details = $strings['ServiceAccount_NotFound'] } } else { Write-Verbose -Message $strings['DSA_TestGroupMembership'] $memberOf = @{} $filter = '(&(objectCategory=group)(objectClass=group)(member:1.2.840.113556.1.4.1941:={0}))' -f $account.DistinguishedName $searcher = [adsisearcher]$filter 'objectSid', 'distinguishedName', 'msDS-PrincipalName' | ForEach-Object { [void]($searcher.PropertiesToLoad.Add($_)) } $searcher.FindAll() | ForEach-Object { $memberOf.Add($_.Properties['distinguishedname'][0], [PSCustomObject]@{ 'objectSid' = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @($_.Properties['objectSid'][0], 0)).Value 'msDS-PrincipalName' = $_.Properties['msDS-PrincipalName'][0] }) } $domainSid = (Get-ADDomain).DomainSID.Value $sensitiveGroups = @{} $settings.SensitiveGroups.GetEnumerator() | ForEach-Object { $sensitiveGroups.Add(($_.Value -f $domainSid), $_.Key) } $sensitiveGroupsMembership = @( $memberOf.GetEnumerator() | Where-Object { $sensitiveGroups.ContainsKey($_.Value.objectSid) } | Select-Object -ExpandProperty Name ) $return += [PSCustomObject][ordered]@{ Test = 'SensitiveGroupsMembership' Status = $sensitiveGroupsMembership.Count -eq 0 Details = $sensitiveGroupsMembership } Write-Verbose -Message $strings['DSA_TestDelegation'] $sidsToCheck = @($account.SID.Value) $sidsToCheck += ($memberOf.GetEnumerator() | Where-Object { $sensitiveGroupsMembership -notcontains $_.Key }).Value.Value $filter = '(|(objectClass=domain)(objectClass=organizationalUnit)(objectClass=group))' $searcher = [adsisearcher]$filter $searcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree $delegatedObjects = $searcher.FindAll() | ForEach-Object { $de = $_.GetDirectoryEntry() $permissions = $de.PsBase.ObjectSecurity.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) if ($permissions | Where-Object { ($_.AccessControlType -eq 'Allow') -and ($sidsToCheck -contains $_.IdentityReference.Value) }) { $de.distinguishedName.Value } } $return += [PSCustomObject][ordered]@{ Test = 'ExplicitDelegation' Status = $delegatedObjects.Count -eq 0 Details = @($delegatedObjects | Select-Object -Unique) } Write-Verbose -Message $strings['DSA_TestDeletedObjectsAccess'] $appliedAsExpected = $false $msDSPrincipalNamesToCheck = @($memberOf.GetEnumerator() | ForEach-Object { $_.Value.'msDS-PrincipalName' }) $msDSPrincipalNamesToCheck += $account.'msDS-PrincipalName' $expectedDsacls = @('SPECIAL ACCESS', 'LIST CONTENTS', 'READ PROPERTY') $appliedDsacls = Get-MDIDeletedObjectsContainerPermission if ([string]::IsNullOrEmpty($appliedDsacls)) { Write-Warning -Message $strings['DSA_CannotReadDeletedObjectsContainer'] } else { $dsaDsacls = $appliedDsacls | Where-Object { $msDSPrincipalNamesToCheck -contains $_.Identity } | Select-Object -ExpandProperty Permissions if ($null -eq $dsaDsacls) { $dsaDsacls = 'NONE' } else { $dsaDsacls = $dsaDsacls | Select-Object -Unique $appliedAsExpected = (Compare-Object -ReferenceObject $dsaDsacls -DifferenceObject $expectedDsacls -IncludeEqual -ExcludeDifferent).Count -eq $expectedDsacls.Count } } $return += [PSCustomObject][ordered]@{ Test = 'DeletedObjectsContainerPermission' Status = $appliedAsExpected Details = $dsaDsacls } if ($account.ObjectClass -eq 'user') { Write-Verbose -Message $strings['DSA_TestManager'] $filter = '(|(managedBy={0})(manager={0}))' -f ($account.DistinguishedName -replace '\s', '\20') $searcher = [adsisearcher]$filter $searcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree $managerOf = $searcher.FindAll() $return += [PSCustomObject][ordered]@{ Test = 'ManagerOf' Status = $managerOf.Count -eq 0 Details = @($managerOf | ForEach-Object { $_.Properties['distinguishedname'] }) } } else { Write-Warning $strings['DSA_SkipGmsaTests'] Write-Verbose -Message $strings['DSA_TestPasswordRetrieval'] try { $pwdCheck = Test-ADServiceAccount -Identity $($account.samaccountname) $return += [PSCustomObject][ordered]@{ Test = 'PasswordRetrieval' Status = $pwdCheck Details = $null } } catch { } } } $overallStatus = ($return.Status -eq $false).Count -eq 0 if (-not $Detailed) { $overallStatus } else { $return } Write-Verbose -Message (Get-MDIValidationMessage $overallStatus) } #endregion #region Connectivity helper functions function Test-MDISensorApiConnection { [CmdletBinding(DefaultParameterSetName = 'UseCurrentConfiguration')] param( [Parameter(Mandatory = $true, ParameterSetName = 'BypassConfiguration')] [switch] $BypassConfiguration, [Parameter(Mandatory = $true, ParameterSetName = 'BypassConfiguration')] [string] $SensorApiUrl, [Parameter(Mandatory = $false, ParameterSetName = 'BypassConfiguration')] [string] $ProxyUrl, [Parameter(Mandatory = $false, ParameterSetName = 'BypassConfiguration')] [PSCredential] $ProxyCredential ) $sensorApiPath = 'tri/sensor/api/ping' $protocol = @{ 80 = 'http' 443 = 'https' } if ($PSCmdlet.ParameterSetName -eq 'BypassConfiguration') { $params = @{ URI = $SensorApiUrl } if ($ProxyUrl) { $params.Add('Proxy', $ProxyUrl) } if ($ProxyCredential) { $params.Add('ProxyCredential', $ProxyCredential) } } else { $sensorConfiguration = Get-MDISensorConfiguration if ([string]::IsNullOrEmpty($sensorConfiguration)) { Write-Error $strings['Sensor_ErrorReadingSensorConfiguration'] -ErrorAction Stop } else { $params = @{ URI = '{0}://{1}' -f $protocol[$sensorConfiguration.WorkspaceApplicationSensorApiWebClientConfigurationServiceEndpoint.Port], $sensorConfiguration.WorkspaceApplicationSensorApiWebClientConfigurationServiceEndpoint.Address } $params.Add("UseBasicParsing", $true) if ($sensorConfiguration.SensorProxyConfiguration.IsProxyEnabled) { $params.Add('Proxy', $sensorConfiguration.SensorProxyConfiguration.Url) if ($sensorConfiguration.SensorProxyConfiguration.IsAuthenticationProxyEnabled) { $decryptParams = @{ CertificateThumbprint = $sensorConfiguration.SensorProxyConfiguration.CertificateThumbprint EncryptedString = $sensorConfiguration.SensorProxyConfiguration.EncryptedUserPasswordData } $passwd = Get-MDIDecryptedPassword @decryptParams $proxyCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @( $sensorConfiguration.SensorProxyConfiguration.UserName, ($passwd | ConvertTo-SecureString -AsPlainText -Force) ) $params.Add('ProxyCredential', $proxyCredential) } } } } try { if ($params.URI -notmatch "$sensorApiPath`$") { $params.URI = '{0}/{1}' -f $params.URI, $sensorApiPath } $response = Invoke-WebRequest @params (200 -eq $response.StatusCode) } catch { Write-Verbose -Message $_.Exception.Message $false } } #endregion #region Post deployment configuration helper functions function Use-MDIConfigName { param( [Parameter(Mandatory)] [string[]] $Configuration, [Parameter(Mandatory)] [string[]] $ActionItem ) $ActionItem += 'All' @(Compare-Object -ReferenceObject $Configuration -DifferenceObject $ActionItem -ExcludeDifferent -IncludeEqual).Count -gt 0 } function Get-MDIConfiguration { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode, [Parameter(Mandatory = $true)] [ValidateSet('AdfsAuditing', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs', 'CAAuditing', 'ConfigurationContainerAuditing', 'DomainObjectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'All')] [string[]] $Configuration ) DynamicParam { if ($Mode -eq 'Domain') { $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $false $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("GpoNamePrefix", [string], $paramAttributesCollect) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $false $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParam3 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [string], $paramAttributesCollect) $paramDictionary.Add("GpoNamePrefix", $dynParam1) $paramDictionary.Add("Server", $dynParam3) return $paramDictionary } } begin { if ($Mode -eq 'Domain') { $GpoNamePrefix = $PSBoundParameters['GpoNamePrefix'] $Server = $PSBoundParameters['Server'] } } process { $mdiParams = @{ Detailed = $true GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiParams.Add("Server",$Server) } $results = @{} if (Use-MDIConfigName $Configuration 'AdfsAuditing') { $results.Add('AdfsAuditing', (Test-MDIAdfsAuditing -Detailed)) } if (Use-MDIConfigName $Configuration 'AdvancedAuditPolicyCAs') { if ($Mode -eq 'LocalMachine') { $results.Add('AdvancedAuditPolicyCAs', (Test-MDIAdvancedAuditPolicyCAs -Detailed)) } else { $results.Add('AdvancedAuditPolicyCAs', (Test-MDIAdvancedAuditPolicyCAsGPO @mdiParams)) } } if (Use-MDIConfigName $Configuration 'AdvancedAuditPolicyDCs') { if ($Mode -eq 'LocalMachine') { $results.Add('AdvancedAuditPolicyDCs', (Test-MDIAdvancedAuditPolicyDCs -Detailed)) } else { $results.Add('AdvancedAuditPolicyDCs', (Test-MDIAdvancedAuditPolicyDCsGPO @mdiParams)) } } if (Use-MDIConfigName $Configuration 'CAAuditing') { if ($Mode -eq 'LocalMachine') { $results.Add('CAAuditing', (Test-MDICAAuditing -Detailed)) } else { $results.Add('CAAuditing', (Test-MDICAAuditingGPO @mdiParams)) } } if (Use-MDIConfigName $Configuration 'ConfigurationContainerAuditing') { $results.Add('ConfigurationContainerAuditing', (Test-MDIConfigurationContainerAuditing -Detailed)) } if (Use-MDIConfigName $Configuration 'DomainObjectAuditing') { $results.Add('DomainObjectAuditing', (Test-MDIDomainObjectAuditing -Detailed)) } if (Use-MDIConfigName $Configuration 'NTLMAuditing') { if ($Mode -eq 'LocalMachine') { $results.Add('NTLMAuditing', (Test-MDINTLMAuditing -Detailed)) } else { $results.Add('NTLMAuditing', (Test-MDINTLMAuditingGPO @mdiParams)) } } if (Use-MDIConfigName $Configuration 'ProcessorPerformance') { if ($Mode -eq 'LocalMachine') { $results.Add('ProcessorPerformance', (Test-MDIProcessorPerformance -Detailed)) } else { $results.Add('ProcessorPerformance', (Test-MDIProcessorPerformanceGPO @mdiParams)) } } if ($Configuration -contains 'All') { $Configuration += $results.GetEnumerator() | Select-Object -ExpandProperty Name } $Configuration | Select-Object -Unique | Where-Object { $_ -ne 'All' } | ForEach-Object { [PSCustomObject]@{ Configuration = $_ Mode = $Mode Status = $results[$_].Status Details = $results[$_].Details } } } } function Test-MDIConfiguration { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode, [Parameter(Mandatory = $true)] [ValidateSet('AdfsAuditing', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs', 'CAAuditing', 'ConfigurationContainerAuditing', 'DomainObjectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'All')] [string[]] $Configuration ) DynamicParam { if ($Mode -eq 'Domain') { $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $false $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("GpoNamePrefix", [string], $paramAttributesCollect) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $false $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParam3 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [string], $paramAttributesCollect) $paramDictionary.Add("GpoNamePrefix", $dynParam1) $paramDictionary.Add("Server", $dynParam3) return $paramDictionary } } begin { if ($Mode -eq 'Domain') { $GpoNamePrefix = $PSBoundParameters['GpoNamePrefix'] $ServiceAccountName = $PSBoundParameters['ServiceAccountName'] $Server = $PSBoundParameters['Server'] } } process { $results = if ($Mode -eq 'Domain') { $Server = Get-MDIDC -Server $Server $mdiParms = @{ Configuration = $Configuration Mode = "Domain" GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiParms.Add("Server",$Server) } Get-MDIConfiguration @mdiParms } else { Get-MDIConfiguration -Configuration $Configuration -Mode LocalMachine } if ('All' -eq $Configuration) { @($results | Where-Object { $_.Status -eq $false }).Count -eq 0 } else { $results.Status } } } function Set-MDIConfiguration { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateSet('AdfsAuditing', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs', 'CAAuditing', 'ConfigurationContainerAuditing', 'DomainObjectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'All')] [string[]] $Configuration, [Parameter(Mandatory = $false)] [switch] $Force ) DynamicParam { if ($Mode -eq 'Domain') { $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $false $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("GpoNamePrefix", [string], $paramAttributesCollect) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $false $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParam2 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("CreateGpoDisabled", [switch], $paramAttributesCollect) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $false $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParam3 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("SkipGpoLink", [switch], $paramAttributesCollect) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $false $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParam5 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [string], $paramAttributesCollect) $paramDictionary.Add("GpoNamePrefix", $dynParam1) $paramDictionary.Add("CreateGpoDisabled", $dynParam2) $paramDictionary.Add("SkipGpoLink", $dynParam3) $paramDictionary.Add("Server", $dynParam5) return $paramDictionary } } begin { if ($Mode -eq 'Domain') { $GpoNamePrefix = $PSBoundParameters['GpoNamePrefix'] $CreateGpoDisabled = $PSBoundParameters['CreateGpoDisabled'] $SkipGpoLink = $PSBoundParameters['SkipGpoLink'] $ServiceAccountName = $PSBoundParameters['ServiceAccountName'] $Server = $PSBoundParameters['Server'] } } Process { $Server = Get-MDIDC -Server $Server foreach ($config in $Configuration) { $mdiParams = @{ CreateGpoDisabled = $CreateGpoDisabled SkipGpoLink = $SkipGpoLink GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiParams.Add("Server",$Server) } Write-Verbose ($strings['Configuration_Set'] -f $config) if (Use-MDIConfigName $config 'AdfsAuditing') { Set-MDIAdfsAuditing } if (Use-MDIConfigName $config 'AdvancedAuditPolicyCAs') { if ($Mode -eq 'LocalMachine') { Set-MDIAdvancedAuditPolicyCAs } else { Set-MDIAdvancedAuditPolicyCAsGPO @mdiParams } } if (Use-MDIConfigName $config 'AdvancedAuditPolicyDCs') { if ($Mode -eq 'LocalMachine') { Set-MDIAdvancedAuditPolicyDCs } else { Set-MDIAdvancedAuditPolicyDCsGPO @mdiParams } } if (Use-MDIConfigName $config 'CAAuditing') { if ($Mode -eq 'LocalMachine') { Set-MDICAAuditing } else { Set-MDICAAuditingGPO @mdiParams } } if (Use-MDIConfigName $config 'ConfigurationContainerAuditing') { Set-MDIConfigurationContainerAuditing -Force:$Force } if (Use-MDIConfigName $config 'DomainObjectAuditing') { Set-MDIDomainObjectAuditing } if (Use-MDIConfigName $config 'NTLMAuditing') { if ($Mode -eq 'LocalMachine') { Set-MDINTLMAuditing } else { Set-MDINTLMAuditingGPO @mdiParams } } if (Use-MDIConfigName $config 'ProcessorPerformance') { if ($Mode -eq 'LocalMachine') { Set-MDIProcessorPerformance } else { Set-MDIProcessorPerformanceGPO @mdiParams } } } } } function New-MDIConfigurationReport { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory = $false)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode = 'Domain', [Parameter(Mandatory = $false)] [string] $GpoNamePrefix, [switch] $OpenHtmlReport ) if (-not(Test-Path -Path $Path)) { [void](New-Item -Path $Path -ItemType Directory -Force -ErrorAction SilentlyContinue) } $reportTarget = if ($Mode -eq 'Domain') { $env:USERDNSDOMAIN } else { '{0}.{1}' -f $env:COMPUTERNAME, $env:USERDNSDOMAIN } $configurations = Get-MDIConfiguration -Configuration All -Mode $Mode -GpoNamePrefix $GpoNamePrefix $jsonReportFile = Resolve-MDIPath -Path ( Join-Path -Path $Path -ChildPath ('MDI-configuration-report-{0}.json' -f $reportTarget)) $htmlReportFile = Resolve-MDIPath -Path ( Join-Path -Path $Path -ChildPath ('MDI-configuration-report-{0}.html' -f $reportTarget)) $css = @' <style> body { font-family: Arial, sans-serif, 'Open Sans'; } table { border-collapse: collapse; } td, th { border: 1px solid #aeb0b5; padding: 5px; text-align: left; vertical-align: middle; } tr:nth-child(even) { background-color: #f2f2f2; } th { padding: 8px; text-align: left; background-color: #e4e2e0; color: #212121; } .red {background-color: #cd2026; color: #ffffff; } .green {background-color: #4aa564; color: #212121; } ul { list-style: none; padding-left: 0.5em;} </style> '@ $colors = @{$true = 'green'; $false = 'red' } $status = @{$true = $strings['DomainReport_StatusPass']; $false = $strings['DomainReport_StatusFail'] } $tblHeader = '<tr><th>{0}</th><th>{1}</th><th>{2}</th></tr>' -f $strings['DomainReport_Configuration'], $strings['DomainReport_Status'], $strings['DomainReport_CommandToFix'] $tblContent = @($configurations | Sort-Object Configuration | ForEach-Object { $gpoPrefixIfUsed = if ([string]::IsNullOrEmpty($GpoNamePrefix)) { '' } else { " -GpoNamePrefix $GpoNamePrefix" } "<tr><td><a href='https://aka.ms/mdi/{0}'>{0}</a></td><td class='{1}'>{2}</td><td>{3}{0}{4}</td></tr>" -f ` $_.Configuration, $colors[$_.Status], $status[$_.Status], 'Set-MDIConfiguration -Mode Domain -Configuration ', $gpoPrefixIfUsed }) -join [environment]::NewLine $htmlContent = @' <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head>{0}</head><body> <h2>{1}</h2> {2} <br/><br/> <table> {3} {4} </table> <br/> <hr> <ul> <li>{5}</li> </ul> <hr> <br/>{6} <a href='{7}'>{7}</a><br/> <br/>{8} '@ -f $css, ($strings['DomainReport_Title'] -f $reportTarget), $strings['DomainReport_Subtitle'], $tblHeader, $tblContent, $strings['DomainReport_NoteMessage'], $strings['DomainReport_DetailsMessage'], $jsonReportFile, ($strings['DomainReport_CreatedBy'] -f "<a href='https://aka.ms/mdi/psmodule'>DefenderForIdentity</a>") Write-Verbose ('{0}: {1}' -f $strings['DomainReport_JsonMessage'], $jsonReportFile) $configurations | ConvertTo-Json -Depth 5 | Format-Json | Out-File -FilePath $jsonReportFile -Force -Encoding utf8 Write-Verbose ('{0}: {1}' -f $strings['DomainReport_HtmlMessage'], $htmlReportFile) $htmlContent | Out-File -FilePath $htmlReportFile -Force -Encoding utf8 $reportPath = (Resolve-Path -Path $htmlReportFile).Path if ($OpenHtmlReport) { Invoke-Item -Path $reportPath } } #endregion # SIG # Begin signature block # MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD7T+bs0spUwLVc # oH2dX68rhf33kjfYYqE//0xCIWrUSaCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMFBhby3JVdm3XsiCxQu283v # Ta+39XGjvjkwIEy3WUj8MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEALc3b1p3k9qWeMwmkmuyiK+Ta6Qc2kE633UqQDsbpim62lYJjDA7YnouU # w/FHJq/fpe/QKDnQiHUbz8U+xLyHvYrAG2566+pOeZdmDZyg7wfCFRcVfofSK3gV # 0XonORdH0lYOMzfnqdgKx3wlPQexu905i6iwEpP0Ary3Dgye2PUFi29ir8GK1JGH # iggCtY+vfP5JO8H9t6C1MHZlCYC5VlbsQQNCsneg0rAVXVBgTGde6a24G3jYnkFZ # 1uscIUEMg3xaF1GiX54uXY8O/uhs4KDvacciuR4N9UU0IhbZ9bZLODqWi+JTJckQ # JD6RJ42+3kalNu8DRwreHaM9Ewz6iqGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC # F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCANr5UcgKk8Hy/uuspJvK5s4LbAe73vSYe3cDumiRuvHwIGZr4GLjei # GBMyMDI0MDgyMTEwMDAxNC41NjJaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODYwMy0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHqMIIHIDCCBQigAwIBAgITMwAAAfGzRfUn6MAW1gABAAAB8TANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # NTVaFw0yNTAzMDUxODQ1NTVaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODYwMy0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCxulCZttIf8X97rW9/J+Q4Vg9PiugB1ya1/DRxxLW2 # hwy4QgtU3j5fV75ZKa6XTTQhW5ClkGl6gp1nd5VBsx4Jb+oU4PsMA2foe8gP9bQN # PVxIHMJu6TYcrrn39Hddet2xkdqUhzzySXaPFqFMk2VifEfj+HR6JheNs2LLzm8F # DJm+pBddPDLag/R+APIWHyftq9itwM0WP5Z0dfQyI4WlVeUS+votsPbWm+RKsH4F # QNhzb0t/D4iutcfCK3/LK+xLmS6dmAh7AMKuEUl8i2kdWBDRcc+JWa21SCefx5SP # hJEFgYhdGPAop3G1l8T33cqrbLtcFJqww4TQiYiCkdysCcnIF0ZqSNAHcfI9SAv3 # gfkyxqQNJJ3sTsg5GPRF95mqgbfQbkFnU17iYbRIPJqwgSLhyB833ZDgmzxbKmJm # dDabbzS0yGhngHa6+gwVaOUqcHf9w6kwxMo+OqG3QZIcwd5wHECs5rAJZ6PIyFM7 # Ad2hRUFHRTi353I7V4xEgYGuZb6qFx6Pf44i7AjXbptUolDcVzYEdgLQSWiuFajS # 6Xg3k7Cy8TiM5HPUK9LZInloTxuULSxJmJ7nTjUjOj5xwRmC7x2S/mxql8nvHSCN # 1OED2/wECOot6MEe9bL3nzoKwO8TNlEStq5scd25GA0gMQO+qNXV/xTDOBTJ8zBc # GQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFLy2xe59sCE0SjycqE5Erb4YrS1gMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQDhSEjSBFSCbJyl3U/QmFMW2eLPBknnlsfI # D/7gTMvANEnhq08I9HHbbqiwqDEHSvARvKtL7j0znICYBbMrVSmvgDxU8jAGqMyi # LoM80788So3+T6IZV//UZRJqBl4oM3bCIQgFGo0VTeQ6RzYL+t1zCUXmmpPmM4xc # ScVFATXj5Tx7By4ShWUC7Vhm7picDiU5igGjuivRhxPvbpflbh/bsiE5tx5cuOJE # JSG+uWcqByR7TC4cGvuavHSjk1iRXT/QjaOEeJoOnfesbOdvJrJdbm+leYLRI67N # 3cd8B/suU21tRdgwOnTk2hOuZKs/kLwaX6NsAbUy9pKsDmTyoWnGmyTWBPiTb2rp # 5ogo8Y8hMU1YQs7rHR5hqilEq88jF+9H8Kccb/1ismJTGnBnRMv68Ud2l5LFhOZ4 # nRtl4lHri+N1L8EBg7aE8EvPe8Ca9gz8sh2F4COTYd1PHce1ugLvvWW1+aOSpd8N # nwEid4zgD79ZQxisJqyO4lMWMzAgEeFhUm40FshtzXudAsX5LoCil4rLbHfwYtGO # pw9DVX3jXAV90tG9iRbcqjtt3vhW9T+L3fAZlMeraWfh7eUmPltMU8lEQOMelo/1 # ehkIGO7YZOHxUqeKpmF9QaW8LXTT090AHZ4k6g+tdpZFfCMotyG+E4XqN6ZWtKEB # QiE3xL27BDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN # MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg2MDMtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQD7 # n7Bk4gsM2tbU/i+M3BtRnLj096CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6m/EZjAiGA8yMDI0MDgyMTAxNDE1 # OFoYDzIwMjQwODIyMDE0MTU4WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDqb8Rm # AgEAMAcCAQACAgEAMAcCAQACAhNFMAoCBQDqcRXmAgEAMDYGCisGAQQBhFkKBAIx # KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI # hvcNAQELBQADggEBAHHlVXwxyY+ytRLFYIWhed3D1JpIkqIgBQC3iBpCvkatnYY5 # 5kZXx9GtC72fxGAVksjojTxC/qvj0XB2HF14VZjFoYWpmLbbHApCrEe/E/OgEeu6 # ji/Of3yo/18E579hz4vDITgF9hp8xyCba9Kz8bnEL/WxQnN5Lr9Z8uyw3MdS7fTW # 79aAuK1JdKqL19drqNrGVUxQeRWsmzGn0HLVgEEgjQKuavY12KqKOqfDhwKnJT2Q # wuH9AkFUs8PWtfkQLfNwcNelv3+8npVigVsN3q2Zi1rmBmVJpwBBu3mI3KYxQh7v # BB2uGoBES9zBBKuwt6IXKzDA4n84404a2hfvHdExggQNMIIECQIBATCBkzB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAfGzRfUn6MAW1gABAAAB8TAN # BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G # CSqGSIb3DQEJBDEiBCDFFLUKYex4MV0+CfjtM5qwZ6gev83dzMtdaydALZZrHDCB # +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EINV3/T5hS7ijwao466RosB7wwEib # t0a1P5EqIwEj9hF4MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAHxs0X1J+jAFtYAAQAAAfEwIgQgobu/SzQa7l//nPELEA7xxCIIlQyS # gK5YH9ocrSQ/nu4wDQYJKoZIhvcNAQELBQAEggIAWJ3hKP06OC4Uliev9iTty7ML # wM/3OtE8TBucMaBw7+OMll/wOteOZJTilE/luHBVD5ru+MuJZWa8xM5GCJ+8KUei # +VLA1QylvhSkWK8rlDQb4rPW+0ciL2OmIkLaGhjcHZi91aZA2TREeibvdj60T8YB # EOSZVbf+nXx7UhRFYBzQ1gpGed9hYdjtpgNTnsrQsYrKCfhq8/RHbSDiQpywG9KB # /+P/mEjY4KoPlQkycouF9KxQgrns1hOWaptd4xPakLUthEgh6rzDpq/YFOEf+5Ny # +uWxaPgacNI/Rb3Df5B+vhcujuYQWRlvoXoFslYj2XZZ2RIcBmUMpvYqrHK6Pypc # Zf4THQgPKddU/TJup2L1bAnrcT2hVeFJZz8yfhQTRXGs/1ki+tvzKfqhMo/ZmnCS # Ak3PagNvNW+NYkHZDVAvTcL7kMXR1k2krClWRfIJ+QS3IPPNRYjKPFRrF0FdkT1O # aqCJKB4vXqfBH4Hcr41R3+XqNy7d1S9wS2/T0DrvTXk0SNyjKDNwDoHQOG9xkve7 # sK91ZQ7JVAjjI2EoZ1xqJ++uGy/VeNLxBjMiXYp6z+CyJ45lCfddoOt2CyWqfHBy # zjP76vr5dA7mASaOpt1MBr05gEgdclJcdpDo+lwy15/OHGJu0QeJmGLJp/FHhk6t # bSdRZ7mpeU3LkCtgTdc= # SIG # End signature block |