PasswordSolution.psm1
function ConvertFrom-DistinguishedName { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER DistinguishedName Parameter description .PARAMETER ToOrganizationalUnit Parameter description .PARAMETER ToDC Parameter description .PARAMETER ToDomainCN Parameter description .EXAMPLE $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToOrganizationalUnit Output: OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName Output: Przemyslaw Klys .NOTES General notes #> [CmdletBinding()] param([alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName, [switch] $ToOrganizationalUnit, [switch] $ToDC, [switch] $ToDomainCN) process { foreach ($Distinguished in $DistinguishedName) { if ($ToDomainCN) { $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' $CN = $DN -replace ',DC=', '.' -replace "DC=" $CN } elseif ($ToOrganizationalUnit) { [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value } elseif ($ToDC) { $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' } else { $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$' $Output = foreach ($_ in $Distinguished) { $_ -match $Regex $Matches } $Output.cn } } } } function Convert-UserAccountControl { [cmdletBinding()] param([alias('UAC')][int] $UserAccountControl, [string] $Separator) $UserAccount = [ordered] @{"SCRIPT" = 1 "ACCOUNTDISABLE" = 2 "HOMEDIR_REQUIRED" = 8 "LOCKOUT" = 16 "PASSWD_NOTREQD" = 32 "ENCRYPTED_TEXT_PWD_ALLOWED" = 128 "TEMP_DUPLICATE_ACCOUNT" = 256 "NORMAL_ACCOUNT" = 512 "INTERDOMAIN_TRUST_ACCOUNT" = 2048 "WORKSTATION_TRUST_ACCOUNT" = 4096 "SERVER_TRUST_ACCOUNT" = 8192 "DONT_EXPIRE_PASSWORD" = 65536 "MNS_LOGON_ACCOUNT" = 131072 "SMARTCARD_REQUIRED" = 262144 "TRUSTED_FOR_DELEGATION" = 524288 "NOT_DELEGATED" = 1048576 "USE_DES_KEY_ONLY" = 2097152 "DONT_REQ_PREAUTH" = 4194304 "PASSWORD_EXPIRED" = 8388608 "TRUSTED_TO_AUTH_FOR_DELEGATION" = 16777216 "PARTIAL_SECRETS_ACCOUNT" = 67108864 } $Output = foreach ($_ in $UserAccount.Keys) { $binaryAnd = $UserAccount[$_] -band $UserAccountControl if ($binaryAnd -ne "0") { $_ } } if ($Separator) { $Output -join $Separator } else { $Output } } function Get-GitHubLatestRelease { [CmdLetBinding()] param([alias('ReleasesUrl')][uri] $Url) $ProgressPreference = 'SilentlyContinue' Try { [Array] $JsonOutput = (Invoke-WebRequest -Uri $Url -ErrorAction Stop | ConvertFrom-Json) foreach ($JsonContent in $JsonOutput) { [PSCustomObject] @{PublishDate = [DateTime] $JsonContent.published_at CreatedDate = [DateTime] $JsonContent.created_at PreRelease = [bool] $JsonContent.prerelease Version = [version] ($JsonContent.name -replace 'v', '') Tag = $JsonContent.tag_name Branch = $JsonContent.target_commitish Errors = '' } } } catch { [PSCustomObject] @{PublishDate = $null CreatedDate = $null PreRelease = $null Version = $null Tag = $null Branch = $null Errors = $_.Exception.Message } } $ProgressPreference = 'Continue' } function Get-WinADForestDetails { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [string] $Filter = '*', [switch] $TestAvailability, [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All', [int[]] $Ports = 135, [int] $PortsTimeout = 100, [int] $PingCount = 1, [switch] $Extended, [System.Collections.IDictionary] $ExtendedForestInformation) if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } if (-not $ExtendedForestInformation) { $Findings = [ordered] @{} try { if ($Forest) { $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest } else { $ForestInformation = Get-ADForest -ErrorAction Stop } } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)" return } if (-not $ForestInformation) { return } $Findings['Forest'] = $ForestInformation $Findings['ForestDomainControllers'] = @() $Findings['QueryServers'] = @{} $Findings['DomainDomainControllers'] = @{} [Array] $Findings['Domains'] = foreach ($_ in $ForestInformation.Domains) { if ($IncludeDomains) { if ($_ -in $IncludeDomains) { $_.ToLower() } continue } if ($_ -notin $ExcludeDomains) { $_.ToLower() } } foreach ($Domain in $ForestInformation.Domains) { try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop $OrderedDC = [ordered] @{Domain = $DC.Domain Forest = $DC.Forest HostName = [Array] $DC.HostName IPv4Address = $DC.IPv4Address IPv6Address = $DC.IPv6Address Name = $DC.Name Site = $DC.Site } } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)" continue } if ($Domain -eq $Findings['Forest']['Name']) { $Findings['QueryServers']['Forest'] = $OrderedDC } $Findings['QueryServers']["$Domain"] = $OrderedDC } [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { $QueryServer = $Findings['QueryServers'][$Domain]['HostName'][0] [Array] $AllDC = try { try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop } catch { Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)" continue } foreach ($S in $DomainControllers) { if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } } if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } } $Server = [ordered] @{Domain = $Domain HostName = $S.HostName Name = $S.Name Forest = $ForestInformation.RootDomain Site = $S.Site IPV4Address = $S.IPV4Address IPV6Address = $S.IPV6Address IsGlobalCatalog = $S.IsGlobalCatalog IsReadOnly = $S.IsReadOnly IsSchemaMaster = ($S.OperationMasterRoles -contains 'SchemaMaster') IsDomainNamingMaster = ($S.OperationMasterRoles -contains 'DomainNamingMaster') IsPDC = ($S.OperationMasterRoles -contains 'PDCEmulator') IsRIDMaster = ($S.OperationMasterRoles -contains 'RIDMaster') IsInfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster') OperatingSystem = $S.OperatingSystem OperatingSystemVersion = $S.OperatingSystemVersion OperatingSystemLong = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion LdapPort = $S.LdapPort SslPort = $S.SslPort DistinguishedName = $S.ComputerObjectDN Pingable = $null WinRM = $null PortOpen = $null Comment = '' } if ($TestAvailability) { if ($Test -eq 'All' -or $Test -like 'Ping*') { $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount } if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status } if ($Test -eq 'All' -or '*PortOpen*') { $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status } } [PSCustomObject] $Server } } catch { [PSCustomObject]@{Domain = $Domain HostName = '' Name = '' Forest = $ForestInformation.RootDomain IPV4Address = '' IPV6Address = '' IsGlobalCatalog = '' IsReadOnly = '' Site = '' SchemaMaster = $false DomainNamingMasterMaster = $false PDCEmulator = $false RIDMaster = $false InfrastructureMaster = $false LdapPort = '' SslPort = '' DistinguishedName = '' Pingable = $null WinRM = $null PortOpen = $null Comment = $_.Exception.Message -replace "`n", " " -replace "`r", " " } } if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC } [Array] $Findings['DomainDomainControllers'][$Domain] } if ($Extended) { $Findings['DomainsExtended'] = @{} $Findings['DomainsExtendedNetBIOS'] = @{} foreach ($DomainEx in $Findings['Domains']) { try { $Findings['DomainsExtended'][$DomainEx] = Get-ADDomain -Server $Findings['QueryServers'][$DomainEx].HostName[0] | ForEach-Object { [ordered] @{AllowedDNSSuffixes = $_.AllowedDNSSuffixes | ForEach-Object -Process { $_ } ChildDomains = $_.ChildDomains | ForEach-Object -Process { $_ } ComputersContainer = $_.ComputersContainer DeletedObjectsContainer = $_.DeletedObjectsContainer DistinguishedName = $_.DistinguishedName DNSRoot = $_.DNSRoot DomainControllersContainer = $_.DomainControllersContainer DomainMode = $_.DomainMode DomainSID = $_.DomainSID.Value ForeignSecurityPrincipalsContainer = $_.ForeignSecurityPrincipalsContainer Forest = $_.Forest InfrastructureMaster = $_.InfrastructureMaster LastLogonReplicationInterval = $_.LastLogonReplicationInterval LinkedGroupPolicyObjects = $_.LinkedGroupPolicyObjects | ForEach-Object -Process { $_ } LostAndFoundContainer = $_.LostAndFoundContainer ManagedBy = $_.ManagedBy Name = $_.Name NetBIOSName = $_.NetBIOSName ObjectClass = $_.ObjectClass ObjectGUID = $_.ObjectGUID ParentDomain = $_.ParentDomain PDCEmulator = $_.PDCEmulator PublicKeyRequiredPasswordRolling = $_.PublicKeyRequiredPasswordRolling | ForEach-Object -Process { $_ } QuotasContainer = $_.QuotasContainer ReadOnlyReplicaDirectoryServers = $_.ReadOnlyReplicaDirectoryServers | ForEach-Object -Process { $_ } ReplicaDirectoryServers = $_.ReplicaDirectoryServers | ForEach-Object -Process { $_ } RIDMaster = $_.RIDMaster SubordinateReferences = $_.SubordinateReferences | ForEach-Object -Process { $_ } SystemsContainer = $_.SystemsContainer UsersContainer = $_.UsersContainer } } $NetBios = $Findings['DomainsExtended'][$DomainEx]['NetBIOSName'] $Findings['DomainsExtendedNetBIOS'][$NetBios] = $Findings['DomainsExtended'][$DomainEx] } catch { Write-Warning "Get-WinADForestDetails - Error gathering Domain Information for domain $DomainEx - $($_.Exception.Message)" continue } } } if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } $Findings } else { $Findings = Copy-DictionaryManual -Dictionary $ExtendedForestInformation [Array] $Findings['Domains'] = foreach ($_ in $Findings.Domains) { if ($IncludeDomains) { if ($_ -in $IncludeDomains) { $_.ToLower() } continue } if ($_ -notin $ExcludeDomains) { $_.ToLower() } } foreach ($_ in [string[]] $Findings.DomainDomainControllers.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainDomainControllers.Remove($_) } } foreach ($_ in [string[]] $Findings.DomainsExtended.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainsExtended.Remove($_) $NetBiosName = $Findings.DomainsExtended.$_.'NetBIOSName' if ($NetBiosName) { $Findings.DomainsExtendedNetBIOS.Remove($NetBiosName) } } } [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { [Array] $AllDC = foreach ($S in $Findings.DomainDomainControllers["$Domain"]) { if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } } if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } } $S } if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC } [Array] $Findings['DomainDomainControllers'][$Domain] } $Findings } } function Start-TimeLog { [CmdletBinding()] param() [System.Diagnostics.Stopwatch]::StartNew() } function Stop-TimeLog { [CmdletBinding()] param ([Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time, [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner', [switch] $Continue) Begin {} Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } } End { if (-not $Continue) { $Time.Stop() } return $TimeToExecute } } function Write-Color { <# .SYNOPSIS Write-Color is a wrapper around Write-Host. It provides: - Easy manipulation of colors, - Logging output to file (log) - Nice formatting options out of the box. .DESCRIPTION Author: przemyslaw.klys at evotec.pl Project website: https://evotec.xyz/hub/scripts/write-color-ps1/ Project support: https://github.com/EvotecIT/PSWriteColor Original idea: Josh (https://stackoverflow.com/users/81769/josh) .EXAMPLE Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1 .EXAMPLE Write-Color "1. ", "Option 1" -Color Yellow, Green Write-Color "2. ", "Option 2" -Color Yellow, Green Write-Color "3. ", "Option 3" -Color Yellow, Green Write-Color "4. ", "Option 4" -Color Yellow, Green Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1 .EXAMPLE Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss" Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" .EXAMPLE # Added in 0.5 Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow wc -t "my text" -c yellow -b green wc -text "my text" -c red .NOTES Additional Notes: - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx #> [alias('Write-Colour')] [CmdletBinding()] param ([alias ('T')] [String[]]$Text, [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White, [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null, [alias ('Indent')][int] $StartTab = 0, [int] $LinesBefore = 0, [int] $LinesAfter = 0, [int] $StartSpaces = 0, [alias ('L')] [string] $LogFile = '', [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss', [alias ('LogTimeStamp')][bool] $LogTime = $true, [int] $LogRetry = 2, [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode', [switch] $ShowTime, [switch] $NoNewLine) $DefaultColor = $Color[0] if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) { Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated." return } if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } } if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } } if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } } if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline } if ($Text.Count -ne 0) { if ($Color.Count -ge $Text.Count) { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } } } else { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline } } else { for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline } } } } if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host } if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } } if ($Text.Count -and $LogFile) { $TextToFile = "" for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] } $Saved = $false $Retry = 0 Do { $Retry++ try { if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } $Saved = $true } catch { if ($Saved -eq $false -and $Retry -eq $LogRetry) { $PSCmdlet.WriteError($_) } else { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)" } } } Until ($Saved -eq $true -or $Retry -ge $LogRetry) } } function ConvertTo-OperatingSystem { <# .SYNOPSIS Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD .DESCRIPTION Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD .PARAMETER OperatingSystem Operating System as returned by Active Directory .PARAMETER OperatingSystemVersion Operating System Version as returned by Active Directory .EXAMPLE $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object { $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force $_ } $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table .NOTES General notes #> [CmdletBinding()] param([string] $OperatingSystem, [string] $OperatingSystemVersion) if ($OperatingSystem -like '*Windows 10*') { $Systems = @{'10.0 (19043)' = 'Windows 10 21H1' '10.0 (19042)' = 'Windows 10 20H2' '10.0 (19041)' = 'Windows 10 2004' '10.0 (18363)' = "Windows 10 1909" '10.0 (18362)' = "Windows 10 1903" '10.0 (17763)' = "Windows 10 1809" '10.0 (17134)' = "Windows 10 1803" '10.0 (16299)' = "Windows 10 1709" '10.0 (15063)' = "Windows 10 1703" '10.0 (14393)' = "Windows 10 1607" '10.0 (10586)' = "Windows 10 1511" '10.0 (10240)' = "Windows 10 1507" '10.0 (18898)' = 'Windows 10 Insider Preview' '10.0.19043' = 'Windows 10 21H1' '10.0.19042' = 'Windows 10 20H2' '10.0.19041' = 'Windows 10 2004' '10.0.18363' = "Windows 10 1909" '10.0.18362' = "Windows 10 1903" '10.0.17763' = "Windows 10 1809" '10.0.17134' = "Windows 10 1803" '10.0.16299' = "Windows 10 1709" '10.0.15063' = "Windows 10 1703" '10.0.14393' = "Windows 10 1607" '10.0.10586' = "Windows 10 1511" '10.0.10240' = "Windows 10 1507" '10.0.18898' = 'Windows 10 Insider Preview' } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystem } } elseif ($OperatingSystem -like '*Windows Server*') { $Systems = @{'5.2 (3790)' = 'Windows Server 2003' '6.1 (7601)' = 'Windows Server 2008 R2' '10.0 (18362)' = "Windows Server, version 1903 (Semi-Annual Channel) 1903" '10.0 (17763)' = "Windows Server 2019 (Long-Term Servicing Channel) 1809" '10.0 (17134)' = "Windows Server, version 1803 (Semi-Annual Channel) 1803" '10.0 (14393)' = "Windows Server 2016 (Long-Term Servicing Channel) 1607" '10.0.18362' = "Windows Server, version 1903 (Semi-Annual Channel) 1903" '10.0.17763' = "Windows Server 2019 (Long-Term Servicing Channel) 1809" '10.0.17134' = "Windows Server, version 1803 (Semi-Annual Channel) 1803" '10.0.14393' = "Windows Server 2016 (Long-Term Servicing Channel) 1607" } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystem } } else { $System = $OperatingSystem } if ($System) { $System } else { 'Unknown' } } function Copy-DictionaryManual { [CmdletBinding()] param([System.Collections.IDictionary] $Dictionary) $clone = @{} foreach ($Key in $Dictionary.Keys) { $value = $Dictionary.$Key $clonedValue = switch ($Dictionary.$Key) { { $null -eq $_ } { $null continue } { $_ -is [System.Collections.IDictionary] } { Copy-DictionaryManual -Dictionary $_ continue } { $type = $_.GetType() $type.IsPrimitive -or $type.IsValueType -or $_ -is [string] } { $_ continue } default { $_ | Select-Object -Property * } } if ($value -is [System.Collections.IList]) { $clone[$Key] = @($clonedValue) } else { $clone[$Key] = $clonedValue } } $clone } function Test-ComputerPort { [CmdletBinding()] param ([alias('Server')][string[]] $ComputerName, [int[]] $PortTCP, [int[]] $PortUDP, [int]$Timeout = 5000) begin { if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } } process { foreach ($Computer in $ComputerName) { foreach ($P in $PortTCP) { $Output = [ordered] @{'ComputerName' = $Computer 'Port' = $P 'Protocol' = 'TCP' 'Status' = $null 'Summary' = $null 'Response' = $null } $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue if ($TcpClient.TcpTestSucceeded) { $Output['Status'] = $TcpClient.TcpTestSucceeded $Output['Summary'] = "TCP $P Successful" } else { $Output['Status'] = $false $Output['Summary'] = "TCP $P Failed" $Output['Response'] = $Warnings } [PSCustomObject]$Output } foreach ($P in $PortUDP) { $Output = [ordered] @{'ComputerName' = $Computer 'Port' = $P 'Protocol' = 'UDP' 'Status' = $null 'Summary' = $null } $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P) $UdpClient.Client.ReceiveTimeout = $Timeout $Encoding = [System.Text.ASCIIEncoding]::new() $byte = $Encoding.GetBytes("Evotec") [void]$UdpClient.Send($byte, $byte.length) $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0) try { $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint) [string]$Data = $Encoding.GetString($Bytes) If ($Data) { $Output['Status'] = $true $Output['Summary'] = "UDP $P Successful" $Output['Response'] = $Data } } catch { $Output['Status'] = $false $Output['Summary'] = "UDP $P Failed" $Output['Response'] = $_.Exception.Message } $UdpClient.Close() $UdpClient.Dispose() [PSCustomObject]$Output } } } end { if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } } } function Test-WinRM { [CmdletBinding()] param ([alias('Server')][string[]] $ComputerName) $Output = foreach ($Computer in $ComputerName) { $Test = [PSCustomObject] @{Output = $null Status = $null ComputerName = $Computer } try { $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop $Test.Status = $true } catch { $Test.Status = $false } $Test } $Output } function Add-ManagerInformation { [CmdletBinding()] param( [System.Collections.IDictionary] $SummaryDictionary, [string] $Type, [string] $ManagerType, [Object] $Key, [PSCustomObject] $User, [PSCustomObject] $Rule # [bool] $Enabled ) #if ($Enabled) { if ($Key) { if ($Key -is [string]) { $KeyDN = $Key } else { $KeyDN = $Key.DisplayName } if (-not $SummaryDictionary[$KeyDN]) { $SummaryDictionary[$KeyDN] = [ordered] @{ Manager = $Key ManagerDefault = [ordered] @{} ManagerNotCompliant = [ordered] @{} Security = [ordered] @{} } } $SummaryDictionary[$KeyDN][$Type][$User.DistinguishedName] = [ordered] @{ Manager = $User.ManagerDN User = $User Rule = $Rule ManagerOption = $Type Output = [ordered] @{} } $Default = [ordered] @{ DisplayName = $User.DisplayName Enabled = $User.Enabled SamAccountName = $User.SamAccountName Domain = $User.Domain DateExpiry = $User.DateExpiry DaysToExpire = $User.DaysToExpire PasswordLastSet = $User.PasswordLastSet PasswordExpired = $User.PasswordExpired } if ($Type -ne 'ManagerDefault') { $Extended = [ordered] @{ 'Status' = $ManagerType 'Manager' = $User.Manager 'Manager Email' = $User.ManagerEmail } $SummaryDictionary[$KeyDN][$Type][$User.DistinguishedName]['Output'] = [PSCustomObject] ( $Extended + $Default) } else { $SummaryDictionary[$KeyDN][$Type][$User.DistinguishedName]['Output'] = [PSCustomObject] $Default } } # } } function Add-ParametersToString { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER String Parameter description .PARAMETER Parameter Parameter description .EXAMPLE $Test = 'this is a string $Test - and $Test2 AND $tEST3' Add-ParametersToString -String $Test -Parameter @{ Testooo = 'sdsds' Test = 'oh my god' Test2 = 'ole ole' TEST3 = '56555' } .NOTES General notes #> [CmdletBinding()] param( [string] $String, [System.Collections.IDictionary] $Parameter ) $Sorted = $Parameter.Keys | Sort-Object { $_.length } -Descending foreach ($Key in $Sorted) { $String = $String -ireplace [Regex]::Escape("`$$Key"), $Parameter[$Key] } $String } function Get-GitHubVersion { [cmdletBinding()] param( [Parameter(Mandatory)][string] $Cmdlet, [Parameter(Mandatory)][string] $RepositoryOwner, [Parameter(Mandatory)][string] $RepositoryName ) $App = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue if ($App) { [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases" -Verbose:$false) $LatestVersion = $GitHubReleases[0] if (-not $LatestVersion.Errors) { if ($App.Version -eq $LatestVersion.Version) { "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)" } elseif ($App.Version -lt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?" } elseif ($App.Version -gt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!" } } else { "Current: $($App.Version)" } } } function Find-Password { [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string] $OverwriteEmailProperty, [switch] $AsHashTable ) $Today = Get-Date $Properties = @( 'Manager', 'DisplayName', 'GivenName', 'Surname', 'SamAccountName', 'EmailAddress', 'msDS-UserPasswordExpiryTimeComputed', 'PasswordExpired', 'PasswordLastSet', 'PasswordNotRequired', 'Enabled', 'PasswordNeverExpires', 'Mail', 'MemberOf', 'LastLogonDate', 'Name' 'userAccountControl' 'msExchMailboxGuid' 'pwdLastSet' if ($OverwriteEmailProperty) { $OverwriteEmailProperty } ) # We're caching all users to make sure it's speedy gonzales when querying for Managers if (-not $CachedUsers) { $CachedUsers = [ordered] @{ } } if (-not $Cache) { $Cache = [ordered] @{ } } Write-Color -Text "[i] Discovering forest information" -Color White, Yellow, White, Yellow, White, Yellow, White $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExcludeDomains $ExcludeDomains -IncludeDomains $IncludeDomains -ExtendedForestInformation $ExtendedForestInformation [Array] $Users = foreach ($Domain in $ForestInformation.Domains) { Write-Color -Text "[i] Discovering DC for domain ", "$($Domain)", " in forest ", $ForestInformation.Name -Color White, Yellow, White, Yellow, White, Yellow, White $Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] Write-Color -Text "[i] Getting users from ", "$($Domain)", " using ", $Server -Color White, Yellow, White, Yellow, White, Yellow, White try { Get-ADUser -Server $Server -Filter '*' -Properties $Properties -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Color '[e] Error: ', $ErrorMessage -Color White, Red } } foreach ($User in $Users) { $Cache[$User.DistinguishedName] = $User } Write-Color -Text "[i] Preparing all users for password expirations in forest ", $Forest.Name -Color White, Yellow, White, Yellow, White, Yellow, White foreach ($User in $Users) { #$UserManager = $Cache["$($User.Manager)"] if ($User.Manager) { $Manager = $Cache[$User.Manager].DisplayName $ManagerSamAccountName = $Cache[$User.Manager].SamAccountName $ManagerEmail = $Cache[$User.Manager].Mail $ManagerEnabled = $Cache[$User.Manager].Enabled $ManagerLastLogon = $Cache[$User.Manager].LastLogonDate if ($ManagerLastLogon) { $ManagerLastLogonDays = $( - $($ManagerLastLogon - $Today).Days) } else { $ManagerLastLogonDays = $null } if ($ManagerEnabled -and $ManagerEmail) { if ((Test-EmailAddress -EmailAddress $ManagerEmail).IsValid -eq $true) { $ManagerStatus = 'Enabled' } else { $ManagerStatus = 'Enabled, bad email' } } elseif ($ManagerEnabled) { $ManagerStatus = 'No email' } else { $ManagerStatus = 'Disabled' } } else { if ($User.ObjectClass -eq 'user') { $ManagerStatus = 'Missing' } else { $ManagerStatus = 'Not available' } $Manager = $null $ManagerSamAccountName = $null $ManagerEmail = $null $ManagerEnabled = $null $ManagerLastLogon = $null $ManagerLastLogonDays = $null } if ($OverwriteEmailProperty) { # fix this for a user $EmailTemp = $User.$OverwriteEmailProperty if ($EmailTemp -like '*@*') { $EmailAddress = $EmailTemp } else { $EmailAddress = $User.EmailAddress } # Fix this for manager as well if ($Cache["$($User.Manager)"]) { if ($Cache["$($User.Manager)"].$OverwriteEmailProperty -like '*@*') { # $UserManager.Mail = $UserManager.$OverwriteEmailProperty $ManagerEmail = $Cache["$($User.Manager)"].$OverwriteEmailProperty } } } else { $EmailAddress = $User.EmailAddress } if ($User.PasswordLastSet) { $PasswordDays = (New-TimeSpan -Start ($User.PasswordLastSet) -End ($Today)).Days } if ($User."msDS-UserPasswordExpiryTimeComputed" -ne 9223372036854775807) { # This is standard situation where users password is expiring as needed try { $DateExpiry = ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed")) } catch { $DateExpiry = $User."msDS-UserPasswordExpiryTimeComputed" } try { $DaysToExpire = (New-TimeSpan -Start ($Today) -End ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed"))).Days } catch { $DaysToExpire = $null } $PasswordNeverExpires = $User.PasswordNeverExpires } else { # This is non-standard situation. This basically means most likely Fine Grained Group Policy is in action where it makes PasswordNeverExpires $true # Since FGP policies are a bit special they do not tick the PasswordNeverExpires box, but at the same time value for "msDS-UserPasswordExpiryTimeComputed" is set to 9223372036854775807 $PasswordNeverExpires = $true } if ($PasswordNeverExpires -or $null -eq $User.PasswordLastSet) { # If password last set is null or password never expires is set to true, then date of expiry and days to expire is not applicable $DateExpiry = $null $DaysToExpire = $null } if ($User.pwdLastSet -eq 0 -and $DateExpiry.Year -eq 1601) { $PasswordAtNextLogon = $true } else { $PasswordAtNextLogon = $false } $UserAccountControl = Convert-UserAccountControl -UserAccountControl $User.UserAccountControl if ($UserAccountControl -contains 'INTERDOMAIN_TRUST_ACCOUNT') { continue } if ($User.'msExchMailboxGuid') { $HasMailbox = $true } else { $HasMailbox = $false } $MyUser = [ordered] @{ UserPrincipalName = $User.UserPrincipalName SamAccountName = $User.SamAccountName Domain = ConvertFrom-DistinguishedName -DistinguishedName $User.DistinguishedName -ToDomainCN Enabled = $User.Enabled HasMailbox = $HasMailbox EmailAddress = $EmailAddress DateExpiry = $DateExpiry DaysToExpire = $DaysToExpire PasswordExpired = $User.PasswordExpired PasswordDays = $PasswordDays PasswordLastSet = $User.PasswordLastSet PasswordNotRequired = $User.PasswordNotRequired PasswordNeverExpires = $PasswordNeverExpires PasswordAtNextLogon = $PasswordAtNextLogon Manager = $Manager ManagerSamAccountName = $ManagerSamAccountName ManagerEmail = $ManagerEmail ManagerStatus = $ManagerStatus ManagerLastLogonDays = $ManagerLastLogonDays DisplayName = $User.DisplayName Name = $User.Name GivenName = $User.GivenName Surname = $User.Surname OrganizationalUnit = ConvertFrom-DistinguishedName -DistinguishedName $User.DistinguishedName -ToOrganizationalUnit MemberOf = $User.MemberOf DistinguishedName = $User.DistinguishedName ManagerDN = $User.Manager } foreach ($Property in $ConditionProperties) { $MyUser["$Property"] = $User.$Property } #[PSCustomObject] $MyUser $CachedUsers["$($User.DistinguishedName)"] = [PSCustomObject] $MyUser } if ($AsHashTable) { $CachedUsers } else { $CachedUsers.Values } } function Find-PasswordNotification { [CmdletBinding()] param( [Parameter(Mandatory)][string] $SearchPath, [switch] $Manager ) if ($SearchPath) { if (Test-Path -LiteralPath $SearchPath) { try { $SummarySearch = Import-Clixml -LiteralPath $SearchPath -ErrorAction Stop #$SummarySearch = Get-Content -LiteralPath $SearchPath -Raw | ConvertFrom-Json } catch { Write-Color -Text "[e]", " Couldn't load the file $SearchPath", ". Skipping...", $_.Exception.Message -Color White, Yellow, White, Yellow, White, Yellow, White } if ($SummarySearch -and $Manager) { $SummarySearch.EmailEscalations.Values } elseif ($SummarySearch -and $Manager -eq $false) { $SummarySearch.EmailSent.Values } } } } function Send-PasswordEmail { [CmdletBinding()] param( [scriptblock] $Template, [PSCustomObject] $User, [Array] $ManagedUsers, [Array] $ManagedUsersManagerNotCompliant, [Array] $SummaryUsersEmails, [Array] $SummaryManagersEmails, [Array] $SummaryEscalationEmails, [string] $TimeToProcess, [Array] $Attachments, [System.Collections.IDictionary] $EmailParameters, [string] $Subject ) if ($Template) { $SourceParameters = [ordered] @{ ManagerDisplayName = $User.DisplayName ManagerUsersTable = $ManagedUsers ManagerUsersTableManagerNotCompliant = $ManagedUsersManagerNotCompliant SummaryEscalationEmails = $SummaryEscalationEmails SummaryManagersEmails = $SummaryManagersEmails SummaryUsersEmails = $SummaryUsersEmails TimeToProcess = $TimeToProcess # Only works if User is set UserPrincipalName = $User.UserPrincipalName # : adm.pklys@ad.evotec.xyz SamAccountName = $User.SamAccountName # : adm.pklys Domain = $User.Domain # : ad.evotec.xyz Enabled = $User.Enabled EmailAddress = $User.EmailAddress # : DateExpiry = $User.DateExpiry # : DaysToExpire = $User.DaysToExpire # : PasswordExpired = $User.PasswordExpired # : False PasswordLastSet = $User.PasswordLastSet # : 05.09.2020 11:07:29 PasswordNotRequired = $User.PasswordNotRequired # : False PasswordNeverExpires = $User.PasswordNeverExpires # : True ManagerSamAccountName = $User.ManagerSamAccountName # : przemyslaw.klys ManagerEmail = $User.ManagerEmail # : przemyslaw.klys@evotec.pl ManagerStatus = $User.ManagerStatus # : Enabled ManagerLastLogonDays = $User.ManagerLastLogonDays # : 0 Manager = $User.Manager # : Przemysław Kłys DisplayName = $User.DisplayName # : Administrator Przemysław Kłys GivenName = $User.GivenName # : Administrator Przemysław Surname = $User.Surname # : Kłys OrganizationalUnit = $User.OrganizationalUnit # : OU=Special,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz MemberOf = $User.MemberOf # : {CN=GDS-TestGroup4,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz, CN=GDS-TestGroup2,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz, CN=Domain Admins,CN=Users,DC=ad,DC=evotec,DC=xyz} DistinguishedName = $User.DistinguishedName # : CN=Administrator Przemysław Kłys,OU=Special,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz ManagerDN = $User.ManagerDN # : CN=Przemysław Kłys,OU=Users,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz } $Body = EmailBody -EmailBody $Template -Parameter $SourceParameters # Below command would require to define variables as they are used in scriptblock #$EmailParameters.Subject = $ExecutionContext.InvokeCommand.ExpandString($Subject) # following replacement is a bit more cumbersome the the one above but a bit more secure and doesn't require creating 20+ unused variables $EmailParameters.Subject = Add-ParametersToString -String $Subject -Parameter $SourceParameters $EmailParameters.Body = $Body if ($Attachments) { $EmailParameters.Attachment = $Attachments } else { $EmailParameters.Attachment = @() } Send-EmailMessage @EmailParameters } } function Start-PasswordSolution { [CmdletBinding()] param( [Parameter(Mandatory)][System.Collections.IDictionary] $EmailParameters, [string] $OverwriteEmailProperty, [Parameter(Mandatory)][System.Collections.IDictionary] $UserSection, [Parameter(Mandatory)][System.Collections.IDictionary] $ManagerSection, [Parameter(Mandatory)][System.Collections.IDictionary] $SecuritySection, [Parameter(Mandatory)][System.Collections.IDictionary] $AdminSection, [Parameter(Mandatory)][Array] $Rules, [scriptblock] $TemplatePreExpiry, [string] $TemplatePreExpirySubject, [scriptblock] $TemplatePostExpiry, [string] $TemplatePostExpirySubject, [Parameter(Mandatory)][scriptblock] $TemplateManager, [Parameter(Mandatory)][string] $TemplateManagerSubject, [Parameter(Mandatory)][scriptblock] $TemplateSecurity, [Parameter(Mandatory)][string] $TemplateSecuritySubject, [Parameter(Mandatory)][scriptblock] $TemplateManagerNotCompliant, [Parameter(Mandatory)][string] $TemplateManagerNotCompliantSubject, [Parameter(Mandatory)][scriptblock] $TemplateAdmin, [Parameter(Mandatory)][string] $TemplateAdminSubject, [Parameter(Mandatory)][System.Collections.IDictionary] $Logging, [Parameter(Mandatory)][System.Collections.IDictionary] $HTMLOptions, [string] $FilePath, [string] $SearchPath ) $TimeStart = Start-TimeLog $Script:Reporting = [ordered] @{} $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Start-PasswordSolution' -RepositoryOwner 'evotecit' -RepositoryName 'PasswordSolution' $TodayDate = Get-Date $Today = Get-Date -Format "yyyy-MM-dd HH:mm:ss" if (-not $Logging) { $Logging = @{ ShowTime = $true LogFile = "" TimeFormat = "yyyy-MM-dd HH:mm:ss" } } $PSDefaultParameterValues = @{ "Write-Color:LogFile" = $Logging.LogFile "Write-Color:ShowTime" = $Logging.ShowTime "Write-Color:TimeFormat" = $Logging.TimeFormat } if ($Logging.LogFile) { $FolderPath = [io.path]::GetDirectoryName($Logging.LogFile) if (-not (Test-Path -LiteralPath $FolderPath)) { $null = New-Item -Path $FolderPath -ItemType Directory -Force } $CurrentLogs = Get-ChildItem -LiteralPath $FolderPath | Sort-Object -Property CreationTime -Descending | Select-Object -Skip $Logging.LogMaximum if ($CurrentLogs) { Write-Color -Text '[i] ', "Logs directory has more than ", $Logging.LogMaximum, " log files. Cleanup required..." -Color Yellow, DarkCyan, Red, DarkCyan foreach ($Log in $CurrentLogs) { try { Remove-Item -LiteralPath $Log.FullName -Confirm:$false Write-Color -Text '[+] ', "Deleted ", "$($Log.FullName)" -Color Yellow, White, Green } catch { Write-Color -Text '[-] ', "Couldn't delete log file $($Log.FullName). Error: ', "$($_.Exception.Message) -Color Yellow, White, Red } } } } if ($SearchPath) { if (Test-Path -LiteralPath $SearchPath) { try { Write-Color -Text "[i]", " Loading file ", $SearchPath -Color White, Yellow, White, Yellow, White, Yellow, White $SummarySearch = Import-Clixml -LiteralPath $SearchPath -ErrorAction Stop } catch { Write-Color -Text "[e]", " Couldn't load the file $SearchPath", ". Skipping...", $_.Exception.Message -Color White, Yellow, White, Yellow, White, Yellow, White } } } if (-not $SummarySearch) { $SummarySearch = [ordered] @{ EmailSent = [ordered] @{ } EmailManagers = [ordered] @{ } EmailEscalations = [ordered] @{ } } } $Summary = [ordered] @{} $Summary['Notify'] = [ordered] @{} $Summary['NotifyManager'] = [ordered] @{} $Summary['NotifySecurity'] = [ordered] @{} $Summary['Rules'] = [ordered] @{} Write-Color -Text "[i]", " Starting process to find expiring users" -Color Yellow, White, Green, White, Green, White, Green, White $CachedUsers = Find-Password -AsHashTable -OverwriteEmailProperty $OverwriteEmailProperty foreach ($Rule in $Rules) { # Go for each rule and check if the user is in any of those rules if ($Rule.Enable -eq $true) { Write-Color -Text "[i]", " Processing rule ", $Rule.Name, ' status: ', $Rule.Enable -Color Yellow, White, Green, White, Green, White, Green, White # Lets create summary for the rule if (-not $Summary['Rules'][$Rule.Name] ) { $Summary['Rules'][$Rule.Name] = [ordered] @{} } # this will make sure to expand array of multiple arrays of ints if provided # for example: (-150..-100),(-60..0), 1, 2, 3 $Rule.Reminders = $Rule.Reminders | ForEach-Object { $_ } foreach ($User in $CachedUsers.Values) { if ($User.Enabled -eq $false) { # We don't want to have disabled users continue } if ($Rule.ExcludeOU.Count -gt 0) { $FoundOU = $false foreach ($OU in $Rule.ExcludeOU) { if ($User.OrganizationalUnit -like $OU) { $FoundOU = $true break } } # if OU is found we need to exclude the user if ($FoundOU) { continue } } if ($Rule.IncludeOU.Count -gt 0) { # Rule defined that only user withi specific OU has to be found $FoundOU = $false foreach ($OU in $Rule.IncludeOU) { if ($User.OrganizationalUnit -like $OU) { $FoundOU = $true break } } if (-not $FoundOU) { continue } } if ($Rule.ExcludeGroup.Count -gt 0) { # Rule defined that only user withi specific group has to be found $FoundGroup = $false foreach ($Group in $Rule.ExcludeGroup) { if ($User.MemberOf -contains $Group) { $FoundGroup = $true break } } # If found, we need to skip user if ($FoundGroup) { continue } } if ($Rule.IncludeGroup.Count -gt 0) { # Rule defined that only user withi specific group has to be found $FoundGroup = $false foreach ($Group in $Rule.IncludeGroup) { if ($User.MemberOf -contains $Group) { $FoundGroup = $true break } } if (-not $FoundGroup) { continue } } if ($Rule.IncludeName.Count -gt 0) { $IncludeName = $false foreach ($Name in $Rule.IncludeName) { foreach ($Property in $Rule.IncludeNameProperties) { if ($User.$Property -like $Name) { $IncludeName = $true break } } if ($IncludeName) { break } } if (-not $IncludeName) { continue } } if ($Summary['Notify'][$User.DistinguishedName] -and $Summary['Notify'][$User.DistinguishedName].ProcessManagersOnly -ne $true) { # User already exists in the notifications - rules are overlapping, we only take the first one # We also check for ProcessManagersOnly because we don't want first rule to ignore any other rules for users continue } if ($Rule.IncludePasswordNeverExpires -and $Rule.IncludeExpiring) { if ($User.PasswordNeverExpires -eq $true) { $DaysToPasswordExpiry = $Rule.PasswordNeverExpiresDays - $User.PasswordDays $User.DaysToExpire = $DaysToPasswordExpiry } } elseif ($Rule.IncludeExpiring) { if ($User.PasswordNeverExpires -eq $true) { # we skip those that never expire continue } } elseif ($Rule.IncludePasswordNeverExpires) { if ($User.PasswordNeverExpires -eq $true) { $DaysToPasswordExpiry = $Rule.PasswordNeverExpiresDays - $User.PasswordDays $User.DaysToExpire = $DaysToPasswordExpiry } else { # we skip users who expire continue } } else { Write-Color -Text "[i]", " Processing rule ", $Rule.Name, " doesn't include IncludePasswordNeverExpires nor IncludeExpiring so skipping." -Color Yellow, White, Green, White, Green, White, Green, White continue } # Lets find users that expire, and match our rule if ($User.DaysToExpire -in $Rule.Reminders) { # check if we need to notify user or just manager if (-not $Rule.ProcessManagersOnly) { if ($Logging.NotifyOnUserMatchingRule) { Write-Color -Text "[i]", " User ", $User.DisplayName, " (", $User.UserPrincipalName, ")", " days to expire: ", $User.DaysToExpire -Color Yellow, White, Yellow, White, Yellow, White, White, Blue } $Summary['Notify'][$User.DistinguishedName] = [ordered] @{ User = $User Rule = $Rule ProcessManagersOnly = $Rule.ProcessManagersOnly } $Summary['Rules'][$Rule.Name][$User.DistinguishedName] = [ordered] @{ User = $User Rule = $Rule ProcessManagersOnly = $Rule.ProcessManagersOnly } } } if ($Rule.SendToManager) { if ($Rule.SendToManager.Manager -and $Rule.SendToManager.Manager.Enable -eq $true -and $User.ManagerStatus -eq 'Enabled' -and $User.ManagerEmail -like "*@*") { $SendToManager = $true # Manager is enabled and has an email, this is standard situation for manager in AD # But before we go and do that, maybe user wants to send emails to managers if those users are in specific group or OU if ($Rule.SendToManager.Manager.IncludeOU.Count -gt 0) { # Rule defined that only user withi specific OU has to be found $FoundOU = $false foreach ($OU in $Rule.SendToManager.Manager.IncludeOU) { if ($User.OrganizationalUnit -like $OU) { $FoundOU = $true break } } if (-not $FoundOU) { $SendToManager = $false } } if ($SendToManager -and $Rule.SendToManager.Manager.ExcludeOU.Count -gt 0) { $FoundOU = $false foreach ($OU in $Rule.SendToManager.Manager.ExcludeOU) { if ($User.OrganizationalUnit -like $OU) { $FoundOU = $true break } } # if OU is found we need to exclude the user if ($FoundOU) { $SendToManager = $false } } if ($SendToManager -and $Rule.SendToManager.Manager.ExcludeGroup.Count -gt 0) { # Rule defined that only user withi specific group has to be found $FoundGroup = $false foreach ($Group in $Rule.SendToManager.Manager.ExcludeGroup) { if ($User.MemberOf -contains $Group) { $FoundGroup = $true break } } # if Group found, we need to skip this user if ($FoundGroup) { $SendToManager = $false } } if ($SendToManager -and $Rule.SendToManager.Manager.IncludeGroup.Count -gt 0) { # Rule defined that only user withi specific group has to be found $FoundGroup = $false foreach ($Group in $Rule.SendToManager.Manager.IncludeGroup) { if ($User.MemberOf -contains $Group) { $FoundGroup = $true break } } if (-not $FoundGroup) { $SendToManager = $false } } if ($SendToManager) { $SendToManager = $false if ($Rule.SendToManager.Manager.Reminders.Default.Enable -eq $true -and $null -eq $Rule.SendToManager.Manager.Reminders.Default.Reminder -and $User.DaysToExpire -in $Rule.Reminders) { # Use default reminder as per user, not per manager $SendToManager = $true } elseif ($Rule.SendToManager.Manager.Reminders.Default.Enable -eq $true -and $User.DaysToExpire -in $Rule.SendToManager.Manager.Reminders.Default.Reminder) { # User manager reminder as per manager config $SendToManager = $true } if (-not $SendToManager -and $Rule.SendToManager.Manager.Reminders.OnDay -and $Rule.SendToManager.Manager.Reminders.OnDay.Enable -eq $true) { foreach ($Day in $Rule.SendToManager.Manager.Reminders.OnDay.Days) { if ($Day -eq "$($TodayDate.DayOfWeek)") { if ($Rule.SendToManager.Manager.Reminders.OnDay.ComparisonType -eq 'lt') { if ($User.DaysToExpire -lt $Rule.SendToManager.Manager.Reminders.OnDay.Reminder) { $SendToManager = $true break } } elseif ($Rule.SendToManager.Manager.Reminders.OnDay.ComparisonType -eq 'gt') { if ($User.DaysToExpire -gt $Rule.SendToManager.Manager.Reminders.OnDay.Reminder) { $SendToManager = $true break } } elseif ($Rule.SendToManager.Manager.Reminders.OnDay.ComparisonType -eq 'eq') { if ($User.DaysToExpire -eq $Rule.SendToManager.Manager.Reminders.OnDay.Reminder) { $SendToManager = $true break } } elseif ($Rule.SendtoManager.Manager.Reminders.OnDay.ComparisonType -eq 'in') { if ($User.DaysToExpire -in $Rule.SendToManager.Manager.Reminders.OnDay.Reminder) { $SendToManager = $true break } } } } } if (-not $SendToManager -and $Rule.SendToManager.Manager.Reminders.OnDayOfMonth -and $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Enable -eq $true) { foreach ($Day in $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Days) { if ($Day -eq $TodayDate.Day) { if ($Rule.SendToManager.Manager.Reminders.OnDayOfMonth.ComparisonType -eq 'lt') { if ($User.DaysToExpire -lt $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder) { $SendToManager = $true break } } elseif ($Rule.SendToManager.Manager.Reminders.OnDayOfMonth.ComparisonType -eq 'gt') { if ($User.DaysToExpire -gt $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder) { $SendToManager = $true break } } elseif ($Rule.SendToManager.Manager.Reminders.OnDayOfMonth.ComparisonType -eq 'eq') { if ($User.DaysToExpire -eq $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder) { $SendToManager = $true break } } elseif ($Rule.SendtoManager.Manager.Reminders.OnDayOfMonth.ComparisonType -eq 'in') { if ($User.DaysToExpire -in $Rule.SendToManager.Manager.Reminders.OnDayOfMonth.Reminder) { $SendToManager = $true break } } } } } if ($SendToManager) { $Splat = [ordered] @{ SummaryDictionary = $Summary['NotifyManager'] Type = 'ManagerDefault' ManagerType = 'Ok' Key = $User.ManagerDN User = $User Rule = $Rule } Add-ManagerInformation @Splat } } } } # Lets find users that have no manager, manager is not enabled or manager has no email if ($Rule.SendToManager -and $Rule.SendToManager.ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.Enable -eq $true -and $Rule.SendToManager.ManagerNotCompliant.Manager) { # Not compliant (missing, disabled, no email), covers all the below options if ($Rule.SendToManager.ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.Enable -and $Rule.SendToManager.ManagerNotCompliant.Manager) { $ManagerNotCompliant = $true # But before we go and do that, maybe user wants to send emails to managers only if those users are in specific group or OU if ($Rule.SendToManager.ManagerNotCompliant.IncludeOU.Count -gt 0) { # Rule defined that only user withi specific OU has to be found $FoundOU = $false foreach ($OU in $Rule.SendToManager.ManagerNotCompliant.IncludeOU) { if ($User.OrganizationalUnit -like $OU) { $FoundOU = $true break } } if (-not $FoundOU) { $ManagerNotCompliant = $false } } if ($ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.ExcludeOU.Count -gt 0) { $FoundOU = $false foreach ($OU in $Rule.SendToManager.ManagerNotCompliant.ExcludeOU) { if ($User.OrganizationalUnit -like $OU) { $FoundOU = $true break } } # if OU is found we need to exclude the user if ($FoundOU) { $ManagerNotCompliant = $false } } if ($ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.ExcludeGroup.Count -gt 0) { # Rule defined that only user withi specific group has to be found $FoundGroup = $false foreach ($Group in $Rule.SendToManager.ManagerNotCompliant.ExcludeGroup) { if ($User.MemberOf -contains $Group) { $FoundGroup = $true break } } # if Group found, we need to skip this user if ($FoundGroup) { $ManagerNotCompliant = $false } } if ($ManagerNotCompliant -and $Rule.SendToManager.ManagerNotCompliant.IncludeGroup.Count -gt 0) { # Rule defined that only user withi specific group has to be found $FoundGroup = $false foreach ($Group in $Rule.SendToManager.ManagerNotCompliant.IncludeGroup) { if ($User.MemberOf -contains $Group) { $FoundGroup = $true break } } if (-not $FoundGroup) { $ManagerNotCompliant = $false } } if ($Rule.SendToManager.ManagerNotCompliant.Reminders) { $ManagerNotCompliant = $false if ($Rule.SendToManager.ManagerNotCompliant.Reminders.Default -and $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Enable -eq $true) { $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Reminder = $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Reminder | ForEach-Object { $_ } if ($User.DaysToExpire -in $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Reminder) { $ManagerNotCompliant = $true } } if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay -and $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Enable -eq $true) { foreach ($Day in $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Days) { if ($Day -eq "$($TodayDate.DayOfWeek)") { if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType -eq 'lt') { if ($User.DaysToExpire -lt $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder) { $ManagerNotCompliant = $true break } } elseif ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType -eq 'gt') { if ($User.DaysToExpire -gt $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder) { $ManagerNotCompliant = $true break } } elseif ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType -eq 'eq') { if ($User.DaysToExpire -eq $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder) { $ManagerNotCompliant = $true break } } elseif ($Rule.SendtoManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType -eq 'in') { if ($User.DaysToExpire -in $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder) { $ManagerNotCompliant = $true break } } } } } if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth -and $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Enable -eq $true) { foreach ($Day in $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Days) { if ($Day -eq $TodayDate.Day) { if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType -eq 'lt') { if ($User.DaysToExpire -lt $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder) { $ManagerNotCompliant = $true break } } elseif ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType -eq 'gt') { if ($User.DaysToExpire -gt $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder) { $ManagerNotCompliant = $true break } } elseif ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType -eq 'eq') { if ($User.DaysToExpire -eq $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder) { $ManagerNotCompliant = $true break } } elseif ($Rule.SendtoManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType -eq 'in') { if ($User.DaysToExpire -in $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder) { $ManagerNotCompliant = $true break } } } } } } if ($ManagerNotCompliant -eq $true) { if ($Rule.SendToManager.ManagerNotCompliant.MissingEmail -and $User.ManagerStatus -in 'Enabled, bad email', 'No email') { # Manager is enabled but missing email $Splat = [ordered] @{ SummaryDictionary = $Summary['NotifyManager'] Type = 'ManagerNotCompliant' ManagerType = if ($User.ManagerStatus -eq 'Enabled, bad email') { 'Manager has bad email' } else { 'Manager has no email' } Key = $Rule.SendToManager.ManagerNotCompliant.Manager User = $User Rule = $Rule } Add-ManagerInformation @Splat } elseif ($Rule.SendToManager.ManagerNotCompliant.Disabled -and $User.ManagerStatus -eq 'Disabled') { # Manager is disabled, regardless if he/she has email $Splat = [ordered] @{ SummaryDictionary = $Summary['NotifyManager'] Type = 'ManagerNotCompliant' ManagerType = 'Manager disabled' Key = $Rule.SendToManager.ManagerNotCompliant.Manager User = $User Rule = $Rule } Add-ManagerInformation @Splat } elseif ($Rule.SendToManager.ManagerNotCompliant.LastLogon -and $User.ManagerLastLogonDays -ge $Rule.SendToManager.ManagerNotCompliant.LastLogonDays) { # Manager Last Logon over X days $Splat = [ordered] @{ SummaryDictionary = $Summary['NotifyManager'] Type = 'ManagerNotCompliant' ManagerType = 'Manager not logging in' Key = $Rule.SendToManager.ManagerNotCompliant.Manager User = $User Rule = $Rule } Add-ManagerInformation @Splat } elseif ($Rule.SendToManager.ManagerNotCompliant.Missing -and $User.ManagerStatus -eq 'Missing') { # Manager is missing $Splat = [ordered] @{ SummaryDictionary = $Summary['NotifyManager'] Type = 'ManagerNotCompliant' ManagerType = 'Manager not set' Key = $Rule.SendToManager.ManagerNotCompliant.Manager User = $User Rule = $Rule } Add-ManagerInformation @Splat } } } } # Lets find users that require escalation if ($Rule.SendToManager -and $Rule.SendToManager.SecurityEscalation -and $Rule.SendToManager.SecurityEscalation.Enable -eq $true -and $Rule.SendToManager.SecurityEscalation.Manager) { $SecurityEscalation = $true if ($Rule.SendToManager.SecurityEscalation.IncludeOU.Count -gt 0) { # Rule defined that only user withi specific OU has to be found $FoundOU = $false foreach ($OU in $Rule.SendToManager.SecurityEscalation.IncludeOU) { if ($User.OrganizationalUnit -like $OU) { $FoundOU = $true break } } if (-not $FoundOU) { $SecurityEscalation = $false } } if ($SecurityEscalation -and $Rule.SendToManager.SecurityEscalation.ExcludeOU.Count -gt 0) { $FoundOU = $false foreach ($OU in $Rule.SendToManager.SecurityEscalation.ExcludeOU) { if ($User.OrganizationalUnit -like $OU) { $FoundOU = $true break } } # if OU is found we need to exclude the user if ($FoundOU) { $SecurityEscalation = $false } } if ($SecurityEscalation -and $Rule.SendToManager.SecurityEscalation.ExcludeGroup.Count -gt 0) { # Rule defined that only user withi specific group has to be found $FoundGroup = $false foreach ($Group in $Rule.SendToManager.SecurityEscalation.ExcludeGroup) { if ($User.MemberOf -contains $Group) { $FoundGroup = $true break } } # if Group found, we need to skip this user if ($FoundGroup) { $SecurityEscalation = $false } } if ($SecurityEscalation -and $Rule.SendToManager.SecurityEscalation.IncludeGroup.Count -gt 0) { # Rule defined that only user withi specific group has to be found $FoundGroup = $false foreach ($Group in $Rule.SendToManager.SecurityEscalation.IncludeGroup) { if ($User.MemberOf -contains $Group) { $FoundGroup = $true break } } if (-not $FoundGroup) { $SecurityEscalation = $false } } if ($Rule.SendToManager.SecurityEscalation.Reminders) { $SecurityEscalation = $false if ($Rule.SendToManager.SecurityEscalation.Reminders.Default -and $Rule.SendToManager.SecurityEscalation.Reminders.Default.Enable -eq $true) { $Rule.SendToManager.SecurityEscalation.Reminders.Default.Reminder = $Rule.SendToManager.SecurityEscalation.Reminders.Default.Reminder | ForEach-Object { $_ } if ($User.DaysToExpire -in $Rule.SendToManager.SecurityEscalation.Reminders.Default.Reminder) { $SecurityEscalation = $true } } if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay -and $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Enable -eq $true) { foreach ($Day in $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Days) { if ($Day -eq "$($TodayDate.DayOfWeek)") { if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.ComparisonType -eq 'lt') { if ($User.DaysToExpire -lt $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder) { $SecurityEscalation = $true break } } elseif ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.ComparisonType -eq 'gt') { if ($User.DaysToExpire -gt $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder) { $SecurityEscalation = $true break } } elseif ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.ComparisonType -eq 'eq') { if ($User.DaysToExpire -eq $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder) { $SecurityEscalation = $true break } } elseif ($Rule.SendtoManager.SecurityEscalation.Reminders.OnDay.ComparisonType -eq 'in') { if ($User.DaysToExpire -in $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder) { $SecurityEscalation = $true break } } } } } if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth -and $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Enable -eq $true) { foreach ($Day in $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Days) { if ($Day -eq $TodayDate.Day) { if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType -eq 'lt') { if ($User.DaysToExpire -lt $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder) { $SecurityEscalation = $true break } } elseif ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType -eq 'gt') { if ($User.DaysToExpire -gt $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder) { $SecurityEscalation = $true break } } elseif ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType -eq 'eq') { if ($User.DaysToExpire -eq $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder) { $SecurityEscalation = $true break } } elseif ($Rule.SendtoManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType -eq 'in') { if ($User.DaysToExpire -in $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder) { $SecurityEscalation = $true break } } } } } } if ($SecurityEscalation) { $Splat = [ordered] @{ SummaryDictionary = $Summary['NotifySecurity'] Type = 'Security' ManagerType = 'Escalation' Key = $Rule.SendToManager.SecurityEscalation.Manager User = $User Rule = $Rule } Add-ManagerInformation @Splat } } } } else { Write-Color -Text "[i]", " Processing rule ", $Rule.Name, ' status: ', $Rule.Enable -Color Red, White, Red, White, Red, White, Red, White } } if ($UserSection.Enable) { Write-Color -Text "[i] Sending notifications to users " -Color White, Yellow, White, Yellow, White, Yellow, White $CountUsers = 0 [Array] $SummaryUsersEmails = foreach ($Notify in $Summary['Notify'].Values) { $CountUsers++ $User = $Notify.User $Rule = $Notify.Rule # This shouldn't happen, but just in case, to be removed later on, as ProcessManagerOnly is skipping earlier on if ($Notify.ProcessManagersOnly -eq $true) { if ($Logging.NotifyOnSkipUserManagerOnly) { Write-Color -Text "[i]", " Skipping User (Manager Only - $($Rule.Name)) ", $User.DisplayName, " (", $User.UserPrincipalName, ")", " days to expire: ", $User.DaysToExpire -Color Yellow, White, Magenta, White, Magenta, White, White, Blue } continue } $EmailSplat = [ordered] @{} if ($Notify.User.DaysToExpire -ge 0) { if ($Notify.Rule.TemplatePreExpiry) { # User uses template per rule $EmailSplat.Template = $Notify.Rule.TemplatePreExpiry } elseif ($TemplatePreExpiry) { # User uses global template $EmailSplat.Template = $TemplatePreExpiry } else { # User uses built-in template $EmailSplat.Template = { } } if ($Notify.Rule.TemplatePreExpirySubject) { $EmailSplat.Subject = $Notify.Rule.TemplatePreExpirySubject } elseif ($TemplatePreExpirySubject) { $EmailSplat.Subject = $TemplatePreExpirySubject } else { $EmailSplat.Subject = '[Password] Your password will expire on $DateExpiry ($TimeToExpire days)' } } else { if ($Notify.Rule.TemplatePostExpiry) { $EmailSplat.Template = $Notify.Rule.TemplatePostExpiry } elseif ($TemplatePostExpiry) { $EmailSplat.Template = $TemplatePostExpiry } else { $EmailSplat.Template = { } } if ($Notify.Rule.TemplatePostExpirySubject) { $EmailSplat.Subject = $Notify.Rule.TemplatePostExpirySubject } elseif ($TemplatePostExpirySubject) { $EmailSplat.Subject = $TemplatePostExpirySubject } else { $EmailSplat.Subject = '[Password] Your password expired on $DateExpiry ($TimeToExpire days ago)' } } $EmailSplat.User = $Notify.User $EmailSplat.EmailParameters = $EmailParameters if ($UserSection.SendToDefaultEmail -ne $true) { $EmailSplat.EmailParameters.To = $Notify.User.EmailAddress } else { $EmailSplat.EmailParameters.To = $UserSection.DefaultEmail } if ($Notify.User.EmailAddress -like "*@*") { # Regardless if we send email to default email or to user, if user doesn't have email address we shouldn't send an email $EmailResult = Send-PasswordEmail @EmailSplat [PSCustomObject] @{ UserPrincipalName = $EmailSplat.User.UserPrincipalName SamAccountName = $EmailSplat.User.SamAccountName Domain = $EmailSplat.User.Domain Rule = $Notify.Rule.Name Status = $EmailResult.Status StatusWhen = Get-Date -Format "yyyy-MM-dd HH:mm:ss" StatusError = $EmailResult.Error SentTo = $EmailResult.SentTo DateExpiry = $EmailSplat.User.DateExpiry DaysToExpire = $EmailSplat.User.DaysToExpire PasswordExpired = $EmailSplat.User.PasswordExpired PasswordNeverExpires = $EmailSplat.User.PasswordNeverExpires PasswordLastSet = $EmailSplat.User.PasswordLastSet } } else { # Email not sent $EmailResult = @{ Status = $false Error = 'No email address for user' SentTo = '' } [PSCustomObject] @{ UserPrincipalName = $EmailSplat.User.UserPrincipalName SamAccountName = $EmailSplat.User.SamAccountName Domain = $EmailSplat.User.Domain Rule = $Notify.Rule.Name Status = $EmailResult.Status StatusWhen = Get-Date -Format "yyyy-MM-dd HH:mm:ss" StatusError = $EmailResult.Error SentTo = $EmailResult.SentTo DateExpiry = $EmailSplat.User.DateExpiry DaysToExpire = $EmailSplat.User.DaysToExpire PasswordExpired = $EmailSplat.User.PasswordExpired PasswordNeverExpires = $EmailSplat.User.PasswordNeverExpires PasswordLastSet = $EmailSplat.User.PasswordLastSet } } if ($Logging.NotifyOnUserSend) { Write-Color -Text "[i]", " Sending notifications to users ", $Notify.User.DisplayName, " (", $Notify.User.EmailAddress, ")", " status: ", $EmailResult.Status, " sent to: ", $EmailResult.SentTo, ", details: ", $EmailResult.Error -Color Yellow, White, Yellow, White, Yellow, White, White, Blue, White, Blue } if ($UserSection.SendCountMaximum -gt 0) { if ($UserSection.SendCountMaximum -le $CountUsers) { Write-Color -Text "[i]", " Send count maximum reached. There may be more accounts that match the rule." -Color Red, DarkMagenta break } } } Write-Color -Text "[i] Sending notifications to users (sent: ", $SummaryUsersEmails.Count, " out of ", $Summary['Notify'].Values.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White } else { Write-Color -Text "[i] Sending notifications to users is ", "disabled!" -Color White, Yellow, DarkMagenta } if ($ManagerSection.Enable) { Write-Color -Text "[i] Sending notifications to managers " -Color White, Yellow, White, Yellow, White, Yellow, White $CountManagers = 0 [Array] $SummaryManagersEmails = foreach ($Manager in $Summary['NotifyManager'].Keys) { $CountManagers++ if ($CachedUsers[$Manager]) { # This user is "findable" in AD $ManagerUser = $CachedUsers[$Manager] } else { # This user is provided by user in config file $ManagerUser = $Summary['NotifyManager'][$Manager]['Manager'] } [Array] $ManagedUsers = $Summary['NotifyManager'][$Manager]['ManagerDefault'].Values.Output [Array] $ManagedUsersManagerNotCompliant = $Summary['NotifyManager'][$Manager]['ManagerNotCompliant'].Values.Output $EmailSplat = [ordered] @{} if ($Summary['NotifyManager'][$Manager].ManagerDefault.Count -gt 0) { if ($TemplateManager) { # User uses global template $EmailSplat.Template = $TemplateManager } else { # User uses built-in template $EmailSplat.Template = { } } if ($TemplateManagerSubject) { $EmailSplat.Subject = $TemplateManagerSubject } else { $EmailSplat.Subject = "[Password Expiring] Dear Manager - Your accounts are expiring!" } } elseif ($Summary['NotifyManager'][$Manager].ManagerNotCompliant.Count -gt 0) { if ($TemplateManagerNotCompliant) { # User uses global template $EmailSplat.Template = $TemplateManagerNotCompliant } else { # User uses built-in template $EmailSplat.Template = { } } if ($TemplateManagerNotCompliantSubject) { $EmailSplat.Subject = $TemplateManagerNotCompliantSubject } else { $EmailSplat.Subject = "[Password Escalation] Accounts are expiring with non-compliant manager" } } $EmailSplat.User = $ManagerUser $EmailSplat.ManagedUsers = $ManagedUsers $EmailSplat.ManagedUsersManagerNotCompliant = $ManagedUsersManagerNotCompliant $EmailSplat.EmailParameters = $EmailParameters if ($ManagerSection.SendToDefaultEmail -ne $true) { $EmailSplat.EmailParameters.To = $ManagerUser.EmailAddress } else { $EmailSplat.EmailParameters.To = $ManagerSection.DefaultEmail } if ($Logging.NotifyOnManagerSend) { Write-Color -Text "[i] Sending notifications to managers ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow } $EmailResult = Send-PasswordEmail @EmailSplat if ($Logging.NotifyOnManagerSend) { Write-Color -Text "[r] Sending notifications to managers ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ") (status: ", $EmailResult.Status, " sent to: ", $EmailResult.SentTo, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow } [PSCustomObject] @{ DisplayName = $ManagerUser.DisplayName SamAccountName = $ManagerUser.SamAccountName Domain = $ManagerUser.Domain Status = $EmailResult.Status StatusWhen = Get-Date -Format "yyyy-MM-dd HH:mm:ss" SentTo = $EmailResult.SentTo StatusError = $EmailResult.Error Accounts = $ManagedUsers.SamAccountName AccountsCount = $ManagedUsers.Count Template = 'Unknown' ManagerNotCompliant = $ManagedUsersManagerNotCompliant.SamAccountName ManagerNotCompliantCount = $ManagedUsersManagerNotCompliant.Count #ManagerDisabled = $ManagedUsersManagerDisabled.SamAccountName #ManagerDisabledCount = $ManagedUsersManagerDisabled.Count #ManagerMissing = $ManagedUsersManagerMissing.SamAccountName #ManagerMissingCount = $ManagedUsersManagerMissing.Count #ManagerMissingEmail = $ManagedUsersManagerMissingEmail.SamAccountName #ManagerMissingEmailCount = $ManagedUsersManagerMissingEmail.Count } if ($ManagerSection.SendCountMaximum -gt 0) { if ($ManagerSection.SendCountMaximum -le $CountManagers) { Write-Color -Text "[i]", " Send count maximum reached. There may be more managers that match the rule." -Color Red, DarkMagenta break } } } Write-Color -Text "[i] Sending notifications to managers (sent: ", $SummaryManagersEmails.Count, " out of ", $Summary['NotifyManager'].Values.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White #Write-Color -Text "[i] Sending notifications to managers (sent: ", $SummaryManagersEmails.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White } else { Write-Color -Text "[i] Sending notifications to managers is ", "disabled!" -Color White, Yellow, DarkMagenta } if ($SecuritySection.Enable) { Write-Color -Text "[i] Sending notifications to security " -Color White, Yellow, White, Yellow, White, Yellow, White $CountSecurity = 0 [Array] $SummaryEscalationEmails = foreach ($Manager in $Summary['NotifySecurity'].Keys) { $CountSecurity++ # This user is provided by user in config file $ManagerUser = $Summary['NotifySecurity'][$Manager]['Manager'] [Array] $ManagedUsers = $Summary['NotifySecurity'][$Manager]['Security'].Values.Output $EmailSplat = [ordered] @{} if ($Summary['NotifySecurity'][$Manager].Security.Count -gt 0) { # User uses global template $EmailSplat.Template = $TemplateSecurity if ($TemplateSecuritySubject) { $EmailSplat.Subject = $TemplateSecuritySubject } else { $EmailSplat.Subject = "[Password Expiring] Dear Security - Accounts expired" } } else { continue } if ($SecuritySection.AttachCSV -and $ManagedUsers.Count -gt 0) { $ManagedUsers | Export-Csv -LiteralPath $Env:TEMP\ManagedUsersSecurity.csv -NoTypeInformation -Force -Encoding UTF8 -ErrorAction Stop $EmailSplat.Attachments = @( if (Test-Path -LiteralPath "$Env:TEMP\ManagedUsersSecurity.csv") { "$Env:TEMP\ManagedUsersSecurity.csv" } ) } $EmailSplat.User = $ManagerUser $EmailSplat.ManagedUsers = $ManagedUsers | Select-Object -Property 'Status', 'DisplayName', 'Enabled', 'SamAccountName', 'Domain', 'DateExpiry', 'DaysToExpire', 'PasswordLastSet', 'PasswordExpired' #$EmailSplat.ManagedUsersManagerNotCompliant = $ManagedUsersManagerNotCompliant #$EmailSplat.ManagedUsersManagerDisabled = $ManagedUsersManagerDisabled #$EmailSplat.ManagedUsersManagerMissing = $ManagedUsersManagerMissing #$EmailSplat.ManagedUsersManagerMissingEmail = $ManagedUsersManagerMissingEmail $EmailSplat.EmailParameters = $EmailParameters if ($SecuritySection.SendToDefaultEmail -ne $true) { $EmailSplat.EmailParameters.To = $ManagerUser.EmailAddress } else { $EmailSplat.EmailParameters.To = $SecuritySection.DefaultEmail } if ($Logging.NotifyOnSecuritySend) { Write-Color -Text "[i] Sending notifications to security ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow } $EmailResult = Send-PasswordEmail @EmailSplat if ($Logging.NotifyOnSecuritySend) { Write-Color -Text "[r] Sending notifications to security ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ") (status: ", $EmailResult.Status, " sent to: ", $EmailResult.SentTo, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow } [PSCustomObject] @{ DisplayName = $ManagerUser.DisplayName SamAccountName = $ManagerUser.SamAccountName Domain = $ManagerUser.Domain Status = $EmailResult.Status StatusWhen = Get-Date -Format "yyyy-MM-dd HH:mm:ss" SentTo = $EmailResult.SentTo StatusError = $EmailResult.Error Accounts = $ManagedUsers.SamAccountName AccountsCount = $ManagedUsers.Count Template = 'Unknown' # ManagerNotCompliant = $ManagedUsersManagerNotCompliant.SamAccountName # ManagerNotCompliantCount = $ManagedUsersManagerNotCompliant.Count #ManagerDisabled = $ManagedUsersManagerDisabled.SamAccountName #ManagerDisabledCount = $ManagedUsersManagerDisabled.Count #ManagerMissing = $ManagedUsersManagerMissing.SamAccountName #ManagerMissingCount = $ManagedUsersManagerMissing.Count #ManagerMissingEmail = $ManagedUsersManagerMissingEmail.SamAccountName #ManagerMissingEmailCount = $ManagedUsersManagerMissingEmail.Count } if ($SecuritySection.SendCountMaximum -gt 0) { if ($SecuritySection.SendCountMaximum -le $CountSecurity) { Write-Color -Text "[i]", " Send count maximum reached. There may be more managers that match the rule." -Color Red, DarkMagenta break } } } Write-Color -Text "[i] Sending notifications to security (sent: ", $SummaryEscalationEmails.Count, " out of ", $Summary['NotifySecurity'].Values.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White } else { Write-Color -Text "[i] Sending notifications to security is ", "disabled!" -Color White, Yellow, DarkMagenta } if ($HTMLOptions.DisableWarnings -eq $true) { $WarningAction = 'SilentlyContinue' } else { $WarningAction = 'Continue' } $TimeEnd = Stop-TimeLog -Time $TimeStart -Option OneLiner if ($AdminSection.Enable) { Write-Color -Text "[i] Sending summary information " -Color White, Yellow, White, Yellow, White, Yellow, White $CountSecurity = 0 [Array] $SummaryEmail = @( $CountSecurity++ # This user is provided by user in config file $ManagerUser = $AdminSection.Manager $EmailSplat = [ordered] @{} # User uses global template $EmailSplat.Template = $TemplateAdmin $EmailSplat.Subject = $TemplateAdminSubject $EmailSplat.User = $ManagerUser $EmailSplat.SummaryUsersEmails = $SummaryUsersEmails $EmailSplat.SummaryManagersEmails = $SummaryManagersEmails $EmailSplat.SummaryEscalationEmails = $SummaryEscalationEmails $EmailSplat.TimeToProcess = $TimeEnd $EmailSplat.EmailParameters = $EmailParameters $EmailSplat.EmailParameters.To = $AdminSection.Manager.EmailAddress Write-Color -Text "[i] Sending summary information ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow $EmailResult = Send-PasswordEmail @EmailSplat Write-Color -Text "[r] Sending summary information ", $ManagerUser.DisplayName, " (", $ManagerUser.EmailAddress, ") (SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail, ") (status: ", $EmailResult.Status, " sent to: ", $EmailResult.SentTo, ")" -Color White, Yellow, White, Yellow, White, Yellow, White, Yellow, White, Yellow [PSCustomObject] @{ DisplayName = $ManagerUser.DisplayName SamAccountName = $ManagerUser.SamAccountName Domain = $ManagerUser.Domain Status = $EmailResult.Status StatusWhen = Get-Date -Format "yyyy-MM-dd HH:mm:ss" SentTo = $EmailResult.SentTo StatusError = $EmailResult.Error Template = 'Unknown' } ) Write-Color -Text "[i] Sending summary information (sent: ", $SummaryEmail.Count, ")" -Color White, Yellow, White, Yellow, White, Yellow, White } else { Write-Color -Text "[i] Sending summary information is ", "disabled!" -Color White, Yellow, DarkMagenta } if ($HTMLOptions.Enable) { Write-Color -Text "[i]", " Generating HTML report " -Color White, Yellow, Green # Create report New-HTML { New-HTMLHeader { New-HTMLSection -Invisible { New-HTMLSection { New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue } -JustifyContent flex-start -Invisible New-HTMLSection { New-HTMLText -Text "Password Solution - $($Script:Reporting['Version'])" -Color Blue } -JustifyContent flex-end -Invisible } } New-TableOption -DataStore JavaScript -ArrayJoin -BoolAsString if ($HTMLOptions.ShowConfiguration) { New-HTMLTab -Name "About" { New-HTMLTab -Name "Configuration" { New-HTMLSection -Invisible { New-HTMLSection -HeaderText "Email Configuration" { New-HTMLList { foreach ($Key in $EmailParameters.Keys) { if ($Key -ne 'Password') { New-HTMLListItem -Text $Key, ": ", $EmailParameters[$Key] -FontWeight normal, normal, bold } else { New-HTMLListItem -Text $Key, ": ", "REDACTED" -FontWeight normal, normal, bold } } } } New-HTMLSection -HeaderText "Logging" { New-HTMLList { foreach ($Key in $Logging.Keys) { if ($Key -ne 'Password') { New-HTMLListItem -Text $Key, ": ", $Logging[$Key] -FontWeight normal, normal, bold } else { New-HTMLListItem -Text $Key, ": ", "REDACTED" -FontWeight normal, normal, bold } } } } New-HTMLSection -HeaderText "HTMLOptions" { New-HTMLList { foreach ($Key in $HTMLOptions.Keys) { if ($Key -ne 'Password') { New-HTMLListItem -Text $Key, ": ", $HTMLOptions[$Key] -FontWeight normal, normal, bold } else { New-HTMLListItem -Text $Key, ": ", "REDACTED" -FontWeight normal, normal, bold } } } } New-HTMLSection -HeaderText "Other" { New-HTMLList { New-HTMLListItem -Text 'FilePath', ": ", $FilePath -FontWeight normal, normal, bold New-HTMLListItem -Text 'SearchPath', ": ", $SearchPath -FontWeight normal, normal, bold } } } New-HTMLSection -Invisible { New-HTMLSection -HeaderText "User Section" { New-HTMLList { New-HTMLListItem -Text "Enabled: ", $UserSection.Enable -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "SendCountMaximum: ", $UserSection.SendCountMaximum -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "SendToDefaultEmail: ", $UserSection.SendToDefaultEmail -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "DefaultEmail: ", ($UserSection.DefaultEmail -join ", ") -FontWeight normal, bold -TextDecoration underline, none } } New-HTMLSection -HeaderText "Manager Section" { New-HTMLList { New-HTMLListItem -Text "Enabled: ", $ManagerSection.Enable -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "SendCountMaximum: ", $ManagerSection.SendCountMaximum -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "SendToDefaultEmail: ", $ManagerSection.SendToDefaultEmail -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "DefaultEmail: ", ($ManagerSection.DefaultEmail -join ", ") -FontWeight normal, bold -TextDecoration underline, none } } New-HTMLSection -HeaderText "Security Section" { New-HTMLList { New-HTMLListItem -Text "Enabled: ", $SecuritySection.Enable -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "SendCountMaximum: ", $SecuritySection.SendCountMaximum -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "SendToDefaultEmail: ", $SecuritySection.SendToDefaultEmail -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "DefaultEmail: ", ($SecuritySection.DefaultEmail -join ", ") -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "Attach CSV: ", ($SecuritySection.AttachCSV -join ",") -FontWeight normal, bold -TextDecoration underline, none } } New-HTMLSection -HeaderText "Admin Section" { New-HTMLList { New-HTMLListItem -Text "Enabled: ", $AdminSection.Enable -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "Subject: ", $AdminSection.Subject -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "Manager: ", $AdminSection.Manager.DisplayName -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "Manager Email: ", ($AdminSection.Manager.EmailAddress -join ", ") -FontWeight normal, bold -TextDecoration underline, none } } } } New-HTMLTab -Name 'Rules Configuration' { New-HTMLText -Text "There are ", $Rules.Count, " rules defined in the Password Solution. ", "Please keep in mind that order of the rules matter." -FontWeight normal, bold, normal -Color None, Blue, None foreach ($Rule in $Rules) { if ($Rule.Enable) { $SectionColor = 'SpringGreen' } else { $SectionColor = 'Coral' } New-HTMLSection -HeaderText "Rule $($Rule.Name)" -CanCollapse -HeaderBackGroundColor $SectionColor { New-HTMLList { if ($Rule.Enable) { New-HTMLListItem -Text "Rule ", $Rule.Name, " is ", "enabled" -FontWeight normal, bold, normal, bold, normal, normal -Color None, None, None, Green } else { New-HTMLListItem -Text "Rule ", $Rule.Name, " is ", "disabled" -FontWeight normal, bold, normal, bold, normal, normal -Color None, None, None, Red } New-HTMLList { New-HTMLListItem -Text "Notify till expiry on ", $($Rule.Reminders -join ","), " day " -FontWeight normal, bold, normal if ($Rule.IncludeExpiring) { New-HTMLListItem -Text "Include expiring accounts is ", "enabled" -FontWeight bold, bold -Color None, Green } else { New-HTMLListItem -Text "Include expiring accounts is ", "disabled" -FontWeight bold, bold -Color None, Red } if ($Rule.IncludePasswordNeverExpires) { New-HTMLListItem -Text "Include passwords never expiring with ", $Rule.PasswordNeverExpiresDays, " days rule" -FontWeight bold -Color Amethyst } else { New-HTMLListItem -Text "Do not include passwords that never expire." -FontWeight bold -Color Blue } if ($Rule.IncludeName.Count -gt 0 -and $Rule.IncludeNameProperties.Count -gt 0) { New-HTMLListItem -Text "Apply naming rule to require that account contains of of names ", $($Rule.IncludeName -join ", "), " in at least one property ", ($Rule.IncludeNameProperties -join ", ") -FontWeight normal, bold, normal, bold, normal -Color None, Blue, None, Blue } else { New-HTMLListItem -Text "Do not apply special name rules" -Color Blue -FontWeight bold } if ($Rule.IncludeOU) { New-HTMLListItem -Text "Apply Organizational Unit inclusion on ", ($Rule.IncludeOU -join ", ") -FontWeight normal, bold -Color None, Blue } else { New-HTMLListItem -Text "Do not apply Organizational Unit limit" -Color Blue -FontWeight bold } if ($Rule.ExcludeOU) { New-HTMLListItem -Text "Apply Organizational Unit exclusion on ", $Rule.ExcludeOU -FontWeight normal, bold -Color None, Green } else { New-HTMLListItem -Text "Do not exclude any Organizational Unit" -Color Blue -FontWeight bold } if ($Rule.IncludeGroup) { New-HTMLListItem -Text "Appply Group Membership inclusion (direct only) ", ($Rule.IncludeGroup -join ", ") } else { New-HTMLListItem -Text "Do not apply Group Membership limit" } if ($Rule.ExcludeGroup) { New-HTMLListItem -Text "Apply Group Membership exclusion (direct only): ", ($Rule.ExcludeGroup -join ", ") } else { New-HTMLListItem -Text "Do not apply Group Membership exclusion" } New-HTMLListItem -Text "Send to manager" -NestedListItems { New-HTMLList { if ($Rule.SendToManager.Manager.Enable) { New-HTMLListItem -Text "Manager ", " is ", 'enabled' -FontWeight bold, normal, bold -Color None, None, Green { } } else { New-HTMLListItem -Text "Manager ", " is ", 'disabled' -FontWeight bold, normal, bold -Color None, None, Red { } } if ($Rule.SendToManager.ManagerNotCompliant.Enable) { New-HTMLListItem -Text "Manager Escalation", " is ", 'enabled' -FontWeight bold, normal, bold -Color None, None, Green { New-HTMLList { New-HTMLListItem -Text "Manager Name: ", $Rule.SendToManager.ManagerNotCompliant.Manager.DisplayName -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "Manager Email Address: ", $Rule.SendToManager.ManagerNotCompliant.Manager.EmailAddress -FontWeight normal, bold -TextDecoration underline, none } New-HTMLList { New-HTMLListItem -Text "Rules: " { New-HTMLList { if ($Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Enable) { New-HTMLListItem -Text "Default: ", $Rule.SendToManager.ManagerNotCompliant.Reminders.Default.Enable } else { New-HTMLListItem -Text "Default rule is ", "disabled" -FontWeight bold, bold -Color None, Red } if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Enable) { New-HTMLListItem -Text @( "On day (of the week) is ", "enabled" " on days: ", $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Days, " with comparison ", $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.ComparisonType, ' and reminder ', $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDay.Reminder ) -FontWeight bold, bold, normal, bold, normal, bold, normal, bold -Color None, Green, None } else { New-HTMLListItem -Text "On day of week rule is ", "disabled" -FontWeight bold, bold -Color None, Red } if ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Enable) { New-HTMLListItem -Text @( "On day of month rule is ", "enabled", " on days ", ($Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Days -join ","), " with comparison ", $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.ComparisonType, ' reminder ', $Rule.SendToManager.ManagerNotCompliant.Reminders.OnDayOfMonth.Reminder ) -FontWeight bold, bold, normal, bold, normal, bold, normal, bold -Color None, Green, None } else { New-HTMLListItem -Text "On day of month rule is ", "disabled" -FontWeight bold, bold -Color None, Red } } } } } } else { New-HTMLListItem -Text "Manager Escalation", " is ", "disabled" -FontWeight bold, normal, bold -Color None, None, Red } if ($Rule.SendToManager.SecurityEscalation.Enable) { New-HTMLListItem -Text "Security Escalation ", $Rule.SendToManager.SecurityEscalation.Enable -FontWeight normal, bold { New-HTMLList { New-HTMLListItem -Text "Manager Name: ", $Rule.SendToManager.SecurityEscalation.Manager.DisplayName -FontWeight normal, bold -TextDecoration underline, none New-HTMLListItem -Text "Manager Email Address: ", $Rule.SendToManager.SecurityEscalation.Manager.EmailAddress -FontWeight normal, bold -TextDecoration underline, none } New-HTMLList { New-HTMLListItem -Text "Rules: " { New-HTMLList { if ($Rule.SendToManager.SecurityEscalation.Reminders.Default.Enable) { New-HTMLListItem -Text "Default: ", $Rule.SendToManager.SecurityEscalation.Reminders.Default.Enable } else { New-HTMLListItem -Text "Default rule is ", "disabled" -FontWeight bold, bold -Color None, Red } if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Enable) { New-HTMLListItem -Text @( "On day (of the week) is ", "enabled" " on days: ", $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Days, " with comparison ", $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.ComparisonType, ' and reminder ', $Rule.SendToManager.SecurityEscalation.Reminders.OnDay.Reminder ) -FontWeight bold, bold, normal, bold, normal, bold, normal, bold -Color None, Green, None } else { New-HTMLListItem -Text "On day of week rule is ", "disabled" -FontWeight bold, bold -Color None, Red } if ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Enable) { New-HTMLListItem -Text @( "On day of month rule is ", "enabled", " on days ", ($Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Days -join ","), " with comparison ", $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.ComparisonType, ' reminder ', $Rule.SendToManager.SecurityEscalation.Reminders.OnDayOfMonth.Reminder ) -FontWeight bold, bold, normal, bold, normal, bold, normal, bold -Color None, Green, None } else { New-HTMLListItem -Text "On day of month rule is ", "disabled" -FontWeight bold, bold -Color None, Red } } } } } } else { New-HTMLListItem -Text "Security Escalation", " is ", "disabled" -FontWeight bold, normal, bold -Color None, None, Red } } } } } } } } } } if ($HTMLOptions.ShowAllUsers) { New-HTMLTab -Name 'All Users' { New-HTMLTable -DataTable $CachedUsers.Values -Filtering { New-TableCondition -Name 'Enabled' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string -Operator eq New-TableCondition -Name 'HasMailbox' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string -Operator eq New-TableCondition -Name 'PasswordExpired' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string New-TableCondition -Name 'PasswordNeverExpires' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Missing', 'Disabled' -BackgroundColor Salmon -Operator in New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Enabled' -BackgroundColor LawnGreen New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Not available' -BackgroundColor BlueSmoke } } } if ($HTMLOptions.ShowRules) { foreach ($Rule in $Summary['Rules'].Keys) { if ((Measure-Object -InputObject $Summary['Rules'][$Rule].Values.User).Count -gt 0) { $Color = 'LawnGreen' $IconSolid = 'Star' } else { $Color = 'Salmon' $IconSolid = 'Stop' } New-HTMLTab -Name $Rule -TextColor $Color -IconColor $Color -IconSolid $IconSolid { New-HTMLTable -DataTable $Summary['Rules'][$Rule].Values.User -Filtering { New-TableCondition -Name 'Enabled' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string New-TableCondition -Name 'HasMailbox' -BackgroundColor LawnGreen -FailBackgroundColor BlueSmoke -Value $true -ComparisonType string -Operator eq New-TableCondition -Name 'PasswordExpired' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string New-TableCondition -Name 'PasswordNeverExpires' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Missing', 'Disabled' -BackgroundColor Salmon -Operator in New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Enabled' -BackgroundColor LawnGreen New-TableCondition -Name 'ManagerStatus' -HighlightHeaders Manager, ManagerSamAccountName, ManagerEmail, ManagerStatus -ComparisonType string -Value 'Not available' -BackgroundColor BlueSmoke } } } } if ($HTMLOptions.ShowUsersSent) { if ((Measure-Object -InputObject $SummaryUsersEmails).Count -gt 0) { $Color = 'BrightTurquoise' $IconSolid = 'sticky-note' } else { $Color = 'Amaranth' $IconSolid = 'stop-circle' } New-HTMLTab -Name 'Email sent to users' -TextColor $Color -IconColor $Color -IconSolid $IconSolid { New-HTMLTable -DataTable $SummaryUsersEmails { New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary' New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo' New-TableCondition -Name 'PasswordExpired' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string New-TableCondition -Name 'PasswordNeverExpires' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $false -ComparisonType string } -Filtering } } if ($HTMLOptions.ShowManagersSent) { if ((Measure-Object -InputObject $SummaryManagersEmails).Count -gt 0) { $Color = 'BrightTurquoise' $IconSolid = 'sticky-note' } else { $Color = 'Amaranth' $IconSolid = 'stop-circle' } New-HTMLTab -Name 'Email sent to manager' -TextColor $Color -IconColor $Color -IconSolid $IconSolid { New-HTMLTable -DataTable $SummaryManagersEmails { New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary' New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo' } -Filtering } } if ($HTMLOptions.ShowEscalationSent) { if ((Measure-Object -InputObject $SummaryEscalationEmails).Count -gt 0) { $Color = 'BrightTurquoise' $IconSolid = 'sticky-note' } else { $Color = 'Amaranth' $IconSolid = 'stop-circle' } New-HTMLTab -Name 'Email sent to Security' -TextColor $Color -IconColor $Color -IconSolid $IconSolid { New-HTMLTable -DataTable $SummaryEscalationEmails { New-TableHeader -Names 'Status', 'StatusError', 'SentTo', 'StatusWhen' -Title 'Email Summary' New-TableCondition -Name 'Status' -BackgroundColor LawnGreen -FailBackgroundColor Salmon -Value $true -ComparisonType string -HighlightHeaders 'Status', 'StatusWhen', 'StatusError', 'SentTo' } -Filtering } } } -ShowHTML:$HTMLOptions.ShowHTML -FilePath $FilePath -Online:$HTMLOptions.Online -WarningAction $WarningAction Write-Color -Text "[i]" , " Generating HTML report ", "Done" -Color White, Yellow, Green } if ($SearchPath) { Write-Color -Text "[i]" , " Saving Search report " -Color White, Yellow, Green $SummarySearch['EmailSent'][$Today] = $SummaryUsersEmails $SummarySearch['EmailEscalations'][$Today] = $SummaryEscalationEmails $SummarySearch['EmailManagers'][$Today] = $SummaryManagersEmails try { $SummarySearch | Export-Clixml -LiteralPath $SearchPath } catch { Write-Color -Text "[e]", " Couldn't save to file $SearchPath", ". Error: ", $_.Exception.Message -Color White, Yellow, White, Yellow, White, Yellow, White } Write-Color -Text "[i]" , " Saving Search report ", "Done" -Color White, Yellow, Green } } Export-ModuleMember -Function @('Find-Password', 'Find-PasswordNotification', 'Send-PasswordEmail', 'Start-PasswordSolution') -Alias @() # SIG # Begin signature block # MIIdWQYJKoZIhvcNAQcCoIIdSjCCHUYCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUde36yIsQgAyI88pLMRpWTKdf # 0VCgghhnMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B # AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg # Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg # +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT # XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5 # a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g # 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1 # roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf # GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G # A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL # gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3 # cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr # EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+ # fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q # Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu # 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw # 8jCCBP4wggPmoAMCAQICEA1CSuC+Ooj/YEAhzhQA8N0wDQYJKoZIhvcNAQELBQAw # cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ # d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk # IElEIFRpbWVzdGFtcGluZyBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAxMDYwMDAw # MDBaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4G # A1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjEwggEiMA0GCSqGSIb3DQEBAQUA # A4IBDwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXKY2tR1zoRQr0KdXVNlLQMULUmEP4d # yG+RawyW5xpcSO9E5b+bYc0VkWJauP9nC5xj/TZqgfop+N0rcIXeAhjzeG28ffnH # bQk9vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2j8aj4S8bOrdh1nPsTm0zinxdRS1L # sVDmQTo3VobckyON91Al6GTm3dOPL1e1hyDrDo4s1SPa9E14RuMDgzEpSlwMMYpK # jIjF9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1uxHTDCm2mCtNv8VlS8H6GHq756Wwog # L0sJyZWnjbL61mOLTqVyHO6fegFz+BnW/g1JhL0BAgMBAAGjggG4MIIBtDAOBgNV # HQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD # CDBBBgNVHSAEOjA4MDYGCWCGSAGG/WwHATApMCcGCCsGAQUFBwIBFhtodHRwOi8v # d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0jBBgwFoAU9LbhIB3+Ka7S5GGlsqIl # ssgXNW4wHQYDVR0OBBYEFDZEho6kurBmvrwoLR1ENt3janq8MHEGA1UdHwRqMGgw # MqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMu # Y3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk # LXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz # cC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jYWNlcnRzLmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURUaW1lc3RhbXBpbmdDQS5jcnQw # DQYJKoZIhvcNAQELBQADggEBAEgc3LXpmiO85xrnIA6OZ0b9QnJRdAojR6OrktIl # xHBZvhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQCZk3taaQP9rhwz2Lo9VFKeHk2eie3 # 8+dSn5On7UOee+e03UEiifuHokYDTvz0/rdkd2NfI1Jpg4L6GlPtkMyNoRdzDfTz # ZTlwS/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO+MDhXM/EEXLnG2RJ2CKadRVC9S0y # OIHa9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j1h9bm/cuG08THfdKDXF+l7f0P4Tr # weOjSaH6zqe/Vs+6WXZhiV9+p7SOZ3j5NpjhyyjaW4emii8wggUwMIIEGKADAgEC # AhAECRgbX9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVT # MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j # b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEw # MjIxMjAwMDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV # BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEi # MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7 # RZmxOttE9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p # 0WfTxvspJ8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj # 6YgsIJWuHEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grk # V7tKtel05iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHy # DxL0xY4PwaLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMB # AAGjggHNMIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAT # BgNVHSUEDDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG # GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh # Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCB # gQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD # ZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNl # cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgG # CmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu # Y29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1 # DlgwHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEL # BQADggEBAD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q # 3yBVN7Dh9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/ # kLEbBw6RFfu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dc # IFzZcbEMj7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6 # dGRrsutmQ9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT # +hKUGIUukpHqaGxEMrJmoecYpJpkUe8wggUxMIIEGaADAgECAhAKoSXW1jIbfkHk # Bdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMT # G0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0z # MTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0 # IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB # AQUAA4IBDwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5 # fU1ofue2oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb # 6+NGRwYaVX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU # 46gJcWvgzyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mI # UF79Zm5WYScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfx # FwbvPc3WTe8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAd # BgNVHQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SS # y4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMC # AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUF # BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6 # Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j # cnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E # aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkw # RzA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj # ZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLp # UYdWac3v3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQd # aq6Z+CeiZr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC # 4HLHmNY8ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+ # tpJn+1Nhiaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6H # USHkWGCbugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIv # IjayS6JKldj1po5SMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkq # hkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j # MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT # SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoX # DTIzMDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tp # ZTERMA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlz # IEVWT1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjAN # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqD # Bqlnr3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfF # jVye3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub # +3tii0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf # 3tZZzO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6 # Ea41zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQAB # o4IBxTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0O # BBYEFBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE # DDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2Ny # bDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUw # QzA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl # cnQuY29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNp # Z25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1s # z4lsLARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsR # XPHUF/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHI # NrTCvPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8 # Rj9yG4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6 # o6ESJre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNp # ezQug9ufqExx6lHYDjGCBFwwggRYAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYD # VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAv # BgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EC # EATV3B9I6snYUgC6zZqbKqcwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI # oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB # CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFLS4HwGv3GWevGQwx8ww # b4drd9vvMA0GCSqGSIb3DQEBAQUABIIBAJNvWSR1uQvi/LS+aOVzjMbK/f0vWyuq # Zsnm6LWXznsh7qsQ4w+lu+awAZfPCJmoB73vOp0MGuAlRELz+iNMqw7bxZtDPzAQ # ow80/QkuWyO3fDr52eU54sEy3rek6iPxHjJDEfEFrTW6fcsmt2urbSiBQvZR2Xlr # WN83tjg5sfc+Yh2MQbfDVuWeZU1UrTBFsT55BHBqIT8l5r9OMUtzr2f0bI9Eub7s # Kpteb3bwH9natX4b09iYplWNkZbnaQIAC3zVTg0STPkuAfnzQZ4KsT3bT0baMaBQ # MTAtgd9Kr4QkvAMnlF2SooZDVwvDO9W4UVQ7qqXX+Vf1eilnlJGXqsChggIwMIIC # LAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G # A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQ # DUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzEL # BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIxMDcyMTE3NDY1NVowLwYJKoZI # hvcNAQkEMSIEINRXOfQWWHHwNQmGNfEZg9p7WaQSQP7uYJnDH9kzU/mbMA0GCSqG # SIb3DQEBAQUABIIBABYn8f7wYvFDrNNffeEPSGptn0262RKByGoNQXO4OM5DOvuB # 8Upqk36xylsFGp5NauU+9LWj14KNA/uL59jZVuHoELjNqCgSeuk6eay9RhbAxqGS # 3zs2gANn1wMJ1iMUl/BGOYo1cKSeiUo+naqPt1aiRARoQvBJfjfddMNW7mAL4dNI # 5xPMXCGN05iVksfdzZRbF0YAtjNsSxiinCIJOe2b1VtHEHdqsMqcc2Y8Pde4k/P/ # y9d01OZNtafQC9XLDIaVTh2APFniLSU0wfG48WKz9j3kr/tAWQ/xq3ibuTDAvdbS # KiLYEUmPupOMl6ArxgmhtHbXiDH4u51WOrOuSpQ= # SIG # End signature block |