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' } RemoteSAM = @{ GpoName = '{0} - Remote SAM Access' GpoRegSet = @{ 'System\CurrentControlSet\Control\Lsa\RestrictRemoteSAM=1' = 'O:BAG:BAD:(A;;RC;;;BA)(A;;RC;;;{0})' } RegistrySet = @{ 'System\CurrentControlSet\Control\Lsa\RestrictRemoteSAM' = 'O:BAG:BAD:(A;;RC;;;BA)(A;;RC;;;{0})' } DenyGPPermissions = [ordered]@{ '{0}-516' = 'GpoApply' } } 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' } } EntraConnectAuditing = @{ GpoName = '{0} - Advanced Audit and URA Policy for Entra Connect' GptSet = @{ 'SeServiceLogonRight' = '*{0},*S-1-5-80-0' } AuditSet = @' Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value ,System,Audit Logon,{0cce9215-69ae-11d9-bed3-505054503030},Success and Failure,,3 '@ } 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]@{ '{0}-517' = 'GpoApply' '{0}-516' = 'GpoRead' 'S-1-5-11' = '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]@{ '{0}-517' = 'GpoApply' '{0}-516' = 'GpoRead' 'S-1-5-11' = '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) domainNetBiosName = [string]((Get-ADDomain -ErrorAction SilentlyContinue).NetBIOSName) } 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 Get-MDISenseIdentityBinPath { [CmdletBinding()] param() $processParams = @{ Name = "SenseIdentity" FileVersionInfo = $true } $senseIdentityProcess = Get-Process @processParams ($senseIdentityProcess).FileName | split-path } function Stop-MDISensor { [CmdletBinding()] param() Stop-Service -Name AATPSensorUpdater -Force } function Start-MDISensor { [CmdletBinding()] param() Start-Service -Name AATPSensorUpdater } function Get-MDISensorProcessInformation { [CmdletBinding()] param() [PSCustomObject]@{ AATPSensor = $(try { (Get-Service AATPSensor -ErrorAction SilentlyContinue).Status } catch { $null }) AATPSensorUpdater = $(try {(Get-Service AATPSensorUpdater -ErrorAction SilentlyContinue).Status } catch { $null }) SenseIdentity = $(try {if ((Get-Process -Name SenseIdentity -ErrorAction SilentlyContinue).Count -gt 0) {'Running'} else {$null}} catch { $null }) } } #endregion #region Service account configuration functions function Get-MDIDSA { [CmdletBinding()] param( [string] $Identity, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $returnVal = $null $adobjectParams = @{ ldapFilter = '(&(|(samaccountname={0})(samaccountname={0}$))(|(objectClass=user)(objectClass=msDS-GroupManagedServiceAccount)))' -f $Identity }; if (-not [string]::IsNullOrEmpty($server)) { $adobjectParams.Add("Server", $Server) } try { $returnVal = Get-ADObject @adobjectParams -ErrorAction SilentlyContinue -properties * if ($returnVal -eq $null) { throw } } catch { $returnVal = $null Write-Warning ($strings['DSA_CannotFindIdentity'] -f $Identity) } return $returnVal } 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-MDIForestSid $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 { $null = 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 { $null = 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() $sensorProcesses = Get-MDISensorProcessInformation if ($sensorProcesses.SenseIdentity -eq 'Running') { try { $sensorConfiguration = ((Get-ItemProperty 'hklm:\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection' OnboardingInfo -ErrorAction SilentlyContinue).OnboardingInfo | ConvertFrom-Json).body | ConvertFrom-Json | Select-Object orgId, geoLocationUrl, datacenter if ($null -ne $sensorConfiguration) { $proxyUrl = $(try {(Get-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name ProxyServer -ErrorAction SilentlyContinue).ProxyServer} catch {$null}) $SensorProxyConfiguration = [PSCustomObject]@{ IsProxyEnabled = $(if ($null -ne $proxyUrl) {$true} else {$false}) Url = $proxyUrl TelemetryProxyServer = $(try {(Get-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name TelemetryProxyServer -ErrorAction SilentlyContinue).TelemetryProxyServer} catch {$null}) } } $sensorConfiguration | Add-Member -MemberType NoteProperty -Name SensorProxyConfiguration -Value $SensorProxyConfiguration -Force } catch { $sensorConfiguration = $null Write-Warning -Message $strings['Sensor_ErrorReadingSensorConfiguration'] } } else { $sensorBinPath = Get-MDISensorBinPath if ($null -eq $sensorBinPath) { $sensorConfiguration = $null Write-Warning -Message $strings['Sensor_ErrorReadingSensorConfiguration'] } 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)) { if ('Set' -eq $operation) { [System.Uri] $resultUri = $null if (-not [System.Uri]::TryCreate($ProxyUrl, [System.UriKind]::Absolute, [ref] $resultUri)) { if (-not $ProxyUrl.StartsWith('http://')) { $ProxyUrl = 'http://'+$ProxyUrl } } } $sensorProcesses = Get-MDISensorProcessInformation $sensorConfiguration = Get-MDISensorConfiguration if ($null -eq $sensorConfiguration) { Write-Error $strings['Sensor_ErrorReadingSensorConfiguration'] -ErrorAction Stop } if ($sensorProcesses.SenseIdentity -eq 'Running') { if ([string]::IsNullOrEmpty($ProxyUrl)) { try { $null = Remove-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name TelemetryProxyServer -ErrorAction Stop $null = Remove-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name ProxyServer -ErrorAction Stop } catch { Write-Warning -Message $strings['Sensor_ProxyConfigurationActionFail'] } } else { try { $null = New-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name TelemetryProxyServer -PropertyType String -Value $ProxyUrl.Replace('http://','') -Force -ErrorAction Stop $null = New-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name ProxyServer -PropertyType String -Value $ProxyUrl -Force -ErrorAction Stop } catch { Write-Warning -Message $strings['Sensor_ProxyConfigurationActionFail'] } } } else { 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 Set-MDIGpoApplyPermission { [CmdletBinding()] param( [Parameter(Mandatory)] [guid] $Guid, [parameter(Mandatory)] [string]$Identity, [parameter(Mandatory)] [ValidateSet("Allow", "Deny")] [string]$PermissionType, [Parameter(Mandatory = $false)] [string] $Server ) $returnVal = $false $domainDn = ([adsi]'').distinguishedName.Value if ([string]::IsNullOrEmpty($server)) { $gpo = [ADSI]"LDAP://CN=`{$($Guid)`},CN=Policies,CN=System,$domainDn" } else { $gpo = [ADSI]"LDAP://$Server/CN=`{$($Guid)`},CN=Policies,CN=System,$domainDn" } if ($gpo -ne $null) { $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule( [System.Security.Principal.NTAccount]"$($settings.domainNetBiosName)\$Identity", "ExtendedRight", $PermissionType, [Guid]"edacfd8f-ffb3-11d1-b41d-00a0c968f939" ) $acl = $gpo.ObjectSecurity $acl.AddAccessRule($rule) | Out-Null try { $gpo.CommitChanges() | Out-Null $returnVal = $true } catch { Write-Warning -Message $strings['GPO_UnableToSetPermissions'] $returnVal = $false } } return $returnVal } 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 = $false)] [string[]] $Extension, [Parameter(Mandatory = $false)] [string] $RawExtension = $null, [Parameter(Mandatory = $false)] [string] $Server ) Write-Verbose -Message $strings['GPO_SetExtension'] $return = $null $Server = Get-MDIDC -Server $Server if ([string]::IsNullOrEmpty($RawExtension)) { $extensions = $Extension | ForEach-Object { "{$_}" } $extensionGuids = '[{0}]' -f [string]::Join('', $extensions) $Replace = @{gPCMachineExtensionNames = $extensionGuids } } else { $Replace = @{gPCMachineExtensionNames = $RawExtension } } $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)] [switch] $ManualLinkRequired, [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)) { if ($ManualLinkRequired) { Write-Warning -Message ($strings['GPO_ManualLinkRequired'] -f $GPO.DisplayName) } 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') & "$($env:SystemRoot)\system32\dsacls.exe" $params $params = @("$deletedObjectsDN", '/G', "$($param2):LCRP") & "$($env:SystemRoot)\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 Active Directory Recycle Bin functions function Get-MDIAdRecycleBin { [CmdletBinding()] param( [string] $Server ) $Server = Get-MDIDC -Server $Server $recycleBinParams = @{ Filter = 'name -like "Recycle Bin Feature"' }; if (-not [string]::IsNullOrEmpty($server)) { $recycleBinParams.Add("Server", $Server) } $value = if ((Get-ADOptionalFeature @recycleBinParams).EnabledScopes) {$true} else {$false} if ($value) { $recycleBinSetting = $strings['DomainRecycleBin_Enabled'] } else { $recycleBinSetting = $strings['DomainRecycleBin_Disabled'] } [PSCustomObject]@{ Name = $strings['DomainRecycleBin_Descriptor'] ActualValue = $value ExpectedValue = $true } } function Set-MDIAdRecycleBin { [CmdletBinding()] param( [string] $Server ) $Server = Get-MDIDC -Server $Server if ((Get-MDIDomainMode) -gt 3 -and (Get-MDIForestMode) -gt 3) { try { if ($env:username -in @( (Get-AdGroupMember -Identity ($settings.SensitiveGroups["Domain Admins"] -f $(Get-MDIDomainSid) ) | ForEach-Object {$_.SamAccountName} ) )) { $domainDn = ([adsi]'LDAP://rootDSE').properties["defaultNamingContext"] $enableAdRecycleBinParams = @{ Identity = "CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$domainDn" Scope = "ForestOrConfigurationSet" Target = $env:USERDNSDOMAIN }; if (-not [string]::IsNullOrEmpty($server)) { $enableAdRecycleBinParams.Add("Server", $Server) } $null = Enable-ADOptionalFeature @enableAdRecycleBinParams write-verbose -Message $strings["DomainRecycleBin_EnableSuccess"] } else { throw } } catch { Write-Warning -Message $strings['DomainRecycleBin_EnableFailed'] } } else { Write-Warning -Message $strings['DomainRecycleBin_ForestDomainFail'] } } function Test-MDIAdRecycleBin { [CmdletBinding()] param( [switch] $Detailed, [string] $Server ) $Server = Get-MDIDC -Server $Server Write-Verbose -Message $strings['DomainRecycleBin_Validation'] $recycleBinParams = @{ }; if (-not [string]::IsNullOrEmpty($server)) { $recycleBinParams.Add("Server", $Server) } $status = [bool](Get-MDIAdRecycleBin @recycleBinParams | where-object { $_.ActualValue -eq $_.ExpectedValue}) if ($status) { $recycleBinSetting = $strings['DomainRecycleBin_Enabled'] } else { $recycleBinSetting = $strings['DomainRecycleBin_Disabled'] } Write-Verbose (Get-MDIValidationMessage $status) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $status Details = $recycleBinSetting }) } else { $status } } #endregion #region RemoteSAM helper functions function Get-MDIRemoteSAM { [CmdletBinding()] param( [string] $Identity ) $identitySid = (Get-MDIDSA -Identity $Identity).objectSid.value $settings.RemoteSAM.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 -f $identitySid [PSCustomObject]@{ Path = $path Name = $name ActualValue = $value ExpectedValue = $expected } } } function Set-MDIRemoteSAM { [CmdletBinding()] param( [string] $Identity ) $identitySid = (Get-MDIDSA -Identity $Identity).objectSid.value $settings.RemoteSAM.RegistrySet.GetEnumerator() | ForEach-Object { $name = ($_.Name -split '\\')[-1] $path = 'HKLM:\{0}' -f ($_.Name -replace $name) $value = $_.Value -f $identitySid Set-ItemProperty -Path $path -Name $name -Value $value -ErrorAction Stop } } function Test-MDIRemoteSAM { [CmdletBinding()] param( [switch] $Detailed, [string] $Identity ) Write-Verbose -Message $strings['RemoteSAM_Validate'] $remoteSAMSettings = Get-MDIRemoteSAM -Identity $Identity $status = @($remoteSAMSettings | Where-Object { $_.ActualValue -match $_.ExpectedValue }).Count -eq $settings.RemoteSAM.RegistrySet.Count Write-Verbose (Get-MDIValidationMessage $status) if ($Detailed) { [PSCustomObject]([ordered]@{ Status = $status Details = $remoteSAMSettings }) } else { $status } } function Get-MDIRemoteSAMGPO { [CmdletBinding()] param( [string] $GpoNamePrefix, [string] $Identity, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $identitySid = (Get-MDIDSA -Identity $Identity).objectSid.value $gpoName = Get-MDIGPOName -Name $settings.RemoteSAM.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 'RestrictRemoteSAM' } $RegistryValue = foreach ($opt in $options) { $valueName = ($opt.KeyName -split '\\')[-1] $path = $opt.KeyName -replace '(.*)\\(\w+)', '$1' [PSCustomObject]@{ KeyName = $path ValueName = $valueName Value = $opt.Display.DisplayString ExpectedValue = (($settings.RemoteSAM.RegistrySet.GetEnumerator() | Where-Object { ('MACHINE\{0}' -f $_.Name) -eq (Join-Path -Path $path -ChildPath $valueName) }).Value -f $identitySid) } } $gpo | Select-Object -Property *, @{N = 'RegistryValue'; E = { $RegistryValue } } } } function Set-MDIRemoteSAMGPO { [CmdletBinding()] param( [string] $Identity, [switch] $SkipGpoLink, [switch] $CreateGpoDisabled, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $identitySid = (Get-MDIDSA -Identity $Identity).objectSid.value $domainSid = (Get-ADDomain).DomainSID.Value $gpoName = Get-MDIGPOName -Name $settings.RemoteSAM.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.RemoteSAM.GpoRegSet.GetEnumerator() | ForEach-Object { $value = $_.Value -f $identitySid $fileContent += '{2}MACHINE\{0}={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 $settings.RemoteSAM.DenyGPPermissions.GetEnumerator() | ForEach-Object { $adObjectParams = @{ LDAPFilter = "(objectSid=$($_.Name -f $domainSid))" Properties = "samaccountname" }; if (-not [string]::IsNullOrEmpty($server)) { $adObjectParams.Add("Server", $server) } $identitySamAccountName = (Get-ADObject @adObjectParams).samaccountname $mdiApplyParams = @{ Guid = $gpo.Id.Guid Identity = $identitySamAccountName PermissionType = "Deny" }; if (-not [string]::IsNullOrEmpty($server)) { $mdiApplyParams.Add("Server", $Server) } $gpoAclUpdate = Set-MDIGpoApplyPermission @mdiApplyParams if (-not $gpoAclUpdate) { Write-Warning $strings['GPO_UnableToSetPermissions'] } } if ($null -ne $gpoUpdated) { if (-not $SkipGpoLink) { if (-not $SkipGpoLink) { Write-Warning -Message ($strings['GPO_ManualLinkRequired'] -f $GPO.DisplayName) } } } else { Write-Warning $strings['GPO_UnableToSetExtension'] } } function Test-MDIRemoteSAMGPO { [CmdletBinding()] param( [switch] $Detailed, [string] $Identity, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.RemoteSAM.GpoName -GpoNamePrefix $GpoNamePrefix Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName) $mdiGpoParams = @{ Identity = $Identity GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server", $Server) } $state = $false $gpo = Get-MDIRemoteSAMGPO @mdiGpoParams if ($gpo) { $gpSetOk = @($gpo.RegistryValue | Where-Object { ([string]::Compare($_.Value, $_.ExpectedValue) -eq 0) }).Count -eq $settings.RemoteSAM.RegistrySet.Count if ($gpSetOk) { $mdiGpoCheckParams = @{ GPO = $gpo ManualLinkRequired = $true }; 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) { $RegistryValue = $gpo.RegistryValue [PSCustomObject]([ordered]@{ Status = $state Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, @{N = 'RegistryValue'; E = { [string]($gpo.RegistryValue -join ',') } } } else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] } }) } else { $state } } #endregion #region EntraConnect Auditing helper functions function Add-MDIServiceLogonRight { [CmdletBinding()] param( [string] $Identity ) $identitySamAccountName = (Get-MDIDSA -Identity $Identity).sAMAccountName $secPol = Get-MDIServiceLogonRight $filePath = '{0}\secpol.inf' -f $env:temp $dbPath = '{0}\secpol.db' -f $env:temp ($secPol) -replace '^SeServiceLogonRight .+', "`$0,$identitySamAccountName" | Set-Content "$filePath" -Force $null = & "$($env:SystemRoot)\system32\secedit.exe" @('/import', '/cfg', "$filePath", '/db', "$dbPath", '/areas', 'USER_RIGHTS', '/overwrite', '/quiet') $null = & "$($env:SystemRoot)\system32\secedit.exe" @('/configure', '/cfg', "$filePath", '/db', "$dbPath", '/areas', 'USER_RIGHTS', '/overwrite', '/quiet') Remove-Item $filePath -ErrorAction SilentlyContinue Remove-Item $dbPath -ErrorAction SilentlyContinue } function Get-MDIServiceLogonRight { [CmdletBinding()] param() $filePath = '{0}\secpol.inf' -f $env:temp $null = & "$($env:SystemRoot)\system32\secedit.exe" @('/export', '/cfg', "$filePath") $content = Get-Content $filePath Remove-Item $filePath -ErrorAction SilentlyContinue return $content } function Get-MDIEntraConnectAuditing { [CmdletBinding()] param() $relevantGUIDs = @($settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv) | Select-Object -ExpandProperty 'Subcategory GUID' -Unique $auditResult = Get-MDIAdvAuditPolicy | Where-Object { $_.'Subcategory GUID' -in $relevantGUIDs } $secPol = ((Get-MDIServiceLogonRight) -match 'SeServiceLogonRight') return [PSCustomObject](@{ AuditSettings = $auditResult GptSettings = $secPol }) } function Test-MDIEntraConnectAuditing { [CmdletBinding()] param( [switch] $Detailed, [string] $Identity ) Write-Verbose -Message $strings['AdvancedPolicyEntra_Validate'] $identitySid = (Get-MDIDSA -Identity $Identity).objectSid.value $existingEntraConnectSettings = Get-MDIEntraConnectAuditing $result = Test-MDIAdvAuditPolicy -ExpectedAuditing @($settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv) -Detailed:$Detailed if ($Detailed) { Write-Verbose -Message (Get-MDIValidationMessage $result.Status) } else { Write-Verbose -Message (Get-MDIValidationMessage $result) } $result | Add-Member -MemberType NoteProperty -Name GptSettings -Value $existingEntraConnectSettings.GptSettings $result.Status = $result.status -and ($($result.GptSettings) -match $identitySid) return $result } function Set-MDIEntraConnectAuditing { [CmdletBinding()] param( [string] $Identity ) Write-Verbose -Message $strings['AdvancedPolicyEntra_Set'] Add-MDIServiceLogonRight -Identity $Identity $settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv | ForEach-Object { $param = @{ SubcategoryGUID = $_.'Subcategory GUID' InclusionSetting = $_.'Inclusion Setting' } Set-MDIAdvAuditPolicy @param } } function Get-MDIEntraConnectAuditingGPO { [CmdletBinding()] param( [string] $Identity, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $identitySid = (Get-MDIDSA -Identity $Identity).objectSid.value $gpoName = Get-MDIGPOName -Name $settings.EntraConnectAuditing.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.EntraConnectAuditing.AuditSet | 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 } } $currentSettings = ($report.GPO.Computer.ExtensionData.Extension.UserRightsAssignment | Where-Object { $_.name -eq 'SeServiceLogonRight' }).member.sid.'#text' | sort $expectedSettings = (($settings.EntraConnectAuditing.GptSet['SeServiceLogonRight'] -split ',').trim('*') -f $identitySid) -split ' ' | sort $GptSettings = [PSCustomObject]@{ UserRightsAssignment = 'Logon as a Service' SettingValue = [string]$expectedSettings -join ',' ExpectedValue = [string]$(foreach ($gpt in $expectedSettings) { $currentSettings | Where-Object { -not [string]::IsNullOrEmpty($_) } | Where-Object { $_ -eq $gpt } }) -join ',' } } $gpo | Select-Object -Property *, @{N = 'AuditSettings'; E = { $AuditSettings } }, @{N = 'GptSettings'; E = { $GptSettings | select SettingValue, ExpectedValue } } } function Test-MDIEntraConnectAuditingGPO { [CmdletBinding()] param( [switch] $Detailed, [string] $Identity, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $gpoName = Get-MDIGPOName -Name $settings.EntraConnectAuditing.GpoName -GpoNamePrefix $GpoNamePrefix Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName) $mdiGpoParams = @{ Identity = $Identity GpoNamePrefix = $GpoNamePrefix }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoParams.Add("Server", $Server) } $state = $false $gpo = Get-MDIEntraConnectAuditingGPO @mdiGpoParams if ($gpo) { $expectedSettings = @($settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv) $gpSetOk = ((@(($gpo.GptSettings | ? { $_.SettingValue -match $_.ExpectedValue })).count -eq 1)) -and (@($gpo.AuditSettings | Where-Object { $_.SettingValue -match $_.ExpectedValue }).Count -eq $expectedSettings.Count) if ($gpSetOk) { $mdiGpoCheckParams = @{ GPO = $gpo ManualLinkRequired = $true }; 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, @{N = "AuditSettings"; E = { [string]($gpo.AuditSettings -join ',') } }, @{N = "GptSettings"; E = { [string]($gpo.GptSettings -join ',') } } } else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] } }) } else { $state } } function Set-MDIEntraConnectAuditingGPO { [CmdletBinding()] param( [switch] $SkipGpoLink, [string] $Identity, [switch] $CreateGpoDisabled, [string] $GpoNamePrefix, [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server $identitySid = (Get-MDIDSA -Identity $Identity).objectSid.value if ($null -eq $identitySid) { throw } $gpoName = Get-MDIGPOName -Name $settings.EntraConnectAuditing.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 [Privilege Rights] '@ $settings.EntraConnectAuditing.GptSet.GetEnumerator() | ForEach-Object { $fileContent += '{2}{0}={1}' -f $_.Name, $($_.Value -f $identitySid), [System.Environment]::NewLine } [System.Io.File]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'GptTmpl.inf'), $fileContent, (New-Object System.Text.UTF8Encoding)) $auditFilePath = '{0}\Machine\Microsoft\Windows NT\Audit' -f $gpo.gPCFileSysPath try { New-Item -Path $auditFilePath -ItemType Directory -Force | Out-Null } catch {} [System.io.file]::WriteAllLines((Join-Path -Path $auditFilePath -ChildPath 'audit.csv'), $settings.EntraConnectAuditing.AuditSet, (New-Object System.Text.ASCIIEncoding)) if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled } $gpo.MakeAclConsistent() $stringBuilderGpc = [System.Text.StringBuilder]::new() [void]$stringBuilderGpc.Append("{$($settings.gpoExtensions['Security'])}") [void]$stringBuilderGpc.Append("{$($settings.gpoExtensions['Computer Restricted Groups'])}") $stringBuilderAudit = [System.Text.StringBuilder]::new() [void]$stringBuilderAudit.Append("{$($settings.gpoExtensions['Audit Policy Configuration'])}") [void]$stringBuilderAudit.Append("{$($settings.gpoExtensions['Audit Configuration Extension'])}") $mdiGpoMachineExtensionParams = @{ Guid = $gpo.Id.Guid RawExtension = '[{0}][{1}]' -f $stringBuilderGpc.ToString(), $stringBuilderAudit.ToString() }; if (-not [string]::IsNullOrEmpty($server)) { $mdiGpoMachineExtensionParams.Add("Server", $Server) } $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams if ($null -ne $gpoUpdated) { if (-not $SkipGpoLink) { Write-Warning -Message ($strings['GPO_ManualLinkRequired'] -f $GPO.DisplayName) } } else { Write-Warning $strings['GPO_UnableToSetExtension'] } } #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 = Get-MDIADObjectName -SidMask $_.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) { $mapping = @{}; $settings.AdvancedAuditPolicyCAs.GPPermissions.GetEnumerator() | ForEach-Object { $mapping[(Get-MDIADObjectName -SidMask $_.Key)] = $_.Key } $gpDelegationOk = @($gpo.Delegation | Where-Object { $settings.AdvancedAuditPolicyCAs.GPPermissions[$mapping[$_.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 = Get-MDIADObjectName -SidMask $_.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 -Type DWord -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 = Get-MDIADObjectName -SidMask $_.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) { $mapping = @{}; $settings.CAAuditing.GPPermissions.GetEnumerator() | ForEach-Object { $mapping[(Get-MDIADObjectName -SidMask $_.Key)] = $_.Key } $gpDelegationOk = @($gpo.Delegation | Where-Object { $settings.CAAuditing.GPPermissions[$mapping[$_.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 = Get-MDIADObjectName -SidMask $_.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-MDIDomainMode { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server (([adsi]'LDAP://rootDSE').properties["domainFunctionality"]).value } function Get-MDIForestMode { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server (([adsi]'LDAP://rootDSE').properties["forestFunctionality"]).value } function Get-MDIDomainSid { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server (Get-AdDomain -server $Server).DomainSid.value } function Get-MDIForestSid { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $Server ) $Server = Get-MDIDC -Server $Server (Get-AdDomain -server $((Get-AdDomain).forest)).DomainSid.value } 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 } function Get-MDIADObjectName { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidatePattern('^S-1-\d{1}(-\d+){1,2}$|^\{0\}-\d{3}$')] [string] $SidMask ) if ($SidMask -match '^\{0\}-\d{3}$') { $sid = $SidMask -f ((Get-ADDomain).DomainSID.Value) Get-ADObject -Filter { objectSid -eq $sid } | Select-Object -ExpandProperty Name } else { (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $SidMask ).Translate([System.Security.Principal.NTAccount]).Value -replace '(.*)\\(.*)', '$2'; } } #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-MDIDomainSid -Server $Server $forestSid = Get-MDIForestSid -Server $Server $sensitiveGroups = @{} $settings.SensitiveGroups.GetEnumerator() | ForEach-Object { if ($_.key -match "Enterprise Admins" -or $_.key -match "Schema Admins") { $sensitiveGroups.Add(($_.Value -f $forestSid), $_.Key) } else { $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 ) $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 { $sensorProcesses = Get-MDISensorProcessInformation $sensorConfiguration = Get-MDISensorConfiguration if ($sensorProcesses.SenseIdentity -eq 'Running') { $sensorApiPath = 'cnc/identity/api' $URI = '{0}{1}' -f $sensorConfiguration.geoLocationUrl, $sensorApiPath } else { $sensorApiPath = 'tri/sensor/api/ping' $URI = '{0}://{1}' -f $protocol[$sensorConfiguration.WorkspaceApplicationSensorApiWebClientConfigurationServiceEndpoint.Port], $sensorConfiguration.WorkspaceApplicationSensorApiWebClientConfigurationServiceEndpoint.Address } if ([string]::IsNullOrEmpty($sensorConfiguration)) { Write-Error $strings['Sensor_ErrorReadingSensorConfiguration'] -ErrorAction Stop } else { $params = @{ URI = $URI } $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 if ($params.URI -match "(\/tri\/sensor\/api\/ping)$") { (200 -eq $response.StatusCode) } else { throw } } catch { if (($params.URI -match "(\/cnc\/identity\/api)$") -and ($_.Exception.Response.StatusCode -eq 'InternalServerError')) { $true } else { 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', 'AdRecycleBin', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs', 'CAAuditing', 'ConfigurationContainerAuditing', 'DomainObjectAuditing', 'EntraConnectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'RemoteSAM', 'All')] [string[]] $Configuration ) DynamicParam { $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) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $true $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParamIdentity = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Identity", [string], $paramAttributesCollect) if ($Mode -eq 'Domain') { $paramDictionary.Add("GpoNamePrefix", $dynParam1) $paramDictionary.Add("Server", $dynParam3) } if ([bool](($($Configuration -join ',')) -match 'EntraConnectAuditing|RemoteSAM|All')) { $paramDictionary.Add("Identity", $dynParamIdentity) } return $paramDictionary } begin { foreach ($key in $PSBoundParameters.Keys) { if ($MyInvocation.MyCommand.Parameters.$key.isDynamic) { Set-Variable -Name $key -Value $PSBoundParameters.$key } } } 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 'AdRecycleBin') { $results.Add('AdRecycleBin', (Test-MDIAdRecycleBin -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 'EntraConnectAuditing') { if ($Mode -eq 'LocalMachine') { $results.Add('EntraConnectAuditing', (Test-MDIEntraConnectAuditing -Detailed -Identity $Identity)) } else { $results.Add('EntraConnectAuditing', (Test-MDIEntraConnectAuditingGPO @mdiParams -Identity $Identity)) } } 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 (Use-MDIConfigName $Configuration 'RemoteSAM') { if ($Mode -eq 'LocalMachine') { $results.Add('RemoteSAM', (Test-MDIRemoteSAM -Detailed -Identity $Identity)) } else { $results.Add('RemoteSAM', (Test-MDIRemoteSAMGPO @mdiParams -Identity $Identity)) } } if ($Configuration -contains 'All') { $Configuration += $results.GetEnumerator() | Select-Object -ExpandProperty Name } $Configuration | Select-Object -Unique | Sort-Object -Property Configuration | 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', 'AdRecycleBin', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs', 'CAAuditing', 'ConfigurationContainerAuditing', 'DomainObjectAuditing', 'EntraConnectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'RemoteSAM', 'All')] [string[]] $Configuration ) DynamicParam { $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) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $true $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParamIdentity = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Identity", [string], $paramAttributesCollect) if ($Mode -eq 'Domain') { $paramDictionary.Add("GpoNamePrefix", $dynParam1) $paramDictionary.Add("Server", $dynParam3) } if ([bool](($($Configuration -join ',')) -match 'EntraConnectAuditing|RemoteSAM|All')) { $paramDictionary.Add("Identity", $dynParamIdentity) } return $paramDictionary } begin { foreach ($key in $PSBoundParameters.Keys) { if ($MyInvocation.MyCommand.Parameters.$key.isDynamic) { Set-Variable -Name $key -Value $PSBoundParameters.$key } } } process { $results = if ($Mode -eq 'Domain') { $Server = Get-MDIDC -Server $Server $mdiParams = @{ Configuration = $Configuration Mode = "Domain" GpoNamePrefix = $GpoNamePrefix } if (-not [string]::IsNullOrEmpty($server)) { $mdiParams.Add("Server", $Server) } if (-not [string]::IsNullOrEmpty($Identity)) { $mdiParams.Add("Identity", $Identity) } Get-MDIConfiguration @mdiParams } 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', 'AdRecycleBin', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs', 'CAAuditing', 'ConfigurationContainerAuditing', 'DomainObjectAuditing', 'EntraConnectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'RemoteSAM', 'All')] [string[]] $Configuration, [Parameter(Mandatory = $false)] [switch] $Force ) DynamicParam { $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) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $true $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParamIdentity = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Identity", [string], $paramAttributesCollect) if ($Mode -eq 'Domain') { $paramDictionary.Add("GpoNamePrefix", $dynParam1) $paramDictionary.Add("CreateGpoDisabled", $dynParam2) $paramDictionary.Add("SkipGpoLink", $dynParam3) $paramDictionary.Add("Server", $dynParam5) } if ([bool](($($Configuration -join ',')) -match 'EntraConnectAuditing|RemoteSAM|All')) { $paramDictionary.Add("Identity", $dynParamIdentity) } return $paramDictionary } begin { foreach ($key in $PSBoundParameters.Keys) { if ($MyInvocation.MyCommand.Parameters.$key.isDynamic) { Set-Variable -Name $key -Value $PSBoundParameters.$key } } } 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 'AdRecycleBin') { Set-MDIAdRecycleBin } 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 'EntraConnectAuditing') { if ($Mode -eq 'LocalMachine') { Set-MDIEntraConnectAuditing -Identity $Identity } else { Set-MDIEntraConnectAuditingGPO @mdiParams -Identity $Identity } } 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 } } if (Use-MDIConfigName $config 'RemoteSAM') { if ($Mode -eq 'LocalMachine') { Set-MDIRemoteSAM -Identity $Identity } else { Set-MDIRemoteSAMGPO @mdiParams -Identity $Identity } } } } } function New-MDIConfigurationReport { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory = $false)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode = 'Domain', [switch] $OpenHtmlReport ) DynamicParam { $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) $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute $paramAttributes.Mandatory = $true $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute] $paramAttributesCollect.Add($paramAttributes) $dynParamIdentity = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Identity", [string], $paramAttributesCollect) if ($Mode -eq 'Domain') { $paramDictionary.Add("Identity", $dynParamIdentity) $paramDictionary.Add("Server", $dynParam3) } return $paramDictionary } begin { foreach ($key in $PSBoundParameters.Keys) { if ($MyInvocation.MyCommand.Parameters.$key.isDynamic) { Set-Variable -Name $key -Value $PSBoundParameters.$key } } } process { 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 } $getMdiConfigParams = @{ Configuration = "All" Mode = $Mode GpoNamePrefix = $GpoNamePrefix } if (-not [string]::IsNullOrEmpty($Server)) { $getMdiConfigParams.Add("Server", $Server) } if (-not [string]::IsNullOrEmpty($Identity)) { $getMdiConfigParams.Add("Identity", $Identity) } $configurations = Get-MDIConfiguration @getMdiConfigParams $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><th>{3}</th></tr>' -f $strings['DomainReport_Configuration'], $strings['DomainReport_Status'], $strings['DomainReport_GpoHeader'], $strings['DomainReport_CommandToFix'] $tblContent = @($configurations | Sort-Object Configuration | ForEach-Object { $gpoPrefixIfUsed = if ([string]::IsNullOrEmpty($GpoNamePrefix)) { '' } else { " -GpoNamePrefix $GpoNamePrefix" } $gpoNameTested = if ($_.Details.ToString() -match "GPO") { ($_.Details.ToString().replace(" - GPO not found",'')).replace("'",'') } "<tr><td><a href='https://aka.ms/mdi/{0}'>{0}</a></td><td class='{1}'>{2}</td><td>{5}</td><td>{3}{0}{4}</td></tr>" -f ` $_.Configuration, $colors[$_.Status], $status[$_.Status], 'Set-MDIConfiguration -Mode Domain -Configuration ', $gpoPrefixIfUsed, $(if ([string]::IsNullOrEmpty($gpoNameTested)) {$strings['DomainReport_GpoNotApplicable']} else {$gpoNameTested}) }) -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 # MIIoRQYJKoZIhvcNAQcCoIIoNjCCKDICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDKikfcIK5a9pvn # m5/YIPAkUtXEd+qDPwsloBs7xEq196CCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGiUwghohAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIO3mTZj4mG+OL5rathJ4pInu # WptrYVaUVxxf3Az5npFAMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAaDPzMZFxpljQGlz4Pij6eErRM81Q5nkAHBTPFwRGtj2Gg/qtoylOX25i # 6BRecil13EqLFvDuZ9apiqSM7bhjFhFuHOxjWC2NcVQKxC8jUv2RwCZFtWEPkUTb # 1HuC3RnS9KK6dvSh2Q6PAJvSB2lZF8XzuKb/TnThLX2v683JjFminckf6/8UMHSe # 5CzvYD35CymF35tmd7WkXLOQPS6OVT/xnH+v+b2idjFYhGnHWONHv3LWF0CKl3s2 # SeoYgGpLrEho5wiI2h0dCaKELsXvVHX9d1twkInn4GudAMR5xXZwWdxg5EJU6Y6e # UgcjG8z/K66O3nEgmZjVGJydp7ZWuKGCF68wgherBgorBgEEAYI3AwMBMYIXmzCC # F5cGCSqGSIb3DQEHAqCCF4gwgheEAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq # hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCBFvjAhFT1Pn5cauwdDKOuIWaAPkVCbmALxrsXs0qvSzgIGZ7YtbyqH # GBIyMDI1MDIyMzEwMDYwMS4wM1owBIACAfSggdmkgdYwgdMxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs # YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO # OjZGMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloIIR/jCCBygwggUQoAMCAQICEzMAAAH8GKCvzGlahzoAAQAAAfwwDQYJ # KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjQw # NzI1MTgzMTE0WhcNMjUxMDIyMTgzMTE0WjCB0zELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl # cmF0aW9ucyBMaW1pdGVkMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046NkYxQS0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Uw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUMAovGltBw9Vg8eUkfJb # tbmUFmMlZYOMx+XlbKG4DKU/8sObaFvjKZSZLKZXQQVFByYzwHKnCOFY/wXvHiI7 # zx370Zn0xCY2HeNIOiGFdeB/ys40QDJ8I9WYnMzvK2y6tn3Ghxu4Qvv4/sSQjCg6 # 4wWVMXYb/O6rHUT7AA2yWk+o+lvw8LnoFDGAkIjUs4XmWTcB48nvw4vdrD812WS9 # 1WXdWAFI9fg1rC3dSBSh+4+f9yn2/AooHC4hZAQVnzfsZdchOyF3Yx+zqhh/FcS2 # aPZIEYSHFWzvERG5YZvRwrpp/LudoqRtkja/VSqzG5m33iuf97LbKe+6eXHRHr9v # qc2QLWs5MB9aWmwCP9CXPIzq5hNLFhkLZhbMtttGXSVG1LP7hN2lRT+gBlIH5j6z # XZGqaDOLtFXE1wjaWb/+wISboDrecIkKBi0z4st72lOyGX9Z/w4649BGID6y1OyD # z0E4b21uVrPaR946Rh/mF0fymEBu464NB+vqzlhrpb69nPoTRmx6fOLQ60x/lEJr # aEANhWBTEP6CsLwSm19Z5UBaBgJpAWF4039kY1AySTvxXrfKg8F98kQC74HnGVM9 # iiKNR2j01ei8magZePpHxOCyj5A9oAYcrEJsdrVAv0BIwXc6zWOuiAIcaLKR+nV0 # oaeYDnAlPo3HsF52VIOwCQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFJjxpSuxRrOH # EHIfv12GJAIv/tB2MB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8G # A1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBs # BggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy # MDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH # AwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBPuyz9kBnRtNoW # yuyIlh3/8tDoiYpv0llIqJHD3GzyHCTlj/vOKCf2Aa3B5EKELaUnwZVEeCsWQHEA # KO64+mtLP9RhfufnbYkpy52da4h4TWnfQFmuo9M6exwP3wNmLpY1MDKadpLuK3mo # CA0iet4AltNTZwCiwmh5cAevnRiBIaWYkz5w3qZgsAvpNMUy0Vgmukr1MzlHKHl5 # mabpRMmcaDSO3WkX/a7w9J3Pd0PLsMXsoMlp3aofFk5G8zeT1Xxifckjlgs+4uyj # Ynmzd+lfIJWBH+GrzqDnON31tFHLKILyIsib833mrodZWwJ7JJ62up+wPJhZK3Av # 3qHLsMjIsvmKHxgUx3QB2a9NGjKWYAO4rATZNAJO8+eOcuTvKklbb23XtjJrhX4m # XPniwGc9TuQh5hmy9RP5gqDKQ/VAH6n65R1Efp7v1VqLP6J7Basglni1eQMyYvbW # eHSP7nwTV5wBgO9PoHjIUh6ifED/oaX1ezsktyI8IgLqEZ2WKIQGnJh5QXSiFkWf # s0pC7zQhnSN3nXVYDZurrH1pSr/MXJ/wSDD526dSPUq02hamrtW4mpqlyRiya+aZ # gdlcKMrUS6ztXUj5peOsFi6rIz1Cl4VlThTTVgbXm5bBQJqPS5mVqH9EZJgx3zjL # 0MKo6l94oTo8syTuEueG9w5CufE/qTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKb # SZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmlj # YXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIy # NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXI # yjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjo # YH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1y # aa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v # 3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pG # ve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viS # kR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYr # bqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlM # jgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSL # W6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AF # emzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIu # rQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIE # FgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWn # G1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEW # M2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5 # Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi # AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV # 9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js # Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAx # MC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2 # LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv # 6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZn # OlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1 # bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4 # rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU # 6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDF # NLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/ # HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdU # CbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKi # excdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTm # dHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZq # ELQdVTNYs6FwZvKhggNZMIICQQIBATCCAQGhgdmkgdYwgdMxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs # YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO # OjZGMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBOQSklc5qojbB+oGzDg0tXCpiqqqCBgzCB # gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUA # AgUA62VI8jAiGA8yMDI1MDIyMzA3MTI1MFoYDzIwMjUwMjI0MDcxMjUwWjB3MD0G # CisGAQQBhFkKBAExLzAtMAoCBQDrZUjyAgEAMAoCAQACAgWaAgH/MAcCAQACAhJC # MAoCBQDrZppyAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAI # AgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAJZlP8TfDmCf # rKWDTcd0ZQyJptgoOXDQzyXfefBjtDojxAGM2+TUHYLo7JB51dY6Vc7zIRo1A92q # R9XZsLvTQ60CxelEu+u+lOnpCl38WdUwO5mzP8cU0fGVKhRxQFHXu7/BRe0A7yJe # 93IY/4DGk/53hIjq1em1+lYNxaXjFE97ZtigxSyOjynDTQqP7cejFHZnFyXic+hm # hVNfvIdQMpf6L0IuwivJB+p42lH4qJpGCLxYbQ0WFUPMx15vr2CXwRTRAyI7aQ+U # JvL8/mM7nFliATriNDdC/yJ/lhRBwqJtqlLaXPe9x/IjHXzhQ1AplPlqFF+1VoVf # jiG9QmxaiLkxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg # MjAxMAITMwAAAfwYoK/MaVqHOgABAAAB/DANBglghkgBZQMEAgEFAKCCAUowGgYJ # KoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCADdlDYrRvc # hrpR98+XdnqTlJa5xPWEtq5fguC3IDCVJjCB+gYLKoZIhvcNAQkQAi8xgeowgecw # geQwgb0EIJVCr5C77+H8E5U/jDB5TBse4JSGH5PuGrd3kwJo0S1iMIGYMIGApH4w # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAH8GKCvzGlahzoAAQAA # AfwwIgQg+Y9nuYN3TdpOrOqiWIDdeGdgFucHPGEQ4O+Lvkhhh4EwDQYJKoZIhvcN # AQELBQAEggIAmyvPr0z2jDRSHRV9rXVpkbV0T3Chdm810Zm/0NlcO/SwOjXw4m+J # vXJb7Efn26h/MmlzT3PeOYhjYGEwbskdSGTLgDC8+JGPGNpo0D6gy5UXUJomGp4T # eroMYNZ/qHcIUjaN6mjqNJFUwWmaT/wb86w69wvA2YdM6BIdPLCm269tfenoq2Ns # LLrbrjidq4ax7GHYTXCjSYosO+D6E60YbbA87pEo2cOPR3dx1yXeX7kUJ1PCGWQN # Dg92Q+QrqudIJc2LqO4GZXA//YhxrJWMa+aIRqfQvpjAWn6lcIxarWnDIH/jC6Bs # Jvg+NPZywYGJjsWavKJ1WpDCpcZHMMrn2QDkjATZevirSR+QQOq4W5jKPkXT+63k # bsDc9SraRW3jpjq3MPUedQXa6NYpKAjMpvXRiVuBwTARV4FYvbfKMqw9pvgxLJJI # 1HfZU59Tyj5pzlzVTeJJG9Fj1a5aCN+TBXL0QgaTXsSbvV2FRQwcndUtS8CzwdSN # FhEUYw4FG0tp4t9XcU0y1cfq+Z9L4brNeDakUvg4GrnTDyjF7AU4AtK7pb8sZpOz # fPUjyz42WgwpI93mXNcqgw7/P6oHOdjmUwG810Lu77vNs/mRFhvsq6W7Sb2tyLgp # qlUjiq9uO8ZqkIQLr7J0qsT0tVXJlpJ3N9u+VIIZZeOpHjMphQP3+a4= # SIG # End signature block |