GPOZaurr.psm1
function ConvertFrom-DistinguishedName { <# .SYNOPSIS Converts a Distinguished Name to CN, OU, Multiple OUs or DC .DESCRIPTION Converts a Distinguished Name to CN, OU, Multiple OUs or DC .PARAMETER DistinguishedName Distinguished Name to convert .PARAMETER ToOrganizationalUnit Converts DistinguishedName to Organizational Unit .PARAMETER ToDC Converts DistinguishedName to DC .PARAMETER ToDomainCN Converts DistinguishedName to Domain Canonical Name (CN) .PARAMETER ToCanonicalName Converts DistinguishedName to Canonical Name .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 .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit -IncludeParent Output: OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit Output: OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE $Con = @( 'CN=Windows Authorization Access Group,CN=Builtin,DC=ad,DC=evotec,DC=xyz' 'CN=Mmm,DC=elo,CN=nee,DC=RootDNSServers,CN=MicrosoftDNS,CN=System,DC=ad,DC=evotec,DC=xyz' 'CN=e6d5fd00-385d-4e65-b02d-9da3493ed850,CN=Operations,CN=DomainUpdates,CN=System,DC=ad,DC=evotec,DC=xyz' 'OU=Domain Controllers,DC=ad,DC=evotec,DC=pl' 'OU=Microsoft Exchange Security Groups,DC=ad,DC=evotec,DC=xyz' ) ConvertFrom-DistinguishedName -DistinguishedName $Con -ToLastName Output: Windows Authorization Access Group Mmm e6d5fd00-385d-4e65-b02d-9da3493ed850 Domain Controllers Microsoft Exchange Security Groups .EXAMPLEE ConvertFrom-DistinguishedName -DistinguishedName 'DC=ad,DC=evotec,DC=xyz' -ToCanonicalName ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName ConvertFrom-DistinguishedName -DistinguishedName 'CN=test,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName Output: ad.evotec.xyz ad.evotec.xyz\Production\Users ad.evotec.xyz\Production\Users\test .NOTES General notes #> [CmdletBinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'ToOrganizationalUnit')] [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')] [Parameter(ParameterSetName = 'ToDC')] [Parameter(ParameterSetName = 'ToDomainCN')] [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'ToLastName')] [Parameter(ParameterSetName = 'ToCanonicalName')] [alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName, [Parameter(ParameterSetName = 'ToOrganizationalUnit')][switch] $ToOrganizationalUnit, [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][alias('ToMultipleOU')][switch] $ToMultipleOrganizationalUnit, [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][switch] $IncludeParent, [Parameter(ParameterSetName = 'ToDC')][switch] $ToDC, [Parameter(ParameterSetName = 'ToDomainCN')][switch] $ToDomainCN, [Parameter(ParameterSetName = 'ToLastName')][switch] $ToLastName, [Parameter(ParameterSetName = 'ToCanonicalName')][switch] $ToCanonicalName ) Process { foreach ($Distinguished in $DistinguishedName) { if ($ToDomainCN) { $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' $CN = $DN -replace ',DC=', '.' -replace "DC=" if ($CN) { $CN } } elseif ($ToOrganizationalUnit) { if ($Distinguished -match '^CN=[^,\\]+(?:\\,[^,\\]+)*,(.+)$') { $matches[1] } elseif ($Distinguished -match '^(OU=|CN=)') { $Distinguished } } elseif ($ToMultipleOrganizationalUnit) { $Parts = $Distinguished -split '(?<!\\),' $Results = [System.Collections.ArrayList]::new() if ($IncludeParent) { $null = $Results.Add($Distinguished) } for ($i = 1; $i -lt $Parts.Count; $i++) { $CurrentPath = $Parts[$i..($Parts.Count - 1)] -join ',' if ($CurrentPath -match '^(OU=|CN=)' -and $CurrentPath -notmatch '^DC=') { $null = $Results.Add($CurrentPath) } } foreach ($R in $Results) { if ($R -match '^(OU=|CN=)') { $R } } } elseif ($ToDC) { $Value = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' if ($Value) { $Value } } elseif ($ToLastName) { $NewDN = $Distinguished -split ",DC=" if ($NewDN[0].Contains(",OU=")) { [Array] $ChangedDN = $NewDN[0] -split ",OU=" } elseif ($NewDN[0].Contains(",CN=")) { [Array] $ChangedDN = $NewDN[0] -split ",CN=" } else { [Array] $ChangedDN = $NewDN[0] } if ($ChangedDN[0].StartsWith('CN=')) { $ChangedDN[0] -replace 'CN=', '' } else { $ChangedDN[0] -replace 'OU=', '' } } elseif ($ToCanonicalName) { $Domain = $null $Rest = $null foreach ($O in $Distinguished -split '(?<!\\),') { if ($O -match '^DC=') { $Domain += $O.Substring(3) + '.' } else { $Rest = $O.Substring(3) + '\' + $Rest } } if ($Domain -and $Rest) { $Domain.Trim('.') + '\' + ($Rest.TrimEnd('\') -replace '\\,', ',') } elseif ($Domain) { $Domain.Trim('.') } elseif ($Rest) { $Rest.TrimEnd('\') -replace '\\,', ',' } } else { $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$' $Found = $Distinguished -match $Regex if ($Found) { $Matches.cn } } } } } function ConvertFrom-SID { <# .SYNOPSIS Small command that can resolve SID values .DESCRIPTION Small command that can resolve SID values .PARAMETER SID Value to resolve .PARAMETER OnlyWellKnown Only resolve SID when it's well know SID. Otherwise return $null .PARAMETER OnlyWellKnownAdministrative Only resolve SID when it's administrative well know SID. Otherwise return $null .PARAMETER DoNotResolve Uses only dicrionary values without querying AD .EXAMPLE ConvertFrom-SID -SID 'S-1-5-8', 'S-1-5-9', 'S-1-5-11', 'S-1-5-18', 'S-1-1-0' -DoNotResolve .NOTES General notes #> [cmdletbinding(DefaultParameterSetName = 'Standard')] param( [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'OnlyWellKnown')] [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')] [string[]] $SID, [Parameter(ParameterSetName = 'OnlyWellKnown')][switch] $OnlyWellKnown, [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')][switch] $OnlyWellKnownAdministrative, [Parameter(ParameterSetName = 'Standard')][switch] $DoNotResolve ) $WellKnownAdministrative = @{ 'S-1-5-18' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SYSTEM' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-32-544' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } } $wellKnownSIDs = @{ 'S-1-0' = [PSCustomObject] @{ Name = 'Null AUTHORITY' SID = 'S-1-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-0-0' = [PSCustomObject] @{ Name = 'NULL SID' SID = 'S-1-0-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-1' = [PSCustomObject] @{ Name = 'WORLD AUTHORITY' SID = 'S-1-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-1-0' = [PSCustomObject] @{ Name = 'Everyone' SID = 'S-1-1-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2' = [PSCustomObject] @{ Name = 'LOCAL AUTHORITY' SID = 'S-1-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2-0' = [PSCustomObject] @{ Name = 'LOCAL' SID = 'S-1-2-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2-1' = [PSCustomObject] @{ Name = 'CONSOLE LOGON' SID = 'S-1-2-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3' = [PSCustomObject] @{ Name = 'CREATOR AUTHORITY' SID = 'S-1-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-0' = [PSCustomObject] @{ Name = 'CREATOR OWNER' SID = 'S-1-3-0' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-3-1' = [PSCustomObject] @{ Name = 'CREATOR GROUP' SID = 'S-1-3-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-2' = [PSCustomObject] @{ Name = 'CREATOR OWNER SERVER' SID = 'S-1-3-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-3' = [PSCustomObject] @{ Name = 'CREATOR GROUP SERVER' SID = 'S-1-3-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-4' = [PSCustomObject] @{ Name = 'OWNER RIGHTS' SID = 'S-1-3-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-0' = [PSCustomObject] @{ Name = 'NT SERVICE\ALL SERVICES' SID = 'S-1-5-80-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-4' = [PSCustomObject] @{ Name = 'Non-unique Authority' SID = 'S-1-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5' = [PSCustomObject] @{ Name = 'NT AUTHORITY' SID = 'S-1-5' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-1' = [PSCustomObject] @{ Name = 'NT AUTHORITY\DIALUP' SID = 'S-1-5-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-2' = [PSCustomObject] @{ Name = 'NT AUTHORITY\NETWORK' SID = 'S-1-5-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-3' = [PSCustomObject] @{ Name = 'NT AUTHORITY\BATCH' SID = 'S-1-5-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-4' = [PSCustomObject] @{ Name = 'NT AUTHORITY\INTERACTIVE' SID = 'S-1-5-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-6' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SERVICE' SID = 'S-1-5-6' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-7' = [PSCustomObject] @{ Name = 'NT AUTHORITY\ANONYMOUS LOGON' SID = 'S-1-5-7' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-8' = [PSCustomObject] @{ Name = 'NT AUTHORITY\PROXY' SID = 'S-1-5-8' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-9' = [PSCustomObject] @{ Name = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS' SID = 'S-1-5-9' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-10' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SELF' SID = 'S-1-5-10' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-11' = [PSCustomObject] @{ Name = 'NT AUTHORITY\Authenticated Users' SID = 'S-1-5-11' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-12' = [PSCustomObject] @{ Name = 'NT AUTHORITY\RESTRICTED' SID = 'S-1-5-12' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-13' = [PSCustomObject] @{ Name = 'NT AUTHORITY\TERMINAL SERVER USER' SID = 'S-1-5-13' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-14' = [PSCustomObject] @{ Name = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON' SID = 'S-1-5-14' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-15' = [PSCustomObject] @{ Name = 'NT AUTHORITY\This Organization' SID = 'S-1-5-15' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-17' = [PSCustomObject] @{ Name = 'NT AUTHORITY\IUSR' SID = 'S-1-5-17' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-18' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SYSTEM' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-19' = [PSCustomObject] @{ Name = 'NT AUTHORITY\LOCAL SERVICE' SID = 'S-1-5-19' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-20' = [PSCustomObject] @{ Name = 'NT AUTHORITY\NETWORK SERVICE' SID = 'S-1-5-20' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-544' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-32-545' = [PSCustomObject] @{ Name = 'BUILTIN\Users' SID = 'S-1-5-32-545' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-546' = [PSCustomObject] @{ Name = 'BUILTIN\Guests' SID = 'S-1-5-32-546' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-547' = [PSCustomObject] @{ Name = 'BUILTIN\Power Users' SID = 'S-1-5-32-547' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-548' = [PSCustomObject] @{ Name = 'BUILTIN\Account Operators' SID = 'S-1-5-32-548' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-549' = [PSCustomObject] @{ Name = 'BUILTIN\Server Operators' SID = 'S-1-5-32-549' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-550' = [PSCustomObject] @{ Name = 'BUILTIN\Print Operators' SID = 'S-1-5-32-550' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-551' = [PSCustomObject] @{ Name = 'BUILTIN\Backup Operators' SID = 'S-1-5-32-551' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-552' = [PSCustomObject] @{ Name = 'BUILTIN\Replicators' SID = 'S-1-5-32-552' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-10' = [PSCustomObject] @{ Name = 'NT AUTHORITY\NTLM Authentication' SID = 'S-1-5-64-10' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-14' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SChannel Authentication' SID = 'S-1-5-64-14' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-21' = [PSCustomObject] @{ Name = 'NT AUTHORITY\Digest Authentication' SID = 'S-1-5-64-21' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80' = [PSCustomObject] @{ Name = 'NT SERVICE' SID = 'S-1-5-80' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-83-0' = [PSCustomObject] @{ Name = 'NT VIRTUAL MACHINE\Virtual Machines' SID = 'S-1-5-83-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-0' = [PSCustomObject] @{ Name = 'Untrusted Mandatory Level' SID = 'S-1-16-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-4096' = [PSCustomObject] @{ Name = 'Low Mandatory Level' SID = 'S-1-16-4096' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-8192' = [PSCustomObject] @{ Name = 'Medium Mandatory Level' SID = 'S-1-16-8192' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-8448' = [PSCustomObject] @{ Name = 'Medium Plus Mandatory Level' SID = 'S-1-16-8448' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-12288' = [PSCustomObject] @{ Name = 'High Mandatory Level' SID = 'S-1-16-12288' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-16384' = [PSCustomObject] @{ Name = 'System Mandatory Level' SID = 'S-1-16-16384' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-20480' = [PSCustomObject] @{ Name = 'Protected Process Mandatory Level' SID = 'S-1-16-20480' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-28672' = [PSCustomObject] @{ Name = 'Secure Process Mandatory Level' SID = 'S-1-16-28672' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-554' = [PSCustomObject] @{ Name = 'BUILTIN\Pre-Windows 2000 Compatible Access' SID = 'S-1-5-32-554' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-555' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Desktop Users' SID = 'S-1-5-32-555' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-556' = [PSCustomObject] @{ Name = 'BUILTIN\Network Configuration Operators' SID = 'S-1-5-32-556' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-557' = [PSCustomObject] @{ Name = 'BUILTIN\Incoming Forest Trust Builders' SID = 'S-1-5-32-557' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-558' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Monitor Users' SID = 'S-1-5-32-558' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-559' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Log Users' SID = 'S-1-5-32-559' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-560' = [PSCustomObject] @{ Name = 'BUILTIN\Windows Authorization Access Group' SID = 'S-1-5-32-560' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-561' = [PSCustomObject] @{ Name = 'BUILTIN\Terminal Server License Servers' SID = 'S-1-5-32-561' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-562' = [PSCustomObject] @{ Name = 'BUILTIN\Distributed COM Users' SID = 'S-1-5-32-562' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-568' = [PSCustomObject] @{ Name = 'BUILTIN\IIS_IUSRS' SID = 'S-1-5-32-568' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-569' = [PSCustomObject] @{ Name = 'BUILTIN\Cryptographic Operators' SID = 'S-1-5-32-569' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-573' = [PSCustomObject] @{ Name = 'BUILTIN\Event Log Readers' SID = 'S-1-5-32-573' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-574' = [PSCustomObject] @{ Name = 'BUILTIN\Certificate Service DCOM Access' SID = 'S-1-5-32-574' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-575' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Remote Access Servers' SID = 'S-1-5-32-575' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-576' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Endpoint Servers' SID = 'S-1-5-32-576' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-577' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Management Servers' SID = 'S-1-5-32-577' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-578' = [PSCustomObject] @{ Name = 'BUILTIN\Hyper-V Administrators' SID = 'S-1-5-32-578' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-579' = [PSCustomObject] @{ Name = 'BUILTIN\Access Control Assistance Operators' SID = 'S-1-5-32-579' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-580' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Management Users' SID = 'S-1-5-32-580' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-90-0' = [PSCustomObject] @{ Name = 'Window Manager\Window Manager Group' SID = 'S-1-5-90-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' = [PSCustomObject] @{ Name = 'NT SERVICE\WdiServiceHost' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3880718306-3832830129-1677859214-2598158968-1052248003' = [PSCustomObject] @{ Name = 'NT SERVICE\MSSQLSERVER' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLSERVERAGENT' SID = 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLTELEMETRY' SID = 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' = [PSCustomObject] @{ Name = 'NT SERVICE\ADSync' SID = 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' = [PSCustomObject] @{ Name = 'NT Service\himds' SID = 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' DomainName = '' Type = 'WellKnownGroup' Error = '' } } foreach ($S in $SID) { if ($OnlyWellKnownAdministrative) { if ($WellKnownAdministrative[$S]) { $WellKnownAdministrative[$S] } } elseif ($OnlyWellKnown) { if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } } else { if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } else { if ($DoNotResolve) { if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512" -or $S -like "S-1-5-21-*-518") { [PSCustomObject] @{ Name = $S SID = $S DomainName = '' Type = 'Administrative' Error = '' } } else { [PSCustomObject] @{ Name = $S SID = $S DomainName = '' Error = '' Type = 'NotAdministrative' } } } else { if (-not $Script:LocalComputerSID) { $Script:LocalComputerSID = Get-LocalComputerSid } try { if ($S.Length -le 18) { $Type = 'NotAdministrative' $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value [PSCustomObject] @{ Name = $Name SID = $S DomainName = '' Type = $Type Error = '' } } else { if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512" -or $S -like "S-1-5-21-*-518") { $Type = 'Administrative' } else { $Type = 'NotAdministrative' } $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value [PSCustomObject] @{ Name = $Name SID = $S DomainName = if ($S -like "$Script:LocalComputerSID*") { '' } else { (ConvertFrom-NetbiosName -Identity $Name).DomainName } Type = $Type Error = '' } } } catch { [PSCustomObject] @{ Name = $S SID = $S DomainName = '' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' Type = 'Unknown' } } } } } } } function Convert-Identity { <# .SYNOPSIS Small command that tries to resolve any given object .DESCRIPTION Small command that tries to resolve any given object - be it SID, DN, FSP or Netbiosname .PARAMETER Identity Type to resolve in form of Identity, DN, SID .PARAMETER SID Allows to pass SID directly, rather then going thru verification process .PARAMETER Name Allows to pass Name directly, rather then going thru verification process .PARAMETER Force Allows to clear cache, useful when you want to force refresh .EXAMPLE $Identity = @( 'S-1-5-4' 'S-1-5-4' 'S-1-5-11' 'S-1-5-32-549' 'S-1-5-32-550' 'S-1-5-32-548' 'S-1-5-64-10' 'S-1-5-64-14' 'S-1-5-64-21' 'S-1-5-18' 'S-1-5-19' 'S-1-5-32-544' 'S-1-5-20-20-10-51' # Wrong SID 'S-1-5-21-853615985-2870445339-3163598659-512' 'S-1-5-21-3661168273-3802070955-2987026695-512' 'S-1-5-21-1928204107-2710010574-1926425344-512' 'CN=Test Test 2,OU=Users,OU=Production,DC=ad,DC=evotec,DC=pl' 'Test Local Group' 'przemyslaw.klys@evotec.pl' 'test2' 'NT AUTHORITY\NETWORK' 'NT AUTHORITY\SYSTEM' 'S-1-5-21-853615985-2870445339-3163598659-519' 'TEST\some' 'EVOTECPL\Domain Admins' 'NT AUTHORITY\INTERACTIVE' 'INTERACTIVE' 'EVOTEC\Domain Admins' 'EVOTECPL\Domain Admins' 'Test\Domain Admins' 'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # Valid 'CN=S-1-5-21-1928204107-2710010574-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # not valid 'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # cached ) $TestOutput = Convert-Identity -Identity $Identity -Verbose Output: Name SID DomainName Type Error ---- --- ---------- ---- ----- NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\Authenticated Users S-1-5-11 WellKnownGroup BUILTIN\Server Operators S-1-5-32-549 WellKnownGroup BUILTIN\Print Operators S-1-5-32-550 WellKnownGroup BUILTIN\Account Operators S-1-5-32-548 WellKnownGroup NT AUTHORITY\NTLM Authentication S-1-5-64-10 WellKnownGroup NT AUTHORITY\SChannel Authentication S-1-5-64-14 WellKnownGroup NT AUTHORITY\Digest Authentication S-1-5-64-21 WellKnownGroup NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative NT AUTHORITY\NETWORK SERVICE S-1-5-19 WellKnownGroup BUILTIN\Administrators S-1-5-32-544 WellKnownAdministrative S-1-5-20-20-10-51 S-1-5-20-20-10-51 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative EVOTECPL\TestingAD S-1-5-21-3661168273-3802070955-2987026695-1111 ad.evotec.pl NotAdministrative EVOTEC\Test Local Group S-1-5-21-853615985-2870445339-3163598659-3610 ad.evotec.xyz NotAdministrative EVOTEC\przemyslaw.klys S-1-5-21-853615985-2870445339-3163598659-1105 ad.evotec.xyz NotAdministrative test2 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." NT AUTHORITY\NETWORK S-1-5-2 WellKnownGroup NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative EVOTEC\Enterprise Admins S-1-5-21-853615985-2870445339-3163598659-519 ad.evotec.xyz Administrative TEST\some S-1-5-21-1928204107-2710010574-1926425344-1106 test.evotec.pl NotAdministrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative S-1-5-21-1928204107-2710010574-512 S-1-5-21-1928204107-2710010574-512 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'Identity')] param( [parameter(ParameterSetName = 'Identity', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][string[]] $Identity, [parameter(ParameterSetName = 'SID', Mandatory)][System.Security.Principal.SecurityIdentifier[]] $SID, [parameter(ParameterSetName = 'Name', Mandatory)][string[]] $Name, [switch] $Force ) Begin { if (-not $Script:GlobalCacheSidConvert -or $Force) { $Script:GlobalCacheSidConvert = @{ 'NT AUTHORITY\SYSTEM' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'BUILTIN\Administrators' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'BUILTIN\Users' = [PSCustomObject] @{ Name = 'BUILTIN\Users' SID = 'S-1-5-32-545' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Guests' = [PSCustomObject] @{ Name = 'BUILTIN\Guests' SID = 'S-1-5-32-546' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Power Users' = [PSCustomObject] @{ Name = 'BUILTIN\Power Users' SID = 'S-1-5-32-547' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Account Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Account Operators' SID = 'S-1-5-32-548' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Server Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Server Operators' SID = 'S-1-5-32-549' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Print Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Print Operators' SID = 'S-1-5-32-550' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Backup Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Backup Operators' SID = 'S-1-5-32-551' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Replicator' = [PSCustomObject] @{ Name = 'BUILTIN\Replicators' SID = 'S-1-5-32-552' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Pre-Windows 2000 Compatible Access' = [PSCustomObject] @{ Name = 'BUILTIN\Pre-Windows 2000 Compatible Access' SID = 'S-1-5-32-554' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Remote Desktop Users' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Desktop Users' SID = 'S-1-5-32-555' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Network Configuration Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Network Configuration Operators' SID = 'S-1-5-32-556' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Incoming Forest Trust Builders' = [PSCustomObject] @{ Name = 'BUILTIN\Incoming Forest Trust Builders' SID = 'S-1-5-32-557' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Performance Monitor Users' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Monitor Users' SID = 'S-1-5-32-558' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Performance Log Users' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Log Users' SID = 'S-1-5-32-559' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Windows Authorization Access Group' = [PSCustomObject] @{ Name = 'BUILTIN\Windows Authorization Access Group' SID = 'S-1-5-32-560' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Terminal Server License Servers' = [PSCustomObject] @{ Name = 'BUILTIN\Terminal Server License Servers' SID = 'S-1-5-32-561' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Distributed COM Users' = [PSCustomObject] @{ Name = 'BUILTIN\Distributed COM Users' SID = 'S-1-5-32-562' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\IIS_IUSRS' = [PSCustomObject] @{ Name = 'BUILTIN\IIS_IUSRS' SID = 'S-1-5-32-568' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Cryptographic Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Cryptographic Operators' SID = 'S-1-5-32-569' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Event Log Readers' = [PSCustomObject] @{ Name = 'BUILTIN\Event Log Readers' SID = 'S-1-5-32-573' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Certificate Service DCOM Access' = [PSCustomObject] @{ Name = 'BUILTIN\Certificate Service DCOM Access' SID = 'S-1-5-32-574' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Remote Access Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Remote Access Servers' SID = 'S-1-5-32-575' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Endpoint Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Endpoint Servers' SID = 'S-1-5-32-576' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Management Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Management Servers' SID = 'S-1-5-32-577' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Hyper-V Administrators' = [PSCustomObject] @{ Name = 'BUILTIN\Hyper-V Administrators' SID = 'S-1-5-32-578' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Access Control Assistance Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Access Control Assistance Operators' SID = 'S-1-5-32-579' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Remote Management Users' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Management Users' SID = 'S-1-5-32-580' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'Window Manager\Window Manager Group' = [PSCustomObject] @{ Name = 'Window Manager\Window Manager Group' SID = 'S-1-5-90-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\WdiServiceHost' = [PSCustomObject] @{ Name = 'NT SERVICE\WdiServiceHost' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\MSSQLSERVER' = [PSCustomObject] @{ Name = 'NT SERVICE\MSSQLSERVER' SID = 'S-1-5-80-3880718306-3832830129-1677859214-2598158968-1052248003' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\SQLSERVERAGENT' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLSERVERAGENT' SID = 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\SQLTELEMETRY' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLTELEMETRY' SID = 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\ADSync' = [PSCustomObject] @{ Name = 'NT SERVICE\ADSync' SID = 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT Service\himds' = [PSCustomObject] @{ Name = 'NT Service\himds' SID = 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' DomainName = '' Type = 'WellKnownGroup' Error = '' } } } } Process { if ($Identity) { foreach ($Ident in $Identity) { $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+") if ($Script:GlobalCacheSidConvert[$Ident]) { Write-Verbose "Convert-Identity - Processing $Ident (Cache)" $Script:GlobalCacheSidConvert[$Ident] } elseif ($MatchRegex.Success) { Write-Verbose "Convert-Identity - Processing $Ident (SID)" if ($MatchRegex.Value -ne $Ident) { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $MatchRegex.Value } else { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $Ident } $Script:GlobalCacheSidConvert[$Ident] } elseif ($Ident -like '*DC=*') { Write-Verbose "Convert-Identity - Processing $Ident (DistinguishedName)" try { $Object = [adsi]"LDAP://$($Ident)" $SIDValue = [System.Security.Principal.SecurityIdentifier]::new($Object.objectSid.Value, 0).Value $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue } catch { $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{ Name = $Ident SID = $null DomainName = '' Type = 'Unknown' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' } } $Script:GlobalCacheSidConvert[$Ident] } else { Write-Verbose "Convert-Identity - Processing $Ident (Other)" try { $SIDValue = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue } catch { $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{ Name = $Ident SID = $null DomainName = '' Type = 'Unknown' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' } } $Script:GlobalCacheSidConvert[$Ident] } } } else { if ($SID) { foreach ($S in $SID) { if ($Script:GlobalCacheSidConvert[$S]) { $Script:GlobalCacheSidConvert[$S] } else { $Script:GlobalCacheSidConvert[$S] = ConvertFrom-SID -SID $S $Script:GlobalCacheSidConvert[$S] } } } else { foreach ($Ident in $Name) { if ($Script:GlobalCacheSidConvert[$Ident]) { $Script:GlobalCacheSidConvert[$Ident] } else { $Script:GlobalCacheSidConvert[$Ident] = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value $Script:GlobalCacheSidConvert[$Ident] } } } } } End { } } function Copy-Dictionary { <# .SYNOPSIS Copies dictionary/hashtable .DESCRIPTION Copies dictionary uusing PS Serializer. Replaces usage of BinnaryFormatter due to no support in PS 7.4 .PARAMETER Dictionary Dictionary to copy .EXAMPLE $Test = [ordered] @{ Test = 'Test' Test1 = @{ Test2 = 'Test2' Test3 = @{ Test4 = 'Test4' } } Test2 = @( "1", "2", "3" ) Test3 = [PSCustomObject] @{ Test4 = 'Test4' Test5 = 'Test5' } } $New1 = Copy-Dictionary -Dictionary $Test $New1 .NOTES #> [alias('Copy-Hashtable', 'Copy-OrderedHashtable')] [cmdletbinding()] param( [System.Collections.IDictionary] $Dictionary ) $clone = [System.Management.Automation.PSSerializer]::Serialize($Dictionary, [int32]::MaxValue) return [System.Management.Automation.PSSerializer]::Deserialize($clone) } function Find-DatesCurrentDayMinuxDaysX { param($days) $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays( - $Days) $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddMilliseconds(-1) $DateParameters = @{ DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesCurrentHour { <# .SYNOPSIS Finds the start and end dates for the current hour. .DESCRIPTION This function calculates the start and end dates for the current hour. .EXAMPLE PS C:\> Find-DatesCurrentHour DateFrom DateTo -------- ------ 10/20/2021 12:00:00 AM 10/20/2021 1:00:00 AM #> $DateTodayStart = (Get-Date -Minute 0 -Second 0 -Millisecond 0) $DateTodayEnd = $DateTodayStart.AddHours(1) $DateParameters = @{ DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesDayPrevious { <# .SYNOPSIS Finds the date parameters for the previous day. .DESCRIPTION This function calculates the date parameters for the previous day based on the current date. .EXAMPLE Find-DatesDayPrevious Returns the date parameters for the previous day. #> $DateToday = (Get-Date).Date $DateYesterday = $DateToday.AddDays(-1) $DateParameters = @{ DateFrom = $DateYesterday DateTo = $dateToday } return $DateParameters } function Find-DatesDayToday { <# .SYNOPSIS Finds the start and end dates of the current day. .DESCRIPTION This function calculates the start and end dates of the current day based on the current date. #> $DateToday = (Get-Date).Date $DateTodayEnd = $DateToday.AddDays(1).AddSeconds(-1) $DateParameters = @{ DateFrom = $DateToday DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesMonthCurrent { <# .SYNOPSIS Finds the start and end dates of the current month. .DESCRIPTION This function calculates the start and end dates of the current month based on the current date. .EXAMPLE Find-DatesMonthCurrent Returns the start and end dates of the current month. #> $DateMonthFirstDay = (Get-Date -Day 1).Date $DateMonthLastDay = Get-Date $DateMonthFirstDay.AddMonths(1).AddSeconds(-1) $DateParameters = @{ DateFrom = $DateMonthFirstDay DateTo = $DateMonthLastDay } return $DateParameters } function Find-DatesMonthPast { param([bool] $Force) $DateToday = (Get-Date).Date $DateMonthFirstDay = (Get-Date -Day 1).Date $DateMonthPreviousFirstDay = $DateMonthFirstDay.AddMonths(-1) if ($Force -eq $true -or $DateToday -eq $DateMonthFirstDay) { $DateParameters = @{ DateFrom = $DateMonthPreviousFirstDay DateTo = $DateMonthFirstDay } return $DateParameters } else { return $null } } function Find-DatesPastHour { <# .SYNOPSIS Finds the date range for the past hour. .DESCRIPTION This function calculates the date range for the past hour, starting from the beginning of the previous hour up to the current hour. .EXAMPLE Find-DatesPastHour Returns a hashtable with DateFrom and DateTo keys representing the date range for the past hour. #> $DateTodayEnd = Get-Date -Minute 0 -Second 0 -Millisecond 0 $DateTodayStart = $DateTodayEnd.AddHours(-1) $DateParameters = @{ DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesQuarterCurrent { param([bool] $Force) $Today = (Get-Date) $Quarter = [Math]::Ceiling($Today.Month / 3) $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3)) $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1) $DateParameters = @{ DateFrom = $StartDate DateTo = $EndDate } return $DateParameters } function Find-DatesQuarterLast { param([bool] $Force) $Today = (Get-Date).AddDays(-90) $Yesterday = ((Get-Date).AddDays(-1)) $Quarter = [Math]::Ceiling($Today.Month / 3) $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3)) $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1) if ($Force -eq $true -or $Yesterday.Date -eq $EndDate.Date) { $DateParameters = @{ DateFrom = $StartDate DateTo = $EndDate } return $DateParameters } else { return $null } } function Format-ToTitleCase { <# .SYNOPSIS Formats string or number of strings to Title Case .DESCRIPTION Formats string or number of strings to Title Case allowing for prettty display .PARAMETER Text Sentence or multiple sentences to format .PARAMETER RemoveWhiteSpace Removes spaces after formatting string to Title Case. .PARAMETER RemoveChar Array of characters to remove .EXAMPLE Format-ToTitleCase 'me' Output: Me .EXAMPLE 'me i feel good' | Format-ToTitleCase Output: Me I Feel Good Not Feel .EXAMPLE 'me i feel', 'not feel' | Format-ToTitleCase Output: Me I Feel Good Not Feel .EXAMPLE Format-ToTitleCase -Text 'This is my thing' -RemoveWhiteSpace Output: ThisIsMyThing .EXAMPLE Format-ToTitleCase -Text "This is my thing: That - No I don't want all chars" -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':' .NOTES General notes #> [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)][string[]] $Text, [switch] $RemoveWhiteSpace, [string[]] $RemoveChar ) Begin { } Process { $Conversion = foreach ($T in $Text) { $Output = (Get-Culture).TextInfo.ToTitleCase($T) foreach ($Char in $RemoveChar) { $Output = $Output -replace $Char } if ($RemoveWhiteSpace) { $Output = $Output -replace ' ', '' } $Output } $Conversion } End { } } function Get-ADACL { <# .SYNOPSIS Retrieves and filters access control list (ACL) information for Active Directory objects. .DESCRIPTION This function retrieves and filters access control list (ACL) information for specified Active Directory objects. It allows for detailed filtering based on various criteria such as principal, access control type, object type, inheritance type, and more. .PARAMETER ADObject Specifies the Active Directory object or objects to retrieve ACL information from. .PARAMETER Extended Indicates whether to retrieve extended ACL information. .PARAMETER ResolveTypes Indicates whether to resolve principal types for ACL filtering. .PARAMETER Principal Specifies the principal to filter ACL information for. .PARAMETER Inherited Indicates to include only inherited ACLs. .PARAMETER NotInherited Indicates to include only non-inherited ACLs. .PARAMETER Bundle Indicates whether to bundle ACL information for each object. .PARAMETER AccessControlType Specifies the access control type to filter ACL information for. .PARAMETER IncludeObjectTypeName Specifies the object types to include in ACL filtering. .PARAMETER IncludeInheritedObjectTypeName Specifies the inherited object types to include in ACL filtering. .PARAMETER ExcludeObjectTypeName Specifies the object types to exclude in ACL filtering. .PARAMETER ExcludeInheritedObjectTypeName Specifies the inherited object types to exclude in ACL filtering. .PARAMETER IncludeActiveDirectoryRights Specifies the Active Directory rights to include in ACL filtering. .PARAMETER IncludeActiveDirectoryRightsExactMatch Specifies the Active Directory rights to include in the filter as an exact match (all rights must be present). .PARAMETER ExcludeActiveDirectoryRights Specifies the Active Directory rights to exclude in ACL filtering. .PARAMETER IncludeActiveDirectorySecurityInheritance Specifies the inheritance types to include in ACL filtering. .PARAMETER ExcludeActiveDirectorySecurityInheritance Specifies the inheritance types to exclude in ACL filtering. .PARAMETER ADRightsAsArray Indicates to return Active Directory rights as an array. .EXAMPLE Get-ADACL -ADObject 'CN=Users,DC=contoso,DC=com' -ResolveTypes -Principal 'Domain Admins' -Bundle Retrieves and bundles ACL information for the 'Domain Admins' principal in the 'Users' container. .NOTES General notes #> [cmdletbinding()] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [alias('Identity')][Array] $ADObject, [switch] $Extended, [alias('ResolveTypes')][switch] $Resolve, [string] $Principal, [switch] $Inherited, [switch] $NotInherited, [switch] $Bundle, [System.Security.AccessControl.AccessControlType] $AccessControlType, [Alias('ObjectTypeName')][string[]] $IncludeObjectTypeName, [Alias('InheritedObjectTypeName')][string[]] $IncludeInheritedObjectTypeName, [string[]] $ExcludeObjectTypeName, [string[]] $ExcludeInheritedObjectTypeName, [Alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights, [System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRightsExactMatch, [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights, [Alias('InheritanceType', 'IncludeInheritanceType')][System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance, [Alias('ExcludeInheritanceType')][System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance, [switch] $ADRightsAsArray ) Begin { if (-not $Script:ForestGUIDs) { Write-Verbose "Get-ADACL - Gathering Forest GUIDS" $Script:ForestGUIDs = Get-WinADForestGUIDs } if (-not $Script:ForestDetails) { Write-Verbose "Get-ADACL - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } if ($Principal -and $Resolve) { $PrincipalRequested = Convert-Identity -Identity $Principal -Verbose:$false } } Process { foreach ($Object in $ADObject) { $ADObjectData = $null if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { if ($Object.ntSecurityDescriptor) { $ADObjectData = $Object } [string] $DistinguishedName = $Object.DistinguishedName [string] $CanonicalName = $Object.CanonicalName if ($CanonicalName) { $CanonicalName = $CanonicalName.TrimEnd('/') } [string] $ObjectClass = $Object.ObjectClass } elseif ($Object -is [string]) { [string] $DistinguishedName = $Object [string] $CanonicalName = '' [string] $ObjectClass = '' } else { if ($Object.ntSecurityDescriptor) { $ADObjectData = $Object [string] $DistinguishedName = $Object.DistinguishedName [string] $CanonicalName = $Object.CanonicalName if ($CanonicalName) { $CanonicalName = $CanonicalName.TrimEnd('/') } [string] $ObjectClass = $Object.ObjectClass } else { Write-Warning "Get-ADACL - Object not recognized. Skipping..." continue } } if (-not $ADObjectData) { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] try { $ADObjectData = Get-ADObject -Identity $DistinguishedName -Properties ntSecurityDescriptor, CanonicalName -ErrorAction Stop -Server $QueryServer $ObjectClass = $ADObjectData.ObjectClass $CanonicalName = $ADObjectData.CanonicalName $ACLs = $ADObjectData.ntSecurityDescriptor } catch { Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)" continue } } else { $ACLs = $ADObjectData.ntSecurityDescriptor } $AccessObjects = foreach ($ACL in $ACLs.Access) { $SplatFilteredACL = @{ ACL = $ACL Resolve = $Resolve Principal = $Principal Inherited = $Inherited NotInherited = $NotInherited AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName ExcludeObjectTypeName = $ExcludeObjectTypeName ExcludeInheritedObjectTypeName = $ExcludeInheritedObjectTypeName IncludeActiveDirectoryRights = $IncludeActiveDirectoryRights IncludeActiveDirectoryRightsExactMatch = $IncludeActiveDirectoryRightsExactMatch ExcludeActiveDirectoryRights = $ExcludeActiveDirectoryRights IncludeActiveDirectorySecurityInheritance = $IncludeActiveDirectorySecurityInheritance ExcludeActiveDirectorySecurityInheritance = $ExcludeActiveDirectorySecurityInheritance PrincipalRequested = $PrincipalRequested DistinguishedName = $DistinguishedName Bundle = $Bundle } Remove-EmptyValue -Hashtable $SplatFilteredACL Get-FilteredACL @SplatFilteredACL } if ($Bundle) { if ($Object.CanonicalName) { $CanonicalName = $Object.CanonicalName } else { $CanonicalName = ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToCanonicalName } [PSCustomObject] @{ DistinguishedName = $DistinguishedName CanonicalName = $CanonicalName ACL = $ACLs ACLAccessRules = $AccessObjects Path = $PathACL } } else { $AccessObjects } } } End { } } function Get-ADACLOwner { <# .SYNOPSIS Gets owner from given Active Directory object .DESCRIPTION Gets owner from given Active Directory object .PARAMETER ADObject Active Directory object to get owner from .PARAMETER Resolve Resolves owner to provide more details about said owner .PARAMETER IncludeACL Include additional ACL information along with owner .PARAMETER IncludeOwnerType Include only specific Owner Type, by default all Owner Types are included .PARAMETER ExcludeOwnerType Exclude specific Owner Type, by default all Owner Types are included .EXAMPLE Get-ADACLOwner -ADObject 'CN=Policies,CN=System,DC=ad,DC=evotec,DC=xyz' -Resolve | Format-Table .NOTES General notes #> [cmdletBinding()] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [alias('Identity')][Array] $ADObject, [switch] $Resolve, [alias('AddACL')][switch] $IncludeACL, [validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $IncludeOwnerType, [validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $ExcludeOwnerType #, # [System.Collections.IDictionary] $ADAdministrativeGroups, # [alias('ForestName')][string] $Forest, # [string[]] $ExcludeDomains, # [alias('Domain', 'Domains')][string[]] $IncludeDomains, # [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { if (-not $Script:ForestDetails) { Write-Verbose "Get-ADACL - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } } Process { foreach ($Object in $ADObject) { $ADObjectData = $null if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { if ($Object.ntSecurityDescriptor) { $ADObjectData = $Object } [string] $DistinguishedName = $Object.DistinguishedName [string] $CanonicalName = $Object.CanonicalName [string] $ObjectClass = $Object.ObjectClass } elseif ($Object -is [string]) { [string] $DistinguishedName = $Object [string] $CanonicalName = '' [string] $ObjectClass = '' } else { Write-Warning "Get-ADACLOwner - Object not recognized. Skipping..." continue } try { if (-not $ADObjectData) { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] try { $ADObjectData = Get-ADObject -Identity $DistinguishedName -Properties ntSecurityDescriptor, CanonicalName, ObjectClass -ErrorAction Stop -Server $QueryServer $ObjectClass = $ADObjectData.ObjectClass $CanonicalName = $ADObjectData.CanonicalName $ACLs = $ADObjectData.ntSecurityDescriptor } catch { Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)" continue } } else { $ACLs = $ADObjectData.ntSecurityDescriptor } $Hash = [ordered] @{ DistinguishedName = $DistinguishedName CanonicalName = $CanonicalName ObjectClass = $ObjectClass Owner = $ACLs.Owner } $ErrorMessage = '' } catch { $ACLs = $null $Hash = [ordered] @{ DistinguishedName = $DistinguishedName CanonicalName = $CanonicalName ObjectClass = $ObjectClass Owner = $null } $ErrorMessage = $_.Exception.Message } if ($IncludeACL) { $Hash['ACLs'] = $ACLs } if ($Resolve) { if ($null -eq $Hash.Owner) { $Identity = $null } else { $Identity = Convert-Identity -Identity $Hash.Owner -Verbose:$false } if ($Identity) { $Hash['OwnerName'] = $Identity.Name $Hash['OwnerSid'] = $Identity.SID $Hash['OwnerType'] = $Identity.Type } else { $Hash['OwnerName'] = '' $Hash['OwnerSid'] = '' $Hash['OwnerType'] = '' } if ($PSBoundParameters.ContainsKey('IncludeOwnerType')) { if ($Hash['OwnerType'] -in $IncludeOwnerType) { } else { continue } } if ($PSBoundParameters.ContainsKey('ExcludeOwnerType')) { if ($Hash['OwnerType'] -in $ExcludeOwnerType) { continue } } } $Hash['Error'] = $ErrorMessage [PSCustomObject] $Hash } } End { } } function Get-ADADministrativeGroups { <# .SYNOPSIS Retrieves administrative groups information from Active Directory. .DESCRIPTION This function retrieves information about administrative groups in Active Directory based on the specified parameters. .PARAMETER Type Specifies the type of administrative groups to retrieve. Valid values are 'DomainAdmins' and 'EnterpriseAdmins'. .PARAMETER Forest Specifies the name of the forest to query for administrative groups. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the query. .PARAMETER IncludeDomains Specifies an array of domains to include in the query. .PARAMETER ExtendedForestInformation Specifies additional information about the forest to include in the query. .EXAMPLE Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins Output (Where VALUE is Get-ADGroup output): Name Value ---- ----- ByNetBIOS {EVOTEC\Domain Admins, EVOTEC\Enterprise Admins, EVOTECPL\Domain Admins} ad.evotec.xyz {DomainAdmins, EnterpriseAdmins} ad.evotec.pl {DomainAdmins} .NOTES This function requires Active Directory module to be installed on the system. #> [cmdletBinding()] param( [parameter(Mandatory)][validateSet('DomainAdmins', 'EnterpriseAdmins')][string[]] $Type, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $ADDictionary = [ordered] @{ } $ADDictionary['ByNetBIOS'] = [ordered] @{ } $ADDictionary['BySID'] = [ordered] @{ } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $ADDictionary[$Domain] = [ordered] @{ } $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $DomainInformation = Get-ADDomain -Server $QueryServer if ($Type -contains 'DomainAdmins') { Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-512'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_ $ADDictionary[$Domain]['DomainAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)" $ADDictionary['BySID'][$_.SID.Value] = $_ } } } foreach ($Domain in $ForestInformation.Forest.Domains) { if (-not $ADDictionary[$Domain]) { $ADDictionary[$Domain] = [ordered] @{ } } if ($Type -contains 'EnterpriseAdmins') { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $DomainInformation = Get-ADDomain -Server $QueryServer Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-519'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_ $ADDictionary[$Domain]['EnterpriseAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)" $ADDictionary['BySID'][$_.SID.Value] = $_ } } } return $ADDictionary } function Get-Computer { <# .SYNOPSIS Retrieves various information about a computer or server based on specified types. .DESCRIPTION This function retrieves information about a computer or server based on the specified types. It can gather details about applications, BIOS, CPU, RAM, Disk, Logical Disk, Network, Network Firewall, Operating System, Services, System, Startup, Time, and Windows Updates. .PARAMETER ComputerName Specifies the name of the computer or server to retrieve information from. Defaults to the local computer. .PARAMETER Type Specifies the types of information to retrieve. Valid values include 'Application', 'BIOS', 'CPU', 'RAM', 'Disk', 'DiskLogical', 'Network', 'NetworkFirewall', 'OperatingSystem', 'Services', 'System', 'Startup', 'Time', and 'WindowsUpdates'. If not specified, retrieves all available types. .PARAMETER AsHashtable Indicates whether to return the output as a hashtable. .EXAMPLE Get-Computer -ComputerName "Server01" -Type "CPU", "RAM" Retrieves CPU and RAM information from a remote server named Server01. .EXAMPLE Get-Computer -ComputerName "Workstation01" -Type "Application" -AsHashtable Retrieves application information from a workstation named Workstation01 and returns the output as a hashtable. #> [cmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Application', 'BIOS', 'CPU', 'RAM', 'Disk', 'DiskLogical', 'Network', 'NetworkFirewall', 'OperatingSystem', 'Services', 'System', 'Startup', 'Time', 'WindowsUpdates' )][string[]] $Type, [switch] $AsHashtable ) Begin { } Process { foreach ($Computer in $ComputerName) { $OutputObject = [ordered] @{} if ($Type -contains 'Application' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Application for $Computer" $Application = Get-ComputerApplication -ComputerName $Computer $OutputObject['Application'] = $Application } if ($Type -contains 'BIOS' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing BIOS for $Computer" $BIOS = Get-ComputerBios -ComputerName $Computer $OutputObject['BIOS'] = $BIOS } if ($Type -contains 'CPU' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing CPU for $Computer" $CPU = Get-ComputerCPU -ComputerName $Computer $OutputObject['CPU'] = $CPU } if ($Type -contains 'RAM' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing RAM for $Computer" $RAM = Get-ComputerRAM -ComputerName $Computer $OutputObject['RAM'] = $RAM } if ($Type -contains 'Disk' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Disk for $Computer" $Disk = Get-ComputerDisk -ComputerName $Computer $OutputObject['Disk'] = $Disk } if ($Type -contains 'DiskLogical' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing DiskLogical for $Computer" $DiskLogical = Get-ComputerDiskLogical -ComputerName $Computer $OutputObject['DiskLogical'] = $DiskLogical } if ($Type -contains 'OperatingSystem' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing OperatingSystem for $Computer" $OperatingSystem = Get-ComputerOperatingSystem -ComputerName $Computer $OutputObject['OperatingSystem'] = $OperatingSystem } if ($Type -contains 'Network' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Network for $Computer" $Network = Get-ComputerNetwork -ComputerName $Computer $OutputObject['Network'] = $Network } if ($Type -contains 'NetworkFirewall' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing NetworkFirewall for $Computer" $NetworkFirewall = Get-ComputerNetwork -ComputerName $Computer -NetworkFirewallOnly $OutputObject['NetworkFirewall'] = $NetworkFirewall } if ($Type -contains 'RDP' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing RDP for $Computer" $RDP = Get-ComputerRDP -ComputerName $Computer $OutputObject['RDP'] = $RDP } if ($Type -contains 'Services' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Services for $Computer" $Services = Get-ComputerService -ComputerName $Computer $OutputObject['Services'] = $Services } if ($Type -contains 'System' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing System for $Computer" $System = Get-ComputerSystem -ComputerName $Computer $OutputObject['System'] = $System } if ($Type -contains 'Startup' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Startup for $Computer" $Startup = Get-ComputerStartup -ComputerName $Computer $OutputObject['Startup'] = $Startup } if ($Type -contains 'Time' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Time for $Computer" $Time = Get-ComputerTime -TimeTarget $Computer $OutputObject['Time'] = $Time } if ($Type -contains 'Tasks' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Tasks for $Computer" $Tasks = Get-ComputerTask -ComputerName $Computer $OutputObject['Tasks'] = $Tasks } if ($Type -contains 'WindowsUpdates' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing WindowsUpdates for $Computer" $WindowsUpdates = Get-ComputerWindowsUpdates -ComputerName $Computer $OutputObject['WindowsUpdates'] = $WindowsUpdates } if ($AsHashtable) { $OutputObject } else { [PSCustomObject] $OutputObject } } } } function Get-FileMetaData { <# .SYNOPSIS Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file .DESCRIPTION Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file .PARAMETER File FileName or FileObject .EXAMPLE Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties .EXAMPLE Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Where-Object { $_.Attributes -like '*Hidden*' } | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties #> [CmdletBinding()] param ( [Parameter(Position = 0, ValueFromPipeline)][Object] $File, [ValidateSet('None', 'MACTripleDES', 'MD5', 'RIPEMD160', 'SHA1', 'SHA256', 'SHA384', 'SHA512')][string] $HashAlgorithm = 'None', [switch] $Signature, [switch] $AsHashTable ) Process { foreach ($F in $File) { $MetaDataObject = [ordered] @{} if ($F -is [string]) { if ($F -and (Test-Path -LiteralPath $F)) { $FileInformation = Get-ItemProperty -Path $F if ($FileInformation -is [System.IO.DirectoryInfo]) { continue } } else { Write-Warning "Get-FileMetaData - Doesn't exists. Skipping $F." continue } } elseif ($F -is [System.IO.DirectoryInfo]) { continue } elseif ($F -is [System.IO.FileInfo]) { $FileInformation = $F } else { Write-Warning "Get-FileMetaData - Only files are supported. Skipping $F." continue } $ShellApplication = New-Object -ComObject Shell.Application $ShellFolder = $ShellApplication.Namespace($FileInformation.Directory.FullName) $ShellFile = $ShellFolder.ParseName($FileInformation.Name) $MetaDataProperties = [ordered] @{} 0..400 | ForEach-Object -Process { $DataValue = $ShellFolder.GetDetailsOf($null, $_) $PropertyValue = (Get-Culture).TextInfo.ToTitleCase($DataValue.Trim()).Replace(' ', '') if ($PropertyValue -ne '') { $MetaDataProperties["$_"] = $PropertyValue } } foreach ($Key in $MetaDataProperties.Keys) { $Property = $MetaDataProperties[$Key] $Value = $ShellFolder.GetDetailsOf($ShellFile, [int] $Key) if ($Property -in 'Attributes', 'Folder', 'Type', 'SpaceFree', 'TotalSize', 'SpaceUsed') { continue } If (($null -ne $Value) -and ($Value -ne '')) { $MetaDataObject["$Property"] = $Value } } if ($FileInformation.VersionInfo) { $SplitInfo = ([string] $FileInformation.VersionInfo).Split([char]13) foreach ($Item in $SplitInfo) { $Property = $Item.Split(":").Trim() if ($Property[0] -and $Property[1] -ne '') { if ($Property[1] -in 'False', 'True') { $MetaDataObject["$($Property[0])"] = [bool] $Property[1] } else { $MetaDataObject["$($Property[0])"] = $Property[1] } } } } $MetaDataObject["Attributes"] = $FileInformation.Attributes $MetaDataObject['IsReadOnly'] = $FileInformation.IsReadOnly $MetaDataObject['IsHidden'] = $FileInformation.Attributes -like '*Hidden*' $MetaDataObject['IsSystem'] = $FileInformation.Attributes -like '*System*' if ($Signature) { $DigitalSignature = Get-AuthenticodeSignature -FilePath $FileInformation.Fullname $MetaDataObject['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject $MetaDataObject['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer $MetaDataObject['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber $MetaDataObject['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore $MetaDataObject['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter $MetaDataObject['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint $MetaDataObject['SignatureStatus'] = $DigitalSignature.Status $MetaDataObject['IsOSBinary'] = $DigitalSignature.IsOSBinary } if ($HashAlgorithm -ne 'None') { $MetaDataObject[$HashAlgorithm] = (Get-FileHash -LiteralPath $FileInformation.FullName -Algorithm $HashAlgorithm).Hash } if ($AsHashTable) { $MetaDataObject } else { [PSCustomObject] $MetaDataObject } } } } function Get-FileName { <# .SYNOPSIS Generates a temporary file name with the specified extension. .DESCRIPTION This function generates a temporary file name based on the provided extension. It can generate a temporary file name in the system's temporary folder or just the file name itself. .PARAMETER Extension Specifies the extension for the temporary file name. Default is 'tmp'. .PARAMETER Temporary Indicates whether to generate a temporary file name in the system's temporary folder. .PARAMETER TemporaryFileOnly Indicates whether to generate only the temporary file name without the path. .EXAMPLE Get-FileName -Temporary Generates a temporary file name in the system's temporary folder. Example output: 3ymsxvav.tmp .EXAMPLE Get-FileName -Temporary Generates a temporary file name without the path. Example output: tmpD74C.tmp .EXAMPLE Get-FileName -Temporary -Extension 'xlsx' Generates a temporary file name with the specified extension in the system's temporary folder. Example output: tmp45B6.xlsx .NOTES These examples demonstrate how to use the Get-FileName function to generate temporary file names. #> [CmdletBinding()] param( [string] $Extension = 'tmp', [switch] $Temporary, [switch] $TemporaryFileOnly ) if ($Temporary) { return [io.path]::Combine([System.IO.Path]::GetTempPath(), "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension") } if ($TemporaryFileOnly) { return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension" } } function Get-FileOwner { <# .SYNOPSIS Retrieves the owner of the specified file or folder. .DESCRIPTION This function retrieves the owner of the specified file or folder. It provides options to resolve the owner's identity and output the results as a hashtable or custom object. .PARAMETER Path Specifies the path to the file or folder. .PARAMETER Recursive Indicates whether to search for files recursively in subdirectories. .PARAMETER JustPath Specifies if only the path information should be returned. .PARAMETER Resolve Indicates whether to resolve the owner's identity. .PARAMETER AsHashTable Specifies if the output should be in hashtable format. .EXAMPLE Get-FileOwner -Path "C:\Example\File.txt" Retrieves the owner of the specified file "File.txt". .EXAMPLE Get-FileOwner -Path "C:\Example" -Recursive Retrieves the owners of all files in the "Example" directory and its subdirectories. .EXAMPLE Get-FileOwner -Path "C:\Example\File.txt" -Resolve Retrieves the owner of the specified file "File.txt" and resolves the owner's identity. .EXAMPLE Get-FileOwner -Path "C:\Example\File.txt" -AsHashTable Retrieves the owner of the specified file "File.txt" and outputs the result as a hashtable. #> [cmdletBinding()] param( [Array] $Path, [switch] $Recursive, [switch] $JustPath, [switch] $Resolve, [switch] $AsHashTable ) Begin { } Process { foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } if ($FullPath -and (Test-Path -Path $FullPath)) { if ($JustPath) { $FullPath | ForEach-Object -Process { $ACL = Get-Acl -Path $_ $Object = [ordered]@{ FullName = $_ Owner = $ACL.Owner } if ($Resolve) { $Identity = Convert-Identity -Identity $ACL.Owner if ($Identity) { $Object['OwnerName'] = $Identity.Name $Object['OwnerSid'] = $Identity.SID $Object['OwnerType'] = $Identity.Type } else { $Object['OwnerName'] = '' $Object['OwnerSid'] = '' $Object['OwnerType'] = '' } } if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } } else { Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -Force | ForEach-Object -Process { $File = $_ $ACL = Get-Acl -Path $File.FullName $Object = [ordered] @{ FullName = $_.FullName Extension = $_.Extension CreationTime = $_.CreationTime LastAccessTime = $_.LastAccessTime LastWriteTime = $_.LastWriteTime Attributes = $_.Attributes Owner = $ACL.Owner } if ($Resolve) { $Identity = Convert-Identity -Identity $ACL.Owner if ($Identity) { $Object['OwnerName'] = $Identity.Name $Object['OwnerSid'] = $Identity.SID $Object['OwnerType'] = $Identity.Type } else { $Object['OwnerName'] = '' $Object['OwnerSid'] = '' $Object['OwnerType'] = '' } } if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } } } } } End { } } function Get-FilePermission { <# .SYNOPSIS Retrieves and displays file permissions for the specified file or folder. .DESCRIPTION This function retrieves and displays the file permissions for the specified file or folder. It provides options to filter permissions based on inheritance, resolve access control types, and include extended information. .EXAMPLE Get-FilePermission -Path "C:\Example\File.txt" Description: Retrieves and displays the permissions for the "File.txt" file. .EXAMPLE Get-FilePermission -Path "D:\Folder" -Inherited Description: Retrieves and displays only the inherited permissions for the "Folder" directory. .EXAMPLE Get-FilePermission -Path "E:\Document.docx" -ResolveTypes -Extended Description: Retrieves and displays the resolved access control types and extended information for the "Document.docx" file. .NOTES This function supports various options to customize the output and handle different permission scenarios. #> [alias('Get-PSPermissions', 'Get-FilePermissions')] [cmdletBinding()] param( [Array] $Path, [switch] $Inherited, [switch] $NotInherited, [switch] $ResolveTypes, [switch] $Extended, [switch] $IncludeACLObject, [switch] $AsHashTable, [System.Security.AccessControl.FileSystemSecurity] $ACLS ) foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } $TestPath = Test-Path -Path $FullPath if ($TestPath) { if (-not $ACLS) { try { $ACLS = (Get-Acl -Path $FullPath -ErrorAction Stop) } catch { Write-Warning -Message "Get-FilePermission - Can't access $FullPath. Error $($_.Exception.Message)" continue } } $Output = foreach ($ACL in $ACLS.Access) { if ($Inherited) { if ($ACL.IsInherited -eq $false) { continue } } if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } } $TranslateRights = Convert-GenericRightsToFileSystemRights -OriginalRights $ACL.FileSystemRights $ReturnObject = [ordered] @{ } $ReturnObject['Path' ] = $FullPath $ReturnObject['AccessControlType'] = $ACL.AccessControlType if ($ResolveTypes) { $Identity = Convert-Identity -Identity $ACL.IdentityReference if ($Identity) { $ReturnObject['Principal'] = $ACL.IdentityReference $ReturnObject['PrincipalName'] = $Identity.Name $ReturnObject['PrincipalSid'] = $Identity.Sid $ReturnObject['PrincipalType'] = $Identity.Type } else { $ReturnObject['Principal'] = $Identity $ReturnObject['PrincipalName'] = '' $ReturnObject['PrincipalSid'] = '' $ReturnObject['PrincipalType'] = '' } } else { $ReturnObject['Principal'] = $ACL.IdentityReference.Value } $ReturnObject['FileSystemRights'] = $TranslateRights $ReturnObject['IsInherited'] = $ACL.IsInherited if ($Extended) { $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags } if ($IncludeACLObject) { $ReturnObject['ACL'] = $ACL $ReturnObject['AllACL'] = $ACLS } if ($AsHashTable) { $ReturnObject } else { [PSCustomObject] $ReturnObject } } $Output } else { Write-Warning "Get-PSPermissions - Path $Path doesn't exists. Skipping." } } } function Get-GitHubLatestRelease { <# .SYNOPSIS Gets one or more releases from GitHub repository .DESCRIPTION Gets one or more releases from GitHub repository .PARAMETER Url Url to github repository .EXAMPLE Get-GitHubLatestRelease -Url "https://api.github.com/repos/evotecit/Testimo/releases" | Format-Table .NOTES General notes #> [CmdLetBinding()] param( [parameter(Mandatory)][alias('ReleasesUrl')][uri] $Url ) $ProgressPreference = 'SilentlyContinue' $Responds = Test-Connection -ComputerName $URl.Host -Quiet -Count 1 if ($Responds) { 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 } } } else { [PSCustomObject] @{ PublishDate = $null CreatedDate = $null PreRelease = $null Version = $null Tag = $null Branch = $null Errors = "No connection (ping) to $($Url.Host)" } } $ProgressPreference = 'Continue' } function Get-PSRegistry { <# .SYNOPSIS Get registry key values. .DESCRIPTION Get registry key values. .PARAMETER RegistryPath The registry path to get the values from. .PARAMETER ComputerName The computer to get the values from. If not specified, the local computer is used. .PARAMETER ExpandEnvironmentNames Expand environment names in the registry value. By default it doesn't do that. If you want to expand environment names, use this parameter. .EXAMPLE Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -ComputerName AD1 .EXAMPLE Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' .EXAMPLE Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName AD1,AD2,AD3 | ft -AutoSize .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service' .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Windows PowerShell' | Format-Table -AutoSize .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service' -ComputerName AD1 -Advanced .EXAMPLE Get-PSRegistry -RegistryPath "HKLM:\Software\Microsoft\Powershell\1\Shellids\Microsoft.Powershell\" .EXAMPLE # Get default key and it's value Get-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -Key "" .EXAMPLE # Get default key and it's value (alternative) Get-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -DefaultKey .NOTES General notes #> [cmdletbinding()] param( [alias('Path')][string[]] $RegistryPath, [string[]] $ComputerName = $Env:COMPUTERNAME, [string] $Key, [switch] $Advanced, [switch] $DefaultKey, [switch] $ExpandEnvironmentNames, [Parameter(DontShow)][switch] $DoNotUnmount ) $Script:CurrentGetCount++ Get-PSRegistryDictionaries $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent if ($PSBoundParameters.ContainsKey("Key") -or $DefaultKey) { [Array] $RegistryValues = Get-PSSubRegistryTranslated -RegistryPath $RegistryTranslated -HiveDictionary $Script:HiveDictionary -Key $Key foreach ($Computer in $Computers[0]) { foreach ($R in $RegistryValues) { Get-PSSubRegistry -Registry $R -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } foreach ($Computer in $Computers[1]) { foreach ($R in $RegistryValues) { Get-PSSubRegistry -Registry $R -ComputerName $Computer -Remote -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } } else { [Array] $RegistryValues = Get-PSSubRegistryTranslated -RegistryPath $RegistryTranslated -HiveDictionary $Script:HiveDictionary foreach ($Computer in $Computers[0]) { foreach ($R in $RegistryValues) { Get-PSSubRegistryComplete -Registry $R -ComputerName $Computer -Advanced:$Advanced -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } foreach ($Computer in $Computers[1]) { foreach ($R in $RegistryValues) { Get-PSSubRegistryComplete -Registry $R -ComputerName $Computer -Remote -Advanced:$Advanced -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } } $Script:CurrentGetCount-- if ($Script:CurrentGetCount -eq 0) { if (-not $DoNotUnmount) { Unregister-MountedRegistry } } } function Get-WinADDuplicateObject { <# .SYNOPSIS Get duplicate objects in Active Directory (CNF: and CNF:0ACNF:) .DESCRIPTION Get duplicate objects in Active Directory (CNF: and CNF:0ACNF:) CNF stands for "Conflict". CNF objects are created when there is a naming conflict in the Active Directory. This usually happens during the replication process when two objects are created with the same name in different parts of the replication topology, and then a replication attempt is made. Active Directory resolves this by renaming one of the objects with a CNF prefix and a GUID. The object with the CNF name is usually the loser in the conflict resolution process. .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER PartialMatchDistinguishedName Limit results to specific DistinguishedName .PARAMETER IncludeObjectClass Limit results to specific ObjectClass .PARAMETER ExcludeObjectClass Exclude specific ObjectClass .PARAMETER Extended Provide extended information about the object .PARAMETER NoPostProcessing Do not post process the object, return as is from the AD .EXAMPLE Get-WinADDuplicateObject -Verbose | Format-Table .NOTES General notes #> [alias('Get-WinADForestObjectsConflict')] [CmdletBinding()] Param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string] $PartialMatchDistinguishedName, [string[]] $IncludeObjectClass, [string[]] $ExcludeObjectClass, [switch] $Extended, [switch] $NoPostProcessing ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended foreach ($Domain in $ForestInformation.Domains) { $DomainInformation = $ForestInformation.DomainsExtended[$Domain] Write-Verbose -Message "Get-WinADDuplicateObject - Processing $($Domain)" $Partitions = @( if ($Domain -eq $ForestInformation.Forest) { "CN=Configuration,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)" if ($DomainInformation.SubordinateReferences -contains "DC=ForestDnsZones,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)") { "DC=ForestDnsZones,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)" } else { Write-Warning -Message "Get-WinADDuplicateObject - ForestDnsZones not found for domain '$Domain'. Skipping" } } $ForestInformation['DomainsExtended'][$Domain].DistinguishedName if ($DomainInformation.SubordinateReferences -contains "DC=DomainDnsZones,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)") { "DC=DomainDnsZones,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)" } else { Write-Warning -Message "Get-WinADDuplicateObject - DomainDnsZones not found for domain '$Domain'. Skipping" } ) $DC = $ForestInformation['QueryServers']["$Domain"].HostName[0] foreach ($Partition in $Partitions) { Write-Verbose -Message "Get-WinADDuplicateObject - Processing $($Domain) - $($Partition)" $getADObjectSplat = @{ LDAPFilter = "(|(cn=*\0ACNF:*)(ou=*CNF:*))" Properties = 'DistinguishedName', 'ObjectClass', 'DisplayName', 'SamAccountName', 'Name', 'ObjectCategory', 'WhenCreated', 'WhenChanged', 'ProtectedFromAccidentalDeletion', 'ObjectGUID' Server = $DC SearchScope = 'Subtree' } try { $Objects = Get-ADObject @getADObjectSplat -SearchBase $Partition -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADDuplicateObject - Getting objects from domain '$Domain' / partition: '$Partition' failed. Error: $($Object.Exception.Message)" continue } foreach ($Object in $Objects) { if ($ExcludeObjectClass) { if ($ExcludeObjectClass -contains $Object.ObjectClass) { continue } } if ($IncludeObjectClass) { if ($IncludeObjectClass -notcontains $Object.ObjectClass) { continue } } if ($PartialMatchDistinguishedName) { if ($Object.DistinguishedName -notlike $PartialMatchDistinguishedName) { continue } } if ($NoPostProcessing) { $Object continue } $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $Object.DistinguishedName -ToDomainCN $ConflictObject = [ordered] @{ ConflictDN = $Object.DistinguishedName ConflictWhenChanged = $Object.WhenChanged DomainName = $DomainName ObjectClass = $Object.ObjectClass } $LiveObjectData = [ordered] @{ LiveDn = "N/A" LiveWhenChanged = "N/A" } $RestData = [ordered] @{ DisplayName = $Object.DisplayName Name = $Object.Name.Replace("`n", ' ') SamAccountName = $Object.SamAccountName ObjectCategory = $Object.ObjectCategory WhenCreated = $Object.WhenCreated WhenChanged = $Object.WhenChanged ProtectedFromAccidentalDeletion = $Object.ProtectedFromAccidentalDeletion ObjectGUID = $Object.ObjectGUID.Guid Server = $DC SearchBase = $Partition } if ($Extended) { $LiveObject = $null $ConflictObject = $ConflictObject + $LiveObjectData + $RestData if (Select-String -SimpleMatch "\0ACNF:" -InputObject $ConflictObject.ConflictDn) { $SplitConfDN = $ConflictObject.ConflictDn -split "0ACNF:" try { $LiveObject = Get-ADObject -Identity "$($SplitConfDN[0].TrimEnd("\"))$($SplitConfDN[1].Substring(36))" -Properties WhenChanged -Server $DC -ErrorAction Stop } catch { } if ($LiveObject) { $ConflictObject.LiveDN = $LiveObject.DistinguishedName $ConflictObject.LiveWhenChanged = $LiveObject.WhenChanged } } else { $SplitConfDN = $ConflictObject.ConflictDn -split "CNF:" try { $LiveObject = Get-ADObject -Identity "$($SplitConfDN[0])$($SplitConfDN[1].Substring(36))" -Properties WhenChanged -Server $DC -ErrorAction Stop } catch { } if ($LiveObject) { $ConflictObject.LiveDN = $LiveObject.DistinguishedName $ConflictObject.LiveWhenChanged = $LiveObject.WhenChanged } } } else { $ConflictObject = $ConflictObject + $RestData } [PSCustomObject] $ConflictObject } } } } function Get-WinADForestDetails { <# .SYNOPSIS Get details about Active Directory Forest, Domains and Domain Controllers in a single query .DESCRIPTION Get details about Active Directory Forest, Domains and Domain Controllers in a single query .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExcludeDomainControllers Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER IncludeDomainControllers Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER SkipRODC Skip Read-Only Domain Controllers. By default all domain controllers are included. .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER Filter Filter for Get-ADDomainController .PARAMETER TestAvailability Check if Domain Controllers are available .PARAMETER Test Pick what to check for availability. Options are: All, Ping, WinRM, PortOpen, Ping+WinRM, Ping+PortOpen, WinRM+PortOpen. Default is All .PARAMETER Ports Ports to check for availability. Default is 135 .PARAMETER PortsTimeout Ports timeout for availability check. Default is 100 .PARAMETER PingCount How many pings to send. Default is 1 .PARAMETER PreferWritable Prefer writable domain controllers over read-only ones when returning Query Servers .PARAMETER Extended Return extended information about domains with NETBIOS names .EXAMPLE Get-WinADForestDetails | Format-Table .EXAMPLE Get-WinADForestDetails -Forest 'ad.evotec.xyz' | Format-Table .NOTES General notes #> [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] $PreferWritable, [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 ($Domain in $ForestInformation.Domains) { if ($IncludeDomains) { if ($Domain -in $IncludeDomains) { $Domain.ToLower() } continue } if ($Domain -notin $ExcludeDomains) { $Domain.ToLower() } } [Array] $DomainsActive = foreach ($Domain in $Findings['Forest'].Domains) { try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop -Writable:$PreferWritable.IsPresent $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 $Domain } [Array] $Findings['Domains'] = foreach ($Domain in $Findings['Domains']) { if ($Domain -notin $DomainsActive) { Write-Warning "Get-WinADForestDetails - Domain $Domain doesn't seem to be active (no DCs). Skipping." continue } $Domain } [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 } } } $DSAGuid = (Get-ADObject -Identity $S.NTDSSettingsObjectDN -Server $QueryServer).ObjectGUID $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 NTDSSettingsObjectDN = $S.NTDSSettingsObjectDN DsaGuid = $DSAGuid DsaGuidName = "$DSAGuid._msdcs.$($ForestInformation.RootDomain)" 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 = '' NTDSSettingsObjectDN = '' DsaGuid = '' DsaGuidName = '' 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 } if ($null -ne $Findings['DomainDomainControllers'][$Domain]) { [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 Get-WinADObject { <# .SYNOPSIS Gets Active Directory Object .DESCRIPTION Returns Active Directory Object (Computers, Groups, Users or ForeignSecurityPrincipal) using ADSI .PARAMETER Identity Identity of an object. It can be SamAccountName, SID, DistinguishedName or multiple other options .PARAMETER DomainName Choose domain name the objects resides in. This is optional for most objects .PARAMETER Credential Parameter description .PARAMETER IncludeGroupMembership Queries for group members when object is a group .PARAMETER IncludeAllTypes Allows functions to return all objects types and not only Computers, Groups, Users or ForeignSecurityPrincipal .EXAMPLE Get-WinADObject -Identity 'TEST\Domain Admins' -Verbose Get-WinADObject -Identity 'EVOTEC\Domain Admins' -Verbose Get-WinADObject -Identity 'Domain Admins' -DomainName 'DC=AD,DC=EVOTEC,DC=PL' -Verbose Get-WinADObject -Identity 'Domain Admins' -DomainName 'ad.evotec.pl' -Verbose Get-WinADObject -Identity 'CN=Domain Admins,CN=Users,DC=ad,DC=evotec,DC=pl' Get-WinADObject -Identity 'CN=Domain Admins,CN=Users,DC=ad,DC=evotec,DC=xyz' .NOTES General notes #> [cmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][Array] $Identity, [string] $DomainName, [pscredential] $Credential, [switch] $IncludeGroupMembership, [switch] $IncludeAllTypes, [switch] $AddType, [switch] $Cache, [string[]] $Properties ) Begin { if ($Cache -and -not $Script:CacheObjectsWinADObject) { $Script:CacheObjectsWinADObject = @{} } Add-Type -AssemblyName System.DirectoryServices.AccountManagement $GroupTypes = @{ '2' = @{ Name = 'Distribution Group - Global' Type = 'Distribution' Scope = 'Global' } '4' = @{ Name = 'Distribution Group - Domain Local' Type = 'Distribution' Scope = 'Domain local' } '8' = @{ Name = 'Distribution Group - Universal' Type = 'Distribution' Scope = 'Universal' } '-2147483640' = @{ Name = 'Security Group - Universal' Type = 'Security' Scope = 'Universal' } '-2147483643' = @{ Name = 'Security Group - Builtin Local' Type = 'Security' Scope = 'Builtin local' } '-2147483644' = @{ Name = 'Security Group - Domain Local' Type = 'Security' Scope = 'Domain local' } '-2147483646' = @{ Name = 'Security Group - Global' Type = 'Security' Scope = 'Global' } } } process { foreach ($Ident in $Identity) { if (-not $Ident) { Write-Warning -Message "Get-WinADObject - Identity is empty. Skipping" continue } $ResolvedIdentity = $null if ($Ident.DistinguishedName) { $Ident = $Ident.DistinguishedName } $TemporaryName = $Ident $TemporaryDomainName = $DomainName if ($Cache -and $Script:CacheObjectsWinADObject[$TemporaryName]) { Write-Verbose "Get-WinADObject - Requesting $TemporaryName from Cache" $Script:CacheObjectsWinADObject[$TemporaryName] continue } if (-not $TemporaryDomainName) { $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+") if ($MatchRegex.Success) { $ResolvedIdentity = ConvertFrom-SID -SID $MatchRegex.Value $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $MatchRegex.Value } elseif ($Ident -like '*\*') { $ResolvedIdentity = Convert-Identity -Identity $Ident -Verbose:$false if ($ResolvedIdentity.SID) { $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $ResolvedIdentity.SID } else { $NetbiosConversion = ConvertFrom-NetbiosName -Identity $Ident if ($NetbiosConversion.DomainName) { $TemporaryDomainName = $NetbiosConversion.DomainName $Ident = $NetbiosConversion.Name } } } elseif ($Ident -like '*DC=*') { $DNConversion = ConvertFrom-DistinguishedName -DistinguishedName $Ident -ToDomainCN $TemporaryDomainName = $DNConversion } elseif ($Ident -like '*@*') { $CNConversion = $Ident -split '@', 2 $TemporaryDomainName = $CNConversion[1] $Ident = $CNConversion[0] } elseif ($Ident -like '*.*') { $ResolvedIdentity = Convert-Identity -Identity $Ident -Verbose:$false if ($ResolvedIdentity.SID) { $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $ResolvedIdentity.SID } else { $CNConversion = $Ident -split '\.', 2 $Ident = $CNConversion[0] $TemporaryDomainName = $CNConversion[1] } } else { $ResolvedIdentity = Convert-Identity -Identity $Ident -Verbose:$false if ($ResolvedIdentity.SID) { $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $ResolvedIdentity.SID } else { $NetbiosConversion = ConvertFrom-NetbiosName -Identity $Ident if ($NetbiosConversion.DomainName) { $TemporaryDomainName = $NetbiosConversion.DomainName $Ident = $NetbiosConversion.Name } } } } $Search = [System.DirectoryServices.DirectorySearcher]::new() if ($TemporaryDomainName) { try { $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain', $TemporaryDomainName) } catch { Write-Warning "Get-WinADObject - Building context failed ($TemporaryDomainName), error: $($_.Exception.Message)" } } else { try { $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain') } catch { Write-Warning "Get-WinADObject - Building context failed, error: $($_.Exception.Message)" } } Try { $IdentityGUID = "" ([System.Guid]$Ident).ToByteArray() | ForEach-Object { $IdentityGUID += $("\{0:x2}" -f $_) } } Catch { $IdentityGUID = "null" } $Search.filter = "(|(DistinguishedName=$Ident)(Name=$Ident)(SamAccountName=$Ident)(UserPrincipalName=$Ident)(objectGUID=$IdentityGUID)(objectSid=$Ident))" if ($TemporaryDomainName) { $Search.SearchRoot = "LDAP://$TemporaryDomainName" } if ($PSBoundParameters['Credential']) { $Cred = [System.DirectoryServices.DirectoryEntry]::new("LDAP://$TemporaryDomainName", $($Credential.UserName), $($Credential.GetNetworkCredential().password)) $Search.SearchRoot = $Cred } Write-Verbose "Get-WinADObject - Requesting $Ident ($TemporaryDomainName)" try { $SearchResults = $($Search.FindAll()) } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "Get-WinADObject - Requesting $Ident ($TemporaryDomainName) failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" } else { Write-Warning "Get-WinADObject - Requesting $Ident ($TemporaryDomainName) failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" continue } } if ($SearchResults.Count -lt 1) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "Requesting $Ident ($TemporaryDomainName) failed with no results." } } foreach ($Object in $SearchResults) { $UAC = Convert-UserAccountControl -UserAccountControl ($Object.properties.useraccountcontrol -as [string]) $ObjectClass = ($Object.properties.objectclass -as [array])[-1] if ($ObjectClass -notin 'group', 'contact', 'inetOrgPerson', 'computer', 'user', 'foreignSecurityPrincipal', 'msDS-ManagedServiceAccount', 'msDS-GroupManagedServiceAccount' -and (-not $IncludeAllTypes)) { Write-Warning "Get-WinADObject - Unsupported object ($Ident) of type $ObjectClass. Only user,computer,group, foreignSecurityPrincipal, msDS-ManagedServiceAccount, msDS-GroupManagedServiceAccount are displayed by default. Use IncludeAllTypes switch to display all if nessecary." continue } $Members = $Object.properties.member -as [array] if ($ObjectClass -eq 'group') { if ($IncludeGroupMembership) { $GroupMembers = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context, $Ident).Members try { $Members = [System.Collections.Generic.List[string]]::new() foreach ($Member in $Object.properties.member) { if ($Member) { $Members.Add($Member) } } foreach ($Member in $GroupMembers) { if ($Member.DistinguishedName) { if ($Member.DistinguishedName -notin $Members) { $Members.Add($Member.DistinguishedName) } } elseif ($Member.DisplayName) { $Members.Add($Member.DisplayName) } else { $Members.Add($Member.Sid) } } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw return } else { Write-Warning -Message "Error while parsing group members for $($Ident): $($_.Exception.Message)" } } } } $ObjectDomainName = ConvertFrom-DistinguishedName -DistinguishedName ($Object.properties.distinguishedname -as [string]) -ToDomainCN $DisplayName = $Object.properties.displayname -as [string] $SamAccountName = $Object.properties.samaccountname -as [string] $Name = $Object.properties.name -as [string] if ($ObjectClass -eq 'foreignSecurityPrincipal' -and $DisplayName -eq '') { $DisplayName = $ResolvedIdentity.Name if ($DisplayName -like '*\*') { $NetbiosWithName = $DisplayName -split '\\' if ($NetbiosWithName.Count -eq 2) { $NetbiosUser = $NetbiosWithName[1] $Name = $NetbiosUser $SamAccountName = $NetbiosUser } else { $Name = $DisplayName } } else { $Name = $DisplayName } } $GroupType = $Object.properties.grouptype -as [string] if ($Object.Properties.objectsid) { try { $ObjectSID = [System.Security.Principal.SecurityIdentifier]::new($Object.Properties.objectsid[0], 0).Value } catch { Write-Warning "Get-WinADObject - Getting objectsid failed, error: $($_.Exception.Message)" $ObjectSID = $null } } else { $ObjectSID = $null } $ReturnObject = [ordered] @{ DisplayName = $DisplayName Name = $Name SamAccountName = $SamAccountName ObjectClass = $ObjectClass Enabled = if ($ObjectClass -in 'group', 'contact') { $null } else { $UAC -notcontains 'ACCOUNTDISABLE' } PasswordNeverExpire = if ($ObjectClass -in 'group', 'contact') { $null } else { $UAC -contains 'DONT_EXPIRE_PASSWORD' } DomainName = $ObjectDomainName Distinguishedname = $Object.properties.distinguishedname -as [string] WhenCreated = $Object.properties.whencreated -as [string] WhenChanged = $Object.properties.whenchanged -as [string] UserPrincipalName = $Object.properties.userprincipalname -as [string] ObjectSID = $ObjectSID MemberOf = $Object.properties.memberof -as [array] Members = $Members DirectReports = $Object.Properties.directreports GroupScopedType = $GroupTypes[$GroupType].Name GroupScope = $GroupTypes[$GroupType].Scope GroupType = $GroupTypes[$GroupType].Type Description = $Object.properties.description -as [string] } if ($Properties -contains 'LastLogonDate') { $LastLogon = [int64] $Object.properties.lastlogontimestamp[0] if ($LastLogon -ne 9223372036854775807) { $ReturnObject['LastLogonDate'] = [datetime]::FromFileTimeUtc($LastLogon) } else { $ReturnObject['LastLogonDate'] = $null } } if ($Properties -contains 'PasswordLastSet') { $PasswordLastSet = [int64] $Object.properties.pwdlastset[0] if ($PasswordLastSet -ne 9223372036854775807) { $ReturnObject['PasswordLastSet'] = [datetime]::FromFileTimeUtc($PasswordLastSet) } else { $ReturnObject['PasswordLastSet'] = $null } } if ($Properties -contains 'AccountExpirationDate') { $ExpirationDate = [int64] $Object.properties.accountexpires[0] if ($ExpirationDate -ne 9223372036854775807) { $ReturnObject['AccountExpirationDate'] = [datetime]::FromFileTimeUtc($ExpirationDate) } else { $ReturnObject['AccountExpirationDate'] = $null } } if ($AddType) { if (-not $ResolvedIdentity) { $ResolvedIdentity = ConvertFrom-SID -SID $ReturnObject['ObjectSID'] } $ReturnObject['Type'] = $ResolvedIdentity.Type } if ($ReturnObject['Type'] -eq 'WellKnownAdministrative') { if (-not $TemporaryDomainName) { $ReturnObject['DomainName'] = '' } } if ($Cache) { $Script:CacheObjectsWinADObject[$TemporaryName] = [PSCustomObject] $ReturnObject $Script:CacheObjectsWinADObject[$TemporaryName] } else { [PSCustomObject] $ReturnObject } } } } } function Get-WinADSharePermission { <# .SYNOPSIS Retrieves the permissions for a specified Windows Active Directory share or shares based on type. .DESCRIPTION This cmdlet retrieves the permissions for a specified Windows Active Directory share or shares based on type. It can target a specific path or retrieve permissions for shares of a specified type across multiple domains in a forest. The cmdlet can also filter the results to include or exclude specific domains and provide additional forest information. .PARAMETER Path Specifies the path to the share for which to retrieve permissions. This parameter is mandatory when using the 'Path' parameter set. .PARAMETER ShareType Specifies the type of share for which to retrieve permissions. This parameter is mandatory when using the 'ShareType' parameter set. Valid values are 'NetLogon' and 'SYSVOL'. .PARAMETER Owner Specifies that the cmdlet should only return the owner of the share instead of the full permissions. .PARAMETER Name Specifies the name of the share for which to retrieve permissions. This parameter is not currently used. .PARAMETER Forest Specifies the name of the forest to target for share permissions retrieval. This parameter is used in conjunction with the 'ShareType' parameter. .PARAMETER ExcludeDomains Specifies an array of domain names to exclude from the share permissions retrieval. .PARAMETER IncludeDomains Specifies an array of domain names to include in the share permissions retrieval. .PARAMETER ExtendedForestInformation Specifies additional information about the forest to use for share permissions retrieval. .EXAMPLE Get-WinADSharePermission -Path "\\domain\share" Retrieves the permissions for the specified share path. .EXAMPLE Get-WinADSharePermission -ShareType NetLogon -Forest MyForest Retrieves the permissions for all NetLogon shares across the specified forest. .EXAMPLE Get-WinADSharePermission -ShareType SYSVOL -IncludeDomains MyDomain1, MyDomain2 Retrieves the permissions for all SYSVOL shares in the specified domains. .NOTES This cmdlet requires the 'Get-WinADForestDetails', 'Get-FileOwner', and 'Get-FilePermission' cmdlets to function. #> [cmdletBinding(DefaultParameterSetName = 'Path')] param( [Parameter(ParameterSetName = 'Path', Mandatory)][string] $Path, [Parameter(ParameterSetName = 'ShareType', Mandatory)][validateset('NetLogon', 'SYSVOL')][string[]] $ShareType, [switch] $Owner, [string[]] $Name, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) if ($ShareType) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $Path = -join ("\\", $Domain, "\$ShareType") @(Get-Item -Path $Path -Force) + @(Get-ChildItem -Path $Path -Recurse:$true -Force -ErrorAction SilentlyContinue -ErrorVariable Err) | ForEach-Object -Process { if ($Owner) { $Output = Get-FileOwner -JustPath -Path $_ -Resolve -AsHashTable $Output['Attributes'] = $_.Attributes [PSCustomObject] $Output } else { $Output = Get-FilePermission -Path $_ -ResolveTypes -Extended -AsHashTable foreach ($O in $Output) { $O['Attributes'] = $_.Attributes [PSCustomObject] $O } } } } } else { if ($Path -and (Test-Path -Path $Path)) { @(Get-Item -Path $Path -Force) + @(Get-ChildItem -Path $Path -Recurse:$true -Force -ErrorAction SilentlyContinue -ErrorVariable Err) | ForEach-Object -Process { if ($Owner) { $Output = Get-FileOwner -JustPath -Path $_ -Resolve -AsHashTable -Verbose $Output['Attributes'] = $_.Attributes [PSCustomObject] $Output } else { $Output = Get-FilePermission -Path $_ -ResolveTypes -Extended -AsHashTable foreach ($O in $Output) { $O['Attributes'] = $_.Attributes [PSCustomObject] $O } } } } } foreach ($e in $err) { Write-Warning "Get-WinADSharePermission - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } } function Remove-ADACL { <# .SYNOPSIS Removes an Access Control List (ACL) entry from an Active Directory object or an NTSecurityDescriptor. .DESCRIPTION This cmdlet is designed to remove a specific ACL entry from an Active Directory object or an NTSecurityDescriptor. It allows for granular control over the removal process by specifying the object, ACL, principal, access rule, access control type, and inheritance settings. Additionally, it provides options to include or exclude specific object types and their inherited types. .PARAMETER ADObject Specifies the Active Directory object from which to remove the ACL entry. This can be a single object or an array of objects. .PARAMETER ACL Specifies the ACL from which to remove the entry. This parameter is mandatory when using the ACL or NTSecurityDescriptor parameter sets. .PARAMETER Principal Specifies the principal (user, group, or computer) for whom the ACL entry is being removed. .PARAMETER AccessRule Specifies the access rule to remove. This can be a specific right or a combination of rights. .PARAMETER AccessControlType Specifies the type of access control to apply. The default is Allow. .PARAMETER IncludeObjectTypeName Specifies the object types to include in the removal process. .PARAMETER IncludeInheritedObjectTypeName Specifies the inherited object types to include in the removal process. .PARAMETER InheritanceType Specifies the inheritance type for the ACL entry. .PARAMETER Force Forces the removal of inherited ACL entries. By default, inherited entries are skipped. .EXAMPLE Remove-ADACL -ADObject "CN=User1,DC=example,DC=com" -Principal "CN=User2,DC=example,DC=com" -AccessRule "ReadProperty, WriteProperty" -AccessControlType Allow This example removes the ACL entry for User2 to read and write properties on User1's object in the example.com domain. .NOTES This cmdlet requires the Active Directory PowerShell module to be installed and imported. #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ADObject')] param( [parameter(ParameterSetName = 'ADObject')][alias('Identity')][Array] $ADObject, [parameter(ParameterSetName = 'NTSecurityDescriptor', Mandatory)] [parameter(ParameterSetName = 'ACL', Mandatory)] [Array] $ACL, [parameter(ParameterSetName = 'ACL', Mandatory)] [parameter(ParameterSetName = 'ADObject')] [string] $Principal, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [Alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [System.Security.AccessControl.AccessControlType] $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [Alias('ObjectTypeName', 'ObjectType')][string[]] $IncludeObjectTypeName, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [Alias('InheritedObjectTypeName', 'InheritedObjectType')][string[]] $IncludeInheritedObjectTypeName, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [alias('ActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [parameter(ParameterSetName = 'NTSecurityDescriptor')] [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [switch] $Force, [parameter(ParameterSetName = 'NTSecurityDescriptor', Mandatory)] [alias('ActiveDirectorySecurity')][System.DirectoryServices.ActiveDirectorySecurity] $NTSecurityDescriptor ) if (-not $Script:ForestDetails) { Write-Verbose "Remove-ADACL - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } if ($PSBoundParameters.ContainsKey('ADObject')) { foreach ($Object in $ADObject) { $getADACLSplat = @{ ADObject = $Object Bundle = $true Resolve = $true IncludeActiveDirectoryRights = $AccessRule Principal = $Principal AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeActiveDirectorySecurityInheritance = $InheritanceType IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName } Remove-EmptyValue -Hashtable $getADACLSplat $MYACL = Get-ADACL @getADACLSplat $removePrivateACLSplat = @{ ACL = $MYACL WhatIf = $WhatIfPreference Force = $Force.IsPresent } Remove-EmptyValue -Hashtable $removePrivateACLSplat Remove-PrivateACL @removePrivateACLSplat } } elseif ($PSBoundParameters.ContainsKey('ACL') -and $PSBoundParameters.ContainsKey('ntSecurityDescriptor')) { foreach ($SubACL in $ACL) { $removePrivateACLSplat = @{ ntSecurityDescriptor = $ntSecurityDescriptor ACL = $SubACL WhatIf = $WhatIfPreference Force = $Force.IsPresent } Remove-EmptyValue -Hashtable $removePrivateACLSplat Remove-PrivateACL @removePrivateACLSplat } } elseif ($PSBoundParameters.ContainsKey('ACL')) { foreach ($SubACL in $ACL) { $removePrivateACLSplat = @{ ACL = $SubACL Principal = $Principal AccessRule = $AccessRule AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName InheritanceType = $InheritanceType WhatIf = $WhatIfPreference Force = $Force.IsPresent } Remove-EmptyValue -Hashtable $removePrivateACLSplat Remove-PrivateACL @removePrivateACLSplat } } } function Remove-EmptyValue { <# .SYNOPSIS Removes empty values from a hashtable recursively. .DESCRIPTION This function removes empty values from a given hashtable. It can be used to clean up a hashtable by removing keys with null, empty string, empty array, or empty dictionary values. The function supports recursive removal of empty values. .PARAMETER Hashtable The hashtable from which empty values will be removed. .PARAMETER ExcludeParameter An array of keys to exclude from the removal process. .PARAMETER Recursive Indicates whether to recursively remove empty values from nested hashtables. .PARAMETER Rerun Specifies the number of times to rerun the removal process recursively. .PARAMETER DoNotRemoveNull If specified, null values will not be removed. .PARAMETER DoNotRemoveEmpty If specified, empty string values will not be removed. .PARAMETER DoNotRemoveEmptyArray If specified, empty array values will not be removed. .PARAMETER DoNotRemoveEmptyDictionary If specified, empty dictionary values will not be removed. .EXAMPLE $hashtable = @{ 'Key1' = ''; 'Key2' = $null; 'Key3' = @(); 'Key4' = @{} } Remove-EmptyValue -Hashtable $hashtable -Recursive Description ----------- This example removes empty values from the $hashtable recursively. #> [alias('Remove-EmptyValues')] [CmdletBinding()] param( [alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable, [string[]] $ExcludeParameter, [switch] $Recursive, [int] $Rerun, [switch] $DoNotRemoveNull, [switch] $DoNotRemoveEmpty, [switch] $DoNotRemoveEmptyArray, [switch] $DoNotRemoveEmptyDictionary ) foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } } if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } } } function Select-Properties { <# .SYNOPSIS Allows for easy selecting property names from one or multiple objects .DESCRIPTION Allows for easy selecting property names from one or multiple objects. This is especially useful with using AllProperties parameter where we want to make sure to get all properties from all objects. .PARAMETER Objects One or more objects .PARAMETER Property Properties to include .PARAMETER ExcludeProperty Properties to exclude .PARAMETER AllProperties All unique properties from all objects .PARAMETER PropertyNameReplacement Default property name when object has no properties .EXAMPLE $Object1 = [PSCustomobject] @{ Name1 = '1' Name2 = '3' Name3 = '5' } $Object2 = [PSCustomobject] @{ Name4 = '2' Name5 = '6' Name6 = '7' } Select-Properties -Objects $Object1, $Object2 -AllProperties #OR: $Object1, $Object2 | Select-Properties -AllProperties -ExcludeProperty Name6 -Property Name3 .EXAMPLE $Object3 = [Ordered] @{ Name1 = '1' Name2 = '3' Name3 = '5' } $Object4 = [Ordered] @{ Name4 = '2' Name5 = '6' Name6 = '7' } Select-Properties -Objects $Object3, $Object4 -AllProperties $Object3, $Object4 | Select-Properties -AllProperties .NOTES General notes #> [CmdLetBinding()] param( [Array][Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] $Objects, [string[]] $Property, [string[]] $ExcludeProperty, [switch] $AllProperties, [string] $PropertyNameReplacement = '*' ) Begin { function Select-Unique { [CmdLetBinding()] param( [System.Collections.IList] $Object ) [Array] $CleanedList = foreach ($O in $Object) { if ($null -ne $O) { $O } } $New = $CleanedList.ToLower() | Select-Object -Unique $Selected = foreach ($_ in $New) { $Index = $Object.ToLower().IndexOf($_) if ($Index -ne -1) { $Object[$Index] } } $Selected } $ObjectsList = [System.Collections.Generic.List[Object]]::new() } Process { foreach ($Object in $Objects) { $ObjectsList.Add($Object) } } End { if ($ObjectsList.Count -eq 0) { Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.' return } if ($ObjectsList[0] -is [System.Collections.IDictionary]) { if ($AllProperties) { [Array] $All = foreach ($_ in $ObjectsList) { $_.Keys } $FirstObjectProperties = Select-Unique -Object $All } else { $FirstObjectProperties = $ObjectsList[0].Keys } if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($Property -contains $_ -and $ExcludeProperty -notcontains $_) { $_ continue } } } elseif ($Property.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($Property -contains $_) { $_ continue } } } elseif ($ExcludeProperty.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($ExcludeProperty -notcontains $_) { $_ continue } } } } elseif ($ObjectsList[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') { $FirstObjectProperties = $PropertyNameReplacement } else { if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty } elseif ($Property.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property $Property } elseif ($ExcludeProperty.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty } if ($AllProperties) { [Array] $All = foreach ($_ in $ObjectsList) { $ListProperties = $_.PSObject.Properties.Name if ($null -ne $ListProperties) { $ListProperties } } $FirstObjectProperties = Select-Unique -Object $All } else { $FirstObjectProperties = $ObjectsList[0].PSObject.Properties.Name } } $FirstObjectProperties } } function Set-ADACLOwner { <# .SYNOPSIS Sets the owner of the ACLs on specified Active Directory objects to a specified principal. .DESCRIPTION This cmdlet sets the owner of the ACLs on specified Active Directory objects to a specified principal. It supports setting the owner on multiple objects at once and can handle both local and remote operations. It also provides verbose and warning messages to facilitate troubleshooting. .PARAMETER ADObject Specifies the Active Directory objects on which to set the owner. This can be a list of objects or a list of Distinguished Names of objects. .PARAMETER Principal Specifies the principal to set as the owner of the ACLs. This can be a string in the format of 'Domain\Username' or 'Username@Domain'. .EXAMPLE Set-ADACLOwner -ADObject 'OU=Users,DC=example,DC=com', 'CN=Computers,DC=example,DC=com' -Principal 'example\DomainAdmins' This example sets the owner of the ACLs on the specified OU and CN to 'example\DomainAdmins'. .EXAMPLE Set-ADACLOwner -ADObject 'CN=User1,DC=example,DC=com', 'CN=Computer1,DC=example,DC=com' -Principal 'DomainAdmins@example.com' This example sets the owner of the ACLs on the specified user and computer to 'DomainAdmins@example.com'. #> [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Identity')][Array] $ADObject, [Parameter(Mandatory)][string] $Principal ) Begin { if ($Principal -is [string]) { if ($Principal -like '*/*') { $SplittedName = $Principal -split '/' [System.Security.Principal.IdentityReference] $PrincipalIdentity = [System.Security.Principal.NTAccount]::new($SplittedName[0], $SplittedName[1]) } else { [System.Security.Principal.IdentityReference] $PrincipalIdentity = [System.Security.Principal.NTAccount]::new($Principal) } } else { return } } Process { foreach ($Object in $ADObject) { if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { [string] $DistinguishedName = $Object.DistinguishedName [string] $CanonicalName = $Object.CanonicalName [string] $ObjectClass = $Object.ObjectClass } elseif ($Object -is [string]) { [string] $DistinguishedName = $Object [string] $CanonicalName = '' [string] $ObjectClass = '' } else { Write-Warning "Set-ADACLOwner - Object not recognized. Skipping..." continue } $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ',' if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Verbose "Set-ADACLOwner - Enabling PSDrives for $DistinguishedName to $DNConverted" New-ADForestDrives -ForestName $ForestName if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..." continue } } $PathACL = "$DNConverted`:\$($DistinguishedName)" try { $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop } catch { Write-Warning "Get-ADACL - Path $DistinguishedName / $PathACL - Error: $($_.Exception.Message)" continue } $CurrentOwner = $ACLs.Owner Write-Verbose "Set-ADACLOwner - Changing owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName)" try { $ACLs.SetOwner($PrincipalIdentity) } catch { Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName): $($_.Exception.Message)" continue } try { Set-Acl -Path $PathACL -AclObject $ACLs -ErrorAction Stop } catch { Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName): $($_.Exception.Message)" } } } End { } } function Set-FileOwner { <# .SYNOPSIS Sets the owner of a file or folder. .DESCRIPTION This function sets the owner of a specified file or folder to the provided owner. .PARAMETER Path Specifies the path to the file or folder. .PARAMETER Recursive Indicates whether to process the items in the specified path recursively. .PARAMETER Owner Specifies the new owner for the file or folder. .PARAMETER Exclude Specifies an array of owners to exclude from ownership change. .PARAMETER JustPath Indicates whether to only change the owner of the specified path without recursing into subfolders. .EXAMPLE Set-FileOwner -Path "C:\Example\File.txt" -Owner "DOMAIN\User1" Description: Sets the owner of the file "File.txt" to "DOMAIN\User1". .EXAMPLE Set-FileOwner -Path "C:\Example\Folder" -Owner "DOMAIN\User2" -Recursive Description: Sets the owner of the folder "Folder" and all its contents to "DOMAIN\User2" recursively. #> [cmdletBinding(SupportsShouldProcess)] param( [Array] $Path, [switch] $Recursive, [string] $Owner, [string[]] $Exlude, [switch] $JustPath ) Begin { } Process { foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } $OwnerTranslated = [System.Security.Principal.NTAccount]::new($Owner) if ($FullPath -and (Test-Path -Path $FullPath)) { if ($JustPath) { $FullPath | ForEach-Object -Process { $File = $_ try { $ACL = Get-Acl -Path $File -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" } if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) { if ($PSCmdlet.ShouldProcess($File, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) { try { $ACL.SetOwner($OwnerTranslated) Set-Acl -Path $File -AclObject $ACL -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" } } } } } else { Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -ErrorAction SilentlyContinue -ErrorVariable err | ForEach-Object -Process { $File = $_ try { $ACL = Get-Acl -Path $File.FullName -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" } if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) { if ($PSCmdlet.ShouldProcess($File.FullName, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) { try { $ACL.SetOwner($OwnerTranslated) Set-Acl -Path $File.FullName -AclObject $ACL -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" } } } } foreach ($e in $err) { Write-Warning "Set-FileOwner - Errors processing $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } } } } } End { } } function Start-TimeLog { <# .SYNOPSIS Starts a new stopwatch for logging time. .DESCRIPTION This function starts a new stopwatch that can be used for logging time durations. .EXAMPLE Start-TimeLog Starts a new stopwatch for logging time. #> [CmdletBinding()] param() [System.Diagnostics.Stopwatch]::StartNew() } function Stop-TimeLog { <# .SYNOPSIS Stops the stopwatch and returns the elapsed time in a specified format. .DESCRIPTION The Stop-TimeLog function stops the provided stopwatch and returns the elapsed time in a specified format. The function can output the elapsed time as a single string or an array of days, hours, minutes, seconds, and milliseconds. .PARAMETER Time Specifies the stopwatch object to stop and retrieve the elapsed time from. .PARAMETER Option Specifies the format in which the elapsed time should be returned. Valid values are 'OneLiner' (default) or 'Array'. .PARAMETER Continue Indicates whether the stopwatch should continue running after retrieving the elapsed time. .EXAMPLE $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() # Perform some operations Stop-TimeLog -Time $stopwatch # Output: "0 days, 0 hours, 0 minutes, 5 seconds, 123 milliseconds" .EXAMPLE $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() # Perform some operations Stop-TimeLog -Time $stopwatch -Option Array # Output: ["0 days", "0 hours", "0 minutes", "5 seconds", "123 milliseconds"] #> [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 delivering a lot of additional features for easier color options. .DESCRIPTION Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options. It provides: - Easy manipulation of colors, - Logging output to file (log) - Nice formatting options out of the box. - Ability to use aliases for parameters .PARAMETER Text Text to display on screen and write to log file if specified. Accepts an array of strings. .PARAMETER Color Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER BackGroundColor Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER StartTab Number of tabs to add before text. Default is 0. .PARAMETER LinesBefore Number of empty lines before text. Default is 0. .PARAMETER LinesAfter Number of empty lines after text. Default is 0. .PARAMETER StartSpaces Number of spaces to add before text. Default is 0. .PARAMETER LogFile Path to log file. If not specified no log file will be created. .PARAMETER DateTimeFormat Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss .PARAMETER LogTime If set to $true it will add time to log file. Default is $true. .PARAMETER LogRetry Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2. .PARAMETER Encoding Encoding of the log file. Default is Unicode. .PARAMETER ShowTime Switch to add time to console output. Default is not set. .PARAMETER NoNewLine Switch to not add new line at the end of the output. Default is not set. .PARAMETER NoConsoleOutput Switch to not output to console. Default all output goes to console. .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 Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow Write-Color -t "my text" -c yellow -b green Write-Color -text "my text" -c red .EXAMPLE Write-Color -Text "Testuję czy się ładnie zapisze, czy będą problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput .NOTES Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings Project support: https://github.com/EvotecIT/PSWriteColor Original idea: Josh (https://stackoverflow.com/users/81769/josh) #> [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, [alias('HideConsole')][switch] $NoConsoleOutput ) if (-not $NoConsoleOutput) { $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) { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))" } 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 ConvertFrom-NetbiosName { <# .SYNOPSIS Converts a NetBIOS name to its corresponding domain name and object name. .DESCRIPTION This function takes a NetBIOS name in the format 'Domain\Object' and converts it to the corresponding domain name and object name. .PARAMETER Identity Specifies the NetBIOS name(s) to convert. .EXAMPLE 'TEST\Domain Admins', 'EVOTEC\Domain Admins', 'EVOTECPL\Domain Admins' | ConvertFrom-NetbiosName Converts the NetBIOS names 'TEST\Domain Admins', 'EVOTEC\Domain Admins', and 'EVOTECPL\Domain Admins' to their corresponding domain names and object names. .EXAMPLE ConvertFrom-NetbiosName -Identity 'TEST\Domain Admins', 'EVOTEC\Domain Admins', 'EVOTECPL\Domain Admins' Converts the NetBIOS names 'TEST\Domain Admins', 'EVOTEC\Domain Admins', and 'EVOTECPL\Domain Admins' to their corresponding domain names and object names. #> [cmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [string[]] $Identity ) process { foreach ($Ident in $Identity) { if ($Ident -like '*\*') { $NetbiosWithObject = $Ident -split "\\" if ($NetbiosWithObject.Count -eq 2) { $LDAPQuery = ([ADSI]"LDAP://$($NetbiosWithObject[0])") $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $LDAPQuery.distinguishedName -ToDomainCN [PSCustomObject] @{ DomainName = $DomainName Name = $NetbiosWithObject[1] } } else { [PSCustomObject] @{ DomainName = '' Name = $Ident } } } else { [PSCustomObject] @{ DomainName = '' Name = $Ident } } } } } function Convert-GenericRightsToFileSystemRights { <# .SYNOPSIS Converts generic rights to file system rights for a given set of original rights. .DESCRIPTION This function maps generic rights to corresponding file system rights based on the provided original rights. .PARAMETER OriginalRights Specifies the original generic rights to be converted to file system rights. .EXAMPLE Convert-GenericRightsToFileSystemRights -OriginalRights GENERIC_READ Converts the generic read rights to file system rights. .NOTES This function is based on the mapping provided in the blog post: https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/ .LINK https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/ #> [cmdletBinding()] param( [System.Security.AccessControl.FileSystemRights] $OriginalRights ) Begin { $FileSystemRights = [System.Security.AccessControl.FileSystemRights] $GenericRights = @{ GENERIC_READ = 0x80000000; GENERIC_WRITE = 0x40000000; GENERIC_EXECUTE = 0x20000000; GENERIC_ALL = 0x10000000; FILTER_GENERIC = 0x0FFFFFFF; } $MappedGenericRights = @{ FILE_GENERIC_EXECUTE = $FileSystemRights::ExecuteFile -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::ReadAttributes -bor $FileSystemRights::Synchronize FILE_GENERIC_READ = $FileSystemRights::ReadAttributes -bor $FileSystemRights::ReadData -bor $FileSystemRights::ReadExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize FILE_GENERIC_WRITE = $FileSystemRights::AppendData -bor $FileSystemRights::WriteAttributes -bor $FileSystemRights::WriteData -bor $FileSystemRights::WriteExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize FILE_GENERIC_ALL = $FileSystemRights::FullControl } } Process { $MappedRights = [System.Security.AccessControl.FileSystemRights]::new() if ($OriginalRights -band $GenericRights.GENERIC_EXECUTE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_EXECUTE } if ($OriginalRights -band $GenericRights.GENERIC_READ) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_READ } if ($OriginalRights -band $GenericRights.GENERIC_WRITE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_WRITE } if ($OriginalRights -band $GenericRights.GENERIC_ALL) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_ALL } (($OriginalRights -bAND $GenericRights.FILTER_GENERIC) -bOR $MappedRights) -as $FileSystemRights } End { } } 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 .EXAMPLE $Registry = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' ConvertTo-OperatingSystem -OperatingSystem $Registry.ProductName -OperatingSystemVersion $Registry.CurrentBuildNumber .NOTES General notes #> [CmdletBinding()] param( [string] $OperatingSystem, [string] $OperatingSystemVersion ) if ($OperatingSystem -like 'Windows 10*' -or $OperatingSystem -like 'Windows 11*') { $Systems = @{ '10.0 (22621)' = 'Windows 11 22H2' '10.0 (22000)' = 'Windows 11 21H2' '10.0 (19045)' = 'Windows 10 22H2' '10.0 (19044)' = 'Windows 10 21H2' '10.0 (19043)' = 'Windows 10 21H1' '10.0 (19042)' = 'Windows 10 20H2' '10.0 (19041)' = 'Windows 10 2004' '10.0 (18898)' = 'Windows 10 Insider Preview' '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.22621' = 'Windows 11 22H2' '10.0.22000' = 'Windows 11 21H2' '10.0.19045' = 'Windows 10 22H2' '10.0.19044' = 'Windows 10 21H2' '10.0.19043' = 'Windows 10 21H1' '10.0.19042' = 'Windows 10 20H2' '10.0.19041' = 'Windows 10 2004' '10.0.18898' = 'Windows 10 Insider Preview' '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" '22621' = 'Windows 11 22H2' '22000' = 'Windows 11 21H2' '19045' = 'Windows 10 22H2' '19044' = 'Windows 10 21H2' '19043' = 'Windows 10 21H1' '19042' = 'Windows 10 20H2' '19041' = 'Windows 10 2004' '18898' = 'Windows 10 Insider Preview' '18363' = "Windows 10 1909" '18362' = "Windows 10 1903" '17763' = "Windows 10 1809" '17134' = "Windows 10 1803" '16299' = "Windows 10 1709" '15063' = "Windows 10 1703" '14393' = "Windows 10 1607" '10586' = "Windows 10 1511" '10240' = "Windows 10 1507" } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystemVersion } } elseif ($OperatingSystem -like 'Windows Server*') { $Systems = @{ '10.0 (20348)' = 'Windows Server 2022' '10.0 (19042)' = 'Windows Server 2019 20H2' '10.0 (19041)' = 'Windows Server 2019 2004' '10.0 (18363)' = 'Windows Server 2019 1909' '10.0 (18362)' = "Windows Server 2019 1903" '10.0 (17763)' = "Windows Server 2019 1809" '10.0 (17134)' = "Windows Server 2016 1803" '10.0 (14393)' = "Windows Server 2016 1607" '6.3 (9600)' = 'Windows Server 2012 R2' '6.1 (7601)' = 'Windows Server 2008 R2' '5.2 (3790)' = 'Windows Server 2003' '10.0.20348' = 'Windows Server 2022' '10.0.19042' = 'Windows Server 2019 20H2' '10.0.19041' = 'Windows Server 2019 2004' '10.0.18363' = 'Windows Server 2019 1909' '10.0.18362' = "Windows Server 2019 1903" '10.0.17763' = "Windows Server 2019 1809" '10.0.17134' = "Windows Server 2016 1803" '10.0.14393' = "Windows Server 2016 1607" '6.3.9600' = 'Windows Server 2012 R2' '6.1.7601' = 'Windows Server 2008 R2' '5.2.3790' = 'Windows Server 2003' '20348' = 'Windows Server 2022' '19042' = 'Windows Server 2019 20H2' '19041' = 'Windows Server 2019 2004' '18363' = 'Windows Server 2019 1909' '18362' = "Windows Server 2019 1903" '17763' = "Windows Server 2019 1809" '17134' = "Windows Server 2016 1803" '14393' = "Windows Server 2016 1607" '9600' = 'Windows Server 2012 R2' '7601' = 'Windows Server 2008 R2' '3790' = 'Windows Server 2003' } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystemVersion } } else { $System = $OperatingSystem } if ($System) { $System } else { 'Unknown' } } function Convert-UserAccountControl { <# .SYNOPSIS Converts the UserAccountControl flags to their corresponding names. .DESCRIPTION This function takes a UserAccountControl value and converts it into a human-readable format by matching the flags to their corresponding names. .PARAMETER UserAccountControl Specifies the UserAccountControl value to be converted. .PARAMETER Separator Specifies the separator to use when joining the converted flags. If not provided, the flags will be returned as a list. .EXAMPLE Convert-UserAccountControl -UserAccountControl 66048 Outputs: "DONT_EXPIRE_PASSWORD, PASSWORD_EXPIRED" .EXAMPLE Convert-UserAccountControl -UserAccountControl 512 -Separator ', ' Outputs: "NORMAL_ACCOUNT" #> [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 Copy-DictionaryManual { <# .SYNOPSIS Copies a dictionary recursively, handling nested dictionaries and lists. .DESCRIPTION This function copies a dictionary recursively, handling nested dictionaries and lists. It creates a deep copy of the input dictionary, ensuring that modifications to the copied dictionary do not affect the original dictionary. .PARAMETER Dictionary The dictionary to be copied. .EXAMPLE $originalDictionary = @{ 'Key1' = 'Value1' 'Key2' = @{ 'NestedKey1' = 'NestedValue1' } } $copiedDictionary = Copy-DictionaryManual -Dictionary $originalDictionary This example demonstrates how to copy a dictionary with nested values. #> [CmdletBinding()] param( [System.Collections.IDictionary] $Dictionary ) $clone = [ordered] @{} 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 Get-ComputerApplication { <# .SYNOPSIS Get software installed on computer or server .DESCRIPTION Get software installed on computer or server .PARAMETER ComputerName Specifies computer on which you want to run the operation. .EXAMPLE Get-ComputerApplications -Verbose | Format-Table .EXAMPLE Get-ComputerApplications -Verbose -ComputerName AD1, AD2 | Format-Table .NOTES General notes #> [alias('Get-ComputerApplications')] [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME ) $ScriptBlock = { $objapp1 = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* $objapp2 = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* $app1 = $objapp1 | Select-Object Displayname, Displayversion , Publisher, Installdate, @{Expression = { 'x64' }; Label = 'WindowsType' } $app2 = $objapp2 | Select-Object Displayname, Displayversion , Publisher, Installdate, @{Expression = { 'x86' }; Label = 'WindowsType' } | Where-Object { -NOT (([string]$_.displayname).contains('Security Update for Microsoft') -or ([string]$_.displayname).contains('Update for Microsoft')) } $app = $app1 + $app2 $app | Where-Object { $null -ne $_.Displayname } } foreach ($Computer in $ComputerName) { try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Computer } if ($Computer -eq $Env:COMPUTERNAME -or $Computer -eq $LocalComputerDNSName) { $Parameters = @{ ScriptBlock = $ScriptBlock } } else { $Parameters = @{ ComputerName = $Computer ScriptBlock = $ScriptBlock } } try { $Data = Invoke-Command @Parameters } catch { Write-Warning "Get-ComputerApplication - No data for computer $Computer" continue } foreach ($Information in $Data) { if ($Information.Installdate) { try { $InstallDate = [datetime]::ParseExact($Information.Installdate, 'yyyyMMdd', $null) } catch { Write-Verbose "Get-ComputerApplication - InstallDate $($Information.Installdate) couldn't be converted." $InstallDate = $null } } else { $InstallDate = $null } [PSCustomObject] @{ DisplayName = $Information.DisplayName Version = $Information.DisplayVersion Publisher = $Information.Publisher Installdate = $InstallDate Type = $Information.WindowsType ComputerName = $Computer } } } } function Get-ComputerBios { <# .SYNOPSIS Retrieves BIOS information from a remote or local computer. .DESCRIPTION This function retrieves BIOS information from a specified computer using CIM/WMI. .PARAMETER ComputerName Specifies the name of the computer to retrieve BIOS information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for communication. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'. .PARAMETER All Switch parameter to retrieve all available BIOS properties. .EXAMPLE Get-ComputerBios -ComputerName "RemoteComputer" -Protocol Wsman Retrieves BIOS information from a remote computer using the Wsman protocol. .EXAMPLE Get-ComputerBios -All Retrieves all available BIOS information from the local computer. #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_bios' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'PSComputerName', 'Status', 'Version', 'PrimaryBIOS', 'Manufacturer', 'ReleaseDate', 'SerialNumber', 'SMBIOSBIOSVersion', 'SMBIOSMajorVersion', 'SMBIOSMinorVersion', 'SystemBiosMajorVersion', 'SystemBiosMinorVersion' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Status = $Data.Status Version = $Data.Version VersionBIOS = -join ($Data.SMBIOSMajorVersion, ".", $Data.SMBIOSMinorVersion, ".", $Data.SystemBiosMajorVersion, ".", $Data.SystemBiosMinorVersion) PrimaryBIOS = $Data.PrimaryBIOS Manufacturer = $Data.Manufacturer ReleaseDate = $Data.ReleaseDate } } } } } function Get-ComputerCPU { <# .SYNOPSIS Retrieves CPU information from specified computers. .DESCRIPTION This function retrieves CPU information from the specified computers. It provides details such as Name, DeviceID, Caption, SystemName, CurrentClockSpeed, MaxClockSpeed, ProcessorID, ThreadCount, Architecture, Status, LoadPercentage, L3CacheSize, Manufacturer, NumberOfCores, NumberOfEnabledCore, and NumberOfLogicalProcessors. .PARAMETER ComputerName Specifies the names of the computers for which to retrieve CPU information. .PARAMETER Protocol Specifies the protocol to use for retrieving CPU information. Valid values are 'Default', 'Dcom', and 'Wsman'. .PARAMETER All Indicates whether to retrieve all available CPU information. .EXAMPLE Get-ComputerCPU -ComputerName Server01, Server02 -Protocol Wsman -All Retrieves all available CPU information from remote computers Server01 and Server02 using Wsman protocol. .EXAMPLE Get-ComputerCPU -ComputerName "Workstation01" -Protocol Default Retrieves CPU information from a single remote computer named Workstation01 using the default protocol. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_processor' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'PSComputerName', 'Name', 'DeviceID', 'Caption', 'SystemName', 'CurrentClockSpeed', 'MaxClockSpeed', 'ProcessorID', 'ThreadCount', 'Architecture', 'Status', 'LoadPercentage', 'L3CacheSize', 'Manufacturer', 'VirtualizationFirmwareEnabled', 'NumberOfCores', 'NumberOfEnabledCore', 'NumberOfLogicalProcessors' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Name = $Data.Name DeviceID = $Data.DeviceID Caption = $Data.Caption CurrentClockSpeed = $Data.CurrentClockSpeed MaxClockSpeed = $Data.MaxClockSpeed ProcessorID = $Data.ProcessorID ThreadCount = $Data.ThreadCount Architecture = $Data.Architecture Status = $Data.Status LoadPercentage = $Data.LoadPercentage Manufacturer = $Data.Manufacturer NumberOfCores = $Data.NumberOfCores NumberOfEnabledCore = $Data.NumberOfEnabledCore NumberOfLogicalProcessors = $Data.NumberOfLogicalProcessors } } } } } function Get-ComputerDisk { <# .SYNOPSIS Retrieves disk information from remote computers. .DESCRIPTION This function retrieves disk information from remote computers specified by the ComputerName parameter. It provides details such as Index, Model, Caption, SerialNumber, Description, MediaType, FirmwareRevision, Partitions, SizeGB, and PNPDeviceID. .PARAMETER ComputerName Specifies the names of the remote computers from which to retrieve disk information. .PARAMETER Protocol Specifies the protocol to be used for retrieving disk information. Valid values are 'Default', 'Dcom', and 'Wsman'. .PARAMETER All Indicates whether to retrieve all available disk information. .EXAMPLE Get-ComputerDisk -ComputerName AD1, AD2, EVO1, AD2019 | Format-Table -AutoSize * Output: WARNING: Get-ComputerSystem - No data for computer AD2019. Most likely an error on receiving side. ComputerName Index Model Caption SerialNumber Description MediaType FirmwareRevision Partitions SizeGB PNPDeviceID ------------ ----- ----- ------- ------------ ----------- --------- ---------------- ---------- ------ ----------- AD1 0 Microsoft Virtual Disk Microsoft Virtual Disk Disk drive Fixed hard disk media 1.0 3 127 SCSI\DISK&VEN_MSFT&PROD_VIRTUAL_DISK\000000 AD2 0 Microsoft Virtual Disk Microsoft Virtual Disk Disk drive Fixed hard disk media 1.0 3 127 SCSI\DISK&VEN_MSFT&PROD_VIRTUAL_DISK\000000 EVO1 0 WDC WD30EFRX-68AX9N0 WDC WD30EFRX-68AX9N0 WD-WMC1T2351095 Disk drive Fixed hard disk media 80.00A80 1 2795 SCSI\DISK&VEN_WDC&PROD_WD30EFRX-68AX9N0\4&191557A4&0&000000 EVO1 2 Samsung SSD 950 PRO 512GB Samsung SSD 950 PRO 512GB 0025_3857_61B0_0EF2. Disk drive Fixed hard disk media 2B0Q 3 477 SCSI\DISK&VEN_NVME&PROD_SAMSUNG_SSD_950\5&35365596&0&000000 EVO1 1 Samsung SSD 860 EVO 500GB Samsung SSD 860 EVO 500GB S3Z2NB0K176976A Disk drive Fixed hard disk media RVT01B6Q 1 466 SCSI\DISK&VEN_SAMSUNG&PROD_SSD\4&191557A4&0&000100 .NOTES This function uses the Get-CimData cmdlet to retrieve disk information from remote computers. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_diskdrive' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Index', 'Model', 'Caption', 'SerialNumber', 'Description', 'MediaType', 'FirmwareRevision', 'Partitions', 'Size', 'PNPDeviceID', 'PSComputerName' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Index = $Data.Index Model = $Data.Model Caption = $Data.Caption SerialNumber = if ($Data.SerialNumber) { $Data.SerialNumber.Trim() } else { '' } Description = $Data.Description MediaType = $Data.MediaType FirmwareRevision = $Data.FirmwareRevision Partitions = $Data.Partitions SizeGB = $Data.Size / 1Gb -as [int] PNPDeviceID = $Data.PNPDeviceID } } } } } function Get-ComputerDiskLogical { <# .SYNOPSIS Retrieves logical disk information for specified computers. .DESCRIPTION This function retrieves logical disk information for the specified computers. It provides details such as DeviceID, DriveType, ProviderName, FreeSpace, UsedSpace, TotalSpace, FreePercent, UsedPercent, and VolumeName. .PARAMETER ComputerName Specifies the names of the computers for which to retrieve disk information. .PARAMETER Protocol Specifies the protocol to use for retrieving disk information. Valid values are 'Default', 'Dcom', and 'Wsman'. .PARAMETER RoundingPlace Specifies the number of decimal places to round the disk space values to. .PARAMETER OnlyLocalDisk Indicates that only local disks should be included in the output. .PARAMETER All Indicates that information for all disks should be retrieved. .EXAMPLE Get-ComputerDiskLogical -ComputerName AD1, AD2, EVOWIN -OnlyLocalDisk | ft -AutoSize Output: ComputerName DeviceID DriveType ProviderName FreeSpace UsedSpace TotalSpace FreePercent UsedPercent VolumeName ------------ -------- --------- ------------ --------- --------- ---------- ----------- ----------- ---------- AD2 C: Local Disk 96,96 29,49 126,45 76,68 23,32 AD1 C: Local Disk 103,17 23,28 126,45 81,59 18,41 EVOWIN C: Local Disk 133,31 343,03 476,34 27,99 72,01 EVOWIN D: Local Disk 2433 361,4 2794,39 87,07 12,93 Media EVOWIN E: Local Disk 66,05 399,7 465,75 14,18 85,82 Testing Environment .NOTES Additional notes about the function. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [string][ValidateSet('GB', 'TB', 'MB')] $Size = 'GB', [int] $RoundingPlace = 2, [int] $RoundingPlacePercent = 2, [switch] $OnlyLocalDisk, [switch] $All ) if (-Not ('Pinvoke.Win32Utils' -as [type])) { Add-Type -TypeDefinition @' using System.Runtime.InteropServices; using System.Text; namespace pinvoke { public static class Win32Utils { [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); } } '@ } [string] $Class = 'win32_logicalDisk' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'DeviceID', 'DriveType', 'ProviderName', 'FreeSpace', 'Size', 'VolumeName', 'PSComputerName' } $DriveType = @{ '0' = 'Unknown' '1' = 'No Root Directory' '2' = 'Removable Disk' '3' = 'Local Disk' '4' = 'Network Drive' '5' = 'Compact Disc' '6' = 'RAM Disk' } $Divider = "1$Size" $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { $Output = foreach ($Info in $Information) { foreach ($Data in $Info) { if ($Info.ComputerName -eq $Env:COMPUTERNAME) { $DiskPartitionName = [System.Text.StringBuilder]::new(1024) if ($Data.DeviceID) { [void][PInvoke.Win32Utils]::QueryDosDevice(($Data.DeviceID), $DiskPartitionName, $DiskPartitionName.Capacity) } $DiskPartitionNumber = $DiskPartitionName.ToString() } else { $DiskPartitionNumber = '' } [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } DeviceID = $Data.DeviceID DriveType = $DriveType["$($Data.DriveType)"] ProviderName = $Data.ProviderName FreeSpace = [Math]::Round($Data.FreeSpace / $Divider, $RoundingPlace) UsedSpace = [Math]::Round(($Data.Size - $Data.FreeSpace) / $Divider, $RoundingPlace) TotalSpace = [Math]::Round($Data.Size / $Divider, $RoundingPlace) FreePercent = if ($Data.Size -gt 0 ) { [Math]::round(($Data.FreeSpace / $Data.Size) * 100, $RoundingPlacePercent) } else { '0' } UsedPercent = if ($Data.Size -gt 0 ) { [Math]::round((($Data.Size - $Data.FreeSpace) / $Data.Size) * 100, $RoundingPlacePercent) } else { '0' } VolumeName = $Data.VolumeName DiskPartition = $DiskPartitionNumber } } } if ($OnlyLocalDisk) { $Output | Where-Object { $_.DriveType -eq 'Local Disk' } } else { $Output } } } function Get-ComputerNetwork { <# .SYNOPSIS Retrieves network information for specified computers. .DESCRIPTION This function retrieves network information for the specified computers, including details about network cards, firewall profiles, and connectivity status. .PARAMETER ComputerName Specifies the name of the computer(s) for which to retrieve network information. .PARAMETER NetworkFirewallOnly Indicates whether to retrieve only firewall information for the specified computers. .PARAMETER NetworkFirewallSummaryOnly Indicates whether to retrieve a summary of firewall information for the specified computers. .EXAMPLE Get-ComputerNetworkCard -ComputerName AD1, AD2, AD3 Output Name NetworkCardName NetworkCardIndex FirewallProfile FirewallStatus IPv4Connectivity IPv6Connectivity Caption Description ElementName DefaultInboundAction DefaultOutboundAction AllowInboundRules AllowLocalFirewallRules AllowLocalIPsecRules AllowUserApps AllowUserPorts AllowUnicastResponseToMulticast NotifyOnListen EnableStealthModeForIPsec LogFileName LogMaxSizeKilobytes LogAllowed LogBlo cked ---- --------------- ---------------- --------------- -------------- ---------------- ---------------- ------- ----------- ----------- -------------------- --------------------- ----------------- ----------------------- -------------------- ------------- -------------- ------------------------------- -------------- ------------------------- ----------- ------------------- ---------- ------ ad.evotec.xyz vEthernet (External Switch) 13 DomainAuthenticated True Internet NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False Network 2 Ethernet 2 2 Private True Internet NoTraffic Block Allow NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False Network Ethernet 2 Private True LocalNetwork NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False ad.evotec.xyz Ethernet 5 3 DomainAuthenticated False Internet NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False Network 2 Ethernet 4 12 Private False LocalNetwork NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False .EXAMPLE Get-ComputerNetworkCard -ComputerName EVOWIN -NetworkFirewallOnly PSComputerName Profile Enabled DefaultInboundAction DefaultOutboundAction AllowInboundRules AllowLocalFirewallRules AllowLocalIPsecRules AllowUserApps AllowUserPorts AllowUnicastResponseToMulticast NotifyOnListen EnableStealthModeForIPsec LogMaxSizeKilobytes LogAllowed LogBlocked LogIgnored Caption Description ElementName InstanceID DisabledInterfaceAliases LogFileName Name CimClass -------------- ------- ------- -------------------- --------------------- ----------------- ----------------------- -------------------- ------------- -------------- ------------------------------- -------------- ------------------------- ------------------- ---------- ---------- ---------- ------- ----------- ----------- ---------- ------------------------ ----------- ---- -------- EVOWIN Domain True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Domain {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Domain root/stand... EVOWIN Private True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Private {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Private root/stand... EVOWIN Public True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Public {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Public root/stand... .NOTES General notes #> [alias('Get-ComputerNetworkCard')] [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [switch] $NetworkFirewallOnly, [switch] $NetworkFirewallSummaryOnly, [alias('Joiner')][string] $Splitter ) [Array] $CollectionComputers = $ComputerName.Where( { $_ -eq $Env:COMPUTERNAME }, 'Split') $Firewall = @{ } $NetworkFirewall = @( if ($CollectionComputers[0].Count -gt 0) { $Firewall[$Env:COMPUTERNAME] = @{ } $Output = Get-NetFirewallProfile foreach ($_ in $Output) { Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -Type NoteProperty -Force $_ if ($_.Name -eq 'Domain') { $Firewall[$Env:COMPUTERNAME]['DomainAuthenticated'] = $_ } else { $Firewall[$Env:COMPUTERNAME][$($_.Name)] = $_ } } } if ($CollectionComputers[1].Count -gt 0) { foreach ($_ in $CollectionComputers[1]) { $Firewall[$_] = @{ } } $Output = Get-NetFirewallProfile -CimSession $CollectionComputers[1] foreach ($_ in $Output) { if ($_.Name -eq 'Domain') { $Firewall[$_.PSComputerName]['DomainAuthenticated'] = $_ } else { $Firewall[$_.PSComputerName][$($_.Name)] = $_ } } } ) if ($NetworkFirewallOnly) { return $NetworkFirewall } if ($NetworkFirewallSummaryOnly) { return $Firewall } $NetworkCards = @( if ($CollectionComputers[0].Count -gt 0) { $Output = Get-NetConnectionProfile foreach ($_ in $Output) { Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -Type NoteProperty -Force $_ } } if ($CollectionComputers[1].Count -gt 0) { Get-NetConnectionProfile -CimSession $CollectionComputers[1] } ) foreach ($_ in $NetworkCards) { $NetworkCardsConfiguration = Get-CimData -ComputerName $ComputerName -Class 'Win32_NetworkAdapterConfiguration' $CurrentCard = foreach ($Configuration in $NetworkCardsConfiguration) { if ($_.PSComputerName -eq $Configuration.PSComputerName) { if ($Configuration.InterfaceIndex -eq $_.InterfaceIndex) { $Configuration } } } $NetbiosTCPIP = @{ '0' = 'Default' '1' = 'Enabled' '2' = 'Disabled' } [PSCustomObject] @{ Name = $_.Name NetworkCardName = $_.InterfaceAlias NetworkCardIndex = $_.InterfaceIndex FirewallProfile = $_.NetworkCategory FirewallStatus = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].'Enabled' IPAddress = $CurrentCard.IPAddress IPGateway = $CurrentCard.DefaultIPGateway IPSubnet = $CurrentCard.IPSubnet IPv4Connectivity = $_.IPv4Connectivity IPv6Connectivity = $_.IPv6Connectivity DNSServerSearchOrder = $CurrentCard.DNSServerSearchOrder DNSDomainSuffixSearchOrder = $CurrentCard.DNSDomainSuffixSearchOrder FullDNSRegistrationEnabled = $CurrentCard.FullDNSRegistrationEnabled DHCPEnabled = $CurrentCard.DHCPEnabled DHCPServer = $CurrentCard.DHCPServer DHCPLeaseObtained = $CurrentCard.DHCPLeaseObtained NetBIOSOverTCPIP = $NetBiosTCPIP["$($CurrentCard.TcpipNetbiosOptions)"] Caption = $_.Caption Description = $_.Description ElementName = $_.ElementName DefaultInboundAction = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].DefaultInboundAction DefaultOutboundAction = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].DefaultOutboundAction AllowInboundRules = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowInboundRules AllowLocalFirewallRules = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowLocalFirewallRules AllowLocalIPsecRules = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowLocalIPsecRules AllowUserApps = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUserApps AllowUserPorts = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUserPorts AllowUnicastResponseToMulticast = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUnicastResponseToMulticast NotifyOnListen = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].NotifyOnListen EnableStealthModeForIPsec = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].EnableStealthModeForIPsec LogFileName = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogFileName LogMaxSizeKilobytes = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogMaxSizeKilobytes LogAllowed = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogAllowed LogBlocked = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogBlocked LogIgnored = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogIgnored ComputerName = $_.PSComputerName } } } function Get-ComputerOperatingSystem { <# .SYNOPSIS Retrieves operating system information from remote computers. .DESCRIPTION This function retrieves operating system information from remote computers using CIM/WMI queries. It provides details such as the operating system name, version, manufacturer, architecture, language, product suite, installation date, last boot-up time, and more. .PARAMETER ComputerName Specifies the name of the remote computer(s) to retrieve the operating system information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for the connection (Default, Dcom, or Wsman). Default is 'Default'. .PARAMETER All Switch parameter to retrieve all available properties of the operating system. .EXAMPLE Get-ComputerOperatingSystem -ComputerName "Server01" -Protocol Wsman Retrieves operating system information from a single remote computer named "Server01" using the Wsman protocol. .EXAMPLE Get-ComputerOperatingSystem -ComputerName "Server01", "Server02" -All Retrieves all available operating system properties from multiple remote computers named "Server01" and "Server02". #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_operatingsystem' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Caption', 'Manufacturer', 'InstallDate', 'OSArchitecture', 'Version', 'SerialNumber', 'BootDevice', 'WindowsDirectory', 'CountryCode', 'OSLanguage', 'OSProductSuite', 'PSComputerName', 'LastBootUpTime', 'LocalDateTime' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Data in $Information) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } OperatingSystem = $Data.Caption OperatingSystemVersion = ConvertTo-OperatingSystem -OperatingSystem $Data.Caption -OperatingSystemVersion $Data.Version OperatingSystemBuild = $Data.Version Manufacturer = $Data.Manufacturer OSArchitecture = $Data.OSArchitecture OSLanguage = ConvertFrom-LanguageCode -LanguageCode $Data.OSLanguage OSProductSuite = [Microsoft.PowerShell.Commands.OSProductSuite] $($Data.OSProductSuite) InstallDate = $Data.InstallDate LastBootUpTime = $Data.LastBootUpTime LocalDateTime = $Data.LocalDateTime SerialNumber = $Data.SerialNumber BootDevice = $Data.BootDevice WindowsDirectory = $Data.WindowsDirectory CountryCode = $Data.CountryCode } } } } function Get-ComputerRAM { <# .SYNOPSIS Retrieves information about the RAM of a specified computer. .DESCRIPTION This function retrieves detailed information about the RAM of a specified computer. It provides various properties such as Manufacturer, Model, Capacity, Speed, and more. .PARAMETER ComputerName Specifies the name of the computer to retrieve RAM information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for retrieving RAM information. Valid values are 'Default', 'Dcom', and 'Wsman'. Defaults to 'Default'. .PARAMETER All Indicates whether to retrieve all available properties of the RAM. If specified, all properties will be retrieved. .PARAMETER Extended Indicates whether to retrieve extended properties of the RAM. If specified, additional properties will be retrieved. .EXAMPLE Get-ComputerRAM -ComputerName "Server01" -Protocol Wsman Retrieves RAM information from a remote computer named Server01 using the Wsman protocol. .EXAMPLE Get-ComputerRAM -ComputerName "WorkstationA" -All Retrieves all available RAM properties from a computer named WorkstationA. #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All, [switch] $Extended ) [string] $Class = 'Win32_physicalmemory ' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = @( 'InstallDate' 'Manufacturer' 'Model' 'OtherIdentifyingInfo' 'PartNumber' 'PoweredOn' 'SerialNumber' 'SKU' 'Tag' 'Version' 'HotSwappable' 'Removable' 'Replaceable' 'FormFactor' 'BankLabel' 'Capacity' 'InterleavePosition' 'MemoryType' 'Speed' 'ConfiguredClockSpeed' 'ConfiguredVoltage' 'DeviceLocator' 'MaxVoltage' 'MinVoltage' 'SMBIOSMemoryType' 'TypeDetail' 'PSComputerName' ) } $FormFactor = @{ '0' = 'Unknown' '1' = 'Other' '2' = 'SIP' '3' = 'DIP' '4' = 'ZIP' '5' = 'SOJ' '6' = 'Proprietary' '7' = 'SIMM' '8' = 'DIMM' '9' = 'TSOP' '10' = 'PGA' '11' = 'RIMM' '12' = 'SODIMM' '13' = 'SRIMM' '14' = 'SMD' '15' = 'SSMP' '16' = 'QFP' '17' = 'TQFP' '18' = 'SOIC' '19' = 'LCC' '20' = 'PLCC' '21' = 'BGA' '22' = 'FPBGA' '23' = 'LGA' } $TypeDetails = @{ '1' = 'Reserved' '2' = 'Other' '4' = 'Unknown' '8' = 'Fast-paged' '16' = 'Static column' '32' = 'Pseudo-static' '64' = 'RAMBUS' '128' = 'Synchronous' '256' = 'CMOS' '512' = 'EDO' '1024' = 'Window DRAM' '2048' = 'Cache DRAM' '4096' = 'Non-volatile' } $InterleavePosition = @{ '0' = "Non-Interleaved" '1' = "First Position" '2' = "Second Position" } $MemoryType = @{ '0' = "Unknown" '1' = "Other" '2' = "DRAM" '3' = "Synchronous DRAM" '4' = "Cache DRAM" '5' = "EDO" '6' = "EDRAM" '7' = "VRAM" '8' = "SRAM" '9' = "ROM" '10' = "ROM" '11' = "FLASH" '12' = "EEPROM" '13' = "FEPROM" '14' = "EPROM" '15' = "CDRAM" '16' = "3DRAM" '17' = "SDRAM" '18' = "SGRAM" '19' = "RDRAM" '20' = "DDR" } $MemoryTypeSMBIOS = @{ '0' = 'Unknown' '1' = 'Other' '2' = 'DRAM' '3' = 'Synchronous DRAM' '4' = 'Cache DRAM' '5' = 'EDO' '6' = 'EDRAM' '7' = 'VRAM' '8' = 'SRAM' '9' = 'RAM' '10' = 'ROM' '11' = 'Flash' '12' = 'EEPROM' '13' = 'FEPROM' '14' = 'EPROM' '15' = 'CDRAM' '16' = '3DRAM' '17' = 'SDRAM' '18' = 'SGRAM' '19' = 'RDRAM' '20' = 'DDR' '21' = 'DDR2' '22' = 'DDR2 FB-DIMM' '24' = 'DDR3' '25' = 'FBD2' '26' = 'DDR4' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { $Ram = [ordered] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Manufacturer = $Data.Manufacturer FormFactor = $FormFactor["$($Data.FormFactor)"] SMBIOSMemoryType = $MemoryTypeSMBIOS["$($Data.SMBIOSMemoryType)"] Size = [math]::round($Data.Capacity / 1GB, 2) Speed = $Data.Speed InterleavePosition = $InterleavePosition["$($Data.InterleavePosition)"] MemoryType = $MemoryType["$($Data.MemoryType)"] TypeDetail = $TypeDetails["$($Data.TypeDetail)"] PartNumber = $Data.PartNumber DeviceLocator = $Data.DeviceLocator } if ($Extended) { $RamExtended = [ordered] @{ InstallDate = $Data.InstallDate Model = $Data.Model OtherIdentifyingInfo = $Data.OtherIdentifyingInfo PoweredOn = $Data.PoweredOn SerialNumber = $Data.SerialNumber SKU = $Data.SKU Tag = $Data.Tag Version = $Data.Version HotSwappable = $Data.HotSwappable Removable = $Data.Removable Replaceable = $Data.Replaceable BankLabel = $Data.BankLabel ConfiguredClockSpeed = $Data.ConfiguredClockSpeed ConfiguredVoltage = $Data.ConfiguredVoltage MaxVoltage = $Data.MaxVoltage MinVoltage = $Data.MinVoltage } [PSCustomObject] ($Ram + $RamExtended) } else { [PSCustomObject] $Ram } } } } } function Get-ComputerRDP { <# .SYNOPSIS Retrieves Remote Desktop Protocol (RDP) settings for a specified computer. .DESCRIPTION This function retrieves RDP settings for a specified computer using the Win32_TSGeneralSetting class. .PARAMETER ComputerName Specifies the name of the computer to retrieve RDP settings for. .EXAMPLE Get-ComputerRDP -ComputerName "Computer01" Retrieves RDP settings for a computer named "Computer01". .EXAMPLE Get-ComputerRDP -ComputerName "Computer02", "Computer03" Retrieves RDP settings for multiple computers named "Computer02" and "Computer03". #> [alias('Get-RDPSecurity')] [cmdletbinding()] param( [string[]] $ComputerName ) $Output = Get-CimData -Class 'Win32_TSGeneralSetting' -NameSpace 'root\cimv2\terminalservices' -ComputerName $ComputerName foreach ($_ in $Output) { $EncryptionLevels = @{ '1' = 'Low' '2' = 'Medium / Client Compatible' '3' = 'High' '4' = 'FIPS Compliant' } $PolicyConfiguredBy = @{ '0' = 'Server' '1' = 'Group policy' '2' = 'Default' } $SecurityLayers = @{ '1' = 'RDP Security Layer' '2' = 'Negotiate' '3' = 'SSL' '4' = 'NEWTBD' } $HashType = @{ '0' = 'Not valid' '1' = 'Self-signed' '2' = 'Custom' } $Connectivity = Test-ComputerPort -ComputerName $_.PSComputerName -PortTCP 3389 -WarningAction SilentlyContinue [PSCustomObject] @{ ComputerName = $_.PSComputerName Name = $_.TerminalName Connectivity = $Connectivity.Status ConnectivitySummary = $Connectivity.Summary SecurityLayer = $SecurityLayers["$($_.SecurityLayer)"] MinimalEncryptionLevel = $EncryptionLevels["$($_.MinEncryptionLevel)"] MinimalEncryptionLevelValue = $_.MinEncryptionLevel PolicySourceUserAuthenticationRequired = $PolicyConfiguredBy["$($_.PolicySourceUserAuthenticationRequired)"] PolicySourceMinimalEncryptionLevel = $PolicyConfiguredBy["$($_.PolicySourceMinEncryptionLevel)"] PolicySourceSecurityLayer = $PolicyConfiguredBy["$($_.PolicySourceSecurityLayer)"] CertificateName = $_.CertificateName CertificateThumbprint = $_.SSLCertificateSHA1Hash CertificateType = $HashType["$($_.SSLCertificateSHA1HashType)"] Transport = $_.Transport Protocol = $_.TerminalProtocol UserAuthenticationRequired = [bool] $_.UserAuthenticationRequired WindowsAuthentication = [bool] $_.WindowsAuthentication } } } function Get-ComputerService { <# .SYNOPSIS Retrieves information about services running on specified computers. .DESCRIPTION This function retrieves information about services running on one or more specified computers. It returns details such as ComputerName, Name, Displayname, Status, and StartType of the services. .EXAMPLE Get-ComputerServices -ComputerName "Computer01" Retrieves information about services running on a single computer named "Computer01". .EXAMPLE Get-ComputerServices -ComputerName "Computer01", "Computer02" Retrieves information about services running on multiple computers named "Computer01" and "Computer02". #> [alias('Get-ComputerServices')] [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME ) Process { foreach ($Computer in $ComputerName) { $Services = Get-PSService -ComputerName $Computer | Select-Object ComputerName, Name, Displayname, Status, StartType $Services } } } function Get-ComputerSplit { <# .SYNOPSIS This function splits the list of computer names provided into two arrays: one containing remote computers and another containing the local computer. .DESCRIPTION The Get-ComputerSplit function takes an array of computer names as input and splits them into two arrays based on whether they are remote computers or the local computer. It determines the local computer by comparing the provided computer names with the local computer name and DNS name. .PARAMETER ComputerName Specifies an array of computer names to split into remote and local computers. .EXAMPLE Get-ComputerSplit -ComputerName "Computer1", "Computer2", $Env:COMPUTERNAME This example splits the computer names "Computer1" and "Computer2" into the remote computers array and the local computer array based on the local computer's name. #> [CmdletBinding()] param( [string[]] $ComputerName ) if ($null -eq $ComputerName) { $ComputerName = $Env:COMPUTERNAME } try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Env:COMPUTERNAME } $ComputersLocal = $null [Array] $Computers = foreach ($Computer in $ComputerName) { if ($Computer -eq '' -or $null -eq $Computer) { $Computer = $Env:COMPUTERNAME } if ($Computer -ne $Env:COMPUTERNAME -and $Computer -ne $LocalComputerDNSName) { $Computer } else { $ComputersLocal = $Computer } } , @($ComputersLocal, $Computers) } function Get-ComputerStartup { <# .SYNOPSIS Retrieves information about startup programs on a remote computer. .DESCRIPTION The Get-ComputerStartup function retrieves information about startup programs on a specified computer using CIM/WMI. .PARAMETER ComputerName Specifies the name of the computer to retrieve startup information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for the connection. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'. .PARAMETER All Indicates whether to retrieve all properties of the startup programs. .EXAMPLE Get-ComputerStartup -ComputerName "RemoteComputer" -Protocol Wsman Retrieves startup program information from a remote computer using the Wsman protocol. .EXAMPLE Get-ComputerStartup -All Retrieves all startup program information from the local computer. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_startupCommand' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Caption', 'Description', 'Command', 'Location', 'Name', 'User', 'UserSID', 'PSComputerName' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Caption = $Data.Caption Description = $Data.Description Command = $Data.Command Location = $Data.Location Name = $Data.Name User = $Data.User UserSID = $Data.UserSID } } } } } function Get-ComputerSystem { <# .SYNOPSIS Retrieves computer system information from remote computers. .DESCRIPTION This function retrieves computer system information from remote computers using CIM/WMI queries. .PARAMETER ComputerName Specifies the names of the remote computers to retrieve system information from. .PARAMETER Protocol Specifies the protocol to use for the remote connection. Valid values are 'Default', 'Dcom', or 'Wsman'. .PARAMETER All Indicates whether to retrieve all available properties of the computer system. .EXAMPLE Get-ComputerSystem -ComputerName AD1, AD2, EVO1, ADFFS | ft -a * Retrieves computer system information for the specified computers and displays it in a table format. .NOTES This function uses CIM/WMI queries to gather system information from remote computers. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'Win32_ComputerSystem' if ($All) { $Properties = '*' } else { $Properties = 'PSComputerName', 'Name', 'Manufacturer' , 'Domain', 'Model' , 'Systemtype', 'PrimaryOwnerName', 'PCSystemType', 'PartOfDomain', 'CurrentTimeZone', 'BootupState', 'Roles', 'SystemFamily' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Name = $Data.Name Manufacturer = $Data.Manufacturer Domain = $Data.Domain Model = $Data.Model Systemtype = $Data.Systemtype PrimaryOwnerName = $Data.PrimaryOwnerName PCSystemType = [Microsoft.PowerShell.Commands.PCSystemType] $Data.PCSystemType PartOfDomain = $Data.PartOfDomain CurrentTimeZone = $Data.CurrentTimeZone BootupState = $Data.BootupState SystemFamily = $Data.SystemFamily Roles = $Data.Roles -join ', ' } } } } } function Get-ComputerTask { <# .SYNOPSIS Get Task Schedule information .DESCRIPTION Get Task Schedule information .PARAMETER ComputerName Specifies computer on which you want to run the operation. .EXAMPLE Get-ComputerTask | Format-Table .NOTES General notes #> [alias('Get-ComputerTasks')] [cmdletbinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME ) foreach ($Computer in $ComputerName) { try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Computer } if ($Computer -eq $Env:COMPUTERNAME -or $Computer -eq $LocalComputerDNSName) { $TaskParameters = @{} } else { $TaskParameters = @{ CimSession = $Computer } } $Tasks = Get-ScheduledTask @TaskParameters foreach ($Task in $Tasks) { $Info = $Task | Get-ScheduledTaskInfo @TaskParameters $Actions = foreach ($_ in $Task.Actions) { -join ($_.Execute, $_.Arguments) } [PSCustomObject] @{ ComputerName = $Computer TaskName = $Task.TaskName TaskPath = $Task.TaskPath State = $Task.State Actions = $Actions Author = $Task.Author Date = $Task.Date Description = $Task.Description Documentation = $Task.Documentation PrincipalDisplayName = $Task.Principal.DisplayName PrincipalUserID = $Task.Principal.UserID PrincipalGroupID = $Task.Principal.GroupID PrincipalLogonType = $Task.Principal.LogonType PrincipalRunLevel = $Task.Principal.RunLevel PrincipalProcessTokenSidType = $Task.Principal.ProcessTokenSidType PrincipalRequiredPrivilege = $Task.Principal.RequiredPrivilege SettingsAllowDemandStart = $Task.Settings.AllowDemandStart SettingsAllowHardTerminate = $Task.Settings.AllowHardTerminate SettingsCompatibility = $Task.Settings.Compatibility SettingsDeleteExpiredTaskAfter = $Task.Settings.DeleteExpiredTaskAfter SettingsDisallowStartIfOnBatteries = $Task.Settings.DisallowStartIfOnBatteries SettingsEnabled = $Task.Settings.Enabled SettingsExecutionTimeLimit = $Task.Settings.ExecutionTimeLimit SettingsHidden = $Task.Settings.Hidden SettingsIdleSettings = $Task.Settings.IdleSettings SettingsMultipleInstances = $Task.Settings.MultipleInstances SettingsNetworkSettings = $Task.Settings.NetworkSettings SettingsPriority = $Task.Settings.Priority SettingsRestartCount = $Task.Settings.RestartCount SettingsRestartInterval = $Task.Settings.RestartInterval SettingsRunOnlyIfIdle = $Task.Settings.RunOnlyIfIdle SettingsRunOnlyIfNetworkAvailable = $Task.Settings.RunOnlyIfNetworkAvailable SettingsStartWhenAvailable = $Task.Settings.StartWhenAvailable SettingsStopIfGoingOnBatteries = $Task.Settings.StopIfGoingOnBatteries SettingsWakeToRun = $Task.Settings.WakeToRun SettingsDisallowStartOnRemoteAppSession = $Task.Settings.DisallowStartOnRemoteAppSession SettingsUseUnifiedSchedulingEngine = $Task.Settings.UseUnifiedSchedulingEngine SettingsMaintenanceSettings = $Task.Settings.MaintenanceSettings SettingsVolatile = $Task.Settings.volatile Source = $Task.Source URI = $Task.URI Version = $Task.Version LastRunTime = $Info.LastRunTime LastTaskResult = $Info.LastTaskResult NextRunTime = $Info.NextRunTime NumberOfMissedRuns = $Info.NumberOfMissedRuns } } } } function Get-ComputerTime { <# .SYNOPSIS Gets time difference between computers and time source including boot time .DESCRIPTION Gets time difference between computers and time source including boot time .PARAMETER TimeSource Parameter description .PARAMETER Domain Parameter description .PARAMETER TimeTarget Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM). .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. .PARAMETER ForceCIM .PARAMETER ToLocal .EXAMPLE Get-ComputerTime -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize Output Name LocalDateTime RemoteDateTime InstallTime LastBootUpTime TimeDifferenceMinutes TimeDifferenceSeconds TimeDifferenceMilliseconds TimeSourceName ---- ------------- -------------- ----------- -------------- --------------------- --------------------- -------------------------- -------------- AD2 13.08.2019 23:40:26 13.08.2019 23:40:26 30.05.2018 18:30:48 09.08.2019 18:40:31 8,33333333333333E-05 0,005 5 AD1.ad.evotec.xyz AD3 13.08.2019 23:40:26 13.08.2019 17:40:26 26.05.2019 17:30:17 09.08.2019 18:40:30 0,000266666666666667 0,016 16 AD1.ad.evotec.xyz EVOWin 13.08.2019 23:40:26 13.08.2019 23:40:26 24.05.2019 22:46:45 09.08.2019 18:40:06 6,66666666666667E-05 0,004 4 AD1.ad.evotec.xyz .EXAMPLE Get-ComputerTime -TimeSource AD1 -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize .EXAMPLE Get-ComputerTime -TimeSource 'pool.ntp.org' -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize .NOTES General notes #> [CmdletBinding()] param( [string] $TimeSource, [string] $Domain = $Env:USERDNSDOMAIN, [alias('ComputerName')][string[]] $TimeTarget = $ENV:COMPUTERNAME, [pscredential] $Credential, [switch] $ForceCIM ) if (-not $TimeSource) { $TimeSource = (Get-ADDomainController -Discover -Service PrimaryDC -DomainName $Domain).HostName } if ($ForceCIM) { $TimeSourceInformation = Get-CimData -ComputerName $TimeSource -Class 'win32_operatingsystem' -Credential $Credential if ($TimeSourceInformation.LocalDateTime) { $TimeSourceInformation = $TimeSourceInformation.LocalDateTime } else { $TimeSourceInformation = $null } } else { $TimeSourceInformation = Get-ComputerTimeNtp -Server $TimeSource -ToLocal } $TimeTargetInformationCache = @{ } $TimeTargetInformation = Get-CimData -ComputerName $TimeTarget -Class 'win32_operatingsystem' -Credential $Credential foreach ($_ in $TimeTargetInformation) { $TimeTargetInformationCache[$_.PSComputerName] = $_ } $TimeLocalCache = @{ } $TimeLocal = Get-CimData -ComputerName $TimeTarget -Class 'Win32_LocalTime' -Credential $Credential foreach ($_ in $TimeLocal) { $TimeLocalCache[$_.PSComputerName] = $_ } $AllResults = foreach ($Computer in $TimeTarget) { $WMIComputerTime = $TimeLocalCache[$Computer] $WMIComputerTarget = $TimeTargetInformationCache[$Computer] if ($WMIComputerTime -and $WMIComputerTime.Year -and $WMIComputerTime.Month) { $RemoteDateTime = Get-Date -Year $WMIComputerTime.Year -Month $WMIComputerTime.Month -Day $WMIComputerTime.Day -Hour $WMIComputerTime.Hour -Minute $WMIComputerTime.Minute -Second $WMIComputerTime.Second } else { $RemoteDateTIme = '' } if ($WMIComputerTarget.LocalDateTime -and $TimeSourceInformation) { $Result = New-TimeSpan -Start $TimeSourceInformation -End $WMIComputerTarget.LocalDateTime $ResultFromBoot = New-TimeSpan -Start $WMIComputerTarget.LastBootUpTime -End $WMIComputerTarget.LocalDateTime [PSCustomObject] @{ Name = $Computer LocalDateTime = $WMIComputerTarget.LocalDateTime RemoteDateTime = $RemoteDateTime InstallTime = $WMIComputerTarget.InstallDate LastBootUpTime = $WMIComputerTarget.LastBootUpTime LastBootUpTimeInDays = if ($null -ne $ResultFromBoot.TotalDays) { [math]::Round($ResultFromBoot.TotalDays, 2) } else { $null } TimeDifferenceMinutes = if ($Result.TotalMinutes -lt 0) { ($Result.TotalMinutes * -1) } else { $Result.TotalMinutes } TimeDifferenceSeconds = if ($Result.TotalSeconds -lt 0) { ($Result.TotalSeconds * -1) } else { $Result.TotalSeconds } TimeDifferenceMilliseconds = if ($Result.TotalMilliseconds -lt 0) { ($Result.TotalMilliseconds * -1) } else { $Result.TotalMilliseconds } TimeSourceName = $TimeSource Status = '' } } else { if ($WMIComputerTarget.LastBootUpTime) { $ResultFromBoot = New-TimeSpan -Start $WMIComputerTarget.LastBootUpTime -End $WMIComputerTarget.LocalDateTime } else { $ResultFromBoot = $null } [PSCustomObject] @{ Name = $Computer LocalDateTime = $WMIComputerTarget.LocalDateTime RemoteDateTime = $RemoteDateTime InstallTime = $WMIComputerTarget.InstallDate LastBootUpTime = $WMIComputerTarget.LastBootUpTime LastBootUpTimeInDays = if ($ResultFromBoot) { [math]::Round($ResultFromBoot.TotalDays, 2) } else { $null } TimeDifferenceMinutes = $null TimeDifferenceSeconds = $null TimeDifferenceMilliseconds = $null TimeSourceName = $TimeSource Status = 'Unable to get time difference.' } } } $AllResults } function Get-ComputerWindowsUpdates { <# .SYNOPSIS Retrieves information about Windows updates installed on specified computers. .DESCRIPTION This function retrieves details about Windows updates installed on one or more computers specified by the ComputerName parameter. .PARAMETER ComputerName Specifies the name of the computer(s) to retrieve Windows update information for. .EXAMPLE Get-ComputerWindowsUpdates -ComputerName "EVOWIN", "AD1" Retrieves Windows update information for computers named "EVOWIN" and "AD1". .NOTES This function uses the Get-HotFix cmdlet to gather information about Windows updates. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME ) foreach ($Computer in $ComputerName) { try { $Data = Get-HotFix -ComputerName $Computer $Output = foreach ($Update in $Data) { [PSCustomObject] @{ ComputerName = $Computer InstalledOn = $Update.InstalledOn Description = $Update.Description KB = $Update.HotFixId InstalledBy = $Update.InstalledBy Caption = $Update.Caption } } $Output | Sort-Object -Descending InstalledOn } catch { Write-Warning -Message "Get-ComputerWindowsUpdates - No data for computer $($Computer). Failed with errror: $($_.Exception.Message)" } } } function Get-FilteredACL { <# .SYNOPSIS Retrieves filtered Active Directory Access Control List (ACL) details based on specified criteria. .DESCRIPTION This function retrieves and filters Active Directory Access Control List (ACL) details based on the provided criteria. It allows for filtering by various parameters such as access control type, inheritance status, active directory rights, and more. .PARAMETER ACL Specifies the Active Directory Access Control List (ACL) to filter. .PARAMETER Resolve If specified, resolves the identity reference in the ACL. .PARAMETER Principal Specifies the principal to filter by. .PARAMETER Inherited If specified, includes only inherited ACLs. .PARAMETER NotInherited If specified, includes only non-inherited ACLs. .PARAMETER AccessControlType Specifies the type of access control to filter by. .PARAMETER IncludeObjectTypeName Specifies the object type names to include in the filter. .PARAMETER IncludeInheritedObjectTypeName Specifies the inherited object type names to include in the filter. .PARAMETER ExcludeObjectTypeName Specifies the object type names to exclude from the filter. .PARAMETER ExcludeInheritedObjectTypeName Specifies the inherited object type names to exclude from the filter. .PARAMETER IncludeActiveDirectoryRights Specifies the Active Directory rights to include in the filter. .PARAMETER IncludeActiveDirectoryRightsExactMatch Specifies the Active Directory rights to include in the filter as an exact match (all rights must be present). .PARAMETER ExcludeActiveDirectoryRights Specifies the Active Directory rights to exclude from the filter. .PARAMETER IncludeActiveDirectorySecurityInheritance Specifies the Active Directory security inheritance types to include in the filter. .PARAMETER ExcludeActiveDirectorySecurityInheritance Specifies the Active Directory security inheritance types to exclude from the filter. .PARAMETER PrincipalRequested Specifies the requested principal object. .PARAMETER Bundle If specified, bundles the filtered ACL details. .PARAMETER DistinguishedName Specifies the distinguished name of the ACL. This parameter is used only to display the distinguished name in the output. .PARAMETER SkipDistinguishedName If specified, skips the distinguished name in the output. .EXAMPLE Get-FilteredACL -ACL $ACL -Resolve -Principal "User1" -Inherited -AccessControlType "Allow" -IncludeObjectTypeName "File" -ExcludeInheritedObjectTypeName "Folder" -IncludeActiveDirectoryRights "Read" -ExcludeActiveDirectoryRights "Write" -IncludeActiveDirectorySecurityInheritance "Descendents" -ExcludeActiveDirectorySecurityInheritance "SelfAndChildren" -PrincipalRequested $PrincipalRequested -Bundle Retrieves and filters Active Directory Access Control List (ACL) details based on the specified criteria. .NOTES Additional information about the function. #> [cmdletBinding()] param( [System.DirectoryServices.ActiveDirectoryAccessRule] $ACL, [alias('ResolveTypes')][switch] $Resolve, [string] $Principal, [switch] $Inherited, [switch] $NotInherited, [System.Security.AccessControl.AccessControlType] $AccessControlType, [Alias('ObjectTypeName')][string[]] $IncludeObjectTypeName, [Alias('InheritedObjectTypeName')][string[]] $IncludeInheritedObjectTypeName, [string[]] $ExcludeObjectTypeName, [string[]] $ExcludeInheritedObjectTypeName, [Alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights, [System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRightsExactMatch, [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights, [Alias('InheritanceType', 'IncludeInheritanceType')][System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance, [Alias('ExcludeInheritanceType')][System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance, [PSCustomObject] $PrincipalRequested, [switch] $Bundle, [string] $DistinguishedName, [switch] $SkipDistinguishedName ) if (-not $Script:ForestGUIDs) { Write-Verbose "Get-ADACL - Gathering Forest GUIDS" $Script:ForestGUIDs = Get-WinADForestGUIDs } if (-not $Script:ForestDetails) { Write-Verbose "Get-ADACL - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } [Array] $ADRights = $ACL.ActiveDirectoryRights -split ', ' if ($AccessControlType) { if ($ACL.AccessControlType -ne $AccessControlType) { continue } } if ($Inherited) { if ($ACL.IsInherited -eq $false) { continue } } if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } } if ($IncludeActiveDirectoryRightsExactMatch) { [Array] $FoundIncludeList = foreach ($Right in $IncludeActiveDirectoryRightsExactMatch) { if ($ADRights -eq $Right) { $true } } if ($FoundIncludeList.Count -ne $IncludeActiveDirectoryRightsExactMatch.Count) { continue } } if ($IncludeActiveDirectoryRights) { $FoundInclude = $false foreach ($Right in $ADRights) { if ($IncludeActiveDirectoryRights -contains $Right) { $FoundInclude = $true break } } if (-not $FoundInclude) { continue } } if ($ExcludeActiveDirectoryRights) { foreach ($Right in $ADRights) { $FoundExclusion = $false if ($ExcludeActiveDirectoryRights -contains $Right) { $FoundExclusion = $true break } if ($FoundExclusion) { continue } } } if ($IncludeActiveDirectorySecurityInheritance) { if ($IncludeActiveDirectorySecurityInheritance -notcontains $ACL.InheritanceType) { continue } } if ($ExcludeActiveDirectorySecurityInheritance) { if ($ExcludeActiveDirectorySecurityInheritance -contains $ACL.InheritanceType) { continue } } $IdentityReference = $ACL.IdentityReference.Value $ReturnObject = [ordered] @{ } if (-not $SkipDistinguishedName) { $ReturnObject['DistinguishedName' ] = $DistinguishedName } if ($CanonicalName) { $ReturnObject['CanonicalName'] = $CanonicalName } if ($ObjectClass) { $ReturnObject['ObjectClass'] = $ObjectClass } $ReturnObject['AccessControlType'] = $ACL.AccessControlType $ReturnObject['Principal'] = $IdentityReference if ($Resolve) { $IdentityResolve = Get-WinADObject -Identity $IdentityReference -AddType -Verbose:$false -Cache if (-not $IdentityResolve) { $ConvertIdentity = Convert-Identity -Identity $IdentityReference -Verbose:$false $ReturnObject['PrincipalType'] = $ConvertIdentity.Type $ReturnObject['PrincipalObjectType'] = 'foreignSecurityPrincipal' $ReturnObject['PrincipalObjectDomain'] = $ConvertIdentity.DomainName $ReturnObject['PrincipalObjectSid'] = $ConvertIdentity.SID } else { if ($ReturnObject['Principal']) { $ReturnObject['Principal'] = $IdentityResolve.Name } $ReturnObject['PrincipalType'] = $IdentityResolve.Type $ReturnObject['PrincipalObjectType'] = $IdentityResolve.ObjectClass $ReturnObject['PrincipalObjectDomain' ] = $IdentityResolve.DomainName $ReturnObject['PrincipalObjectSid'] = $IdentityResolve.ObjectSID } if (-not $ReturnObject['PrincipalObjectDomain']) { $ReturnObject['PrincipalObjectDomain'] = ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDomainCN } if ($PrincipalRequested -and $PrincipalRequested.SID -ne $ReturnObject['PrincipalObjectSid']) { continue } } else { if ($Principal -and $Principal -ne $IdentityReference) { continue } } $ReturnObject['ObjectTypeName'] = $Script:ForestGUIDs["$($ACL.objectType)"] $ReturnObject['InheritedObjectTypeName'] = $Script:ForestGUIDs["$($ACL.inheritedObjectType)"] if ($IncludeObjectTypeName) { if ($IncludeObjectTypeName -notcontains $ReturnObject['ObjectTypeName']) { continue } } if ($IncludeInheritedObjectTypeName) { if ($IncludeInheritedObjectTypeName -notcontains $ReturnObject['InheritedObjectTypeName']) { continue } } if ($ExcludeObjectTypeName) { if ($ExcludeObjectTypeName -contains $ReturnObject['ObjectTypeName']) { continue } } if ($ExcludeInheritedObjectTypeName) { if ($ExcludeInheritedObjectTypeName -contains $ReturnObject['InheritedObjectTypeName']) { continue } } if ($ADRightsAsArray) { $ReturnObject['ActiveDirectoryRights'] = $ADRights } else { $ReturnObject['ActiveDirectoryRights'] = $ACL.ActiveDirectoryRights } $ReturnObject['InheritanceType'] = $ACL.InheritanceType $ReturnObject['IsInherited'] = $ACL.IsInherited if ($Extended) { $ReturnObject['ObjectType'] = $ACL.ObjectType $ReturnObject['InheritedObjectType'] = $ACL.InheritedObjectType $ReturnObject['ObjectFlags'] = $ACL.ObjectFlags $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags } if ($Bundle) { $ReturnObject['Bundle'] = $ACL } [PSCustomObject] $ReturnObject } function Get-LocalComputerSid { <# .SYNOPSIS Get the SID of the local computer. .DESCRIPTION Get the SID of the local computer. .EXAMPLE Get-LocalComputerSid .NOTES General notes #> [cmdletBinding()] param() try { Add-Type -AssemblyName System.DirectoryServices.AccountManagement $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::Machine) $UserPrincipal = [System.DirectoryServices.AccountManagement.UserPrincipal]::new($PrincipalContext) $Searcher = [System.DirectoryServices.AccountManagement.PrincipalSearcher]::new() $Searcher.QueryFilter = $UserPrincipal $User = $Searcher.FindAll() foreach ($U in $User) { if ($U.Sid.Value -like "*-500") { return $U.Sid.Value.TrimEnd("-500") } } } catch { Write-Warning -Message "Get-LocalComputerSid - Error: $($_.Exception.Message)" } } function Get-PSConvertSpecialRegistry { <# .SYNOPSIS Converts special registry paths for specified computers. .DESCRIPTION This function converts special registry paths for the specified computers using the provided HiveDictionary. .PARAMETER RegistryPath Specifies the array of registry paths to convert. .PARAMETER Computers Specifies the array of computers to convert registry paths for. .PARAMETER HiveDictionary Specifies the dictionary containing hive keys and their corresponding values. .PARAMETER ExpandEnvironmentNames Indicates whether to expand environment names in the registry paths. .EXAMPLE Get-PSConvertSpecialRegistry -RegistryPath "Users\Offline_Przemek\Software\Policies1\Microsoft\Windows\CloudContent" -Computers "Computer1", "Computer2" -HiveDictionary $HiveDictionary -ExpandEnvironmentNames Converts the specified registry path for the specified computers using the provided HiveDictionary. #> [cmdletbinding()] param( [Array] $RegistryPath, [Array] $Computers, [System.Collections.IDictionary] $HiveDictionary, [switch] $ExpandEnvironmentNames ) $FixedPath = foreach ($R in $RegistryPath) { foreach ($DictionaryKey in $HiveDictionary.Keys) { $SplitParts = $R.Split("\") $FirstPart = $SplitParts[0] if ($FirstPart -eq $DictionaryKey) { if ($HiveDictionary[$DictionaryKey] -in 'All', 'All+Default', 'Default', 'AllDomain+Default', 'AllDomain', 'AllDomain+Other', 'AllDomain+Other+Default') { foreach ($Computer in $Computers) { $SubKeys = Get-PSRegistry -RegistryPath "HKEY_USERS" -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent -DoNotUnmount if ($SubKeys.PSSubKeys) { $RegistryKeys = ConvertTo-HKeyUser -SubKeys ($SubKeys.PSSubKeys | Sort-Object) -HiveDictionary $HiveDictionary -DictionaryKey $DictionaryKey -RegistryPath $R foreach ($S in $RegistryKeys) { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $S Error = $null ErrorMessage = $null } } } else { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $R Error = $true ErrorMessage = "Couldn't connect to $Computer to list HKEY_USERS" } } } } elseif ($FirstPart -in 'Users', 'HKEY_USERS', 'HKU' -and $SplitParts[1] -and $SplitParts[1] -like "Offline_*") { foreach ($Computer in $Computers) { $SubKeys = Get-PSRegistry -RegistryPath "HKEY_USERS" -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent -DoNotUnmount if ($SubKeys.PSSubKeys) { $RegistryKeys = ConvertTo-HKeyUser -SubKeys ($SubKeys.PSSubKeys + $SplitParts[1] | Sort-Object) -HiveDictionary $HiveDictionary -DictionaryKey $DictionaryKey -RegistryPath $R foreach ($S in $RegistryKeys) { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $S Error = $null ErrorMessage = $null } } } else { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $R Error = $true ErrorMessage = "Couldn't connect to $Computer to list HKEY_USERS" } } } } else { $R } break } } } $FixedPath } function Get-PSRegistryDictionaries { <# .SYNOPSIS Retrieves a set of registry dictionaries for common registry hives and keys. .DESCRIPTION This function retrieves a set of registry dictionaries that provide mappings for common registry hives and keys. These dictionaries can be used to easily reference different registry locations in PowerShell scripts. .EXAMPLE Get-PSRegistryDictionaries Description: Retrieves all the registry dictionaries. #> [cmdletBinding()] param() if ($Script:Dictionary) { return } $Script:Dictionary = @{ 'HKUAD:' = 'HKEY_ALL_USERS_DEFAULT' 'HKUA:' = 'HKEY_ALL_USERS' 'HKUD:' = 'HKEY_DEFAULT_USER' 'HKUDUD:' = 'HKEY_ALL_DOMAIN_USERS_DEFAULT' 'HKUDU:' = 'HKEY_ALL_DOMAIN_USERS' 'HKUDUO:' = 'HKEY_ALL_DOMAIN_USERS_OTHER' 'HKUDUDO:' = 'HKEY_ALL_DOMAIN_USERS_OTHER_DEFAULT' 'HKCR:' = 'HKEY_CLASSES_ROOT' 'HKCU:' = 'HKEY_CURRENT_USER' 'HKLM:' = 'HKEY_LOCAL_MACHINE' 'HKU:' = 'HKEY_USERS' 'HKCC:' = 'HKEY_CURRENT_CONFIG' 'HKDD:' = 'HKEY_DYN_DATA' 'HKPD:' = 'HKEY_PERFORMANCE_DATA' } $Script:HiveDictionary = [ordered] @{ 'HKEY_ALL_USERS_DEFAULT' = 'All+Default' 'HKUAD' = 'All+Default' 'HKEY_ALL_USERS' = 'All' 'HKUA' = 'All' 'HKEY_ALL_DOMAIN_USERS_DEFAULT' = 'AllDomain+Default' 'HKUDUD' = 'AllDomain+Default' 'HKEY_ALL_DOMAIN_USERS' = 'AllDomain' 'HKUDU' = 'AllDomain' 'HKEY_DEFAULT_USER' = 'Default' 'HKUD' = 'Default' 'HKEY_ALL_DOMAIN_USERS_OTHER' = 'AllDomain+Other' 'HKUDUO' = 'AllDomain+Other' 'HKUDUDO' = 'AllDomain+Other+Default' 'HKEY_ALL_DOMAIN_USERS_OTHER_DEFAULT' = 'AllDomain+Other+Default' 'HKEY_CLASSES_ROOT' = 'ClassesRoot' 'HKCR' = 'ClassesRoot' 'ClassesRoot' = 'ClassesRoot' 'HKCU' = 'CurrentUser' 'HKEY_CURRENT_USER' = 'CurrentUser' 'CurrentUser' = 'CurrentUser' 'HKLM' = 'LocalMachine' 'HKEY_LOCAL_MACHINE' = 'LocalMachine' 'LocalMachine' = 'LocalMachine' 'HKU' = 'Users' 'HKEY_USERS' = 'Users' 'Users' = 'Users' 'HKCC' = 'CurrentConfig' 'HKEY_CURRENT_CONFIG' = 'CurrentConfig' 'CurrentConfig' = 'CurrentConfig' 'HKDD' = 'DynData' 'HKEY_DYN_DATA' = 'DynData' 'DynData' = 'DynData' 'HKPD' = 'PerformanceData' 'HKEY_PERFORMANCE_DATA ' = 'PerformanceData' 'PerformanceData' = 'PerformanceData' } $Script:ReverseTypesDictionary = [ordered] @{ 'REG_SZ' = 'string' 'REG_NONE' = 'none' 'REG_EXPAND_SZ' = 'expandstring' 'REG_BINARY' = 'binary' 'REG_DWORD' = 'dword' 'REG_MULTI_SZ' = 'multistring' 'REG_QWORD' = 'qword' 'string' = 'string' 'expandstring' = 'expandstring' 'binary' = 'binary' 'dword' = 'dword' 'multistring' = 'multistring' 'qword' = 'qword' 'none' = 'none' } } function Get-PSSubRegistry { <# .SYNOPSIS Retrieves a subkey from the Windows Registry on a local or remote computer. .DESCRIPTION The Get-PSSubRegistry function retrieves a subkey from the Windows Registry on a local or remote computer. It can be used to access specific registry keys and their values. .PARAMETER Registry Specifies the registry key to retrieve. This parameter should be an IDictionary object containing information about the registry key. .PARAMETER ComputerName Specifies the name of the computer from which to retrieve the registry key. This parameter is optional and defaults to the local computer. .PARAMETER Remote Indicates that the registry key should be retrieved from a remote computer. .PARAMETER ExpandEnvironmentNames Indicates whether environment variable names in the registry key should be expanded. .EXAMPLE Get-PSSubRegistry -Registry $Registry -ComputerName "RemoteComputer" -Remote Retrieves a subkey from the Windows Registry on a remote computer named "RemoteComputer". .EXAMPLE Get-PSSubRegistry -Registry $Registry -ExpandEnvironmentNames Retrieves a subkey from the Windows Registry on the local computer with expanded environment variable names. #> [cmdletBinding()] param( [System.Collections.IDictionary] $Registry, [string] $ComputerName, [switch] $Remote, [switch] $ExpandEnvironmentNames ) if ($Registry.ComputerName) { if ($Registry.ComputerName -ne $ComputerName) { return } } if (-not $Registry.Error) { try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Registry.HiveKey, $ComputerName, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Registry.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) } } else { $PSConnection = $false $PSError = $($Registry.ErrorMessage) } if ($PSError) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } else { try { $SubKey = $BaseHive.OpenSubKey($Registry.SubKeyName, $false) if ($null -ne $SubKey) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $false PSErrorMessage = $null PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($Registry.Key, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($Registry.Key) } PSType = $SubKey.GetValueKind($Registry.Key) } } else { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = "Registry path $($Registry.Registry) doesn't exists." PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } } catch { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Get-PSSubRegistryComplete { <# .SYNOPSIS Retrieves sub-registry information from a specified registry key. .DESCRIPTION This function retrieves sub-registry information from a specified registry key on a local or remote computer. .PARAMETER Registry Specifies the registry key information to retrieve. .PARAMETER ComputerName Specifies the name of the computer from which to retrieve the registry information. .PARAMETER Remote Indicates whether the registry key is located on a remote computer. .PARAMETER Advanced Indicates whether to retrieve advanced registry information. .PARAMETER ExpandEnvironmentNames Indicates whether to expand environment variable names in the registry. .EXAMPLE Get-PSSubRegistryComplete -Registry $Registry -ComputerName "Computer01" -Remote -Advanced Retrieves advanced sub-registry information from the specified registry key on a remote computer named "Computer01". .EXAMPLE Get-PSSubRegistryComplete -Registry $Registry -ComputerName "Computer02" Retrieves sub-registry information from the specified registry key on a local computer named "Computer02". #> [cmdletBinding()] param( [System.Collections.IDictionary] $Registry, [string] $ComputerName, [switch] $Remote, [switch] $Advanced, [switch] $ExpandEnvironmentNames ) if ($Registry.ComputerName) { if ($Registry.ComputerName -ne $ComputerName) { return } } if (-not $Registry.Error) { try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Registry.HiveKey, $ComputerName, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Registry.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) } } else { $PSConnection = $false $PSError = $($Registry.ErrorMessage) } if ($PSError) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError PSSubKeys = $null PSPath = $Registry.Registry PSKey = $Registry.Key } } else { try { $SubKey = $BaseHive.OpenSubKey($Registry.SubKeyName, $false) if ($null -ne $SubKey) { $Object = [ordered] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $false PSErrorMessage = $null PSSubKeys = $SubKey.GetSubKeyNames() PSPath = $Registry.Registry } $Keys = $SubKey.GetValueNames() foreach ($K in $Keys) { if ($K -eq "") { if ($Advanced) { $Object['DefaultKey'] = [ordered] @{ Value = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } Type = $SubKey.GetValueKind($K) } } else { $Object['DefaultKey'] = $SubKey.GetValue($K) } } else { if ($Advanced) { $Object[$K] = [ordered] @{ Value = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } Type = $SubKey.GetValueKind($K) } } else { $Object[$K] = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } } } } [PSCustomObject] $Object } else { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = "Registry path $($Registry.Registry) doesn't exists." PSSubKeys = $null PSPath = $Registry.Registry } } } catch { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message PSSubKeys = $null PSPath = $Registry.Registry } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Get-PSSubRegistryTranslated { <# .SYNOPSIS Retrieves the translated sub-registry information based on the provided RegistryPath, HiveDictionary, and Key. .DESCRIPTION This function retrieves the translated sub-registry information by matching the RegistryPath with the HiveDictionary. It returns an ordered hashtable with details such as Registry, HiveKey, SubKeyName, Key, Error, and ErrorMessage. .PARAMETER RegistryPath Specifies an array of registry paths to be translated. .PARAMETER HiveDictionary Specifies a dictionary containing mappings of hive names to their corresponding keys. .PARAMETER Key Specifies a string key to be included in the output. .EXAMPLE Get-PSSubRegistryTranslated -RegistryPath "HKLM\Software\Microsoft" -HiveDictionary @{ "HKLM" = "HKEY_LOCAL_MACHINE" } -Key "Version" Retrieves the translated sub-registry information for the specified registry path under HKEY_LOCAL_MACHINE hive with the key "Version". .EXAMPLE Get-PSSubRegistryTranslated -RegistryPath "HKCU\Software\Microsoft" -HiveDictionary @{ "HKCU" = "HKEY_CURRENT_USER" } Retrieves the translated sub-registry information for the specified registry path under HKEY_CURRENT_USER hive without specifying a key. #> [cmdletBinding()] param( [Array] $RegistryPath, [System.Collections.IDictionary] $HiveDictionary, [string] $Key ) foreach ($Registry in $RegistryPath) { if ($Registry -is [string]) { $Registry = $Registry.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") $FirstPartSplit = $Registry -split "\\" $FirstPart = $FirstPartSplit[0] } else { $Registry.RegistryPath = $Registry.RegistryPath.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") $FirstPartSplit = $Registry.RegistryPath -split "\\" $FirstPart = $FirstPartSplit[0] } foreach ($Hive in $HiveDictionary.Keys) { if ($Registry -is [string] -and $FirstPart -eq $Hive) { if ($Hive.Length -eq $Registry.Length) { [ordered] @{ Registry = $Registry HiveKey = $HiveDictionary[$Hive] SubKeyName = $null Key = if ($Key -eq "") { $null } else { $Key } Error = $null ErrorMessage = $null } } else { [ordered] @{ Registry = $Registry HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.substring($Hive.Length + 1) Key = if ($Key -eq "") { $null } else { $Key } Error = $null ErrorMessage = $null } } break } elseif ($Registry -isnot [string] -and $FirstPart -eq $Hive) { if ($Hive.Length -eq $Registry.RegistryPath.Length) { [ordered] @{ ComputerName = $Registry.ComputerName Registry = $Registry.RegistryPath HiveKey = $HiveDictionary[$Hive] SubKeyName = $null Key = if ($Key -eq "") { $null } else { $Key } Error = $Registry.Error ErrorMessage = $Registry.ErrorMessage } } else { [ordered] @{ ComputerName = $Registry.ComputerName Registry = $Registry.RegistryPath HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.RegistryPath.substring($Hive.Length + 1) Key = if ($Key -eq "") { $null } else { $Key } Error = $Registry.Error ErrorMessage = $Registry.ErrorMessage } } break } } } } function Convert-ADGuidToSchema { <# .SYNOPSIS Converts Guid to schema properties .DESCRIPTION Converts Guid to schema properties .PARAMETER Guid Guid to Convert to Schema Name .PARAMETER Domain Domain to query. By default the current domain is used .PARAMETER RootDSE RootDSE to query. By default RootDSE is queried from the domain .PARAMETER DisplayName Return the schema name by display name. By default it returns as Name .EXAMPLE $T2 = '570b9266-bbb3-4fad-a712-d2e3fedc34dd' $T = [guid] '570b9266-bbb3-4fad-a712-d2e3fedc34dd' Convert-ADGuidToSchema -Guid $T Convert-ADGuidToSchema -Guid $T2 .NOTES General notes #> [alias('Get-WinADDomainGUIDs', 'Get-WinADForestGUIDs')] [cmdletbinding()] param( [string] $Guid, [string] $Domain, [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE, [switch] $DisplayName ) if (-not $Script:ADSchemaMap -or -not $Script:ADSchemaMapDisplayName) { if ($RootDSE) { $Script:RootDSE = $RootDSE } elseif (-not $Script:RootDSE) { if ($Domain) { $Script:RootDSE = Get-ADRootDSE -Server $Domain } else { $Script:RootDSE = Get-ADRootDSE } } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0] $Script:ADSchemaMap = @{ } $Script:ADSchemaMapDisplayName = @{ } $Script:ADSchemaMapDisplayName['00000000-0000-0000-0000-000000000000'] = 'All' $Script:ADSchemaMap.Add('00000000-0000-0000-0000-000000000000', 'All') Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:StandardRights) { $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID } foreach ($S in $Script:StandardRights) { $Script:ADSchemaMap["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.name $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.lDAPDisplayName } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer took $TimeToExecute" Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:ExtendedRightsGuids) { $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid } foreach ($S in $Script:ExtendedRightsGuids) { $Script:ADSchemaMap["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.name $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.displayName } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer took $TimeToExecute" } if ($Guid) { if ($DisplayName) { $Script:ADSchemaMapDisplayName[$Guid] } else { $Script:ADSchemaMap[$Guid] } } else { if ($DisplayName) { $Script:ADSchemaMapDisplayName } else { $Script:ADSchemaMap } } } function New-ADForestDrives { <# .SYNOPSIS Maps network drives for all domains in a specified Active Directory forest. .DESCRIPTION The New-ADForestDrives function maps network drives for all domains in a specified Active Directory forest. It retrieves domain information and maps drives accordingly. .PARAMETER ForestName Specifies the name of the Active Directory forest to map drives for. .PARAMETER ObjectDN Specifies the distinguished name of the object to map drives for. .EXAMPLE New-ADForestDrives -ForestName "example.com" -ObjectDN "CN=Users,DC=example,DC=com" This example maps network drives for the specified forest and object distinguished name. .NOTES File Name : New-ADForestDrives.ps1 Author : Your Name Prerequisite : This function requires the Active Directory module. #> [cmdletbinding()] param( [string] $ForestName, [string] $ObjectDN ) if (-not $Global:ADDrivesMapped) { if ($ForestName) { $Forest = Get-ADForest -Identity $ForestName } else { $Forest = Get-ADForest } if ($ObjectDN) { $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $ObjectDN -ToDC) -replace '=' -replace ',' if (-not(Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { try { if ($Server) { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Server.Hostname[0] -Scope Global -WhatIf:$false Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $($Server.Hostname[0])" } else { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Domain -Scope Global -WhatIf:$false } } catch { Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $($Server.Hostname[0])" } } } else { foreach ($Domain in $Forest.Domains) { try { $Server = Get-ADDomainController -Discover -DomainName $Domain -Writable $DomainInformation = Get-ADDomain -Server $Server.Hostname[0] } catch { Write-Warning "New-ADForestDrives - Can't process domain $Domain - $($_.Exception.Message)" continue } $ObjectDN = $DomainInformation.DistinguishedName $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $ObjectDN -ToDC) -replace '=' -replace ',' if (-not(Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { try { if ($Server) { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Server.Hostname[0] -Scope Global -WhatIf:$false Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $Server" } else { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Domain -Scope Global -WhatIf:$false } } catch { Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $Server $($_.Exception.Message)" } } } } $Global:ADDrivesMapped = $true } } function Remove-PrivateACL { <# .SYNOPSIS Removes private ACLs based on specified criteria. .DESCRIPTION This function removes private ACLs based on the provided ACL object, principal, access rule, access control type, object type name, inherited object type name, inheritance type, force flag, and NT security descriptor. .PARAMETER ACL Specifies the ACL object to be processed. .PARAMETER Principal Specifies the principal for which the ACL should be removed. .PARAMETER AccessRule Specifies the access rule to be removed. .PARAMETER AccessControlType Specifies the type of access control to be removed. .PARAMETER IncludeObjectTypeName Specifies the object type names to include. .PARAMETER IncludeInheritedObjectTypeName Specifies the inherited object type names to include. .PARAMETER InheritanceType Specifies the inheritance type to consider. .PARAMETER Force Indicates whether to force the removal of inherited ACLs. .PARAMETER NTSecurityDescriptor Specifies the NT security descriptor to be updated. .EXAMPLE Remove-PrivateACL -ACL $ACLObject -Principal "User1" -AccessRule "Read" -AccessControlType "Allow" -IncludeObjectTypeName "File" -Force Removes the specified ACL for User1 with Read access on files. .NOTES Author: Your Name Date: Date #> [cmdletBinding(SupportsShouldProcess)] param( [PSCustomObject] $ACL, [string] $Principal, [alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [System.Security.AccessControl.AccessControlType] $AccessControlType, [Alias('ObjectTypeName')][string[]] $IncludeObjectTypeName, [Alias('InheritedObjectTypeName')][string[]] $IncludeInheritedObjectTypeName, [alias('ActiveDirectorySecurityInheritance', 'IncludeActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [switch] $Force, [alias('ActiveDirectorySecurity')][System.DirectoryServices.ActiveDirectorySecurity] $NTSecurityDescriptor ) $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $ACL.DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] $OutputRequiresCommit = @( if ($ntSecurityDescriptor -and $ACL.PSObject.Properties.Name -notcontains 'ACLAccessRules') { try { if ($Principal) { $PrincipalRequested = Convert-Identity -Identity $Principal -Verbose:$false } $SplatFilteredACL = @{ ACL = $ACL.Bundle Resolve = $true Principal = $Principal AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName IncludeActiveDirectorySecurityInheritance = $InheritanceType ExcludeActiveDirectorySecurityInheritance = $ExcludeActiveDirectorySecurityInheritance PrincipalRequested = $PrincipalRequested Bundle = $Bundle } Remove-EmptyValue -Hashtable $SplatFilteredACL $CheckAgainstFilters = Get-FilteredACL @SplatFilteredACL if (-not $CheckAgainstFilters) { continue } Write-Verbose -Message "Remove-ADACL - Removing access from $($ACL.CanonicalName) (type: $($ACL.ObjectClass), IsInherited: $($ACL.IsInherited)) for $($ACL.Principal) / $($ACL.ActiveDirectoryRights) / $($ACL.AccessControlType) / $($ACL.ObjectTypeName) / $($ACL.InheritanceType) / $($ACL.InheritedObjectTypeName)" if ($ACL.IsInherited) { if ($Force) { $ntSecurityDescriptor.SetAccessRuleProtection($true, $true) } else { Write-Warning "Remove-ADACL - Rule for $($ACL.Principal) / $($ACL.ActiveDirectoryRights) / $($ACL.AccessControlType) / $($ACL.ObjectTypeName) / $($ACL.InheritanceType) / $($ACL.InheritedObjectTypeName) is inherited. Use -Force to remove it." continue } } $ntSecurityDescriptor.RemoveAccessRuleSpecific($ACL.Bundle) $true } catch { Write-Warning "Remove-ADACL - Removing access from $($ACL.CanonicalName) (type: $($ACL.ObjectClass), IsInherited: $($ACL.IsInherited)) failed: $($_.Exception.Message)" $false } } elseif ($ACL.PSObject.Properties.Name -contains 'ACLAccessRules') { foreach ($Rule in $ACL.ACLAccessRules) { if ($Principal) { $PrincipalRequested = Convert-Identity -Identity $Principal -Verbose:$false } $SplatFilteredACL = @{ ACL = $Rule.Bundle Resolve = $true Principal = $Principal AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName IncludeActiveDirectorySecurityInheritance = $InheritanceType ExcludeActiveDirectorySecurityInheritance = $ExcludeActiveDirectorySecurityInheritance PrincipalRequested = $PrincipalRequested Bundle = $Bundle } Remove-EmptyValue -Hashtable $SplatFilteredACL $CheckAgainstFilters = Get-FilteredACL @SplatFilteredACL if (-not $CheckAgainstFilters) { continue } $ntSecurityDescriptor = $ACL.ACL try { Write-Verbose -Message "Remove-ADACL - Removing access from $($Rule.CanonicalName) (type: $($Rule.ObjectClass), IsInherited: $($Rule.IsInherited)) for $($Rule.Principal) / $($Rule.ActiveDirectoryRights) / $($Rule.AccessControlType) / $($Rule.ObjectTypeName) / $($Rule.InheritanceType) / $($Rule.InheritedObjectTypeName)" if ($Rule.IsInherited) { if ($Force) { $ntSecurityDescriptor.SetAccessRuleProtection($true, $true) } else { Write-Warning "Remove-ADACL - Rule for $($Rule.Principal) / $($Rule.ActiveDirectoryRights) / $($Rule.AccessControlType) / $($Rule.ObjectTypeName) / $($Rule.InheritanceType) / $($Rule.InheritedObjectTypeName) is inherited. Use -Force to remove it." continue } } $ntSecurityDescriptor.RemoveAccessRuleSpecific($Rule.Bundle) $true } catch { Write-Warning "Remove-ADACL - Removing access from $($Rule.CanonicalName) (type: $($Rule.ObjectClass), IsInherited: $($Rule.IsInherited)) failed: $($_.Exception.Message)" $false } } } else { $AllRights = $false $ntSecurityDescriptor = $ACL.ACL if ($Principal -like '*-*-*-*') { $Identity = [System.Security.Principal.SecurityIdentifier]::new($Principal) } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) } if ($ObjectType -and $InheritanceType -and $AccessRule -and $AccessControlType) { $ObjectTypeGuid = Convert-ADSchemaToGuid -SchemaName $ObjectType if ($ObjectTypeGuid) { $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType, $ObjectTypeGuid, $InheritanceType) } else { Write-Warning "Remove-PrivateACL - Object type '$ObjectType' not found in schema" return } } elseif ($ObjectType -and $AccessRule -and $AccessControlType) { $ObjectTypeGuid = Convert-ADSchemaToGuid -SchemaName $ObjectType if ($ObjectTypeGuid) { $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType, $ObjectTypeGuid) } else { Write-Warning "Remove-PrivateACL - Object type '$ObjectType' not found in schema" return } } elseif ($AccessRule -and $AccessControlType) { $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType) } else { $AllRights = $true } try { if ($AllRights) { Write-Verbose "Remove-ADACL - Removing access for $($Identity) / $AccessControlType / All Rights" $ntSecurityDescriptor.RemoveAccess($Identity, $AccessControlType) } else { Write-Verbose "Remove-ADACL - Removing access for $($AccessRuleToRemove.IdentityReference) / $($AccessRuleToRemove.ActiveDirectoryRights) / $($AccessRuleToRemove.AccessControlType) / $($AccessRuleToRemove.ObjectType) / $($AccessRuleToRemove.InheritanceType) to $($ACL.DistinguishedName)" $ntSecurityDescriptor.RemoveAccessRule($AccessRuleToRemove) } $true } catch { Write-Warning "Remove-ADACL - Error removing permissions for $($Identity) / $($AccessControlType) due to error: $($_.Exception.Message)" $false } } ) if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) { Write-Verbose "Remove-ADACL - Saving permissions for $($ACL.DistinguishedName) on $($QueryServer)" try { $TemporaryObject = Get-ADObject -Identity $ACL.DistinguishedName -Properties ProtectedFromAccidentalDeletion -Server $QueryServer Set-ADObject -Identity $ACL.DistinguishedName -Replace @{ ntSecurityDescriptor = $ntSecurityDescriptor } -ErrorAction Stop -Server $QueryServer $AfterTemporaryObject = Get-ADObject -Identity $ACL.DistinguishedName -Properties ProtectedFromAccidentalDeletion -Server $QueryServer if ($TemporaryObject.ProtectedFromAccidentalDeletion -ne $AfterTemporaryObject.ProtectedFromAccidentalDeletion) { Write-Warning -Message "Remove-ADACL - Restoring ProtectedFromAccidentalDeletion from $($AfterTemporaryObject.ProtectedFromAccidentalDeletion) to $($TemporaryObject.ProtectedFromAccidentalDeletion) for $($ACL.DistinguishedName) as a workaround on $($QueryServer)" Set-ADObject -Identity $ACL.DistinguishedName -ProtectedFromAccidentalDeletion $TemporaryObject.ProtectedFromAccidentalDeletion -ErrorAction Stop -Server $QueryServer } } catch { Write-Warning "Remove-ADACL - Saving permissions for $($ACL.DistinguishedName) failed: $($_.Exception.Message) oon $($QueryServer)" } } elseif ($OutputRequiresCommit -contains $false) { Write-Warning "Remove-ADACL - Skipping saving permissions for $($ACL.DistinguishedName) due to errors." } else { Write-Verbose "Remove-ADACL - No changes for $($ACL.DistinguishedName)" } } function Resolve-PrivateRegistry { <# .SYNOPSIS Resolves and standardizes registry paths for consistency and compatibility. .DESCRIPTION The Resolve-PrivateRegistry function resolves and standardizes registry paths to ensure uniformity and compatibility across different systems. It cleans up the paths, converts short hive names to full names, and handles special cases like DEFAULT USER mappings. .PARAMETER RegistryPath Specifies an array of registry paths to be resolved and standardized. .EXAMPLE Resolve-PrivateRegistry -RegistryPath 'Users\.DEFAULT_USER\Software\MyApp' Resolves the registry path 'Users\.DEFAULT_USER\Software\MyApp' to 'HKUD\Software\MyApp' for consistent usage. .EXAMPLE Resolve-PrivateRegistry -RegistryPath 'HKCU\Software\MyApp' Resolves the registry path 'HKCU\Software\MyApp' to 'HKEY_CURRENT_USER\Software\MyApp' for compatibility with standard naming conventions. #> [CmdletBinding()] param( [alias('Path')][string[]] $RegistryPath ) foreach ($R in $RegistryPath) { $R = $R.Replace("\\", "\").Replace("\\", "\") If ($R.StartsWith("Users\.DEFAULT_USER") -or $R.StartsWith('HKEY_USERS\.DEFAULT_USER')) { $R = $R.Replace("Users\.DEFAULT_USER", "HKUD") $R.Replace('HKEY_USERS\.DEFAULT_USER', "HKUD") } elseif ($R -like '*:*') { $Found = $false foreach ($DictionaryKey in $Script:Dictionary.Keys) { $SplitParts = $R.Split("\") $FirstPart = $SplitParts[0] if ($FirstPart -eq $DictionaryKey) { $R -replace $DictionaryKey, $Script:Dictionary[$DictionaryKey] $Found = $true break } } if (-not $Found) { $R.Replace(":", "") } } else { $R } } } function Test-ComputerPort { <# .SYNOPSIS Tests the connectivity of a computer on specified TCP and UDP ports. .DESCRIPTION The Test-ComputerPort function tests the connectivity of a computer on specified TCP and UDP ports. It checks if the specified ports are open and reachable on the target computer. .PARAMETER ComputerName Specifies the name of the computer to test the port connectivity. .PARAMETER PortTCP Specifies an array of TCP ports to test connectivity. .PARAMETER PortUDP Specifies an array of UDP ports to test connectivity. .PARAMETER Timeout Specifies the timeout value in milliseconds for the connection test. Default is 5000 milliseconds. .EXAMPLE Test-ComputerPort -ComputerName "Server01" -PortTCP 80,443 -PortUDP 53 -Timeout 3000 Tests the connectivity of Server01 on TCP ports 80 and 443, UDP port 53 with a timeout of 3000 milliseconds. .EXAMPLE Test-ComputerPort -ComputerName "Server02" -PortTCP 3389 -PortUDP 123 Tests the connectivity of Server02 on TCP port 3389, UDP port 123 with the default timeout of 5000 milliseconds. #> [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 { <# .SYNOPSIS Tests the WinRM connectivity on the specified computers. .DESCRIPTION The Test-WinRM function tests the WinRM connectivity on the specified computers and returns the status of the connection. .PARAMETER ComputerName Specifies the names of the computers to test WinRM connectivity on. .EXAMPLE Test-WinRM -ComputerName "Server01", "Server02" Tests the WinRM connectivity on Server01 and Server02. .EXAMPLE Test-WinRM -ComputerName "Server03" Tests the WinRM connectivity on Server03. #> [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 Unregister-MountedRegistry { <# .SYNOPSIS Unregisters mounted registry paths. .DESCRIPTION This function unregisters mounted registry paths that were previously mounted using Mount-PSRegistryPath. .EXAMPLE Unregister-MountedRegistry Description: Unregisters all mounted registry paths. #> [CmdletBinding()] param( ) if ($null -ne $Script:DefaultRegistryMounted) { Write-Verbose -Message "Unregister-MountedRegistry - Dismounting HKEY_USERS\.DEFAULT_USER" $null = Dismount-PSRegistryPath -MountPoint "HKEY_USERS\.DEFAULT_USER" $Script:DefaultRegistryMounted = $null } if ($null -ne $Script:OfflineRegistryMounted) { foreach ($Key in $Script:OfflineRegistryMounted.Keys) { if ($Script:OfflineRegistryMounted[$Key].Status -eq $true) { Write-Verbose -Message "Unregister-MountedRegistry - Dismounting HKEY_USERS\$Key" $null = Dismount-PSRegistryPath -MountPoint "HKEY_USERS\$Key" } } $Script:OfflineRegistryMounted = $null } } function Convert-ADSchemaToGuid { <# .SYNOPSIS Converts name of schema properties to guids .DESCRIPTION Converts name of schema properties to guids .PARAMETER SchemaName Schema Name to convert to guid .PARAMETER All Get hashtable of all schema properties and their guids .PARAMETER Domain Domain to query. By default the current domain is used .PARAMETER RootDSE RootDSE to query. By default RootDSE is queried from the domain .PARAMETER AsString Return the guid as a string .EXAMPLE Convert-ADSchemaToGuid -SchemaName 'ms-Exch-MSO-Forward-Sync-Cookie' .EXAMPLE Convert-ADSchemaToGuid -SchemaName 'ms-Exch-MSO-Forward-Sync-Cookie' -AsString .NOTES General notes #> [CmdletBinding()] param( [string] $SchemaName, [string] $Domain, [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE, [switch] $AsString ) if (-not $Script:ADGuidMap -or -not $Script:ADGuidMapString) { if ($RootDSE) { $Script:RootDSE = $RootDSE } elseif (-not $Script:RootDSE) { if ($Domain) { $Script:RootDSE = Get-ADRootDSE -Server $Domain } else { $Script:RootDSE = Get-ADRootDSE } } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0] $Script:ADGuidMap = [ordered] @{ 'All' = [System.GUID]'00000000-0000-0000-0000-000000000000' } $Script:ADGuidMapString = [ordered] @{ 'All' = '00000000-0000-0000-0000-000000000000' } Write-Verbose "Convert-ADSchemaToGuid - Querying Schema from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:StandardRights) { $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID } foreach ($Guid in $Script:StandardRights) { $Script:ADGuidMapString[$Guid.lDAPDisplayName] = ([System.GUID]$Guid.schemaIDGUID).Guid $Script:ADGuidMapString[$Guid.Name] = ([System.GUID]$Guid.schemaIDGUID).Guid $Script:ADGuidMap[$Guid.lDAPDisplayName] = ([System.GUID]$Guid.schemaIDGUID) $Script:ADGuidMap[$Guid.Name] = ([System.GUID]$Guid.schemaIDGUID) } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADSchemaToGuid - Querying Schema from $QueryServer took $TimeToExecute" Write-Verbose "Convert-ADSchemaToGuid - Querying Extended Rights from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:ExtendedRightsGuids) { $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid } foreach ($Guid in $Script:ExtendedRightsGuids) { $Script:ADGuidMapString[$Guid.Name] = ([System.GUID]$Guid.RightsGuid).Guid $Script:ADGuidMapString[$Guid.DisplayName] = ([System.GUID]$Guid.RightsGuid).Guid $Script:ADGuidMap[$Guid.Name] = ([System.GUID]$Guid.RightsGuid) $Script:ADGuidMap[$Guid.DisplayName] = ([System.GUID]$Guid.RightsGuid) } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADSchemaToGuid - Querying Extended Rights from $QueryServer took $TimeToExecute" } if ($SchemaName) { if ($AsString) { return $Script:ADGuidMapString[$SchemaName] } else { return $Script:ADGuidMap[$SchemaName] } } else { if ($AsString) { $Script:ADGuidMapString } else { $Script:ADGuidMap } } } function ConvertFrom-LanguageCode { <# .SYNOPSIS Converts a language code to its corresponding language name. .DESCRIPTION This function takes a language code as input and returns the corresponding language name. .PARAMETER LanguageCode The language code to convert to a language name. .EXAMPLE ConvertFrom-LanguageCode -LanguageCode 1033 Returns: "English (United States)" .EXAMPLE ConvertFrom-LanguageCode -LanguageCode 1041 Returns: "Japanese" #> [cmdletBinding()] param( [string] $LanguageCode ) $LanguageCodeDictionary = @{ '1' = "Arabic" '4' = "Chinese (Simplified)?? China" '9' = "English" '1025' = "Arabic (Saudi Arabia)" '1026' = "Bulgarian" '1027' = "Catalan" '1028' = "Chinese (Traditional) Taiwan" '1029' = "Czech" '1030' = "Danish" '1031' = "German (Germany)" '1032' = "Greek" '1033' = "English (United States)" '1034' = "Spanish (Traditional Sort)" '1035' = "Finnish" '1036' = "French (France)" '1037' = "Hebrew" '1038' = "Hungarian" '1039' = "Icelandic" '1040' = "Italian (Italy)" '1041' = "Japanese" '1042' = "Korean" '1043' = "Dutch (Netherlands)" '1044' = "Norwegian (Bokmal)" '1045' = "Polish" '1046' = "Portuguese (Brazil)" '1047' = "Rhaeto-Romanic" '1048' = "Romanian" '1049' = "Russian" '1050' = "Croatian" '1051' = "Slovak" '1052' = "Albanian" '1053' = "Swedish" '1054' = "Thai" '1055' = "Turkish" '1056' = "Urdu" '1057' = "Indonesian" '1058' = "Ukrainian" '1059' = "Belarusian" '1060' = "Slovenian" '1061' = "Estonian" '1062' = "Latvian" '1063' = "Lithuanian" '1065' = "Persian" '1066' = "Vietnamese" '1069' = "Basque (Basque)" '1070' = "Serbian" '1071' = "Macedonian (FYROM)" '1072' = "Sutu" '1073' = "Tsonga" '1074' = "Tswana" '1076' = "Xhosa" '1077' = "Zulu" '1078' = "Afrikaans" '1080' = "Faeroese" '1081' = "Hindi" '1082' = "Maltese" '1084' = "Scottish Gaelic (United Kingdom)" '1085' = "Yiddish" '1086' = "Malay (Malaysia)" '2049' = "Arabic (Iraq)" '2052' = "Chinese (Simplified) PRC" '2055' = "German (Switzerland)" '2057' = "English (United Kingdom)" '2058' = "Spanish (Mexico)" '2060' = "French (Belgium)" '2064' = "Italian (Switzerland)" '2067' = "Dutch (Belgium)" '2068' = "Norwegian (Nynorsk)" '2070' = "Portuguese (Portugal)" '2072' = "Romanian (Moldova)" '2073' = "Russian (Moldova)" '2074' = "Serbian (Latin)" '2077' = "Swedish (Finland)" '3073' = "Arabic (Egypt)" '3076' = "Chinese Traditional (Hong Kong SAR)" '3079' = "German (Austria)" '3081' = "English (Australia)" '3082' = "Spanish (International Sort)" '3084' = "French (Canada)" '3098' = "Serbian (Cyrillic)" '4097' = "Arabic (Libya)" '4100' = "Chinese Simplified (Singapore)" '4103' = "German (Luxembourg)" '4105' = "English (Canada)" '4106' = "Spanish (Guatemala)" '4108' = "French (Switzerland)" '5121' = "Arabic (Algeria)" '5127' = "German (Liechtenstein)" '5129' = "English (New Zealand)" '5130' = "Spanish (Costa Rica)" '5132' = "French (Luxembourg)" '6145' = "Arabic (Morocco)" '6153' = "English (Ireland)" '6154' = "Spanish (Panama)" '7169' = "Arabic (Tunisia)" '7177' = "English (South Africa)" '7178' = "Spanish (Dominican Republic)" '8193' = "Arabic (Oman)" '8201' = "English (Jamaica)" '8202' = "Spanish (Venezuela)" '9217' = "Arabic (Yemen)" '9226' = "Spanish (Colombia)" '10241' = "Arabic (Syria)" '10249' = "English (Belize)" '10250' = "Spanish (Peru)" '11265' = "Arabic (Jordan)" '11273' = "English (Trinidad)" '11274' = "Spanish (Argentina)" '12289' = "Arabic (Lebanon)" '12298' = "Spanish (Ecuador)" '13313' = "Arabic (Kuwait)" '13322' = "Spanish (Chile)" '14337' = "Arabic (U.A.E.)" '14346' = "Spanish (Uruguay)" '15361' = "Arabic (Bahrain)" '15370' = "Spanish (Paraguay)" '16385' = "Arabic (Qatar)" '16394' = "Spanish (Bolivia)" '17418' = "Spanish (El Salvador)" '18442' = "Spanish (Honduras)" '19466' = "Spanish (Nicaragua)" '20490' = "Spanish (Puerto Rico)" } $Output = $LanguageCodeDictionary[$LanguageCode] if ($Output) { $Output } else { "Unknown (Undocumented)" } } function ConvertTo-HkeyUser { <# .SYNOPSIS Converts registry paths based on specified criteria. .DESCRIPTION This function converts registry paths based on the provided HiveDictionary, SubKeys, DictionaryKey, and RegistryPath parameters. .PARAMETER HiveDictionary Specifies the dictionary containing the criteria for converting registry paths. .PARAMETER SubKeys Specifies an array of subkeys to process. .PARAMETER DictionaryKey Specifies the key in the RegistryPath to be replaced. .PARAMETER RegistryPath Specifies the original registry path to be converted. .EXAMPLE ConvertTo-HkeyUser -HiveDictionary @{ 'Key1' = 'AllDomain'; 'Key2' = 'All+Default' } -SubKeys @('S-1-5-21-123456789-123456789-123456789-1001', '.DEFAULT') -DictionaryKey 'Key1' -RegistryPath 'HKLM:\Software\Key1\SubKey' Description: Converts the RegistryPath based on the specified criteria in the HiveDictionary for the provided SubKeys. .EXAMPLE ConvertTo-HkeyUser -HiveDictionary @{ 'Key1' = 'Users'; 'Key2' = 'AllDomain+Other' } -SubKeys @('S-1-5-21-123456789-123456789-123456789-1001', 'Offline_User1') -DictionaryKey 'Key2' -RegistryPath 'HKLM:\Software\Key2\SubKey' Description: Converts the RegistryPath based on the specified criteria in the HiveDictionary for the provided SubKeys. #> [CmdletBinding()] param( [System.Collections.IDictionary] $HiveDictionary, [Array] $SubKeys, [string] $DictionaryKey, [string] $RegistryPath ) $OutputRegistryKeys = foreach ($Sub in $Subkeys) { if ($HiveDictionary[$DictionaryKey] -eq 'All') { if ($Sub -notlike "*_Classes*" -and $Sub -ne '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'All+Default') { if ($Sub -notlike "*_Classes*") { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'Default') { if ($Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Default') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") -or $Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Other') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*")) { if (-not $Script:OfflineRegistryMounted) { $Script:OfflineRegistryMounted = Mount-AllRegistryPath foreach ($Key in $Script:OfflineRegistryMounted.Keys) { $RegistryPath.Replace($DictionaryKey, "Users\$Key") } } $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Other+Default') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") -or $Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if (-not $Script:OfflineRegistryMounted) { $Script:OfflineRegistryMounted = Mount-AllRegistryPath foreach ($Key in $Script:OfflineRegistryMounted.Keys) { $RegistryPath.Replace($DictionaryKey, "Users\$Key") } } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain') { if ($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'Users') { if ($Sub -like "Offline_*") { $Script:OfflineRegistryMounted = Mount-AllRegistryPath -MountUsers $Sub foreach ($Key in $Script:OfflineRegistryMounted.Keys) { if ($Script:OfflineRegistryMounted[$Key].Status -eq $true) { $RegistryPath } } } } } $OutputRegistryKeys | Sort-Object -Unique } function Dismount-PSRegistryPath { <# .SYNOPSIS Dismounts a registry path. .DESCRIPTION This function dismounts a registry path specified by the MountPoint parameter. It unloads the registry path using reg.exe command. .PARAMETER MountPoint Specifies the registry path to be dismounted. .PARAMETER Suppress Suppresses the output if set to $true. .EXAMPLE Dismount-PSRegistryPath -MountPoint "HKLM:\Software\MyApp" -Suppress Dismounts the registry path "HKLM:\Software\MyApp" without displaying any output. .EXAMPLE Dismount-PSRegistryPath -MountPoint "HKCU:\Software\Settings" Dismounts the registry path "HKCU:\Software\Settings" and displays output if successful. #> [alias('Dismount-RegistryPath')] [cmdletbinding()] param( [Parameter(Mandatory)][string] $MountPoint, [switch] $Suppress ) [gc]::Collect() $pinfo = [System.Diagnostics.ProcessStartInfo]::new() $pinfo.FileName = "reg.exe" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = " unload $MountPoint" $pinfo.CreateNoWindow = $true $pinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $p = [System.Diagnostics.Process]::new() $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() $Output = $p.StandardOutput.ReadToEnd() $Errors = $p.StandardError.ReadToEnd() if ($Errors) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $Errors } else { Write-Warning -Message "Dismount-PSRegistryPath - Couldn't unmount $MountPoint. $Errors" } } else { if ($Output -like "*operation completed*") { if (-not $Suppress) { return $true } } } if (-not $Suppress) { return $false } } function Get-CimData { <# .SYNOPSIS Helper function for retreiving CIM data from local and remote computers .DESCRIPTION Helper function for retreiving CIM data from local and remote computers .PARAMETER ComputerName Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM). .PARAMETER Protocol Specifies the protocol to use. The acceptable values for this parametDer are: DCOM, Default, or Wsman. .PARAMETER Class Specifies the name of the CIM class for which to retrieve the CIM instances. You can use tab completion to browse the list of classes, because PowerShell gets a list of classes from the local WMI server to provide a list of class names. .PARAMETER Properties Specifies a set of instance properties to retrieve. Use this parameter when you need to reduce the size of the object returned, either in memory or over the network. The object returned also contains the key properties even if you have not listed them using the Property parameter. Other properties of the class are present but they are not populated. .PARAMETER NameSpace Specifies the namespace for the CIM operation. The default namespace is root\cimv2. You can use tab completion to browse the list of namespaces, because PowerShell gets a list of namespaces from the local WMI server to provide a list of namespaces. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. .EXAMPLE Get-CimData -Class 'win32_bios' -ComputerName AD1,EVOWIN .EXAMPLE Get-CimData -Class 'win32_bios' .EXAMPLE Get-CimClass to get all classes .NOTES General notes #> [CmdletBinding()] param( [parameter(Mandatory)][string] $Class, [string] $NameSpace = 'root\cimv2', [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [pscredential] $Credential, [string[]] $Properties = '*' ) $ExcludeProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties', 'SystemCreationClassName', 'CreationClassName' [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $CimObject = @( [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' } $Computers = $ComputersSplit[1] if ($Computers.Count -gt 0) { if ($Protocol -eq 'Default' -and $null -eq $Credential) { Get-CimInstance -ClassName $Class -ComputerName $Computers -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } else { $Option = New-CimSessionOption -Protocol $Protocol $newCimSessionSplat = @{ ComputerName = $Computers SessionOption = $Option ErrorAction = 'SilentlyContinue' } if ($Credential) { $newCimSessionSplat['Credential'] = $Credential } $Session = New-CimSession @newCimSessionSplat -Verbose:$false if ($Session) { Try { $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction Stop -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } catch { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" } try { $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue } catch { Write-Warning -Message "Get-CimData - Failed to remove CimSession $($Session). Failed with errror: $($E.Exception.Message)" } $Info } else { Write-Warning -Message "Get-CimData - Failed to create CimSession for $($Computers). Problem with credentials?" } } foreach ($E in $ErrorsToProcess) { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" } } else { $Computers = $ComputersSplit[0] if ($Computers.Count -gt 0) { $Info = Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsLocal | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties $Info | Add-Member -Name 'PSComputerName' -Value $Computers -MemberType NoteProperty -Force $Info } foreach ($E in $ErrorsLocal) { Write-Warning -Message "Get-CimData - No data for computer $($Env:COMPUTERNAME). Failed with errror: $($E.Exception.Message)" } } ) $CimObject } function Get-ComputerTimeNtp { <# .Synopsis Gets (Simple) Network Time Protocol time (SNTP/NTP, rfc-1305, rfc-2030) from a specified server .DESCRIPTION This function connects to an NTP server on UDP port 123 and retrieves the current NTP time. Selected components of the returned time information are decoded and returned in a PSObject. .PARAMETER Server The NTP Server to contact. Uses pool.ntp.org by default. .EXAMPLE Get-NtpTime uk.pool.ntp.org Gets time from the specified server. .EXAMPLE Get-NtpTime | fl * Get time from default server (pool.ntp.org) and displays all output object attributes. .FUNCTIONALITY Gets NTP time from a specified server. .NOTES Author https://github.com/ChrisWarwick/PowerShell-NTP-Time Slightly simplified for different usage scenarios #> [CmdletBinding()] Param ( [String]$Server = 'pool.ntp.org', [switch]$ToLocal ) $StartOfEpoch = New-Object DateTime(1900, 1, 1, 0, 0, 0, [DateTimeKind]::Utc) [Byte[]]$NtpData = , 0 * 48 $NtpData[0] = 0x1B $Socket = [Net.Sockets.Socket]::new([Net.Sockets.AddressFamily]::InterNetwork, [Net.Sockets.SocketType]::Dgram, [Net.Sockets.ProtocolType]::Udp) $Socket.SendTimeOut = 2000 $Socket.ReceiveTimeOut = 2000 Try { $Socket.Connect($Server, 123) } Catch { $_.Error Write-Warning "Get-ComputerTimeNtp - Failed to connect to server $Server" return } $t1 = Get-Date Try { [Void]$Socket.Send($NtpData) [Void]$Socket.Receive($NtpData) } Catch { Write-Warning "Get-ComputerTimeNtp - Failed to communicate with server $Server" return } $t4 = Get-Date $Socket.Shutdown("Both") $Socket.Close() $LI = ($NtpData[0] -band 0xC0) -shr 6 If ($LI -eq 3) { Write-Warning 'Get-ComputerTimeNtp - Alarm condition from server (clock not synchronized)' return } $IntPart = [BitConverter]::ToUInt32($NtpData[43..40], 0) $FracPart = [BitConverter]::ToUInt32($NtpData[47..44], 0) $t3ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000) $IntPart = [BitConverter]::ToUInt32($NtpData[35..32], 0) $FracPart = [BitConverter]::ToUInt32($NtpData[39..36], 0) $t2ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000) $t1ms = ([TimeZoneInfo]::ConvertTimeToUtc($t1) - $StartOfEpoch).TotalMilliseconds $t4ms = ([TimeZoneInfo]::ConvertTimeToUtc($t4) - $StartOfEpoch).TotalMilliseconds $Offset = (($t2ms - $t1ms) + ($t3ms - $t4ms)) / 2 [DateTime] $NTPDateTime = $StartOfEpoch.AddMilliseconds($t4ms + $Offset) if ($ToLocal) { $NTPDateTime.ToLocalTime() } else { $NTPDateTime } } function Get-PSService { <# .SYNOPSIS Alternative way to Get-Service .DESCRIPTION Alternative way to Get-Service which works using CIM queries .PARAMETER ComputerName ComputerName(s) to query for services .PARAMETER Protocol Protocol to use to gather services .PARAMETER Service Limit output to just few services .PARAMETER All Return all data without filtering .PARAMETER Extended Return more data .EXAMPLE Get-PSService -ComputerName AD1, AD2, AD3, AD4 -Service 'Dnscache', 'DNS', 'PeerDistSvc', 'WebClient', 'Netlogon' .EXAMPLE Get-PSService -ComputerName AD1, AD2 -Extended .EXAMPLE Get-PSService .EXAMPLE Get-PSService -Extended .NOTES General notes #> [cmdletBinding()] param( [alias('Computer', 'Computers')][string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [alias('Services')][string[]] $Service, [switch] $All, [switch] $Extended ) [string] $Class = 'win32_service' [string] $Properties = '*' if ($Service) { $CachedServices = @{} foreach ($S in $Service) { $CachedServices[$S] = $true } } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { if ($Service) { foreach ($Entry in $Information) { if ($CachedServices[$Entry.Name]) { $Entry } } } else { $Information } } else { foreach ($Data in $Information) { if ($Service) { if (-not $CachedServices[$Data.Name]) { continue } } $OutputService = [ordered] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Status = $Data.State Name = $Data.Name ServiceType = $Data.ServiceType StartType = $Data.StartMode DisplayName = $Data.DisplayName } if ($Extended) { $OutputServiceExtended = [ordered] @{ StatusOther = $Data.Status ExitCode = $Data.ExitCode DesktopInteract = $Data.DesktopInteract ErrorControl = $Data.ErrorControl PathName = $Data.PathName Caption = $Data.Caption Description = $Data.Description Started = $Data.Started SystemName = $Data.SystemName AcceptPause = $Data.AcceptPause AcceptStop = $Data.AcceptStop ServiceSpecificExitCode = $Data.ServiceSpecificExitCode StartName = $Data.StartName TagId = $Data.TagId CheckPoint = $Data.CheckPoint DelayedAutoStart = $Data.DelayedAutoStart ProcessId = $Data.ProcessId WaitHint = $Data.WaitHint } [PSCustomObject] ($OutputService + $OutputServiceExtended) } else { [PSCustomObject] $OutputService } } } } function Convert-ADGuidToSchema { <# .SYNOPSIS Converts Guid to schema properties .DESCRIPTION Converts Guid to schema properties .PARAMETER Guid Guid to Convert to Schema Name .PARAMETER Domain Domain to query. By default the current domain is used .PARAMETER RootDSE RootDSE to query. By default RootDSE is queried from the domain .PARAMETER DisplayName Return the schema name by display name. By default it returns as Name .EXAMPLE $T2 = '570b9266-bbb3-4fad-a712-d2e3fedc34dd' $T = [guid] '570b9266-bbb3-4fad-a712-d2e3fedc34dd' Convert-ADGuidToSchema -Guid $T Convert-ADGuidToSchema -Guid $T2 .NOTES General notes #> [alias('Get-WinADDomainGUIDs', 'Get-WinADForestGUIDs')] [cmdletbinding()] param( [string] $Guid, [string] $Domain, [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE, [switch] $DisplayName ) if (-not $Script:ADSchemaMap -or -not $Script:ADSchemaMapDisplayName) { if ($RootDSE) { $Script:RootDSE = $RootDSE } elseif (-not $Script:RootDSE) { if ($Domain) { $Script:RootDSE = Get-ADRootDSE -Server $Domain } else { $Script:RootDSE = Get-ADRootDSE } } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0] $Script:ADSchemaMap = @{ } $Script:ADSchemaMapDisplayName = @{ } $Script:ADSchemaMapDisplayName['00000000-0000-0000-0000-000000000000'] = 'All' $Script:ADSchemaMap.Add('00000000-0000-0000-0000-000000000000', 'All') Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:StandardRights) { $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID } foreach ($S in $Script:StandardRights) { $Script:ADSchemaMap["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.name $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.lDAPDisplayName } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer took $TimeToExecute" Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:ExtendedRightsGuids) { $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid } foreach ($S in $Script:ExtendedRightsGuids) { $Script:ADSchemaMap["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.name $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.displayName } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer took $TimeToExecute" } if ($Guid) { if ($DisplayName) { $Script:ADSchemaMapDisplayName[$Guid] } else { $Script:ADSchemaMap[$Guid] } } else { if ($DisplayName) { $Script:ADSchemaMapDisplayName } else { $Script:ADSchemaMap } } } function Mount-AllRegistryPath { <# .SYNOPSIS Mounts offline registry paths to specified mount points. .DESCRIPTION This function mounts offline registry paths to specified mount points. It iterates through all offline registry profiles and mounts them to the specified mount point. Optionally, you can specify a specific user profile to mount. .PARAMETER MountPoint Specifies the mount point where the registry paths will be mounted. Default is "HKEY_USERS\". .PARAMETER MountUsers Specifies the user profile to mount. If specified, only the specified user profile will be mounted. .EXAMPLE Mount-AllRegistryPath -MountPoint "HKEY_USERS\" -MountUsers "User1" Mounts the offline registry path of user profile "User1" to the default mount point "HKEY_USERS\". .EXAMPLE Mount-AllRegistryPath -MountPoint "HKEY_LOCAL_MACHINE\SOFTWARE" -MountUsers "User2" Mounts the offline registry path of user profile "User2" to the specified mount point "HKEY_LOCAL_MACHINE\SOFTWARE". #> [CmdletBinding()] param( [string] $MountPoint = "HKEY_USERS\", [string] $MountUsers ) $AllProfiles = Get-OfflineRegistryProfilesPath foreach ($Profile in $AllProfiles.Keys) { if ($MountUsers) { if ($MountUsers -ne $Profile) { continue } } $WhereMount = "$MountPoint\$Profile".Replace("\\", "\") Write-Verbose -Message "Mount-OfflineRegistryPath - Mounting $WhereMount to $($AllProfiles[$Profile].FilePath)" $AllProfiles[$Profile].Status = Mount-PSRegistryPath -MountPoint $WhereMount -FilePath $AllProfiles[$Profile].FilePath } $AllProfiles } function Mount-DefaultRegistryPath { <# .SYNOPSIS Mounts the default registry path to a specified mount point. .DESCRIPTION This function mounts the default registry path to a specified mount point. If an error occurs during the process, it provides appropriate feedback. .PARAMETER MountPoint Specifies the mount point where the default registry path will be mounted. Default value is "HKEY_USERS\.DEFAULT_USER". .EXAMPLE Mount-DefaultRegistryPath -MountPoint "HKLM:\Software\CustomMountPoint" Mounts the default registry path to the specified custom mount point "HKLM:\Software\CustomMountPoint". .EXAMPLE Mount-DefaultRegistryPath Mounts the default registry path to the default mount point "HKEY_USERS\.DEFAULT_USER". #> [CmdletBinding()] param( [string] $MountPoint = "HKEY_USERS\.DEFAULT_USER" ) $DefaultRegistryPath = Get-PSRegistry -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -Key 'Default' -ExpandEnvironmentNames -DoNotUnmount if ($PSError -ne $true) { $PathToNTUser = [io.path]::Combine($DefaultRegistryPath.PSValue, 'NTUSER.DAT') Write-Verbose -Message "Mount-DefaultRegistryPath - Mounting $MountPoint to $PathToNTUser" Mount-PSRegistryPath -MountPoint $MountPoint -FilePath $PathToNTUser } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $PSErrorMessage } else { Write-Warning -Message "Mount-DefaultRegistryPath - Couldn't execute. Error: $PSErrorMessage" } } } function Get-OfflineRegistryProfilesPath { <# .SYNOPSIS Retrieves the paths of offline user profiles in the Windows registry. .DESCRIPTION This function retrieves the paths of offline user profiles in the Windows registry by comparing the profiles listed in 'HKEY_USERS' with those in 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'. It then checks for the existence of the 'NTUSER.DAT' file for each profile and returns the paths of offline profiles found. .EXAMPLE Get-OfflineRegistryProfilesPath Retrieves the paths of offline user profiles in the Windows registry and returns a hashtable containing the profile paths. .NOTES Name Value ---- ----- Przemek {[FilePath, C:\Users\Przemek\NTUSER.DAT], [Status, ]} test.1 {[FilePath, C:\Users\test.1\NTUSER.DAT], [Status, ]} #> [CmdletBinding()] param( ) $Profiles = [ordered] @{} $CurrentMapping = (Get-PSRegistry -RegistryPath 'HKEY_USERS' -ExpandEnvironmentNames -DoNotUnmount).PSSubKeys $UsersInSystem = (Get-PSRegistry -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ExpandEnvironmentNames -DoNotUnmount).PSSubKeys $MissingProfiles = foreach ($Profile in $UsersInSystem) { if ($Profile.StartsWith("S-1-5-21") -and $CurrentMapping -notcontains $Profile) { Get-PSRegistry -RegistryPath "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$Profile" -ExpandEnvironmentNames -DoNotUnmount } } foreach ($Profile in $MissingProfiles) { $PathToNTUser = [io.path]::Combine($Profile.ProfileImagePath, 'NTUSER.DAT') $ProfileName = [io.path]::GetFileName($Profile.ProfileImagePath) $StartPath = "Offline_$ProfileName" try { $PathExists = Test-Path -LiteralPath $PathToNTUser -ErrorAction Stop if ($PathExists) { $Profiles[$StartPath] = [ordered] @{ FilePath = $PathToNTUser Status = $null } } } catch { Write-Warning -Message "Mount-OfflineRegistryPath - Couldn't execute. Error: $($_.Exception.Message)" continue } } $Profiles } function Mount-PSRegistryPath { <# .SYNOPSIS Mounts a registry path to a specified location. .DESCRIPTION This function mounts a registry path to a specified location using the reg.exe utility. .PARAMETER MountPoint Specifies the registry mount point where the registry path will be mounted. .PARAMETER FilePath Specifies the file path of the registry hive to be mounted. .EXAMPLE Mount-PSRegistryPath -MountPoint 'HKEY_USERS\.DEFAULT_USER111' -FilePath 'C:\Users\Default\NTUSER.DAT' Mounts the registry hive located at 'C:\Users\Default\NTUSER.DAT' to the registry key 'HKEY_USERS\.DEFAULT_USER111'. .NOTES This function requires administrative privileges to mount registry paths. #> [alias('Mount-RegistryPath')] [cmdletbinding()] param( [Parameter(Mandatory)][string] $MountPoint, [Parameter(Mandatory)][string] $FilePath ) $pinfo = [System.Diagnostics.ProcessStartInfo]::new() $pinfo.FileName = "reg.exe" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = " load $MountPoint $FilePath" $pinfo.CreateNoWindow = $true $pinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $p = [System.Diagnostics.Process]::new() $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() $Output = $p.StandardOutput.ReadToEnd() $Errors = $p.StandardError.ReadToEnd() if ($Errors) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $Errors } else { Write-Warning -Message "Mount-PSRegistryPath - Couldn't mount $MountPoint. $Errors" } } else { if ($Output -like "*operation completed*") { if (-not $Suppress) { return $true } } } if (-not $Suppress) { return $false } } function ConvertFrom-XMLRSOP { <# .SYNOPSIS Converts XML data representing Resultant Set of Policy (RSOP) into a structured PowerShell object. .DESCRIPTION This function takes XML data representing RSOP and converts it into a structured PowerShell object for easier manipulation and analysis. .PARAMETER Content The XML content representing the RSOP data. .PARAMETER ResultsType The type of results being processed. .PARAMETER Splitter The delimiter used to split certain data elements. .EXAMPLE ConvertFrom-XMLRSOP -Content $xmlData -ResultsType "Computer" -Splitter "`n" Description: Converts the XML data in $xmlData representing computer RSOP results using a newline as the splitter. .EXAMPLE ConvertFrom-XMLRSOP -Content $xmlData -ResultsType "User" -Splitter "," Description: Converts the XML data in $xmlData representing user RSOP results using a comma as the splitter. #> [cmdletBinding()] param( [System.Xml.XmlElement]$Content, [string] $ResultsType, # [Microsoft.GroupPolicy.GPRsop] $ResultantSetPolicy, [string] $Splitter = [System.Environment]::NewLine ) $GPOPrimary = [ordered] @{ Summary = $null SummaryDetails = [System.Collections.Generic.List[PSCustomObject]]::new() SummaryDownload = $null GroupPolicies = $null GroupPoliciesLinks = $null GroupPoliciesApplied = $null GroupPoliciesDenied = $null Results = [ordered]@{} } $Object = [ordered] @{ ReadTime = [DateTime] $Content.ReadTime ComputerName = $Content.$ResultsType.Name DomainName = $Content.$ResultsType.Domain OrganizationalUnit = $Content.$ResultsType.SOM Site = $Content.$ResultsType.Site GPOTypes = $Content.$ResultsType.ExtensionData.Name.'#text' -join $Splitter SlowLink = if ($Content.$ResultsType.SlowLink -eq 'true') { $true } else { $false }; } $GPOPrimary['Summary'] = $Object [Array] $GPOPrimary['SecurityGroups'] = foreach ($Group in $Content.$ResultsType.SecurityGroup) { [PSCustomObject] @{ Name = $Group.Name.'#Text' SID = $Group.SID.'#Text' } } [Array] $GPOPrimary['GroupPolicies'] = foreach ($GPO in $Content.$ResultsType.GPO) { $ExtensionName = $GPO.ExtensionName | ForEach-Object { ConvertFrom-CSExtension -CSE $_ -Limited } $GPOObject = [PSCustomObject] @{ Name = $GPO.Name GUID = $GPO.Path.Identifier.'#text' DomainName = if ($GPO.Path.Domain.'#text') { $GPO.Path.Domain.'#text' } else { 'Local Policy' }; Revision = -join ('AD (', $GPO.VersionDirectory, '), SYSVOL (', $GPO.VersionSysvol, ')') IsValid = if ($GPO.IsValid -eq 'true') { $true } else { $false }; Status = if ($GPO.FilterAllowed -eq 'true' -and $GPO.AccessDenied -eq 'false') { 'Applied' } else { 'Denied' }; FilterAllowed = if ($GPO.FilterAllowed -eq 'true') { $true } else { $false }; AccessAllowed = if ($GPO.AccessDenied -eq 'true') { $false } else { $true }; FilterName = $GPO.FilterName ExtensionName = ($ExtensionName | Sort-Object -Unique) -join '; ' SOMOrder = $GPO.Link.SOMOrder -join '; ' AppliedOrder = $GPO.Link.AppliedOrder -join '; ' LinkOrder = $GPO.Link.LinkOrder -join '; ' Enabled = ($GPO.Link.Enabled | ForEach-Object { if ($_ -eq 'true') { $true } else { $false }; }) -join '; ' Enforced = ($GPO.Link.NoOverride | ForEach-Object { if ($_ -eq 'true') { $true } else { $false }; }) -join '; ' SecurityFilter = $GPO.SecurityFilter -join '; ' FilterId = $GPO.FilterID Links = $GPO.Link.SOMPath -join '; ' } $GPOObject } [Array] $GPOPrimary['GroupPoliciesLinks'] = foreach ($GPO in $Content.$ResultsType.GPO) { foreach ($Link in $GPO.Link) { [PSCustomObject] @{ DisplayName = $GPO.Name DomainName = $GPO.Path.Domain.'#text' GUID = $GPO.Path.Identifier.'#text' SOMPath = $Link.SOMPath SOMOrder = $Link.SOMOrder AppliedOrder = $Link.AppliedOrder LinkOrder = $Link.LinkOrder Enabled = if ($Link.Enabled -eq 'true') { $true } else { $false }; Enforced = if ($Link.NoOverride -eq 'true') { $true } else { $false }; } } } [Array] $GPOPrimary['ScopeOfManagement'] = foreach ($SOM in $Content.$ResultsType.SearchedSOM) { [PSCustomObject] @{ Path = $SOM.Path Type = $SOM.Type Order = $SOM.Order BlocksInheritance = if ($SOM.BlocksInheritance -eq 'true') { $true } else { $false }; Blocked = if ($SOM.Blocked -eq 'true') { $true } else { $false }; Reason = if ($SOM.Reason -eq 'true') { $true } else { $false }; } } [Array] $GPOPrimary['ExtensionStatus'] = foreach ($Details in $Content.$ResultsType.ExtensionStatus) { [PSCustomObject] @{ Name = $Details.Name Identifier = $Details.Identifier BeginTime = $Details.BeginTime EndTime = $Details.EndTime LoggingStatus = $Details.LoggingStatus Error = $Details.Error } } [Array] $GPOPrimary['ExtensionData'] = $Content.$ResultsType.ExtensionData.Extension foreach ($Single in $Content.$ResultsType.EventsDetails.SinglePassEventsDetails) { $GPOPrimary['Results']["$($Single.ActivityId)"] = [ordered] @{} $GPOPrimary['Results']["$($Single.ActivityId)"]['SummaryDetails'] = [Ordered] @{ ActivityId = $Single.ActivityId ProcessingTrigger = $Single.ProcessingTrigger ProcessingAppMode = $Single.ProcessingAppMode LinkSpeedInKbps = $Single.LinkSpeedInKbps SlowLinkThresholdInKbps = $Single.SlowLinkThresholdInKbps DomainControllerName = $Single.DomainControllerName DomainControllerIPAddress = $Single.DomainControllerIPAddress PolicyProcessingMode = $Single.PolicyProcessingMode PolicyElapsedTimeInMilliseconds = $Single.PolicyElapsedTimeInMilliseconds ErrorCount = $Single.ErrorCount WarningCount = $Single.WarningCount } $GPOPrimary['SummaryDetails'].Add([PSCustomObject] $GPOPrimary['Results']["$($Single.ActivityId)"]['SummaryDetails']) [Array] $GPOPrimary['Results']["$($Single.ActivityId)"]['ProcessingTime'] = foreach ($Details in $Single.ExtensionProcessingTime) { [PSCustomObject] @{ ExtensionName = $Details.ExtensionName ExtensionGuid = $Details.ExtensionGuid ElapsedTimeInMilliseconds = $Details.ElapsedTimeInMilliseconds ProcessedTimeStamp = $Details.ProcessedTimeStamp } } $EventsLevel = @{ '5' = 'Verbose' '4' = 'Informational' '3' = 'Warning' '2' = 'Error' '1' = 'Critical' '0' = 'LogAlways' } $EventsReason = @{ 'NOTAPPLIED-EMPTY' = 'Not Applied (Empty)' 'DENIED-WMIFILTER' = 'Denied (WMI Filter)' 'DENIED-SECURITY' = 'Denied (Security)' } [Array] $GPOPrimary['Results']["$($Single.ActivityId)"]['Events'] = foreach ($Event in $Single.EventRecord) { [xml] $EventDetails = $Event.EventXML $EventInformation = [ordered] @{ Description = $Event.EventDescription Provider = $EventDetails.Event.System.Provider.Name ProviderGUID = $EventDetails.Event.System.Provider.Guid EventID = $EventDetails.Event.System.EventID Version = $EventDetails.Event.System.Version Level = $EventsLevel[$EventDetails.Event.System.Level] Task = $EventDetails.Event.System.Task Opcode = $EventDetails.Event.System.Opcode Keywords = $EventDetails.Event.System.Keywords TimeCreated = [DateTime] $EventDetails.Event.System.TimeCreated.SystemTime EventRecordID = $EventDetails.Event.System.EventRecordID Correlation = $EventDetails.Event.System.Correlation.ActivityID Execution = -join ("ProcessID: ", $EventDetails.Event.System.Execution.ProcessID, " ThreadID: ", $EventDetails.Event.System.Execution.ThreadID) Channel = $EventDetails.Event.System.Channel Computer = $EventDetails.Event.System.Computer Security = $EventDetails.Event.System.Security.UserID } foreach ($Entry in $EventDetails.Event.EventData.Data) { $EventInformation["$($Entry.Name)"] = $Entry.'#text' } [PSCustomObject] $EventInformation } $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID'] = [ordered] @{} $GroupedEvents = $GPOPrimary['Results']["$($Single.ActivityId)"]['Events'] | Group-Object -Property EventId foreach ($Events in $GroupedEvents) { $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID'][$Events.Name] = $Events.Group } $GPOPrimary['Results']["$($Single.ActivityId)"]['GroupPoliciesApplied'] = & { if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5312']) { [xml] $GPODetailsApplied = -join ('<Details>', $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5312'].GPOinfoList, '</Details>') foreach ($GPO in $GPODetailsApplied.Details.GPO) { $ReturnObject = [ordered] @{ GUID = $GPO.ID DisplayName = $GPO.Name Version = $GPO.Version Link = $GPO.SOM SysvolPath = $GPO.FSPath } $TranslatedExtensions = foreach ($Extension in $GPO.Extensions) { ConvertFrom-CSExtension -CSE $Extension -Limited } $ReturnObject['GPOTypes'] = $TranslatedExtensions -join '; ' [PSCustomObject] $ReturnObject } } } $GPOPrimary['Results']["$($Single.ActivityId)"]['GroupPoliciesDenied'] = & { if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5312']) { [xml] $GPODetailsDenied = -join ('<Details>', $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5313'].GPOinfoList, '</Details>') foreach ($GPO in $GPODetailsDenied.Details.GPO) { [PSCustomObject] @{ GUID = $GPO.ID DisplayName = $GPO.Name Version = $GPO.Version Link = $GPO.SOM SysvolPath = $GPO.FSPath Reason = $EventsReason["$($GPO.Reason)"] } } } } $GPOPrimary['Results']["$($Single.ActivityId)"]['SummaryDownload'] = & { if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126']) { [PSCustomObject] @{ IsBackgroundProcessing = if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].IsBackgroundProcessing -eq 'true') { $true } else { $false }; IsAsyncProcessing = if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].IsAsyncProcessing -eq 'true') { $true } else { $false }; Downloaded = $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].NumberOfGPOsDownloaded Applicable = $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].NumberOfGPOsApplicable DownloadTimeMiliseconds = $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].GPODownloadTimeElapsedInMilliseconds } } } } $GPOPrimary } function ConvertTo-XMLAccountPolicy { <# .SYNOPSIS Converts a PowerShell custom object representing an account policy into XML format. .DESCRIPTION This function takes a PowerShell custom object representing an account policy and converts it into XML format for storage or transmission. .PARAMETER GPO The PowerShell custom object representing the account policy. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLAccountPolicy -GPO $accountPolicyObject -SingleObject Description: Converts the $accountPolicyObject into XML format for a single object. .EXAMPLE $accountPolicies | ConvertTo-XMLAccountPolicy -SingleObject Description: Converts multiple account policies in $accountPolicies into XML format for each object. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = @( $Settings = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType ClearTextPassword = 'Not Set' LockoutBadCount = 'Not Set' LockoutDuration = 'Not Set' MaximumPasswordAge = 'Not Set' MinimumPasswordAge = 'Not Set' MinimumPasswordLength = 'Not Set' PasswordComplexity = 'Not Set' PasswordHistorySize = 'Not Set' ResetLockoutCount = 'Not Set' MaxClockSkew = 'Not Set' MaxRenewAge = 'Not Set' MaxServiceAge = 'Not Set' MaxTicketAge = 'Not Set' TicketValidateClient = 'Not Set' } foreach ($GPOEntry in $GPO.DataSet) { if ($GPOEntry.SettingBoolean) { $Settings[$($GPOEntry.Name)] = if ($GPOEntry.SettingBoolean -eq 'true') { 'Enabled' } elseif ($GPOEntry.SettingBoolean -eq 'false') { 'Disabled' } else { 'Not set' }; } elseif ($GPOEntry.SettingNumber) { $Settings[$($GPOEntry.Name)] = [int64] $GPOEntry.SettingNumber } } [PSCustomObject] $Settings ) $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType ClearTextPassword = 'Not Set' LockoutBadCount = 'Not Set' LockoutDuration = 'Not Set' MaximumPasswordAge = 'Not Set' MinimumPasswordAge = 'Not Set' MinimumPasswordLength = 'Not Set' PasswordComplexity = 'Not Set' PasswordHistorySize = 'Not Set' ResetLockoutCount = 'Not Set' MaxClockSkew = 'Not Set' MaxRenewAge = 'Not Set' MaxServiceAge = 'Not Set' MaxTicketAge = 'Not Set' TicketValidateClient = 'Not Set' } foreach ($GPOEntry in $GPO.DataSet) { if ($GPOEntry.SettingBoolean) { $CreateGPO[$($GPOEntry.Name)] = if ($GPOEntry.SettingBoolean -eq 'true') { 'Enabled' } elseif ($GPOEntry.SettingBoolean -eq 'false') { 'Disabled' } else { 'Not set' }; } elseif ($GPOEntry.SettingNumber) { $CreateGPO[$($GPOEntry.Name)] = [int64] $GPOEntry.SettingNumber } } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } function ConvertTo-XMLAudit { <# .SYNOPSIS Converts a PowerShell custom object representing an audit policy into XML format. .DESCRIPTION This function takes a PowerShell custom object representing an audit policy and converts it into XML format for storage or transmission. .PARAMETER GPO The PowerShell custom object representing the audit policy. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLAudit -GPO $auditPolicyObject -SingleObject Description: Converts the $auditPolicyObject into XML format for a single object. .EXAMPLE $auditPolicies | ConvertTo-XMLAudit -SingleObject Description: Converts multiple audit policies in $auditPolicies into XML format for each object. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) $SettingType = @{ '0' = 'No Auditing' '1' = 'Success' '2' = 'Failure' '3' = 'Success, Failure' } if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = @( $Settings = [ordered]@{ AuditAccountLogon = 'Not configured' AuditAccountManage = 'Not configured' AuditDSAccess = 'Not configured' AuditLogonEvents = 'Not configured' AuditObjectAccess = 'Not configured' AuditPolicyChange = 'Not configured' AuditPrivilegeUse = 'Not configured' AuditProcessTracking = 'Not configured' AuditSystemEvents = 'Not configured' AuditAccountLockout = 'Not configured' AuditApplicationGenerated = 'Not configured' AuditApplicationGroupManagement = 'Not configured' AuditAuditPolicyChange = 'Not configured' AuditAuthenticationPolicyChange = 'Not configured' AuditAuthorizationPolicyChange = 'Not configured' AuditCentralAccessPolicyStaging = 'Not configured' AuditCertificationServices = 'Not configured' AuditComputerAccountManagement = 'Not configured' AuditCredentialValidation = 'Not configured' AuditDetailedDirectoryServiceReplication = 'Not configured' AuditDetailedFileShare = 'Not configured' AuditDirectoryServiceAccess = 'Not configured' AuditDirectoryServiceChanges = 'Not configured' AuditDirectoryServiceReplication = 'Not configured' AuditDistributionGroupManagement = 'Not configured' AuditDPAPIActivity = 'Not configured' AuditFileShare = 'Not configured' AuditFileSystem = 'Not configured' AuditFilteringPlatformConnection = 'Not configured' AuditFilteringPlatformPacketDrop = 'Not configured' AuditFilteringPlatformPolicyChange = 'Not configured' AuditGroupMembership = 'Not configured' AuditHandleManipulation = 'Not configured' AuditIPsecDriver = 'Not configured' AuditIPsecExtendedMode = 'Not configured' AuditIPsecMainMode = 'Not configured' AuditIPsecQuickMode = 'Not configured' AuditKerberosAuthenticationService = 'Not configured' AuditKerberosServiceTicketOperations = 'Not configured' AuditKernelObject = 'Not configured' AuditLogoff = 'Not configured' AuditLogon = 'Not configured' AuditMPSSVCRuleLevelPolicyChange = 'Not configured' AuditNetworkPolicyServer = 'Not configured' AuditNonSensitivePrivilegeUse = 'Not configured' AuditOtherAccountLogonEvents = 'Not configured' AuditOtherAccountManagementEvents = 'Not configured' AuditOtherLogonLogoffEvents = 'Not configured' AuditOtherObjectAccessEvents = 'Not configured' AuditOtherPolicyChangeEvents = 'Not configured' AuditOtherPrivilegeUseEvents = 'Not configured' AuditOtherSystemEvents = 'Not configured' AuditPNPActivity = 'Not configured' AuditProcessCreation = 'Not configured' AuditProcessTermination = 'Not configured' AuditRegistry = 'Not configured' AuditRemovableStorage = 'Not configured' AuditRPCEvents = 'Not configured' AuditSAM = 'Not configured' AuditSecurityGroupManagement = 'Not configured' AuditSecurityStateChange = 'Not configured' AuditSecuritySystemExtension = 'Not configured' AuditSensitivePrivilegeUse = 'Not configured' AuditSpecialLogon = 'Not configured' AuditSystemIntegrity = 'Not configured' AuditUserDeviceClaims = 'Not configured' AuditUserAccountManagement = 'Not configured' } foreach ($GPOEntry in $GPO.DataSet) { if ($GPOEntry.PolicyTarget) { $Category = $GPOEntry.SubcategoryName -replace ' ', '' -replace '-', '' -replace '/', '' if ($Settings["$($Category)"]) { $Settings["$($Category)"] = $SettingType["$($GPOEntry.SettingValue)"] } } else { $SuccessAttempts = try { [bool]::Parse($GPOEntry.SuccessAttempts) } catch { $null }; $FailureAttempts = try { [bool]::Parse($GPOEntry.FailureAttempts) } catch { $null }; if ($SuccessAttempts -and $FailureAttempts) { $Setting = 'Success, Failure' } elseif ($SuccessAttempts) { $Setting = 'Success' } elseif ($FailureAttempts) { $Setting = 'Failure' } else { $Setting = 'Not configured' } $Settings["$($GPOEntry.Name)"] = $Setting } } [PSCustomObject] $Settings ) $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType AuditAccountLogon = 'Not configured' AuditAccountManage = 'Not configured' AuditDSAccess = 'Not configured' AuditLogonEvents = 'Not configured' AuditObjectAccess = 'Not configured' AuditPolicyChange = 'Not configured' AuditPrivilegeUse = 'Not configured' AuditProcessTracking = 'Not configured' AuditSystemEvents = 'Not configured' AuditAccountLockout = 'Not configured' AuditApplicationGenerated = 'Not configured' AuditApplicationGroupManagement = 'Not configured' AuditAuditPolicyChange = 'Not configured' AuditAuthenticationPolicyChange = 'Not configured' AuditAuthorizationPolicyChange = 'Not configured' AuditCentralAccessPolicyStaging = 'Not configured' AuditCertificationServices = 'Not configured' AuditComputerAccountManagement = 'Not configured' AuditCredentialValidation = 'Not configured' AuditDetailedDirectoryServiceReplication = 'Not configured' AuditDetailedFileShare = 'Not configured' AuditDirectoryServiceAccess = 'Not configured' AuditDirectoryServiceChanges = 'Not configured' AuditDirectoryServiceReplication = 'Not configured' AuditDistributionGroupManagement = 'Not configured' AuditDPAPIActivity = 'Not configured' AuditFileShare = 'Not configured' AuditFileSystem = 'Not configured' AuditFilteringPlatformConnection = 'Not configured' AuditFilteringPlatformPacketDrop = 'Not configured' AuditFilteringPlatformPolicyChange = 'Not configured' AuditGroupMembership = 'Not configured' AuditHandleManipulation = 'Not configured' AuditIPsecDriver = 'Not configured' AuditIPsecExtendedMode = 'Not configured' AuditIPsecMainMode = 'Not configured' AuditIPsecQuickMode = 'Not configured' AuditKerberosAuthenticationService = 'Not configured' AuditKerberosServiceTicketOperations = 'Not configured' AuditKernelObject = 'Not configured' AuditLogoff = 'Not configured' AuditLogon = 'Not configured' AuditMPSSVCRuleLevelPolicyChange = 'Not configured' AuditNetworkPolicyServer = 'Not configured' AuditNonSensitivePrivilegeUse = 'Not configured' AuditOtherAccountLogonEvents = 'Not configured' AuditOtherAccountManagementEvents = 'Not configured' AuditOtherLogonLogoffEvents = 'Not configured' AuditOtherObjectAccessEvents = 'Not configured' AuditOtherPolicyChangeEvents = 'Not configured' AuditOtherPrivilegeUseEvents = 'Not configured' AuditOtherSystemEvents = 'Not configured' AuditPNPActivity = 'Not configured' AuditProcessCreation = 'Not configured' AuditProcessTermination = 'Not configured' AuditRegistry = 'Not configured' AuditRemovableStorage = 'Not configured' AuditRPCEvents = 'Not configured' AuditSAM = 'Not configured' AuditSecurityGroupManagement = 'Not configured' AuditSecurityStateChange = 'Not configured' AuditSecuritySystemExtension = 'Not configured' AuditSensitivePrivilegeUse = 'Not configured' AuditSpecialLogon = 'Not configured' AuditSystemIntegrity = 'Not configured' AuditUserDeviceClaims = 'Not configured' AuditUserAccountManagement = 'Not configured' } foreach ($GPOEntry in $GPO.DataSet) { if ($GPOEntry.PolicyTarget) { $Category = $GPOEntry.SubcategoryName -replace ' ', '' -replace '-', '' -replace '/', '' if ($CreateGPO["$($Category)"]) { $CreateGPO["$($Category)"] = $SettingType["$($GPOEntry.SettingValue)"] } } else { $SuccessAttempts = try { [bool]::Parse($GPOEntry.SuccessAttempts) } catch { $null }; $FailureAttempts = try { [bool]::Parse($GPOEntry.FailureAttempts) } catch { $null }; if ($SuccessAttempts -and $FailureAttempts) { $Setting = 'Success, Failure' } elseif ($SuccessAttempts) { $Setting = 'Success' } elseif ($FailureAttempts) { $Setting = 'Failure' } else { $Setting = 'Not configured' } $CreateGPO["$($GPOEntry.Name)"] = $Setting } } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } function ConvertTo-XMLCertificates { <# .SYNOPSIS Converts a PowerShell custom object representing certificate data into XML format. .DESCRIPTION This function takes a PowerShell custom object representing certificate data and converts it into XML format for storage or transmission. .PARAMETER GPO The PowerShell custom object representing the certificate data. .PARAMETER Category An array of categories for the certificate data. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLCertificates -GPO $certificateDataObject -Category @('Category1', 'Category2') -SingleObject Description: Converts the $certificateDataObject into XML format for multiple categories as a single object. .EXAMPLE $certificateDataObjects | ConvertTo-XMLCertificates -Category @('Category1') -SingleObject Description: Converts multiple certificate data objects in $certificateDataObjects into XML format for a single category. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [string[]] $Category, [switch] $SingleObject ) $SkipNames = ('Name', 'LocalName', 'NamespaceURI', 'Prefix', 'NodeType', 'ParentNode', 'OwnerDocument', 'IsEmpty', 'Attributes', 'HasAttributes', 'SchemaInfo', 'InnerXml', 'InnerText', 'NextSibling', 'PreviousSibling', 'ChildNodes', 'FirstChild', 'LastChild', 'HasChildNodes', 'IsReadOnly', 'OuterXml', 'BaseURI', 'PreviousText') if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Setting in $GPO.DataSet) { $SettingName = $Setting.Name -split ":" $MySettings = [ordered] @{ CreatedTime = $GPO.CreatedTime ModifiedTime = $GPO.ModifiedTime ReadTime = $GPO.ReadTime SecurityDescriptor = $GPO.SecurityDescriptor FilterDataAvailable = $GPO.FilterDataAvailable } $Name = $SettingName[1] $MySettings['Name'] = $Name ConvertTo-XMLNested -CreateGPO $MySettings -Setting $Setting -SkipNames $SkipNames if ($MySettings.Data) { $bytes = $MySettings.Data -replace '\r?\n' -split '(?<=\G.{2})' -ne '' -replace '^', '0x' -as [byte[]] $CertificateData = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes) $MySettings['NotBefore'] = $CertificateData.NotBefore $MySettings['NotAfter'] = $CertificateData.NotAfter $MySettings['HasPrivateKey'] = $CertificateData.HasPrivateKey $MySettings['Thumbprint'] = $CertificateData.Thumbprint $MySettings['SerialNumber'] = $CertificateData.SerialNumber $MySettings['Version'] = $CertificateData.Version $MySettings['Handle'] = $CertificateData.Handle $MySettings['SignatureAlgorithm'] = $CertificateData.SignatureAlgorithm.Value $MySettings['SignatureAlgorithmName'] = $CertificateData.SignatureAlgorithm.FriendlyName $MySettings['KeyUsages'] = $CertificateData.Extensions.KeyUsages $MySettings.Remove('Data') } [PSCustomObject] $MySettings } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Setting in $GPO.DataSet) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType } $SettingName = $Setting.Name -split ":" $CreateGPO['CreatedTime'] = $GPO.CreatedTime $CreateGPO['ModifiedTime'] = $GPO.ModifiedTime $CreateGPO['ReadTime'] = $GPO.ReadTime $CreateGPO['SecurityDescriptor'] = $GPO.SecurityDescriptor $CreateGPO['FilterDataAvailable'] = $GPO.FilterDataAvailable $Name = $SettingName[1] $CreateGPO['Name'] = $Name ConvertTo-XMLNested -CreateGPO $CreateGPO -Setting $Setting -SkipNames $SkipNames if ($CreateGPO.Data) { $bytes = $CreateGPO.Data -replace '\r?\n' -split '(?<=\G.{2})' -ne '' -replace '^', '0x' -as [byte[]] $CertificateData = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes) $CreateGPO['NotBefore'] = $CertificateData.NotBefore $CreateGPO['NotAfter'] = $CertificateData.NotAfter $CreateGPO['HasPrivateKey'] = $CertificateData.HasPrivateKey $CreateGPO['Thumbprint'] = $CertificateData.Thumbprint $CreateGPO['SerialNumber'] = $CertificateData.SerialNumber $CreateGPO['Version'] = $CertificateData.Version $CreateGPO['Handle'] = $CertificateData.Handle $CreateGPO['SignatureAlgorithm'] = $CertificateData.SignatureAlgorithm.Value $CreateGPO['SignatureAlgorithmName'] = $CertificateData.SignatureAlgorithm.FriendlyName $CreateGPO['KeyUsages'] = $CertificateData.Extensions.KeyUsages $CreateGPO.Remove('Data') } $CreateGPO['Filters'] = $Setting.Filters $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLDriveMapSettings { <# .SYNOPSIS Converts a PowerShell custom object representing drive mapping settings into XML format. .DESCRIPTION This function takes a PowerShell custom object representing drive mapping settings and converts it into XML format for storage or transmission. .PARAMETER GPO The PowerShell custom object representing the drive mapping settings. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLDriveMapSettings -GPO $driveMapSettingsObject -SingleObject Description: Converts the $driveMapSettingsObject into XML format for a single object. .EXAMPLE $driveMapSettings | ConvertTo-XMLDriveMapSettings -SingleObject Description: Converts multiple drive mapping settings in $driveMapSettings into XML format for each object. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Entry in $GPO.DataSet.Drive) { [PSCustomObject] @{ Changed = [DateTime] $Entry.changed GPOSettingOrder = $Entry.GPOSettingOrder Filter = $Entry.Filter Name = $Entry.Name Status = $Entry.status Action = $Script:Actions["$($Entry.Properties.action)"] ThisDrive = $Entry.Properties.thisDrive AllDrives = $Entry.Properties.allDrives UserName = $Entry.Properties.userName Path = $Entry.Properties.path Label = $Entry.Properties.label Persistent = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent }; UseLetter = if ($Entry.Properties.useLetter -eq '1') { $true } elseif ($Entry.Properties.useLetter -eq '0') { $false } else { $Entry.Properties.useLetter }; Letter = $Entry.Properties.letter } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Entry in $GPO.DataSet.Drive) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Changed = [DateTime] $Entry.changed GPOSettingOrder = $Entry.GPOSettingOrder Filter = $Entry.Filter Name = $Entry.Name Status = $Entry.status Action = $Script:Actions["$($Entry.Properties.action)"] ThisDrive = $Entry.Properties.thisDrive AllDrives = $Entry.Properties.allDrives UserName = $Entry.Properties.userName Path = $Entry.Properties.path Label = $Entry.Properties.label Persistent = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent }; UseLetter = if ($Entry.Properties.useLetter -eq '1') { $true } elseif ($Entry.Properties.useLetter -eq '0') { $false } else { $Entry.Properties.useLetter }; Letter = $Entry.Properties.letter } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLEventLog { <# .SYNOPSIS Converts Group Policy Object (GPO) data to an XML event log format. .DESCRIPTION This function takes a PSCustomObject representing GPO data and converts it to an XML event log format. It creates a structured XML output with specific GPO properties. .PARAMETER GPO Specifies the PSCustomObject containing GPO data to be converted. .EXAMPLE $GPOData = [PSCustomObject]@{ DisplayName = 'Example GPO' DomainName = 'example.com' GUID = '12345678-1234-1234-1234-1234567890AB' GpoType = 'Security' DataSet = @( [PSCustomObject]@{ Log = 'Application' Name = 'AuditLogRetentionPeriod' SettingNumber = '1' }, [PSCustomObject]@{ Log = 'System' Name = 'MaximumLogSize' SettingNumber = '1024' } ) Linked = $true LinksCount = 2 Links = @('OU=Finance,DC=example,DC=com', 'OU=IT,DC=example,DC=com') } ConvertTo-XMLEventLog -GPO $GPOData #> [cmdletBinding()] param( [PSCustomObject] $GPO ) $RetionPeriod = @{ '0' = 'Overwrite events as needed' '1' = 'Overwrite events by days' '2' = 'Do not overwrite events (Clear logs manually)' } $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType ApplicationAuditLogRetentionPeriod = $null ApplicationMaximumLogSize = $null ApplicationRestrictGuestAccess = $null ApplicationRetentionDays = $null SystemAuditLogRetentionPeriod = $null SystemMaximumLogSize = $null SystemRestrictGuestAccess = $null SystemRetentionDays = $null SecurityAuditLogRetentionPeriod = $null SecurityMaximumLogSize = $null SecurityRestrictGuestAccess = $null SecurityRetentionDays = $null } foreach ($GPOEntry in $GPO.DataSet) { if ($GPOEntry.SettingBoolean) { $CreateGPO["$($GPOEntry.Log)$($GPOEntry.Name)"] = if ($GPOEntry.SettingBoolean -eq 'true') { 'Enabled' } elseif ($GPOEntry.SettingBoolean -eq 'false') { 'Disabled' } else { 'Not set' }; } elseif ($GPOEntry.SettingNumber) { if ($GPOEntry.Name -eq 'AuditLogRetentionPeriod') { if ($GPOEntry.SettingNumber) { $CreateGPO["$($GPOEntry.Log)$($GPOEntry.Name)"] = $RetionPeriod[$($GPOEntry.SettingNumber)] } else { $CreateGPO["$($GPOEntry.Log)$($GPOEntry.Name)"] = $GPOEntry.SettingNumber } } else { $CreateGPO["$($GPOEntry.Log)$($GPOEntry.Name)"] = $GPOEntry.SettingNumber } } } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } function ConvertTo-XMLFolderRedirection { <# .SYNOPSIS Converts a PowerShell custom object representing folder redirection settings into XML format. .DESCRIPTION This function takes a PowerShell custom object representing folder redirection settings and converts it into XML format for storage or transmission. .PARAMETER GPO The PowerShell custom object representing the folder redirection settings. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLFolderRedirection -GPO $folderRedirectionSettingsObject -SingleObject Description: Converts the $folderRedirectionSettingsObject into XML format for a single object. .EXAMPLE $folderRedirectionSettings | ConvertTo-XMLFolderRedirection -SingleObject Description: Converts multiple folder redirection settings in $folderRedirectionSettings into XML format for each object. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) $FolderID = @{ "{1777F761-68AD-4D8A-87BD-30B759FA33DD}" = "Favorites" "{FDD39AD0-238F-46AF-ADB4-6C85480369C7}" = "Documents" "{33E28130-4E1E-4676-835A-98395C3BC3BB}" = "Pictures" "{4BD8D571-6D19-48D3-BE97-422220080E43}" = "Music" "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}" = "Videos" "{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}" = "AppDataRoaming" "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}" = "Desktop" "{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}" = "StartMenu" "{374DE290-123F-4565-9164-39C4925E467B}" = "Downloads" "{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}" = "Saved Games" "{56784854-C6CB-462B-8169-88E350ACB882}" = "Contacts" "{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}" = "Searches" "{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}" = "Links" } if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Folder in $GPO.DataSet) { foreach ($Location in $Folder.Location) { [PSCustomObject] @{ ID = $Folder.ID FolderType = $FolderID[$Folder.Id] DestinationPath = $Location.DestinationPath SecuritySID = $Location.SecurityGroup.SID.'#text' SecurityName = $Location.SecurityGroup.Name.'#text' GrantExclusiveRights = if ($Folder.GrantExclusiveRights -eq 'true') { $true } else { $false } MoveContents = if ($Folder.MoveContents -eq 'true') { $true } else { $false } FollowParent = if ($Folder.FollowParent -eq 'true') { $true } else { $false } ApplyToDownLevel = if ($Folder.ApplyToDownLevel -eq 'true') { $true } else { $false } DoNotCare = if ($Folder.DoNotCare -eq 'true') { $true } else { $false } RedirectToLocal = if ($Folder.RedirectToLocal -eq 'true') { $true } else { $false } PolicyRemovalBehavior = $Folder.PolicyRemovalBehavior ConfigurationControl = if ($Folder.ConfigurationControl -eq 'GP') { 'Group Policy' } else { $Folder.ConfigurationControl } PrimaryComputerEvaluation = $Folder.PrimaryComputerEvaluation } } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Folder in $GPO.DataSet) { foreach ($Location in $Folder.Location) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Id = $Folder.Id FolderType = $FolderID[$Folder.Id] DestinationPath = $Location.DestinationPath SecuritySID = $Location.SecurityGroup.SID.'#text' SecurityName = $Location.SecurityGroup.Name.'#text' GrantExclusiveRights = if ($Folder.GrantExclusiveRights -eq 'true') { $true } else { $false } MoveContents = if ($Folder.MoveContents -eq 'true') { $true } else { $false } FollowParent = if ($Folder.FollowParent -eq 'true') { $true } else { $false } ApplyToDownLevel = if ($Folder.ApplyToDownLevel -eq 'true') { $true } else { $false } DoNotCare = if ($Folder.DoNotCare -eq 'true') { $true } else { $false } RedirectToLocal = if ($Folder.RedirectToLocal -eq 'true') { $true } else { $false } PolicyRemovalBehavior = $Folder.PolicyRemovalBehavior ConfigurationControl = if ($Folder.ConfigurationControl -eq 'GP') { 'Group Policy' } else { $Folder.ConfigurationControl } PrimaryComputerEvaluation = $Folder.PrimaryComputerEvaluation Linked = $GPO.Linked LinksCount = $GPO.LinksCount Links = $GPO.Links } [PSCustomObject] $CreateGPO } } } } function ConvertTo-XMLGenericPolicy { <# .SYNOPSIS Converts a PowerShell custom object representing generic policy settings into XML format. .DESCRIPTION This function takes a PowerShell custom object representing generic policy settings and converts it into XML format for storage or transmission. .PARAMETER GPO The PowerShell custom object representing the generic policy settings. .PARAMETER Category An array of categories for the generic policy settings. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLGenericPolicy -GPO $genericPolicyObject -Category @('Category1', 'Category2') -SingleObject Description: Converts the $genericPolicyObject into XML format for multiple categories as a single object. .EXAMPLE $genericPolicyObjects | ConvertTo-XMLGenericPolicy -Category @('Category1') -SingleObject Description: Converts multiple generic policy settings in $genericPolicyObjects into XML format for a single category. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [string[]] $Category, [switch] $SingleObject ) $UsedNames = [System.Collections.Generic.List[string]]::new() [Array] $Policies = foreach ($Cat in $Category) { $GPO.DataSet | Where-Object { $_.Category -like $Cat } } if ($Policies.Count -gt 0) { if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = @( $Settings = [ordered] @{} foreach ($Policy in $Policies) { $Name = Format-ToTitleCase -Text $Policy.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':' $Settings[$Name] = $Policy.State foreach ($Setting in @('DropDownList', 'Numeric', 'EditText', 'Text', 'CheckBox', 'ListBox')) { if ($Policy.$Setting) { foreach ($Value in $Policy.$Setting) { if ($Value.Name) { $SubName = Format-ToTitleCase -Text $Value.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':' $SubName = -join ($Name, $SubName) if ($SubName -notin $UsedNames) { $UsedNames.Add($SubName) } else { $TimesUsed = $UsedNames | Group-Object | Where-Object { $_.Name -eq $SubName } $NumberToUse = $TimesUsed.Count + 1 $UsedNames.Add($SubName) $SubName = -join ($SubName, "$NumberToUse") } if ($Value.Value -is [string]) { $Settings["$SubName"] = $Value.Value } elseif ($Value.Value -is [System.Xml.XmlElement]) { if ($Value.Value.Element) { $Settings["$SubName"] = $Value.Value.Element.Data -join '; ' } elseif ($null -eq $Value.Value.Name) { Write-Verbose "Tracking $Value" } else { $Settings["$SubName"] = $Value.Value.Name } } elseif ($Value.State) { $Settings["$SubName"] = $Value.State } elseif ($null -eq $Value.Value) { } else { Write-Verbose $Value } } } } } } [PSCustomObject] $Settings ) $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType } foreach ($Policy in $Policies) { $Name = Format-ToTitleCase -Text $Policy.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':' $CreateGPO[$Name] = $Policy.State foreach ($Setting in @('DropDownList', 'Numeric', 'EditText', 'Text', 'CheckBox', 'ListBox')) { if ($Policy.$Setting) { foreach ($Value in $Policy.$Setting) { if ($Value.Name) { $SubName = Format-ToTitleCase -Text $Value.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':' $SubName = -join ($Name, $SubName) if ($SubName -notin $UsedNames) { $UsedNames.Add($SubName) } else { $TimesUsed = $UsedNames | Group-Object | Where-Object { $_.Name -eq $SubName } $NumberToUse = $TimesUsed.Count + 1 $UsedNames.Add($SubName) $SubName = -join ($SubName, "$NumberToUse") } if ($Value.Value -is [string]) { $CreateGPO["$SubName"] = $Value.Value } elseif ($Value.Value -is [System.Xml.XmlElement]) { if ($Value.Value.Element) { $CreateGPO["$SubName"] = $Value.Value.Element.Data -join '; ' } elseif ($null -eq $Value.Value.Name) { Write-Verbose "Tracking $Value" } else { $CreateGPO["$SubName"] = $Value.Value.Name } } elseif ($Value.State) { $CreateGPO["$SubName"] = $Value.State } elseif ($null -eq $Value.Value) { } else { Write-Verbose $Value } } } } } } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLGenericPublicKey { <# .SYNOPSIS Converts a PSCustomObject representing a Group Policy Object (GPO) to an XML format. .DESCRIPTION This function takes a PSCustomObject representing a GPO and converts it to an XML format. It extracts specific properties from the GPO object and organizes them into a structured XML output. .PARAMETER GPO Specifies the PSCustomObject representing the GPO to be converted. .PARAMETER Category Specifies an array of strings representing categories to include in the XML output. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple GPO objects. .EXAMPLE ConvertTo-XMLGenericPublicKey -GPO $MyGPO -Category @("Category1", "Category2") -SingleObject Converts the PSCustomObject $MyGPO to an XML format including categories "Category1" and "Category2" as a single object. .EXAMPLE $GPOs | ConvertTo-XMLGenericPublicKey -Category @("Category1") Converts multiple GPOs in the $GPOs array to an XML format including category "Category1". #> [cmdletBinding()] param( [PSCustomObject] $GPO, [string[]] $Category, [switch] $SingleObject ) $SkipNames = ('Name', 'LocalName', 'NamespaceURI', 'Prefix', 'NodeType', 'ParentNode', 'OwnerDocument', 'IsEmpty', 'Attributes', 'HasAttributes', 'SchemaInfo', 'InnerXml', 'InnerText', 'NextSibling', 'PreviousSibling', 'ChildNodes', 'FirstChild', 'LastChild', 'HasChildNodes', 'IsReadOnly', 'OuterXml', 'BaseURI', 'PreviousText') if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Setting in $GPO.DataSet) { $SettingName = $Setting.Name -split ":" $MySettings = [ordered] @{ CreatedTime = $GPO.CreatedTime ModifiedTime = $GPO.ModifiedTime ReadTime = $GPO.ReadTime SecurityDescriptor = $GPO.SecurityDescriptor FilterDataAvailable = $GPO.FilterDataAvailable } $Name = $SettingName[1] $MySettings['Name'] = $Name ConvertTo-XMLNested -CreateGPO $MySettings -Setting $Setting -SkipNames $SkipNames [PSCustomObject] $MySettings } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Setting in $GPO.DataSet) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType } $SettingName = $Setting.Name -split ":" $CreateGPO['CreatedTime'] = $GPO.CreatedTime $CreateGPO['ModifiedTime'] = $GPO.ModifiedTime $CreateGPO['ReadTime'] = $GPO.ReadTime $CreateGPO['SecurityDescriptor'] = $GPO.SecurityDescriptor $CreateGPO['FilterDataAvailable'] = $GPO.FilterDataAvailable $Name = $SettingName[1] $CreateGPO['Name'] = $Name ConvertTo-XMLNested -CreateGPO $CreateGPO -Setting $Setting -SkipNames $SkipNames $CreateGPO['Filters'] = $Setting.Filters $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLGenericSecuritySettings { <# .SYNOPSIS Converts Generic Security Settings to XML format. .DESCRIPTION This function converts Generic Security Settings to XML format for further processing. .PARAMETER GPO Specifies the Group Policy Object (GPO) to convert. .PARAMETER Category Specifies the category of settings to convert. .EXAMPLE ConvertTo-XMLGenericSecuritySettings -GPO $GPOObject -Category 'Security' Description: Converts the security settings of the specified GPO object to XML format. .EXAMPLE ConvertTo-XMLGenericSecuritySettings -GPO $GPOObject -Category 'Network' Description: Converts the network settings of the specified GPO object to XML format. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [string[]] $Category ) $SkipNames = ('Name', 'LocalName', 'NamespaceURI', 'Prefix', 'NodeType', 'ParentNode', 'OwnerDocument', 'IsEmpty', 'Attributes', 'HasAttributes', 'SchemaInfo', 'InnerXml', 'InnerText', 'NextSibling', 'PreviousSibling', 'Value', 'ChildNodes', 'FirstChild', 'LastChild', 'HasChildNodes', 'IsReadOnly', 'OuterXml', 'BaseURI', 'PreviousText') [Array] $Settings = foreach ($Cat in $Category) { $GPO.DataSet | Where-Object { $null -ne $_.$Cat } } if ($Settings.Count -gt 0) { foreach ($Cat in $Category) { foreach ($Setting in $Settings.$Cat) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType } $CreateGPO['Name'] = $Setting.Name $CreateGPO['GPOSettingOrder'] = $Setting.GPOSettingOrder $Properties = $Setting.Properties.PSObject.Properties.Name | Where-Object { $_ -notin $SkipNames } foreach ($Property in $Properties) { $Name = Format-CamelCaseToDisplayName -Text $Property $CreateGPO[$Name] = $Setting.Properties.$Property } $CreateGPO['Filters'] = $Setting.Filters $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } } function ConvertTo-XMLLocalGroups { <# .SYNOPSIS Converts Group Policy Objects (GPO) to XML format for local groups. .DESCRIPTION This function converts Group Policy Objects (GPO) to XML format for local groups. It takes a GPO object and an optional switch to process a single object. .PARAMETER GPO Specifies the Group Policy Object (GPO) to be converted to XML format. .PARAMETER SingleObject Indicates whether to process a single GPO object. .EXAMPLE ConvertTo-XMLLocalGroups -GPO $myGPO Converts the specified GPO object to XML format for local groups. .EXAMPLE ConvertTo-XMLLocalGroups -GPO $myGPO -SingleObject Converts a single GPO object to XML format for local groups. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } if (-not $GPO.DataSet.Group) { continue } [Array] $CreateGPO['Settings'] = foreach ($Group in $GPO.DataSet.Group) { [Array] $Members = foreach ($Member in $Group.Properties.Members.Member) { [ordered] @{ MemberName = $Member.Name MemberAction = $Member.Action MemberSID = $Member.SID } } if ($Members.Count -eq 0) { $Members = @( [ordered] @{ MemberName = $null MemberAction = $null MemberSID = $null } ) } foreach ($Member in $Members) { $GroupObject = [ordered]@{ Changed = [DateTime] $Group.Changed GPOSettingOrder = $Group.GPOSettingOrder Name = $Group.name Action = $Script:Actions["$($Group.Properties.action)"] GroupName = $Group.Properties.groupName NewName = $Group.Properties.newName Description = $Group.Properties.description DeleteAllUsers = if ($Group.Properties.deleteAllUsers -eq '1') { 'Enabled' } elseif ($Group.Properties.deleteAllUsers -eq '0') { 'Disabled' } else { $Group.Properties.deleteAllUsers }; DeleteAllGroups = if ($Group.Properties.deleteAllGroups -eq '1') { 'Enabled' } elseif ($Group.Properties.deleteAllGroups -eq '0') { 'Disabled' } else { $Group.Properties.deleteAllGroups }; RemoveAccounts = if ($Group.Properties.removeAccounts -eq '1') { 'Enabled' } elseif ($Group.Properties.removeAccounts -eq '0') { 'Disabled' } else { $Group.Properties.removeAccounts }; GroupSid = $Group.Properties.groupSid } $Last = [ordered] @{ RunInLoggedOnUserSecurityContext = if ($Group.userContext -eq '1') { 'Enabled' } elseif ($Group.userContext -eq '0') { 'Disabled' } else { $Group.userContext }; RemoveThisItemWhenItIsNoLongerApplied = if ($Group.removePolicy -eq '1') { 'Enabled' } elseif ($Group.removePolicy -eq '0') { 'Disabled' } else { $Group.removePolicy }; Filters = $Group.Filters } $GroupObject = $GroupObject + $Member + $Last [PSCustomObject] $GroupObject } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Group in $GPO.DataSet.Group) { [Array] $Members = foreach ($Member in $Group.Properties.Members.Member) { [ordered] @{ MemberName = $Member.Name MemberAction = $Member.Action MemberSID = $Member.SID } } if ($Members.Count -eq 0) { $Members = @( [ordered] @{ MemberName = $null MemberAction = $null MemberSID = $null } ) } foreach ($Member in $Members) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Changed = [DateTime] $Group.Changed GPOSettingOrder = $Group.GPOSettingOrder Name = $Group.name Action = $Script:Actions["$($Group.Properties.action)"] GroupName = $Group.Properties.groupName NewName = $Group.Properties.newName Description = $Group.Properties.description DeleteAllUsers = if ($Group.Properties.deleteAllUsers -eq '1') { 'Enabled' } elseif ($Group.Properties.deleteAllUsers -eq '0') { 'Disabled' } else { $Group.Properties.deleteAllUsers }; DeleteAllGroups = if ($Group.Properties.deleteAllGroups -eq '1') { 'Enabled' } elseif ($Group.Properties.deleteAllGroups -eq '0') { 'Disabled' } else { $Group.Properties.deleteAllGroups }; RemoveAccounts = if ($Group.Properties.removeAccounts -eq '1') { 'Enabled' } elseif ($Group.Properties.removeAccounts -eq '0') { 'Disabled' } else { $Group.Properties.removeAccounts }; GroupSid = $Group.Properties.groupSid } $Last = [ordered] @{ RunInLoggedOnUserSecurityContext = if ($Group.userContext -eq '1') { 'Enabled' } elseif ($Group.userContext -eq '0') { 'Disabled' } else { $Group.userContext }; RemoveThisItemWhenItIsNoLongerApplied = if ($Group.removePolicy -eq '1') { 'Enabled' } elseif ($Group.removePolicy -eq '0') { 'Disabled' } else { $Group.removePolicy }; Filters = $Group.Filters } $CreateGPO = $CreateGPO + $Member + $Last $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } } function ConvertTo-XMLLocalUser { <# .SYNOPSIS Converts Group Policy Objects (GPO) data to XML format for local users. .DESCRIPTION This function converts GPO data to XML format specifically for local users. It extracts relevant user settings from the GPO data and organizes them into a structured XML format. .PARAMETER GPO Specifies the GPO object containing user data to be converted. .PARAMETER SingleObject Indicates whether to convert a single user object or multiple user objects. .EXAMPLE ConvertTo-XMLLocalUser -GPO $myGPO -SingleObject Converts a single GPO object to XML format for local users. .EXAMPLE ConvertTo-XMLLocalUser -GPO $myGPO Converts multiple GPO objects to XML format for local users. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } if (-not $GPO.DataSet.User) { continue } [Array] $CreateGPO['Settings'] = foreach ($User in $GPO.DataSet.User) { [PSCustomObject] @{ Changed = [DateTime] $User.Changed GPOSettingOrder = $User.GPOSettingOrder Action = $Script:Actions["$($User.Properties.action)"] UserName = $User.Properties.userName NewName = $User.Properties.newName FullName = $User.Properties.fullName Description = $User.Properties.description Password = $User.Properties.cpassword MustChangePasswordAtNextLogon = if ($User.Properties.changeLogon -eq '1') { $true } elseif ($User.Properties.changeLogon -eq '0') { $false } else { $User.Properties.changeLogon }; CannotChangePassword = if ($User.Properties.noChange -eq '1') { $true } elseif ($User.Properties.noChange -eq '0') { $false } else { $User.Properties.noChange }; PasswordNeverExpires = if ($User.Properties.neverExpires -eq '1') { $true } elseif ($User.Properties.neverExpires -eq '0') { $false } else { $User.Properties.neverExpires }; AccountIsDisabled = if ($User.Properties.acctDisabled -eq '1') { $true } elseif ($User.Properties.acctDisabled -eq '0') { $false } else { $User.Properties.acctDisabled }; AccountExpires = try { [DateTime] $User.Properties.expires } catch { $User.Properties.expires }; SubAuthority = $User.Properties.subAuthority } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($User in $GPO.DataSet.User) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Changed = [DateTime] $User.Changed GPOSettingOrder = $User.GPOSettingOrder Action = $Script:Actions["$($User.Properties.action)"] UserName = $User.Properties.userName NewName = $User.Properties.newName FullName = $User.Properties.fullName Description = $User.Properties.description Password = $User.Properties.cpassword MustChangePasswordAtNextLogon = if ($User.Properties.changeLogon -eq '1') { $true } elseif ($User.Properties.changeLogon -eq '0') { $false } else { $User.Properties.changeLogon }; CannotChangePassword = if ($User.Properties.noChange -eq '1') { $true } elseif ($User.Properties.noChange -eq '0') { $false } else { $User.Properties.noChange }; PasswordNeverExpires = if ($User.Properties.neverExpires -eq '1') { $true } elseif ($User.Properties.neverExpires -eq '0') { $false } else { $User.Properties.neverExpires }; AccountIsDisabled = if ($User.Properties.acctDisabled -eq '1') { $true } elseif ($User.Properties.acctDisabled -eq '0') { $false } else { $User.Properties.acctDisabled }; AccountExpires = try { [DateTime] $User.Properties.expires } catch { $User.Properties.expires }; SubAuthority = $User.Properties.subAuthority } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLNested { <# .SYNOPSIS Converts nested XML elements to a structured format for further processing. .DESCRIPTION This function recursively converts nested XML elements to a structured format for easier manipulation and analysis. It extracts properties from the XML elements and organizes them into a hierarchical structure. .PARAMETER CreateGPO Specifies the dictionary representing the XML structure being created. .PARAMETER Setting Specifies the XML element to be processed. .PARAMETER SkipNames Specifies an array of property names to skip during processing. .PARAMETER Name Specifies the name of the current XML element being processed. .EXAMPLE ConvertTo-XMLNested -CreateGPO $MyGPO -Setting $XmlSetting -SkipNames @('Name', 'Value') -Name 'Root' Description: Converts the nested XML element $XmlSetting into a structured format stored in $MyGPO, skipping properties 'Name' and 'Value', with the root element named 'Root'. .EXAMPLE $XmlElements | ForEach-Object { ConvertTo-XMLNested -CreateGPO $Output -Setting $_ -SkipNames @('ID') -Name 'Element' } Description: Processes multiple XML elements in $XmlElements, converting each into a structured format stored in $Output, skipping property 'ID', and naming each element 'Element'. #> [cmdletBinding()] param( [System.Collections.IDictionary] $CreateGPO, [System.Xml.XmlElement] $Setting, [string[]] $SkipNames, [string] $Name ) $Properties = $Setting.PSObject.Properties.Name | Where-Object { $_ -notin $SkipNames } $TempName = $Name foreach ($Property in $Properties) { If ($Property -eq 'Value') { if ($Setting.$Property) { if ($Setting.$Property.Name) { $Name = $Setting.$Property.Name } else { if (-not $Name) { $Name = 'Value' } } if ($Setting.$Property.Number) { $CreateGPO[$Name] = $Setting.$Property.Number } elseif ($Setting.$Property.String) { $CreateGPO[$Name] = $Setting.$Property.String } else { $CreateGPO[$Name] = $Setting.$Property } } } else { $Name = -join ($Name, $Property) $Name = Format-CamelCaseToDisplayName -Text $Name if ($Setting.$Property -is [System.Xml.XmlElement]) { ConvertTo-XMLNested -Setting $Setting.$Property -CreateGPO $CreateGPO -Name $Name -SkipNames $SkipNames } else { $CreateGPO[$Name] = $Setting.$Property } } $Name = $TempName } } function ConvertTo-XMLPolicies { <# .SYNOPSIS Converts Group Policy Object (GPO) data to XML format. .DESCRIPTION This function converts the provided GPO data into XML format for easier processing and analysis. .PARAMETER GPO Specifies the GPO object containing the data to be converted. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple GPO objects. .EXAMPLE ConvertTo-XMLPolicies -GPO $myGPO -SingleObject Converts a single GPO object $myGPO to XML format. .EXAMPLE ConvertTo-XMLPolicies -GPO $GPOList Converts multiple GPO objects in $GPOList to XML format. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Policy in $GPO.DataSet) { [PSCustomObject] @{ PolicyName = $Policy.Name PolicyState = $Policy.State PolicyCategory = $Policy.Category PolicySupported = $Policy.Supported PolicyExplain = $Policy.Explain PolicyText = $Policy.Text PolicyCheckBox = $Policy.CheckBox PolicyDropDownList = $Policy.DropDownList PolicyEditText = $Policy.EditText } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Policy in $GPO.DataSet) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType PolicyName = $Policy.Name PolicyState = $Policy.State PolicyCategory = $Policy.Category PolicySupported = $Policy.Supported PolicyExplain = $Policy.Explain PolicyText = $Policy.Text PolicyCheckBox = $Policy.CheckBox PolicyDropDownList = $Policy.DropDownList PolicyEditText = $Policy.EditText } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLPrinter { <# .SYNOPSIS Converts Group Policy Objects (GPO) to an XML printer format. .DESCRIPTION This function converts GPO objects to an XML printer format, providing detailed information about printers and printer connections. .PARAMETER GPO Specifies the GPO object to be converted. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple GPO objects. .EXAMPLE ConvertTo-XMLPrinter -GPO $myGPO -SingleObject Converts a single GPO object $myGPO to an XML printer format. .EXAMPLE ConvertTo-XMLPrinter -GPO $myGPO Converts multiple GPO objects to an XML printer format. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = @( foreach ($Type in @('SharedPrinter', 'PortPrinter', 'LocalPrinter')) { foreach ($Entry in $GPO.DataSet.$Type) { if ($Entry) { ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type $Type -Limited } } } if ($GPO.GpoCategory -eq 'PrinterConnectionSettings') { foreach ($Entry in $GPO.DataSet) { ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type 'PrinterConnections' -Limited } } ) $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Type in @('SharedPrinter', 'PortPrinter', 'LocalPrinter')) { foreach ($Entry in $GPO.DataSet.$Type) { if ($Entry) { ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type $Type } } } if ($GPO.GpoCategory -eq 'PrinterConnectionSettings') { foreach ($Entry in $GPO.DataSet) { ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type 'PrinterConnections' } } } } function ConvertTo-XMLPrinterInternal { <# .SYNOPSIS Converts printer settings to XML format for internal use. .DESCRIPTION This function converts printer settings to XML format for internal use. It takes a GPO object, entry details, type, and a switch for limited output. The output includes various printer settings in XML format. .PARAMETER GPO The GPO object containing printer settings. .PARAMETER Entry Details of the printer entry. .PARAMETER Type The type of printer setting. .PARAMETER Limited Switch to output limited printer settings. .EXAMPLE ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type "Network" -Limited Converts the specified printer settings to XML format with limited output. .EXAMPLE ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type "Local" Converts the specified printer settings to XML format without limited output. #> [cmdletBinding()] param( [PSCustomObject] $GPO, $Entry, $Type, [switch] $Limited ) if ($Limited) { $CreateGPO = [ordered]@{ Changed = try { [DateTime] $Entry.changed } catch { $Entry.Changed }; BypassErrors = if ($Entry.bypassErrors -eq '1') { $true } else { $false }; GPOSettingOrder = $Entry.GPOSettingOrder Filter = $Entry.Filter type = $Type Action = $null Comment = $Entry.Properties.comment Path = if ($Entry.Properties.path) { $Entry.Properties.Path } elseif ($Entry.Path) { $Entry.Path } else { '' } Location = $Entry.Properties.location HostName = $Entry.Properties.ipAddress LocalName = $Entry.Properties.localName UseDNS = if ($Entry.Properties.useDNS -eq '1') { $true } elseif ($Entry.Properties.useDNS -eq '0') { $false } else { $Entry.Properties.useDNS }; UseIPv6 = if ($Entry.Properties.useIPv6 -eq '1') { $true } elseif ($Entry.Properties.useIPv6 -eq '0') { $false } else { $Entry.Properties.useIPv6 }; Default = if ($Entry.Properties.default -eq '1') { $true } elseif ($Entry.Properties.default -eq '0') { $false } else { $Entry.Properties.default }; SkipLocal = if ($Entry.Properties.skipLocal -eq '1') { $true } elseif ($Entry.Properties.skipLocal -eq '0') { $false } else { $Entry.Properties.skipLocal }; DeleteAllShared = if ($Entry.Properties.deleteAll -eq '1') { $true } elseif ($Entry.Properties.deleteAll -eq '0') { $false } else { $Entry.Properties.deleteAll }; Persistent = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent }; DeleteMaps = if ($Entry.Properties.deleteMaps -eq '1') { $true } elseif ($Entry.Properties.deleteMaps -eq '0') { $false } else { $Entry.Properties.deleteMaps }; LPRSettingsQueueName = $Entry.Properties.lprQueue Protocol = $Entry.Properties.protocol PortNumber = if ($Entry.Properties.portNumber) { $Entry.Properties.portNumber } else { $Entry.Properties.port } DoubleSpool = if ($Entry.Properties.doubleSpool -eq '1') { $true } elseif ($Entry.Properties.doubleSpool -eq '0') { $false } else { $Entry.Properties.doubleSpool }; SNMPEnabled = if ($Entry.Properties.snmpEnabled -eq '1') { $true } elseif ($Entry.Properties.snmpEnabled -eq '0') { $false } else { $Entry.Properties.snmpEnabled }; SNMPCommunityName = $Entry.Properties.snmpCommunity SNMPDeviceIndex = $Entry.Properties.snmpDevIndex } if ($Entry.Properties.Action) { $CreateGPO['Action'] = $Script:Actions["$($Entry.Properties.action)"] } else { $CreateGPO['Action'] = 'Deploy' } [PSCustomObject] $CreateGPO } else { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType GpoCategory = $GPO.GpoCategory GpoSettings = $GPO.GpoSettings Changed = try { [DateTime] $Entry.changed } catch { $Entry.Changed }; BypassErrors = if ($Entry.bypassErrors -eq '1') { $true } else { $false }; GPOSettingOrder = $Entry.GPOSettingOrder Filter = $Entry.Filter type = $Type Action = $null Comment = $Entry.Properties.comment Path = if ($Entry.Properties.path) { $Entry.Properties.Path } elseif ($Entry.Path) { $Entry.Path } else { '' } Location = $Entry.Properties.location HostName = $Entry.Properties.ipAddress LocalName = $Entry.Properties.localName UseDNS = if ($Entry.Properties.useDNS -eq '1') { $true } elseif ($Entry.Properties.useDNS -eq '0') { $false } else { $Entry.Properties.useDNS }; UseIPv6 = if ($Entry.Properties.useIPv6 -eq '1') { $true } elseif ($Entry.Properties.useIPv6 -eq '0') { $false } else { $Entry.Properties.useIPv6 }; Default = if ($Entry.Properties.default -eq '1') { $true } elseif ($Entry.Properties.default -eq '0') { $false } else { $Entry.Properties.default }; SkipLocal = if ($Entry.Properties.skipLocal -eq '1') { $true } elseif ($Entry.Properties.skipLocal -eq '0') { $false } else { $Entry.Properties.skipLocal }; DeleteAllShared = if ($Entry.Properties.deleteAll -eq '1') { $true } elseif ($Entry.Properties.deleteAll -eq '0') { $false } else { $Entry.Properties.deleteAll }; Persistent = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent }; DeleteMaps = if ($Entry.Properties.deleteMaps -eq '1') { $true } elseif ($Entry.Properties.deleteMaps -eq '0') { $false } else { $Entry.Properties.deleteMaps }; LPRSettingsQueueName = $Entry.Properties.lprQueue Protocol = $Entry.Properties.protocol PortNumber = if ($Entry.Properties.portNumber) { $Entry.Properties.portNumber } else { $Entry.Properties.port } DoubleSpool = if ($Entry.Properties.doubleSpool -eq '1') { $true } elseif ($Entry.Properties.doubleSpool -eq '0') { $false } else { $Entry.Properties.doubleSpool }; SNMPEnabled = if ($Entry.Properties.snmpEnabled -eq '1') { $true } elseif ($Entry.Properties.snmpEnabled -eq '0') { $false } else { $Entry.Properties.snmpEnabled }; SNMPCommunityName = $Entry.Properties.snmpCommunity SNMPDeviceIndex = $Entry.Properties.snmpDevIndex } if ($Entry.Properties.Action) { $CreateGPO['Action'] = $Script:Actions["$($Entry.Properties.action)"] } else { $CreateGPO['Action'] = 'Deploy' } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } function ConvertTo-XMLRegistryAutologon { <# .SYNOPSIS Converts a Group Policy Object (GPO) to an XML format for Registry Autologon settings. .DESCRIPTION This function takes a GPO object as input and extracts relevant Registry Autologon settings to create an XML representation. .PARAMETER GPO Specifies the Group Policy Object (GPO) to be converted to XML format for Registry Autologon settings. .EXAMPLE $GPO = [PSCustomObject]@{ DisplayName = "Autologon GPO" DomainName = "example.com" GUID = "12345678-1234-5678-1234-567812345678" GpoType = "Registry" DataSet = [PSCustomObject]@{ Registry = @( [PSCustomObject]@{ Properties = [PSCustomObject]@{ Key = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Name = "AutoAdminLogon" Value = $true Changed = Get-Date } }, [PSCustomObject]@{ Properties = [PSCustomObject]@{ Key = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Name = "DefaultDomainName" Value = "example.com" Changed = Get-Date } }, [PSCustomObject]@{ Properties = [PSCustomObject]@{ Key = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Name = "DefaultUserName" Value = "user" Changed = Get-Date } }, [PSCustomObject]@{ Properties = [PSCustomObject]@{ Key = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Name = "DefaultPassword" Value = "password" Changed = Get-Date } } ) } } ConvertTo-XMLRegistryAutologon -GPO $GPO #> [cmdletBinding()] param( [PSCustomObject] $GPO ) $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType AutoAdminLogon = $null DefaultDomainName = $null DefaultUserName = $null DefaultPassword = $null DateChangedAutoAdminLogon = $null DateChangedDefaultDomainName = $null DateChangedDefaultUserName = $null DateChangedDefaultPassword = $null } foreach ($Registry in $GPO.DataSet.Registry) { if ($Registry.Properties.Key -eq 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon') { if ($Registry.Properties.Name -eq 'AutoAdminLogon') { $CreateGPO['AutoAdminLogon'] = [bool] $Registry.Properties.value $CreateGPO['DateChangedAutoAdminLogon'] = [DateTime] $Registry.changed } elseif ($Registry.Properties.Name -eq 'DefaultDomainName') { $CreateGPO['DefaultDomainName'] = $Registry.Properties.value $CreateGPO['DateChangedDefaultDomainName'] = [DateTime] $Registry.changed } elseif ($Registry.Properties.Name -eq 'DefaultUserName') { $CreateGPO['DefaultUserName'] = $Registry.Properties.value $CreateGPO['DateChangedDefaultUserName'] = [DateTime] $Registry.changed } elseif ($Registry.Properties.Name -eq 'DefaultPassword') { $CreateGPO['DefaultPassword'] = $Registry.Properties.value $CreateGPO['DateChangedDefaultPassword'] = [DateTime] $Registry.changed } } } if ($null -ne $CreateGPO['AutoAdminLogon'] -or $null -ne $CreateGPO['DefaultDomainName'] -or $null -ne $CreateGPO['DefaultUserName'] -or $null -ne $CreateGPO['DefaultPassword'] ) { $CreateGPO['Linked'] = $GPOEntry.Linked $CreateGPO['LinksCount'] = $GPOEntry.LinksCount $CreateGPO['Links'] = $GPOEntry.Links [PSCustomObject] $CreateGPO } } function ConvertTo-XMLRegistryAutologonOnReport { <# .SYNOPSIS Converts Group Policy Object (GPO) settings related to Autologon into an XML report. .DESCRIPTION This function takes a GPO object as input and extracts Autologon related settings to generate an XML report. .PARAMETER GPO Specifies the GPO object containing Autologon settings. .EXAMPLE $GPO = [PSCustomObject]@{ DisplayName = "Autologon GPO" DomainName = "example.com" GUID = "12345678-1234-1234-1234-1234567890AB" GpoType = "Security" Settings = @( [PSCustomObject]@{ Key = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Name = "AutoAdminLogon" Value = $true Changed = (Get-Date) } ) Linked = $true LinksCount = 1 Links = "area1.local" } ConvertTo-XMLRegistryAutologonOnReport -GPO $GPO #> [cmdletBinding()] param( [PSCustomObject] $GPO ) $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType AutoAdminLogon = $null DefaultDomainName = $null DefaultUserName = $null DefaultPassword = $null DateChangedAutoAdminLogon = $null DateChangedDefaultDomainName = $null DateChangedDefaultUserName = $null DateChangedDefaultPassword = $null } foreach ($Registry in $GPO.Settings) { if ($Registry.Key -eq 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon') { if ($Registry.Name -eq 'AutoAdminLogon') { $CreateGPO['AutoAdminLogon'] = [bool] $Registry.value $CreateGPO['DateChangedAutoAdminLogon'] = [DateTime] $Registry.changed } elseif ($Registry.Name -eq 'DefaultDomainName') { $CreateGPO['DefaultDomainName'] = $Registry.value $CreateGPO['DateChangedDefaultDomainName'] = [DateTime] $Registry.changed } elseif ($Registry.Name -eq 'DefaultUserName') { $CreateGPO['DefaultUserName'] = $Registry.value $CreateGPO['DateChangedDefaultUserName'] = [DateTime] $Registry.changed } elseif ($Registry.Name -eq 'DefaultPassword') { $CreateGPO['DefaultPassword'] = $Registry.value $CreateGPO['DateChangedDefaultPassword'] = [DateTime] $Registry.changed } } } if ($null -ne $CreateGPO['AutoAdminLogon'] -or $null -ne $CreateGPO['DefaultDomainName'] -or $null -ne $CreateGPO['DefaultUserName'] -or $null -ne $CreateGPO['DefaultPassword'] ) { $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } function ConvertTo-XMLRegistryInternetExplorerZones { <# .SYNOPSIS Converts registry settings related to Internet Explorer security zones into XML format. .DESCRIPTION This function converts registry settings from a Group Policy Object (GPO) related to Internet Explorer security zones into XML format. It extracts relevant information such as display name, domain name, GUID, GPO type, configuration, policy type, and security zone based on the registry settings provided. .PARAMETER GPO Specifies the Group Policy Object (GPO) containing the registry settings to be converted. .EXAMPLE ConvertTo-XMLRegistryInternetExplorerZones -GPO $GPO Converts the registry settings from the specified Group Policy Object ($GPO) related to Internet Explorer security zones into XML format. #> [cmdletBinding()] param( [PSCustomObject] $GPO ) foreach ($Registry in $GPO.Settings) { $Keys = @( 'Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\' 'Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\' ) $Found = $false foreach ($Key in $Keys) { if ($Registry.Key -like "$Key*") { $Found = $true } } if ($Found -eq $false) { continue } if ($Registry.Key -like 'Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\*') { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType } $CreateGPO['Disabled'] = $Registry.Disabled if ($Registry.Key -like '*EscDomains*') { $CreateGPO['Configuration'] = 'Enhanced Security Configuration (ESC)' } else { $CreateGPO['Configuration'] = 'Domains' } if ($Registry.Hive -eq 'HKEY_CURRENT_USER') { $CreateGPO['Type'] = 'User Policy' } elseif ($Registry.Hive -eq 'HKEY_LOCAL_MACHINE') { $CreateGPO['Type'] = 'Computer Policy' } else { $CreateGPO['Type'] = $Registry.Hive } if ($Registry.Value -eq '00000000') { $CreateGPO['Zone'] = 'My Computer (0)' } elseif ($Registry.Value -eq '00000001') { $CreateGPO['Zone'] = 'Local Intranet Zone (1)' } elseif ($Registry.Value -eq '00000002') { $CreateGPO['Zone'] = 'Trusted Sites Zone (2)' } elseif ($Registry.Value -eq '00000003') { $CreateGPO['Zone'] = 'Internet Zone (3)' } elseif ($Registry.Value -eq '00000004') { $CreateGPO['Zone'] = 'Restricted Sites Zone (4)' } else { $CreateGPO['Zone'] = $Registry.Value } [string] $FullKey = foreach ($Key in $Keys) { if ($Registry.Key -like "$Key*") { $Registry.Key.Replace($Key, '') } } $DomainSplit = $FullKey.Split('\') $Reversed = for ($i = $DomainSplit.Count - 1; $i -ge 0; $i--) { $DomainSplit[$i] } if ($Registry.Name -eq '*') { $CreateGPO['DomainZone'] = $Reversed -join '.' } else { $CreateGPO['DomainZone'] = -join ($Registry.Name, '://', ($Reversed -join '.')) } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLRegistrySettings { <# .SYNOPSIS Converts Group Policy Object (GPO) settings to XML format. .DESCRIPTION This function converts the settings of a Group Policy Object (GPO) to XML format. It can be used to export GPO settings for analysis or backup purposes. .PARAMETER GPO Specifies the Group Policy Object (GPO) to convert to XML format. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple GPO objects. .EXAMPLE ConvertTo-XMLRegistrySettings -GPO $myGPO -SingleObject Converts a single GPO object to XML format. .EXAMPLE ConvertTo-XMLRegistrySettings -GPO $myGPO Converts multiple GPO objects to XML format. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = Get-XMLNestedRegistry -GPO $GPO -DataSet $GPO.DataSet $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { Get-XMLNestedRegistry -GPO $GPO -DataSet $GPO.DataSet } } function ConvertTo-XMLScripts { <# .SYNOPSIS Converts Group Policy Objects (GPO) to XML scripts. .DESCRIPTION This function converts GPO objects to XML scripts for further processing. .PARAMETER GPO Specifies the GPO object to be converted. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple GPO objects. .EXAMPLE ConvertTo-XMLScripts -GPO $myGPO -SingleObject Converts a single GPO object to an XML script. .EXAMPLE ConvertTo-XMLScripts -GPO $myGPO Converts multiple GPO objects to XML scripts. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Script in $GPO.DataSet) { [PSCustomObject] @{ Type = $GPO.DataSet.Type Command = $GPO.DataSet.Command Parameters = $GPO.DataSet.Parameters Order = $GPO.DataSet.Order RunOrder = $GPO.DataSet.RunOrder } } $CreateGPO['DataCount'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Script in $GPO.DataSet) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Type = $Script.Type Command = $Script.Command Parameters = $Script.Parameters Order = $Script.Order RunOrder = $Script.RunOrder } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLSecurityOptions { <# .SYNOPSIS Converts Group Policy Object (GPO) data to XML security options. .DESCRIPTION This function converts GPO data to XML security options for further processing. .PARAMETER GPO Specifies the GPO object to convert. .PARAMETER SingleObject Indicates whether to convert a single GPO object. .EXAMPLE ConvertTo-XMLSecurityOptions -GPO $myGPO -SingleObject Converts a single GPO object to XML security options. .EXAMPLE ConvertTo-XMLSecurityOptions -GPO $myGPO Converts multiple GPO objects to XML security options. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Entry in $GPO.DataSet) { $Object = [ordered] @{} $Object['KeyName'] = $Entry.KeyName $Object['KeyDisplayName'] = $Entry.Display.Name $Object['KeyDisplayUnits'] = $Entry.Display.Units $Object['KeyDisplayBoolean'] = try { [bool]::Parse($Entry.Display.DisplayBoolean) } catch { $null }; $Object['KeyDisplayString'] = $Entry.Display.DisplayString $Object['SystemAccessPolicyName'] = $Entry.SystemAccessPolicyName if ($Entry.SettingString) { $Object['KeyValue'] = $Entry.SettingString } else { $Object['KeyValue'] = $Entry.SettingNumber } [PSCustomObject] $Object } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Entry in $GPO.DataSet) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType } $CreateGPO['KeyName'] = $Entry.KeyName $CreateGPO['KeyDisplayName'] = $Entry.Display.Name $CreateGPO['KeyDisplayUnits'] = $Entry.Display.Units $CreateGPO['KeyDisplayBoolean'] = try { [bool]::Parse($Entry.Display.DisplayBoolean) } catch { $null }; $CreateGPO['KeyDisplayString'] = $Entry.Display.DisplayString $CreateGPO['SystemAccessPolicyName'] = $Entry.SystemAccessPolicyName if ($Entry.SettingString) { $CreateGPO['KeyValue'] = $Entry.SettingString } else { $CreateGPO['KeyValue'] = $Entry.SettingNumber } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLSoftwareInstallation { <# .SYNOPSIS Converts Group Policy Object (GPO) data into XML format for software installation. .DESCRIPTION This function takes GPO data and converts it into XML format suitable for software installation. It creates an XML structure with detailed information about each software installation entry. .PARAMETER GPO Specifies the GPO object containing software installation data. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple objects. .EXAMPLE ConvertTo-XMLSoftwareInstallation -GPO $GPOObject -SingleObject Converts a single GPO object into XML format for software installation. .EXAMPLE ConvertTo-XMLSoftwareInstallation -GPO $GPOObject Converts multiple GPO objects into XML format for software installation. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($MsiInstallerr in $GPO.DataSet) { [PSCustomObject] @{ Identifier = $MsiInstallerr.Identifier Name = $MsiInstallerr.Name Path = $MsiInstallerr.Path MajorVersion = $MsiInstallerr.MajorVersion MinorVersion = $MsiInstallerr.MinorVersion LanguageId = $MsiInstallerr.LanguageId Architecture = $MsiInstallerr.Architecture IgnoreLanguage = if ($MsiInstallerr.IgnoreLanguage -eq 'true') { $true } else { $false } Allowx86Onia64 = if ($MsiInstallerr.Allowx86Onia64 -eq 'true') { $true } else { $false } SupportURL = $MsiInstallerr.SupportURL AutoInstall = if ($MsiInstallerr.AutoInstall -eq 'true') { $true } else { $false } DisplayInARP = if ($MsiInstallerr.DisplayInARP -eq 'true') { $true } else { $false } IncludeCOM = if ($MsiInstallerr.IncludeCOM -eq 'true') { $true } else { $false } SecurityDescriptor = $MsiInstallerr.SecurityDescriptor DeploymentType = $MsiInstallerr.DeploymentType ProductId = $MsiInstallerr.ProductId ScriptPath = $MsiInstallerr.ScriptPath DeploymentCount = $MsiInstallerr.DeploymentCount InstallationUILevel = $MsiInstallerr.InstallationUILevel Upgrades = if ($MsiInstallerr.Upgrades.Mandatory -eq 'true') { $true } else { $false } UninstallUnmanaged = if ($MsiInstallerr.UninstallUnmanaged -eq 'true') { $true } else { $false } LossOfScopeAction = $MsiInstallerr.LossOfScopeAction } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($MsiInstallerr in $GPO.DataSet) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Identifier = $MsiInstallerr.Identifier Name = $MsiInstallerr.Name Path = $MsiInstallerr.Path MajorVersion = $MsiInstallerr.MajorVersion MinorVersion = $MsiInstallerr.MinorVersion LanguageId = $MsiInstallerr.LanguageId Architecture = $MsiInstallerr.Architecture IgnoreLanguage = if ($MsiInstallerr.IgnoreLanguage -eq 'true') { $true } else { $false } Allowx86Onia64 = if ($MsiInstallerr.Allowx86Onia64 -eq 'true') { $true } else { $false } SupportURL = $MsiInstallerr.SupportURL AutoInstall = if ($MsiInstallerr.AutoInstall -eq 'true') { $true } else { $false } DisplayInARP = if ($MsiInstallerr.DisplayInARP -eq 'true') { $true } else { $false } IncludeCOM = if ($MsiInstallerr.IncludeCOM -eq 'true') { $true } else { $false } SecurityDescriptor = $MsiInstallerr.SecurityDescriptor DeploymentType = $MsiInstallerr.DeploymentType ProductId = $MsiInstallerr.ProductId ScriptPath = $MsiInstallerr.ScriptPath DeploymentCount = $MsiInstallerr.DeploymentCount InstallationUILevel = $MsiInstallerr.InstallationUILevel Upgrades = if ($MsiInstallerr.Upgrades.Mandatory -eq 'true') { $true } else { $false } UninstallUnmanaged = if ($MsiInstallerr.UninstallUnmanaged -eq 'true') { $true } else { $false } LossOfScopeAction = $MsiInstallerr.LossOfScopeAction } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLSystemServices { <# .SYNOPSIS Converts Group Policy Objects (GPO) data to an XML format for System Services. .DESCRIPTION This function takes a GPO object and converts its data into an XML format suitable for System Services. It organizes the GPO data including service names, startup modes, security auditing status, permissions, and security descriptors. .PARAMETER GPO Specifies the GPO object to be converted to XML format. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple GPO objects. .EXAMPLE ConvertTo-XMLSystemServices -GPO $myGPO -SingleObject Converts a single GPO object $myGPO to XML format for System Services. .EXAMPLE ConvertTo-XMLSystemServices -GPO $myGPO Converts multiple GPO objects to XML format for System Services. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($GPOEntry in $GPO.DataSet) { [PSCustomObject] @{ ServiceName = $GPOEntry.Name ServiceStartUpMode = $GPOEntry.StartUpMode SecurityAuditingPresent = try { [bool]::Parse($GPOEntry.SecurityDescriptor.AuditingPresent.'#text') } catch { $null }; SecurityPermissionsPresent = try { [bool]::Parse($GPOEntry.SecurityDescriptor.PermissionsPresent.'#text') } catch { $null }; SecurityDescriptor = $GPOEntry.SecurityDescriptor } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($GPOEntry in $GPO.DataSet) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType ServiceName = $GPOEntry.Name ServiceStartUpMode = $GPOEntry.StartUpMode SecurityAuditingPresent = try { [bool]::Parse($GPOEntry.SecurityDescriptor.AuditingPresent.'#text') } catch { $null }; SecurityPermissionsPresent = try { [bool]::Parse($GPOEntry.SecurityDescriptor.PermissionsPresent.'#text') } catch { $null }; SecurityDescriptor = $GPOEntry.SecurityDescriptor } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLSystemServicesNT { <# .SYNOPSIS Converts a Group Policy Object (GPO) to an XML representation for System Services on Windows NT. .DESCRIPTION This function takes a GPO object and converts it into an XML format specifically tailored for System Services on Windows NT. It extracts relevant information about each service defined in the GPO and structures it in an XML format. .PARAMETER GPO Specifies the Group Policy Object (GPO) to be converted to XML. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple GPO objects. .EXAMPLE ConvertTo-XMLSystemServicesNT -GPO $myGPO -SingleObject Converts a single GPO object $myGPO to an XML representation for System Services on Windows NT. .EXAMPLE $GPOs | ConvertTo-XMLSystemServicesNT Converts multiple GPO objects in the $GPOs array to XML representations for System Services on Windows NT. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Service in $GPO.DataSet.NTService) { [PSCustomObject] @{ GPOSettingOrder = $Service.GPOSettingOrder ServiceName = $Service.Properties.serviceName ServiceStartupType = $Service.Properties.startupType ServiceAction = $Service.Properties.serviceAction Timeout = $Service.Properties.timeout FirstFailure = $Service.Properties.firstFailure SecondFailure = $Service.Properties.secondFailure ThirdFailure = $Service.Properties.thirdFailure ResetFailCountDelay = $Service.Properties.resetFailCountDelay RestartComputerDelay = $Service.Properties.restartComputerDelay Filter = $Service.Filter AccountName = $Service.Properties.accountName AllowServiceToInteractWithTheDesktop = if ($Service.Properties.interact -eq 1) { 'Yes' } elseif ($Service.Properties.interact -eq 0) { 'No' } else { $null } RunThisProgram = $Service.Properties.program CommandLineParameters = $Service.Properties.args AppendFailCountToEndOfCommandLine = if ($Service.Properties.append -eq 1) { 'Yes' } elseif ($Service.Properties.append -eq 0) { 'No' } else { $null } } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Service in $GPO.DataSet.NTService) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType GPOSettingOrder = $Service.GPOSettingOrder ServiceName = $Service.Properties.serviceName ServiceStartupType = $Service.Properties.startupType ServiceAction = $Service.Properties.serviceAction Timeout = $Service.Properties.timeout FirstFailure = $Service.Properties.firstFailure SecondFailure = $Service.Properties.secondFailure ThirdFailure = $Service.Properties.thirdFailure ResetFailCountDelay = $Service.Properties.resetFailCountDelay RestartComputerDelay = $Service.Properties.restartComputerDelay Filter = $Service.Filter AccountName = $Service.Properties.accountName AllowServiceToInteractWithTheDesktop = if ($Service.Properties.interact -eq 1) { 'Yes' } elseif ($Service.Properties.interact -eq 0) { 'No' } else { $null } RunThisProgram = $Service.Properties.program CommandLineParameters = $Service.Properties.args AppendFailCountToEndOfCommandLine = if ($Service.Properties.append -eq 1) { 'Yes' } elseif ($Service.Properties.append -eq 0) { 'No' } else { $null } } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } function ConvertTo-XMLTaskScheduler { <# .SYNOPSIS Converts Task Scheduler settings to XML format. .DESCRIPTION This function converts Task Scheduler settings from a PSCustomObject to XML format. It provides detailed information about the task settings for each task. .PARAMETER GPO Specifies the Group Policy Object (GPO) containing the Task Scheduler settings. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLTaskScheduler -GPO $GPOObject -SingleObject Converts the Task Scheduler settings from the specified GPO object to XML format for a single object. .EXAMPLE ConvertTo-XMLTaskScheduler -GPO $GPOObject Converts the Task Scheduler settings from the specified GPO object to XML format for multiple objects. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Entry in $GPO.DataSet.Drive) { [PSCustomObject] @{ Changed = [DateTime] $Entry.changed GPOSettingOrder = $Entry.GPOSettingOrder Filter = $Entry.Filter Name = $Entry.Name Action = $Script:Actions["$($Entry.Properties.action)"] ThisDrive = $Entry.Properties.thisDrive AllDrives = $Entry.Properties.allDrives UserName = $Entry.Properties.userName Path = $Entry.Properties.path Label = $Entry.Properties.label Persistent = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent }; UseLetter = if ($Entry.Properties.useLetter -eq '1') { $true } elseif ($Entry.Properties.useLetter -eq '0') { $false } else { $Entry.Properties.useLetter }; Letter = $Entry.Properties.letter } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Type in @('TaskV2', 'Task', 'ImmediateTaskV2', 'ImmediateTask')) { foreach ($Entry in $GPO.DataSet.$Type) { $ListActions = foreach ($LoopAction in $Entry.Properties.Task.Actions) { foreach ($InternalAction in $LoopAction.Exec) { $Action = [ordered] @{ ActionType = 'Execute' Command = $InternalAction.Command Arguments = $InternalAction.Arguments WorkingDirectory = $InternalAction.WorkingDirectory Server = $Null Subject = $Null To = $Null From = $Null Body = $Null Attachments = $Null } $Action } foreach ($InternalAction in $LoopAction.SendEmail) { $Action = [ordered] @{ ActionType = 'SendEmail' Command = $null Arguments = $null WorkingDirectory = $null Server = $InternalAction.Server Subject = $InternalAction.Subject To = $InternalAction.To From = $InternalAction.From Body = $InternalAction.Body Attachments = $InternalAction.Attachments.File -join '; ' } $Action } } if ($ListActions.Count -eq 0) { $ListActions = @( if ($Entry.Properties.appName) { $Action = [ordered] @{ ActionType = $Script:Actions["$($Entry.Properties.action)"] Command = $Entry.Properties.appName Arguments = $Entry.Properties.args WorkingDirectory = $Entry.Properties.startIn Server = $null Subject = $null To = $null From = $null Body = $null Attachments = $null } $Action } else { $Action = [ordered] @{ ActionType = $Script:Actions["$($Entry.Properties.action)"] Command = $null Arguments = $null WorkingDirectory = $null Server = $null Subject = $null To = $null From = $null Body = $null Attachments = $null } $Action } ) } foreach ($Action in $ListActions) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Type = $Type Changed = [DateTime] $Entry.changed GPOSettingOrder = $Entry.GPOSettingOrder userContext = '' Name = $Entry.Name Status = $Entry.status Action = $Script:Actions["$($Entry.Properties.action)"] runAs = $Entry.Properties.runAs Comment = $Entry.Properties.comment } if ($Entry.Properties.startOnlyIfIdle) { $Middle = [ordered] @{ AllowStartOnDemand = $null DisallowStartIfOnBatteries = $Entry.Properties.noStartIfOnBatteries StopIfGoingOnBatteries = $Entry.Properties.stopIfGoingOnBatteries AllowHardTerminate = $null Enabled = $Entry.Properties.enabled Hidden = $null MultipleInstancesPolicy = $null Priority = $null ExecutionTimeLimit = $null IdleDuration = $Entry.Properties.deadlineMinutes IdleWaitTimeout = $null IdleStopOnIdleEnd = $Entry.Properties.stopOnIdleEnd IdleRestartOnIdle = $Entry.Properties.startOnlyIfIdle RegistrationInfoAuthor = $null RegistrationInfoDescription = $null deleteWhenDone = $Entry.Properties.deleteWhenDone } } else { $Middle = [ordered] @{ AllowStartOnDemand = $Entry.Properties.Task.Settings.AllowStartOnDemand DisallowStartIfOnBatteries = $Entry.Properties.Task.Settings.DisallowStartIfOnBatteries StopIfGoingOnBatteries = $Entry.Properties.Task.Settings.StopIfGoingOnBatteries AllowHardTerminate = $Entry.Properties.Task.Settings.AllowHardTerminate Enabled = $Entry.Properties.Task.Settings.Enabled Hidden = $Entry.Properties.Task.Settings.Hidden MultipleInstancesPolicy = $Entry.Properties.Task.Settings.MultipleInstancesPolicy Priority = $Entry.Properties.Task.Settings.Priority ExecutionTimeLimit = $Entry.Properties.Task.Settings.ExecutionTimeLimit IdleDuration = $Entry.Properties.Task.Settings.IdleSettings.Duration IdleWaitTimeout = $Entry.Properties.Task.Settings.IdleSettings.WaitTimeout IdleStopOnIdleEnd = $Entry.Properties.Task.Settings.IdleSettings.StopOnIdleEnd IdleRestartOnIdle = $Entry.Properties.Task.Settings.IdleSettings.RestartOnIdle RegistrationInfoAuthor = $Entry.Properties.Task.RegistrationInfo.Author RegistrationInfoDescription = $Entry.Properties.Task.RegistrationInfo.Description deleteWhenDone = $Entry.Properties.deleteWhenDone } } $End = [ordered] @{ id = $Entry.Properties.Principals.Principal.id UserId = $Entry.Properties.Principals.Principal.UserId LogonType = $Entry.Properties.Principals.Principal.LogonType RunLevel = $Entry.Properties.Principals.Principal.RunLevel } $CreateGPO = $CreateGPO + $Middle + $End + $Action $Last = [ordered] @{ RunInLoggedOnUserSecurityContext = if ($Entry.userContext -eq '1') { 'Enabled' } elseif ($Entry.userContext -eq '0') { 'Disabled' } else { $Entry.userContext }; RemoveThisItemWhenItIsNoLongerApplied = if ($Entry.removePolicy -eq '1') { 'Enabled' } elseif ($Entry.removePolicy -eq '0') { 'Disabled' } else { $Entry.removePolicy }; Filters = $Group.Filters } $CreateGPO = $CreateGPO + $Last $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } } } function ConvertTo-XMLUserRightsAssignment { <# .SYNOPSIS Converts user rights assignments to XML format. .DESCRIPTION This function converts user rights assignments to XML format based on the provided GPO object. .PARAMETER GPO Specifies the Group Policy Object (GPO) to extract user rights assignments from. .PARAMETER SingleObject Indicates whether to process a single object. .EXAMPLE ConvertTo-XMLUserRightsAssignment -GPO $GPOObject -SingleObject Converts user rights assignments from a single GPO object to XML format. .EXAMPLE ConvertTo-XMLUserRightsAssignment -GPO $GPOObject Converts user rights assignments from multiple GPO objects to XML format. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) $UserRightsTranslation = @{ SeNetworkLogonRight = 'Access this computer from the network' SeMachineAccountPrivilege = 'Add workstations to domain' SeIncreaseQuotaPrivilege = 'Adjust memory quotas for a process' SeInteractiveLogonRight = 'Allow log on locally' SeBackupPrivilege = 'Back up files and directories' SeChangeNotifyPrivilege = 'Bypass traverse checking Everyone' SeSystemTimePrivilege = 'Change the system time' SeCreatePagefilePrivilege = 'Create a pagefile' SeDebugPrivilege = 'Debug programs' SeEnableDelegationPrivilege = 'Enable computer and user accounts to be trusted for delegation' SeRemoteShutdownPrivilege = 'Force shutdown from a remote system' SeAuditPrivilege = 'Generate security audits' SeIncreaseBasePriorityPrivilege = 'Increase scheduling priority' SeLoadDriverPrivilege = 'Load and unload device drivers' SeBatchLogonRight = 'Log on as a batch job' SeSecurityPrivilege = 'Manage auditing and security log' SeSystemEnvironmentPrivilege = 'Modify firmware environment values' SeProfileSingleProcessPrivilege = 'Profile single process' SeSystemProfilePrivilege = 'Profile system performance' SeUndockPrivilege = 'Remove computer from docking station' SeAssignPrimaryTokenPrivilege = 'Replace a process level token' SeRestorePrivilege = 'Restore files and directories' SeShutdownPrivilege = 'Shut down the system' SeTakeOwnershipPrivilege = 'Take ownership of files or other objects' } if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Entry in $GPO.DataSet) { foreach ($Member in $Entry.Member) { [PSCustomObject]@{ 'UserRightsAssignment' = $Entry.Name 'UserRightsAssignmentDescription' = $UserRightsTranslation[$Entry.Name] 'Name' = $Member.Name.'#text' 'Sid' = $Member.SID.'#text' } } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Entry in $GPO.DataSet) { foreach ($Member in $Entry.Member) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType } $CreateGPO['UserRightsAssignment'] = $Entry.Name $CreateGPO['UserRightsAssignmentDescription'] = $UserRightsTranslation[$Entry.Name] $CreateGPO['Name'] = $Member.Name.'#text' $CreateGPO['Sid'] = $Member.SID.'#text' $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } } function ConvertTo-XMLWindowsFirewall { <# .SYNOPSIS Converts a Group Policy Object (GPO) to an XML representation for Windows Firewall settings. .DESCRIPTION This function takes a GPO object and converts it into an XML format suitable for Windows Firewall settings. It can handle single GPO objects or multiple GPO objects. .PARAMETER GPO Specifies the Group Policy Object to be converted to XML. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple GPO objects. .EXAMPLE ConvertTo-XMLWindowsFirewall -GPO $myGPO -SingleObject Converts a single GPO object $myGPO to an XML representation for Windows Firewall settings. .EXAMPLE $GPOs | ConvertTo-XMLWindowsFirewall Converts multiple GPO objects in the $GPOs array to XML representations for Windows Firewall settings. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Policy in $GPO.DataSet) { [PSCustomObject] @{ Name = $Policy.LocalName Version = $Policy.PolicyVersion.Value } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Policy in $GPO.DataSet) { [PSCustomObject]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Name = $Policy.LocalName Version = $Policy.PolicyVersion.Value Linked = $GPO.Linked LinksCount = $GPO.LinksCount Links = $GPO.Links } } } } function ConvertTo-XMLWindowsFirewallConnectionSecurityAuthentiation { <# .SYNOPSIS Converts Windows Firewall Connection Security Authentication settings to XML format. .DESCRIPTION This function converts Windows Firewall Connection Security Authentication settings from a PSCustomObject to XML format. It provides detailed information about the authentication settings for each connection. .PARAMETER GPO Specifies the Group Policy Object (GPO) containing the Windows Firewall Connection Security Authentication settings. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLWindowsFirewallConnectionSecurityAuthentiation -GPO $GPOObject -SingleObject Converts the Windows Firewall Connection Security Authentication settings from the specified GPO object to XML format for a single object. .EXAMPLE ConvertTo-XMLWindowsFirewallConnectionSecurityAuthentiation -GPO $GPOObject Converts the Windows Firewall Connection Security Authentication settings from the specified GPO object to XML format for multiple objects. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Connection in $GPO.DataSet) { [PSCustomObject] @{ Name = $Connection.LocalName Version = $Connection.Version ConnectionGUID = $Connection.GUID Method = $Connection.AuthenticationSuites.Method CAName = $Connection.AuthenticationSuites.CAName CertAccountMapping = if ($Connection.AuthenticationSuites.CertAccountMapping -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.CertAccountMapping -eq 'false') { $false } else { $Connection.AuthenticationSuites.CertAccountMapping } ExcludeCAName = if ($Connection.AuthenticationSuites.ExcludeCAName -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.ExcludeCAName -eq 'false') { $false } else { $Connection.AuthenticationSuites.ExcludeCAName } HealthCert = if ($Connection.AuthenticationSuites.HealthCert -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.HealthCert -eq 'false') { $false } else { $Connection.AuthenticationSuites.HealthCert } } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Connection in $GPO.DataSet) { [PSCustomObject]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Name = $Connection.LocalName Version = $Connection.Version ConnectionGUID = $Connection.GUID Method = $Connection.AuthenticationSuites.Method CAName = $Connection.AuthenticationSuites.CAName CertAccountMapping = if ($Connection.AuthenticationSuites.CertAccountMapping -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.CertAccountMapping -eq 'false') { $false } else { $Connection.AuthenticationSuites.CertAccountMapping } ExcludeCAName = if ($Connection.AuthenticationSuites.ExcludeCAName -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.ExcludeCAName -eq 'false') { $false } else { $Connection.AuthenticationSuites.ExcludeCAName } HealthCert = if ($Connection.AuthenticationSuites.HealthCert -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.HealthCert -eq 'false') { $false } else { $Connection.AuthenticationSuites.HealthCert } Linked = $GPO.Linked LinksCount = $GPO.LinksCount Links = $GPO.Links } } } } function ConvertTo-XMLWindowsFirewallProfile { <# .SYNOPSIS Converts a Windows Firewall profile to XML format. .DESCRIPTION This function takes a Windows Firewall profile object and converts it to XML format for further processing. .PARAMETER GPO Specifies the Windows Firewall profile object to convert. .PARAMETER SingleObject Indicates whether to convert a single object or multiple objects. .EXAMPLE ConvertTo-XMLWindowsFirewallProfile -GPO $FirewallProfile -SingleObject Converts a single Windows Firewall profile object to XML format. .EXAMPLE ConvertTo-XMLWindowsFirewallProfile -GPO $FirewallProfiles -SingleObject:$false Converts multiple Windows Firewall profile objects to XML format. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Profile in $GPO.DataSet) { [PSCustomObject] @{ Profile = $Profile.LocalName EnableFirewall = if ($Profile.EnableFirewall.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } AllowLocalIPsecPolicyMerge = if ($Profile.AllowLocalIPsecPolicyMerge.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } AllowLocalPolicyMerge = if ($Profile.AllowLocalPolicyMerge.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DefaultInboundAction = if ($Profile.DefaultInboundAction.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DefaultOutboundAction = if ($Profile.DefaultOutboundAction.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DisableNotifications = if ($Profile.DisableNotifications.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DisableUnicastResponsesToMulticastBroadcast = if ($Profile.DisableUnicastResponsesToMulticastBroadcast.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DoNotAllowExceptions = if ($Profile.DoNotAllowExceptions.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } LogFilePath = if ($Profile.LogFilePath.Value) { $Profile.LogFilePath.Value } else { 'Not configured' } LogDroppedPackets = if ($Profile.LogDroppedPackets.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } LogFileSize = if ($Profile.LogFileSize.Value) { $Profile.LogFileSize.Value } else { 'Not configured' } LogSuccessfulConnections = if ($Profile.LogSuccessfulConnections.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Profile in $GPO.DataSet) { [PSCustomObject]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Profile = $Profile.LocalName EnableFirewall = if ($Profile.EnableFirewall.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } AllowLocalIPsecPolicyMerge = if ($Profile.AllowLocalIPsecPolicyMerge.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } AllowLocalPolicyMerge = if ($Profile.AllowLocalPolicyMerge.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DefaultInboundAction = if ($Profile.DefaultInboundAction.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DefaultOutboundAction = if ($Profile.DefaultOutboundAction.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DisableNotifications = if ($Profile.DisableNotifications.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DisableUnicastResponsesToMulticastBroadcast = if ($Profile.DisableUnicastResponsesToMulticastBroadcast.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } DoNotAllowExceptions = if ($Profile.DoNotAllowExceptions.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } LogFilePath = if ($Profile.LogFilePath.Value) { $Profile.LogFilePath.Value } else { 'Not configured' } LogDroppedPackets = if ($Profile.LogDroppedPackets.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } LogFileSize = if ($Profile.LogFileSize.Value) { $Profile.LogFileSize.Value } else { 'Not configured' } LogSuccessfulConnections = if ($Profile.LogSuccessfulConnections.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' } Linked = $GPO.Linked LinksCount = $GPO.LinksCount Links = $GPO.Links } } } } function ConvertTo-XMLWindowsFirewallRules { <# .SYNOPSIS Converts Windows Firewall rules to XML format. .DESCRIPTION This function converts Windows Firewall rules to XML format for easier management and analysis. .PARAMETER GPO Specifies the Group Policy Object (GPO) containing the firewall rules to be converted. .PARAMETER SingleObject Indicates whether to convert a single firewall rule object. .EXAMPLE ConvertTo-XMLWindowsFirewallRules -GPO $GPOObject -SingleObject Converts all firewall rules in the specified GPO to XML format. .EXAMPLE ConvertTo-XMLWindowsFirewallRules -GPO $GPOObject Converts all firewall rules in the specified GPO to XML format without creating a single object. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Rule in $GPO.DataSet) { [PSCustomObject] @{ Version = $Rule.Version Type = if ($Rule.Dir -eq 'In') { 'Inbound' } elseif ($Rule.Dir -eq 'Out') { 'Outbound' } else { $Rule.Dir } Name = $Rule.Name Action = $Rule.Action Enabled = if ($Rule.Active -eq 'true') { $true } else { $false } Profile = $Rule.Profile Svc = $Rule.Svc LocalAddressIPv4 = $Rule.LA4 LocalAddressIPv6 = $Rule.LA6 RemoteAddressIPV4 = $Rule.RA4 RemoteAddressIPV6 = $Rule.RA6 LocalPort = $Rule.LPort RemotePort = $Rule.RPort Description = $Rule.Desc EmbedCtxt = $Rule.EmbedCtxt Edge = $Rule.Edge IFType = $Rule.IFType Security = $Rule.Security App = $Rule.App Protocol = $Rule.Protocol RMAuth = $Rule.RMAuth RUAuth = $Rule.RUAuth ICMP4 = $Rule.ICMP4 LocalName = $Rule.LocalName } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Rule in $GPO.DataSet) { [PSCustomObject]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Version = $Rule.Version Type = if ($Rule.Dir -eq 'In') { 'Inbound' } elseif ($Rule.Dir -eq 'Out') { 'Outbound' } else { $Rule.Dir } Name = $Rule.Name Action = $Rule.Action Enabled = if ($Rule.Active -eq 'true') { $true } else { $false } Profile = $Rule.Profile Svc = $Rule.Svc LocalAddressIPv4 = $Rule.LA4 LocalAddressIPv6 = $Rule.LA6 RemoteAddressIPV4 = $Rule.RA4 RemoteAddressIPV6 = $Rule.RA6 LocalPort = $Rule.LPort RemotePort = $Rule.RPort Description = $Rule.Desc EmbedCtxt = $Rule.EmbedCtxt Edge = $Rule.Edge IFType = $Rule.IFType Security = $Rule.Security App = $Rule.App Protocol = $Rule.Protocol RMAuth = $Rule.RMAuth RUAuth = $Rule.RUAuth ICMP4 = $Rule.ICMP4 LocalName = $Rule.LocalName Linked = $GPO.Linked LinksCount = $GPO.LinksCount Links = $GPO.Links } } } } function ConvertTo-XMLWindowsFirewallSecurityRules { <# .SYNOPSIS Converts Windows Firewall security rules data to XML format. .DESCRIPTION This function converts Windows Firewall security rules data to XML format. It takes a GPO object as input and generates XML data representing the security rules. .PARAMETER GPO Specifies the Group Policy Object (GPO) containing the security rules data. .PARAMETER SingleObject Indicates whether to convert a single GPO object or multiple security rules. .EXAMPLE $GPO = Get-GPO -Name "FirewallRules" ConvertTo-XMLWindowsFirewallSecurityRules -GPO $GPO -SingleObject Converts the security rules data from the GPO "FirewallRules" to XML format for a single GPO object. .EXAMPLE $GPOs = Get-GPO -All ConvertTo-XMLWindowsFirewallSecurityRules -GPO $GPOs -SingleObject:$false Converts the security rules data from all GPOs to XML format for multiple security rules. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [switch] $SingleObject ) if ($SingleObject) { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Count = 0 Settings = $null } [Array] $CreateGPO['Settings'] = foreach ($Rule in $GPO.DataSet) { [PSCustomObject] @{ Version = $Rule.Version Name = $Rule.Name Action = $Rule.Action Enabled = if ($Rule.Active -eq 'true') { $true } else { $false } Auth1Set = $Rule.Auth1Set Auth2Set = $Rule.Auth2Set Crypto2Set = $Rule.Crypto2Set Description = $Rule.Desc } } $CreateGPO['Count'] = $CreateGPO['Settings'].Count $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } else { foreach ($Rule in $GPO.DataSet) { [PSCustomObject]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Version = $Rule.Version Name = $Rule.Name Action = $Rule.Action Enabled = if ($Rule.Active -eq 'true') { $true } else { $false } Auth1Set = $Rule.Auth1Set Auth2Set = $Rule.Auth2Set Crypto2Set = $Rule.Crypto2Set Description = $Rule.Desc Linked = $GPO.Linked LinksCount = $GPO.LinksCount Links = $GPO.Links } } } } function Find-GPOPassword { <# .SYNOPSIS Finds and decrypts the password stored in the cpassword attribute of a Group Policy Object (GPO) XML file. .DESCRIPTION This function takes the path to a GPO XML file as input, searches for the cpassword attribute, decrypts it, and returns the password. .PARAMETER Path Specifies the path to the GPO XML file. .EXAMPLE Find-GPOPassword -Path "C:\GPOs\GPO1.xml" Searches for the cpassword attribute in the GPO XML file "GPO1.xml" and returns the decrypted password. #> [cmdletBinding()] param( [string] $Path ) [string]$XMLString = Get-Content -LiteralPath $Path if ($XMLString.Contains("cpassword")) { [string]$Cpassword = [regex]::matches($XMLString, '(cpassword=).+?(?=\")') $Cpassword = $Cpassword.split('(\")')[1] if ($Cpassword.Length -gt 20 -and $Cpassword -notlike '*cpassword*') { $Mod = ($Cpassword.length % 4) switch ($Mod) { '1' { $Cpassword = $Cpassword.Substring(0, $Cpassword.Length - 1) } '2' { $Cpassword += ('=' * (4 - $Mod)) } '3' { $Cpassword += ('=' * (4 - $Mod)) } } $Base64Decoded = [Convert]::FromBase64String($Cpassword) $AesObject = [System.Security.Cryptography.AesCryptoServiceProvider]::new() [Byte[]] $AesKey = @(0x4e, 0x99, 0x06, 0xe8, 0xfc, 0xb6, 0x6c, 0xc9, 0xfa, 0xf4, 0x93, 0x10, 0x62, 0x0f, 0xfe, 0xe8, 0xf4, 0x96, 0xe8, 0x06, 0xcc, 0x05, 0x79, 0x90, 0x20, 0x9b, 0x09, 0xa4, 0x33, 0xb6, 0x6c, 0x1b) $AesIV = New-Object Byte[]($AesObject.IV.Length) $AesObject.IV = $AesIV $AesObject.Key = $AesKey $DecryptorObject = $AesObject.CreateDecryptor() [Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length) $Password = [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock) } else { $Password = '' } } $Password } function Format-CamelCaseToDisplayName { <# .SYNOPSIS Converts a camelCase string to a display name with spaces. .DESCRIPTION This function takes a camelCase string and converts it to a display name by adding spaces between words. .PARAMETER Text The camelCase string(s) to be converted to display name. .PARAMETER AddChar The character to add between words in the display name. .EXAMPLE Format-CamelCaseToDisplayName -Text 'testString' -AddChar ' ' Converts 'testString' to 'Test String' .EXAMPLE Format-CamelCaseToDisplayName -Text 'anotherExample' -AddChar '-' Converts 'anotherExample' to 'Another-Example' #> [cmdletBinding()] param( [string[]] $Text, [string] $AddChar ) foreach ($string in $Text) { $newString = '' $stringChars = $string.GetEnumerator() $charIndex = 0 foreach ($char in $stringChars) { if ([char]::IsUpper($char) -eq 'True' -and $charIndex -gt 0) { $newString = $newString + $AddChar + $char.ToString() } elseif ($charIndex -eq 0) { $newString = $newString + $char.ToString().ToUpper() } else { $newString = $newString + $char.ToString() } $charIndex++ } $newString } } function Get-ADOrganizationalUnitObject { <# .SYNOPSIS Gets number of objects in a given OU/OUs with ability to find only those being affected by GPOs. .DESCRIPTION Gets number of objects in a given OU/OUs with ability to find only those being affected by GPOs. .PARAMETER OrganizationalUnit One or more organizational units to get the number of objects in. .PARAMETER Extended Adds all objects affected for better understanding .PARAMETER Summary Returns only summary for given OU/OUs .PARAMETER IncludeAffectedOnly Ignores any object types that are not Users or Computers .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER AsHashTable Returns results in form of hashtable .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE $OUs = @( 'OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz' 'OU=US,OU=ITR01,DC=ad,DC=evotec,DC=xyz' 'OU=ITR01,DC=ad,DC=evotec,DC=xyz' 'OU=Users,OU=User,OU=SE1,OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz' ) Get-ADOrganizationalUnitObject -OrganizationalUnit $OUs -IncludeAffectedOnly | Format-Table .EXAMPLE $OUs = @( 'OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz' 'OU=US,OU=ITR01,DC=ad,DC=evotec,DC=xyz' 'OU=ITR01,DC=ad,DC=evotec,DC=xyz' 'OU=Users,OU=User,OU=SE1,OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz' ) Get-ADOrganizationalUnitObject -OrganizationalUnit $OUs | Format-Table .EXAMPLE $OUs = @( #'OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz' #'OU=US,OU=ITR01,DC=ad,DC=evotec,DC=xyz' 'OU=Users,OU=User,OU=SE1,OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz' 'OU=ITR01,DC=ad,DC=evotec,DC=xyz' ) Get-ADOrganizationalUnitObject -OrganizationalUnit $OUs -Summary -IncludeAffectedOnly | Format-List .NOTES General notes #> [cmdletBinding()] param( [parameter(Mandatory)][Array] $OrganizationalUnit, [switch] $Extended, [switch] $Summary, [switch] $IncludeAffectedOnly, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $AsHashTable, [System.Collections.IDictionary] $ExtendedForestInformation ) $CachedOu = [ordered] @{} $ListOU = @( foreach ($OU in $OrganizationalUnit) { if ($OU.DistinguishedName) { $OU.DistinguishedName } else { $OU } } ) $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $OUCache = Get-GPOBlockedInheritance -AsHashTable -ExtendedForestInformation $ForestInformation if ($Summary) { $SummaryData = [ordered] @{ ObjectsClasses = [ordered] @{} ObjectsTotalCount = 0 ObjectsBlockedInheritanceCount = 0 ObjectsTotal = [ordered] @{} ObjectsBlockedInheritance = [ordered] @{} DistinguishedName = [System.Collections.Generic.List[string]]::new() } } foreach ($OU in $ListOU) { $Domain = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $OU $ObjectsInOu = Get-ADObject -LDAPFilter "(|(ObjectClass=user)(ObjectClass=contact)(ObjectClass=computer)(ObjectClass=group)(objectClass=inetOrgPerson)(ObjectClass=PrintQueue))" -SearchBase $OU -Server $ForestInformation['QueryServers'][$Domain]['hostname'][0] if (-not $CachedOu[$OU]) { $CachedOu[$OU] = [ordered] @{ DistinguishedName = $OU Domain = $Domain 'ObjectsClasses' = [ordered] @{} 'ObjectsDirectCount' = 0 'ObjectsIndirectCount' = 0 'ObjectsTotalCount' = 0 'ObjectsTotalIncludingBlockedCount' = 0 'ObjectsBlockedInheritanceCount' = 0 } if ($Extended) { $CachedOu[$OU]['ObjectsDirect'] = [ordered] @{} $CachedOu[$OU]['ObjectsIndirect'] = [ordered] @{} $CachedOu[$OU]['ObjectsTotal'] = [ordered] @{} $CachedOu[$OU]['ObjectsTotalIncludingBlocked'] = [ordered] @{} $CachedOu[$OU]['ObjectsBlockedInheritance'] = [ordered] @{} } } foreach ($Object in $ObjectsInOu) { if ($IncludeAffectedOnly) { if ($Object.ObjectClass -notin 'User', 'computer') { continue } } $Place = ConvertFrom-DistinguishedName -ToOrganizationalUnit -DistinguishedName $Object.DistinguishedName if (-not $Place) { } if ($Place -and $OUCache[$Place]) { $BlockedInheritance = $OUCache[$Place].BlockedInheritance } else { $BlockedInheritance = $false } if ($Summary) { $SummaryData['DistinguishedName'].Add($OU) $SummaryData['ObjectsClasses'][$Object.ObjectClass] = '' if (-not $Place -or $Place -eq $OU) { $SummaryData['ObjectsTotal'][$Object.DistinguishedName] = $Object } else { if ($BlockedInheritance) { $SummaryData['ObjectsBlockedInheritance'][$Object.DistinguishedName] = $Object } else { $SummaryData['ObjectsTotal'][$Object.DistinguishedName] = $Object } } } else { if (-not $Place -or $Place -eq $OU) { $CachedOu[$OU]['ObjectsDirectCount']++ $CachedOu[$OU]['ObjectsTotalCount']++ $CachedOu[$OU]['ObjectsClasses'][$Object.ObjectClass] = '' if ($Extended) { $CachedOu[$OU]['ObjectsTotal'][$Object.DistinguishedName] = $Object $CachedOu[$OU]['ObjectsDirect'][$Object.DistinguishedName] = $Object } } else { if ($BlockedInheritance) { $CachedOu[$OU]['ObjectsBlockedInheritanceCount']++ if ($Extended) { $CachedOu[$OU]['ObjectsBlockedInheritance'][$Object.DistinguishedName] = $Object } } else { $CachedOu[$OU]['ObjectsIndirectCount']++ $CachedOu[$OU]['ObjectsTotalCount']++ $CachedOu[$OU]['ObjectsClasses'][$Object.ObjectClass] = '' if ($Extended) { $CachedOu[$OU]['ObjectsTotal'][$Object.DistinguishedName] = $Object $CachedOu[$OU]['ObjectsIndirect'][$Object.DistinguishedName] = $Object } } } $CachedOu[$OU]['ObjectsTotalIncludingBlockedCount']++ if ($Extended) { $CachedOu[$OU]['ObjectsTotalIncludingBlocked'][$Object.DistinguishedName] = $Object } } } } if ($Summary) { foreach ($ObjectDistinguishedName in [string[]] $SummaryData['ObjectsBlockedInheritance'].Keys) { if ($SummaryData['ObjectsTotal'][$ObjectDistinguishedName]) { $SummaryData['ObjectsBlockedInheritance'].Remove($ObjectDistinguishedName) } } $SummaryData['ObjectsTotalCount'] = $SummaryData['ObjectsTotal'].Count $SummaryData['ObjectsBlockedInheritanceCount'] = $SummaryData['ObjectsBlockedInheritance'].Count if (-not $Extended) { $SummaryData.Remove('ObjectsTotal') $SummaryData.Remove('ObjectsBlockedInheritance') } [PSCustomObject] $SummaryData } else { if ($AsHashTable) { $CachedOu } else { $CachedOu.Values | ForEach-Object { [PSCustomObject] $_ } } } } function Get-ChoosenDates { <# .SYNOPSIS Retrieves dates based on the specified date range. .DESCRIPTION This function retrieves dates based on the specified date range. The available date ranges are: - Everything - PastHour - CurrentHour - PastDay - CurrentDay - PastMonth - CurrentMonth - PastQuarter - CurrentQuarter - Last14Days - Last21Days - Last30Days - Last7Days - Last3Days - Last1Days .PARAMETER DateRange Specifies the date range to retrieve dates for. .EXAMPLE Get-ChoosenDates -DateRange PastHour Retrieves dates for the past hour. .EXAMPLE Get-ChoosenDates -DateRange CurrentMonth Retrieves dates for the current month. #> [CmdletBinding()] param( [ValidateSet('Everything', 'PastHour', 'CurrentHour', 'PastDay', 'CurrentDay', 'PastMonth', 'CurrentMonth', 'PastQuarter', 'CurrentQuarter', 'Last14Days', 'Last21Days', 'Last30Days' , 'Last7Days', 'Last3Days', 'Last1Days')][string] $DateRange ) if ($DateRange -eq 'PastHour') { $DatesPastHour = Find-DatesPastHour if ($DatesPastHour) { $DatesPastHour } } if ($DateRange -eq 'CurrentHour') { $DatesCurrentHour = Find-DatesCurrentHour if ($DatesCurrentHour) { $DatesCurrentHour } } if ($DateRange -eq 'PastDay') { $DatesDayPrevious = Find-DatesDayPrevious if ($DatesDayPrevious) { $DatesDayPrevious } } if ($DateRange -eq 'CurrentDay') { $DatesDayToday = Find-DatesDayToday if ($DatesDayToday) { $DatesDayToday } } if ($DateRange -eq 'PastMonth') { $DatesMonthPrevious = Find-DatesMonthPast -Force $true if ($DatesMonthPrevious) { $DatesMonthPrevious } } if ($DateRange -eq 'CurrentMonth') { $DatesMonthCurrent = Find-DatesMonthCurrent if ($DatesMonthCurrent) { $DatesMonthCurrent } } if ($DateRange -eq 'PastQuarter') { $DatesQuarterLast = Find-DatesQuarterLast -Force $true if ($DatesQuarterLast) { $DatesQuarterLast } } if ($DateRange -eq 'CurrentQuarter') { $DatesQuarterCurrent = Find-DatesQuarterCurrent if ($DatesQuarterCurrent) { $DatesQuarterCurrent } } if ($DateRange -eq 'Everything') { $DatesEverything = @{ DateFrom = Get-Date -Year 1900 -Month 1 -Day 1 DateTo = Get-Date -Year 2300 -Month 1 -Day 1 } $DatesEverything } if ($DateRange -eq 'Last1days') { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 1 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } if ($DateRange -eq 'Last3days') { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 3 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } if ($DateRange -eq 'Last7days') { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 7 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } if ($DateRange -eq 'Last14days') { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 14 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } if ($DateRange -eq 'Last21days') { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 21 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } if ($DateRange -eq 'Last30Days') { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 30 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } } function Get-GitHubVersion { <# .SYNOPSIS Retrieves the latest version information from a GitHub repository and compares it with the currently installed version of a specified cmdlet. .DESCRIPTION The Get-GitHubVersion function retrieves the latest version information from a specified GitHub repository and compares it with the version of a specified cmdlet. It then provides feedback on whether an update is available or if the installed version is up to date. .PARAMETER Cmdlet The name of the cmdlet to check for updates. .PARAMETER RepositoryOwner The owner of the GitHub repository where the releases are hosted. .PARAMETER RepositoryName The name of the GitHub repository. .EXAMPLE Get-GitHubVersion -Cmdlet "MyCmdlet" -RepositoryOwner "MyRepoOwner" -RepositoryName "MyRepo" Description: Retrieves the latest version information for "MyCmdlet" from the GitHub repository owned by "MyRepoOwner" with the name "MyRepo". #> [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 Get-GPOBlockedInheritance { <# .SYNOPSIS Retrieves information about Organizational Units (OUs) with blocked inheritance of Group Policy Objects (GPOs). .DESCRIPTION The Get-GPOBlockedInheritance function retrieves information about OUs within a specified forest that have blocked inheritance of GPOs. It returns a list of OUs with their distinguished names and whether they have blocked inheritance enabled. .PARAMETER Filter Specifies a filter to select specific OUs. Default is '*'. .PARAMETER Forest Specifies the name of the forest to query. .PARAMETER ExcludeDomains Specifies an array of domain names to exclude from the query. .PARAMETER IncludeDomains Specifies an array of domain names to include in the query. .PARAMETER AsHashTable Indicates whether to return the results as a hashtable. If specified, the function returns a hashtable with OU distinguished names as keys and blocked inheritance status as values. .PARAMETER ExtendedForestInformation Specifies additional forest information to include in the query. .EXAMPLE Get-GPOBlockedInheritance -Forest 'contoso.com' Retrieves a list of all OUs within the 'contoso.com' forest with blocked inheritance status. .EXAMPLE Get-GPOBlockedInheritance -Forest 'contoso.com' -IncludeDomains 'child1.contoso.com', 'child2.contoso.com' -AsHashTable Retrieves a hashtable of OUs within the 'contoso.com' forest from specified domains with blocked inheritance status. #> [cmdletBinding()] param( [string] $Filter = '*', [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $AsHashTable, [System.Collections.IDictionary] $ExtendedForestInformation ) $OUCache = [ordered] @{} $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $OrganizationalUnits = Get-ADOrganizationalUnit -Filter $Filter -Properties gpOptions, canonicalName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0] foreach ($OU in $OrganizationalUnits) { $OUCache[$OU.DistinguishedName] = [PSCustomObject] @{ DistinguishedName = $OU.DistinguishedName BlockedInheritance = if ($OU.gpOptions -eq 1) { $true } else { $false } } } } if ($AsHashTable) { $OUCache } else { $OUCache.Values } } function Get-GPOCategories { <# .SYNOPSIS Retrieves GPO categories based on the provided GPO object and XML output. .DESCRIPTION This function retrieves GPO categories by analyzing the provided GPO object and XML output. It categorizes GPO settings based on different criteria. .PARAMETER GPO The GPO object containing information about the Group Policy Object. .PARAMETER GPOOutput An array of XML elements representing the GPO output. .PARAMETER Splitter The delimiter used to split GPO settings. .PARAMETER FullObjects A switch parameter to indicate whether to retrieve full GPO objects. .PARAMETER CachedCategories A dictionary containing cached GPO categories. .EXAMPLE Get-GPOCategories -GPO $gpoObject -GPOOutput $xmlOutput -Splitter ":" -FullObjects Description: Retrieves GPO categories for the specified GPO object and XML output, splitting settings using ":" as the delimiter and retrieving full GPO objects. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [System.Xml.XmlElement[]] $GPOOutput, [string] $Splitter, [switch] $FullObjects, [System.Collections.IDictionary] $CachedCategories ) if (-not $CachedCategories) { $CachedCategories = [ordered] @{} } $LinksInformation = Get-LinksFromXML -GPOOutput $GPOOutput -Splitter $Splitter -FullObjects:$FullObjects foreach ($GpoType in @('User', 'Computer')) { if ($GPOOutput.$GpoType.ExtensionData.Extension) { foreach ($ExtensionType in $GPOOutput.$GpoType.ExtensionData.Extension) { if ($ExtensionType) { $GPOSettingTypeSplit = ($ExtensionType.type -split ':') try { $KeysToLoop = $ExtensionType | Get-Member -MemberType Properties -ErrorAction Stop | Where-Object { $_.Name -notin 'type', $GPOSettingTypeSplit[0] -and $_.Name -notin @('Blocked') } } catch { Write-Warning "Get-XMLStandard - things went sideways $($_.Exception.Message)" continue } foreach ($GpoSettings in $KeysToLoop.Name) { $Template = [ordered] @{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.Guid GpoType = $GpoType GpoCategory = $GPOSettingTypeSplit[1] GpoSettings = $GpoSettings } $Template['Linked'] = $LinksInformation.Linked $Template['LinksCount'] = $LinksInformation.LinksCount $Template['Links'] = $LinksInformation.Links $Template['IncludeComments'] = [bool]::Parse($GPOOutput.IncludeComments) $Template['CreatedTime'] = [DateTime] $GPOOutput.CreatedTime $Template['ModifiedTime'] = [DateTime] $GPOOutput.ModifiedTime $Template['ReadTime'] = [DateTime] $GPOOutput.ReadTime $Template['SecurityDescriptor'] = $GPOOutput.SecurityDescriptor $Template['FilterDataAvailable'] = [bool]::Parse($GPOOutput.FilterDataAvailable) $Template['DataSet'] = $ExtensionType.$GpoSettings $ConvertedObject = [PSCustomObject] $Template if (-not $CachedCategories["$($Template.GpoCategory)"]) { $CachedCategories["$($Template.GpoCategory)"] = [ordered] @{} } if (-not $CachedCategories["$($Template.GpoCategory)"]["$($Template.GpoSettings)"]) { $CachedCategories["$($Template.GpoCategory)"]["$($Template.GpoSettings)"] = [System.Collections.Generic.List[PSCustomObject]]::new() } $CachedCategories["$($Template.GpoCategory)"]["$($Template.GpoSettings)"].Add($ConvertedObject) $ConvertedObject } } } } } } function Get-GPOPrivInheritance { <# .SYNOPSIS Retrieves the Group Policy Object (GPO) inheritance information for a given Active Directory object. .DESCRIPTION This function retrieves the inheritance information of Group Policy Objects (GPOs) for a specified Active Directory object. It provides details on how GPOs are linked and inherited by the object. .PARAMETER ADObject Specifies the Active Directory object for which to retrieve GPO inheritance information. .PARAMETER CacheReturnedGPOs Specifies a dictionary containing cached GPO information to optimize retrieval. .PARAMETER ForestInformation Specifies a dictionary containing information about the Active Directory forest. .PARAMETER Domain Specifies the domain for which to retrieve GPO privilege link information. .PARAMETER SkipDomainRoot Indicates whether to skip the Domain Root container. .PARAMETER SkipDomainControllers Indicates whether to skip the Domain Controllers container. .EXAMPLE Get-GPOPrivInheritance -ADObject $ADObject -CacheReturnedGPOs $Cache -ForestInformation $ForestInfo -Domain "example.com" -SkipDomainRoot -SkipDomainControllers Retrieves GPO inheritance information for the specified ADObject in the "example.com" domain, skipping the Domain Root container and Domain Controllers container. .NOTES File Name : Get-GPOPrivInheritance.ps1 Prerequisite : This function requires the Get-GPInheritance function. #> [cmdletBinding()] param( [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, [System.Collections.IDictionary] $CacheReturnedGPOs, [System.Collections.IDictionary] $ForestInformation, [string] $Domain, [switch] $SkipDomainRoot, [switch] $SkipDomainControllers ) foreach ($Object in $ADObject) { if ($SkipDomainRoot) { if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) { continue } } if ($SkipDomainControllers) { if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']) { continue } } $Inheritance = Get-GPInheritance -Target $Object.DistinguishedName foreach ($Link in $Inheritance.GpoLinks) { [PSCustomObject] @{ DisplayName = $Link.DisplayName DomainName = $Domain GUID = $Link.GPOID Enabled = $Link.Enabled Enforced = $Link.Enforced Order = $Link.Order Target = $Object.DistinguishedName TargetCanonical = $Object.CanonicalName TargetObjectClass = $Object.objectClass } } } } function Get-GPOPrivInheritanceLoop { <# .SYNOPSIS Retrieves the Group Policy Object (GPO) inheritance loop for a given Active Directory object. .DESCRIPTION This function retrieves the GPO inheritance loop for a specified Active Directory object. It analyzes the inheritance of GPOs based on the object's location in the Active Directory structure. .PARAMETER ADObject Specifies the Active Directory object for which the GPO inheritance loop needs to be determined. .PARAMETER CacheReturnedGPOs Specifies a dictionary containing cached GPO information to optimize retrieval. .PARAMETER ForestInformation Specifies a dictionary containing information about the Active Directory forest. .PARAMETER Linked Specifies the types of linked objects to consider during the inheritance analysis. Valid values are 'Root', 'DomainControllers', 'OrganizationalUnit'. .PARAMETER SearchBase Specifies the base location in Active Directory to start the search for GPO inheritance. .PARAMETER SearchScope Specifies the scope of the search in Active Directory. .PARAMETER Filter Specifies the filter to apply when searching for Active Directory objects. .EXAMPLE Get-GPOPrivInheritanceLoop -ADObject $ADObject -CacheReturnedGPOs $Cache -ForestInformation $ForestInfo -Linked 'Root' -SearchBase 'DC=contoso,DC=com' -SearchScope Subtree -Filter '(objectClass -eq "organizationalUnit")' Description: Retrieves the GPO inheritance loop for the specified Active Directory object located in the 'contoso.com' domain starting from the root. .EXAMPLE Get-GPOPrivInheritanceLoop -ADObject $ADObject -CacheReturnedGPOs $Cache -ForestInformation $ForestInfo -Linked 'DomainControllers' -SearchBase 'DC=contoso,DC=com' -SearchScope Base -Filter '(objectClass -eq "organizationalUnit")' Description: Retrieves the GPO inheritance loop for the specified Active Directory object located in the 'contoso.com' domain starting from the Domain Controllers container. #> [cmdletBinding()] param( [Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, [System.Collections.IDictionary] $CacheReturnedGPOs, [System.Collections.IDictionary] $ForestInformation, [validateset('Root', 'DomainControllers', 'OrganizationalUnit')][string[]] $Linked, [string] $SearchBase, [Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope, [string] $Filter ) if (-not $ADObject) { if ($Linked) { foreach ($Domain in $ForestInformation.Domains) { $Splat = @{ Properties = 'distinguishedName', 'gplink', 'CanonicalName' Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } if ($Linked -contains 'DomainControllers') { $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer'] $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')" $Splat['SearchBase'] = $SearchBase try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation } if ($Linked -contains 'Root') { $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] $Splat['Filter'] = "objectClass -eq 'domainDNS'" $Splat['SearchBase'] = $SearchBase try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation } if ($Linked -contains 'Site') { } if ($Linked -contains 'OrganizationalUnit') { $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')" $Splat['SearchBase'] = $SearchBase try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -SkipDomainRoot -SkipDomainControllers } } } elseif ($Filter) { foreach ($Domain in $ForestInformation.Domains) { $Splat = @{ Filter = $Filter Properties = 'distinguishedName', 'gplink', 'CanonicalName' Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } if ($PSBoundParameters.ContainsKey('SearchBase')) { $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] $SearchBaseDC = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC if ($SearchBaseDC -ne $DomainDistinguishedName) { continue } $Splat['SearchBase'] = $SearchBase } if ($PSBoundParameters.ContainsKey('SearchScope')) { $Splat['SearchScope'] = $SearchScope } try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation } } } else { Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObject -Domain '' -ForestInformation $ForestInformation } } function Get-GPOPrivLink { <# .SYNOPSIS Retrieves the GPO (Group Policy Object) privilege link information for specified Active Directory objects. .DESCRIPTION This function retrieves the GPO privilege link information for the specified Active Directory objects. It allows skipping certain default containers like Domain Root and Domain Controllers if needed. It also provides options to cache returned GPOs and skip duplicates. .PARAMETER ADObject Specifies the Active Directory objects for which to retrieve GPO privilege link information. .PARAMETER CacheReturnedGPOs Specifies a dictionary to cache returned GPOs for optimization. .PARAMETER ForestInformation Specifies a dictionary containing forest information. .PARAMETER Domain Specifies the domain for which to retrieve GPO privilege link information. .PARAMETER SkipDomainRoot Indicates whether to skip the Domain Root container. .PARAMETER SkipDomainControllers Indicates whether to skip the Domain Controllers container. .PARAMETER AsHashTable Specifies whether to output the GPO information as a hash table. .PARAMETER SkipDuplicates Indicates whether to skip duplicate GPOs. .EXAMPLE Get-GPOPrivLink -ADObject $ADObject -CacheReturnedGPOs $Cache -ForestInformation $ForestInfo -Domain "example.com" -SkipDomainRoot -SkipDuplicates Retrieves GPO privilege link information for the specified ADObject in the "example.com" domain, skipping the Domain Root container and duplicates. .NOTES File Name : Get-GPOPrivLink.ps1 Prerequisite : This function requires the Get-PrivGPOZaurrLink function. #> [cmdletBinding()] param( [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, [System.Collections.IDictionary] $CacheReturnedGPOs, [System.Collections.IDictionary] $ForestInformation, [string] $Domain, [switch] $SkipDomainRoot, [switch] $SkipDomainControllers, [switch] $AsHashTable, [switch] $SkipDuplicates ) foreach ($Object in $ADObject) { if ($SkipDomainRoot) { if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) { continue } } if ($SkipDomainControllers) { if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']) { continue } } $OutputGPOs = Get-PrivGPOZaurrLink -Object $Object -Limited:$Limited.IsPresent -GPOCache $GPOCache foreach ($OutputGPO in $OutputGPOs) { if (-not $SkipDuplicates) { $OutputGPO } else { $UniqueGuid = -join ($OutputGPO.DomainName, $OutputGPO.Guid) if (-not $CacheReturnedGPOs[$UniqueGuid]) { $CacheReturnedGPOs[$UniqueGuid] = $OutputGPO $OutputGPO } } } } } function Get-GPOZaurrLinkInheritance { <# .SYNOPSIS Retrieves the Group Policy Object (GPO) inheritance information for a given Active Directory object. .DESCRIPTION This function retrieves the inheritance information of Group Policy Objects (GPOs) for a specified Active Directory object. It provides details on how GPOs are linked and inherited by the object. .PARAMETER ADObject Specifies the Active Directory object for which to retrieve GPO inheritance information. .PARAMETER Filter Specifies the filter criteria for selecting the types of objects to include in the search. Default value includes 'organizationalUnit', 'domainDNS', and 'site' objects. .PARAMETER SearchBase Specifies the base distinguished name (DN) for the search operation. .PARAMETER SearchScope Specifies the scope of the search operation within Active Directory. .PARAMETER Linked Specifies the type of objects to include in the search. Valid values are 'Root', 'DomainControllers', and 'OrganizationalUnit'. .PARAMETER Limited Indicates whether to limit the search results. If specified, only a limited set of results will be returned. .PARAMETER SkipDuplicates Indicates whether to skip duplicate entries in the search results. .PARAMETER GPOCache Specifies a cache of Group Policy Objects to optimize performance. .PARAMETER Forest Specifies the target forest to search for GPO inheritance information. By default, the current forest is used. .PARAMETER ExcludeDomains Specifies the domains to exclude from the search operation. By default, the entire forest is scanned. .PARAMETER IncludeDomains Specifies the specific domains to include in the search operation. By default, the entire forest is scanned. .PARAMETER ExtendedForestInformation Specifies additional information about the forest to include in the search results. .PARAMETER AsHashTable Indicates whether to return the results as a hash table. .PARAMETER Summary Indicates whether to provide a summary of the GPO inheritance information. .EXAMPLE $Output = Get-GPOZaurrLinkInheritance -Summary $Output | Format-Table $Output[5] $Output[5].Links | Format-Table $Output[5].LinksObjects | Format-Table .NOTES This function is an improved version of Get-GPInheritance and provides better support for sites. It is recommended for retrieving GPO inheritance information. #> [cmdletbinding(DefaultParameterSetName = 'All')] param( [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, # weirdly enough site doesn't really work this way unless you give it 'CN=Configuration,DC=ad,DC=evotec,DC=xyz' as SearchBase [parameter(ParameterSetName = 'Filter')][string] $Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')", [parameter(ParameterSetName = 'Filter')][string] $SearchBase, [parameter(ParameterSetName = 'Filter')][Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope, [parameter(ParameterSetName = 'Linked', Mandatory)][validateset('Root', 'DomainControllers', 'OrganizationalUnit')][string[]] $Linked, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $Limited, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $SkipDuplicates, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [System.Collections.IDictionary] $GPOCache, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [alias('ForestName')][string] $Forest, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [string[]] $ExcludeDomains, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [alias('Domain', 'Domains')][string[]] $IncludeDomains, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [System.Collections.IDictionary] $ExtendedForestInformation, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $AsHashTable, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $Summary ) Begin { $CacheReturnedGPOs = [ordered] @{} $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation if (-not $GPOCache -and -not $Limited) { $GPOCache = @{ } foreach ($Domain in $ForestInformation.Forest.Domains) { if ($ForestInformation['QueryServers'][$Domain]) { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] Get-GPO -All -DomainName $Domain -Server $QueryServer | ForEach-Object { $GPOCache["$Domain$($_.ID.Guid)"] = $_ } } else { Write-Warning -Message "Get-GPOZaurrLinkInheritance - Couldn't get query server for $Domain. Skipped." } } } } Process { if (-not $Filter -and -not $Linked) { } $getGPOPrivInheritanceLoopSplat = @{ Linked = $Linked ForestInformation = $ForestInformation CacheReturnedGPOs = $CacheReturnedGPOs SearchScope = $SearchScope SearchBase = $SearchBase ADObject = $ADObject Filter = $Filter } Remove-EmptyValue -Hashtable $getGPOPrivInheritanceLoopSplat -Recursive if ($AsHashTable -or $Summary) { $HashTable = [ordered] @{} $SummaryHashtable = [ordered] @{} $Links = Get-GPOPrivInheritanceLoop @getGPOPrivInheritanceLoopSplat foreach ($Link in $Links) { $Key = -join ($Link.DomainName, $Link.GUID) if (-not $HashTable[$Key]) { $HashTable[$Key] = [System.Collections.Generic.List[PSCustomObject]]::new() } $HashTable[$Key].Add($Link) } foreach ($Key in $HashTable.Keys) { [Array] $Link = $HashTable[$Key] $EnabledLinks = $Link.Enabled.Where( { $_ -eq $true }, 'split') if ($EnabledLinks[0].Count -gt 0) { $IsLinked = $true } else { $IsLinked = $false } $SummaryLink = [PSCustomObject] @{ DisplayName = $Link[0].DisplayName DomainName = $Link[0].DomainName GUID = $Link[0].GUID Linked = $IsLinked LinksCount = $Link.Count LinksEnabledCount = $EnabledLinks[0].Count LinksDisabledCount = $EnabledLinks[1].Count Links = $Link.Target LinksObjects = $Link } $SummaryHashtable[$Key] = $SummaryLink } if ($AsHashTable -and $Summary) { $SummaryHashtable } elseif ($AsHashTable) { $HashTable } elseif ($Summary) { $SummaryHashtable.Values } } else { Get-GPOPrivInheritanceLoop @getGPOPrivInheritanceLoopSplat } } End { } } function Get-GPOZaurrLinkLoop { <# .SYNOPSIS Retrieves Group Policy Object (GPO) links for Active Directory objects based on specified criteria. .DESCRIPTION This function retrieves GPO links for Active Directory objects based on the provided parameters. It allows searching for GPO links within specific sites, domains, or organizational units. The function can handle duplicates and provides flexibility in defining search criteria. .PARAMETER ADObject Specifies the Active Directory objects for which GPO links will be retrieved. .PARAMETER CacheReturnedGPOs A dictionary cache of returned Group Policy Objects for efficient processing. .PARAMETER ForestInformation Information about the forest structure and domains for targeted searches. .PARAMETER Linked Specifies the type of objects to search for GPO links. Valid values are 'All', 'Root', 'DomainControllers', 'Site', or 'OrganizationalUnit'. .PARAMETER SearchBase Specifies the base location for the search within Active Directory. .PARAMETER SearchScope Specifies the scope of the search within Active Directory. .PARAMETER Filter Specifies additional filters to apply during the search. .PARAMETER SkipDuplicates Indicates whether to skip duplicate GPO links during processing. .PARAMETER Site Specifies the site(s) to search for GPO links. .EXAMPLE Get-GPOZaurrLinkLoop -Site 'SiteA', 'SiteB' -ForestInformation $ForestInfo Description: Retrieves GPO links for sites 'SiteA' and 'SiteB' within the forest using the provided forest information. .EXAMPLE Get-GPOZaurrLinkLoop -SearchBase 'OU=Users,DC=contoso,DC=com' -SearchScope Subtree -Filter '(objectClass -eq "user")' Description: Retrieves GPO links for all user objects within the specified organizational unit 'Users' in the 'contoso.com' domain. #> [cmdletBinding()] param( [Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, [System.Collections.IDictionary] $CacheReturnedGPOs, [System.Collections.IDictionary] $ForestInformation, [validateset('All', 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string[]] $Linked, [string] $SearchBase, [Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope, [string] $Filter, [switch] $SkipDuplicates, [string[]] $Site ) if (-not $ADObject) { if ($Site) { foreach ($S in $Site) { foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-GPOZaurrLink - Getting GPO links for site $Site" if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) { $Splat = @{ Properties = 'distinguishedName', 'gplink', 'CanonicalName' Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } $Splat['Filter'] = "(objectClass -eq 'site') -and (name -eq '$S')" $Splat['SearchBase'] = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } if ($ADObjectGPO) { Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates } } } } } elseif ($SearchBase -or $SearchScope -or $Filter) { foreach ($Domain in $ForestInformation.Domains) { if (-not $Filter) { $Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')" } $Splat = @{ Filter = $Filter Properties = 'distinguishedName', 'gplink', 'CanonicalName' Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } if ($PSBoundParameters.ContainsKey('SearchBase')) { $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] $SearchBaseDC = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC if ($SearchBaseDC -ne $DomainDistinguishedName) { continue } $Splat['SearchBase'] = $SearchBase } if ($PSBoundParameters.ContainsKey('SearchScope')) { $Splat['SearchScope'] = $SearchScope } try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } if ($ADObjectGPO) { Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates } } } elseif (-not $Filter) { if (-not $Linked) { $Linked = 'All' } foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain" $Splat = @{ Properties = 'distinguishedName', 'gplink', 'CanonicalName' Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } if ($Linked -contains 'Root' -or $Linked -contains 'All') { Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at ROOT level" $Splat['Filter'] = "objectClass -eq 'domainDNS'" $Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } if ($ADObjectGPO) { Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates } } if ($Linked -contains 'Site' -or $Linked -contains 'All') { Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at SITE level" if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) { $Splat['Filter'] = "(objectClass -eq 'site')" $Splat['SearchBase'] = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } if ($ADObjectGPO) { Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates } } } if ($Linked -contains 'DomainControllers' -or $Linked -contains 'All') { Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at DC level" $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')" $Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer'] try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } if ($ADObjectGPO) { Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates } } if ($Linked -contains 'OrganizationalUnit' -or $Linked -contains 'All') { Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at OU level" $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')" $Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] try { $ADObjectGPO = Get-ADObject @Splat } catch { Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" } if ($ADObjectGPO) { Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -SkipDomainRoot -SkipDomainControllers -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates } } } } } else { Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObject -Domain '' -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates } } function Get-LinksFromXML { <# .SYNOPSIS Retrieves links from XML data. .DESCRIPTION This function retrieves links from XML data provided as input. It processes the XML data to extract relevant information about the links. .PARAMETER GPOOutput Specifies the XML data containing information about the links. .PARAMETER Splitter Specifies the delimiter to use when joining multiple links. .PARAMETER FullObjects Indicates whether to return full objects with additional properties for each link. .EXAMPLE Get-LinksFromXML -GPOOutput $xmlData -Splitter ";" -FullObjects Retrieves links from the XML data $xmlData, separates them with a semicolon, and returns full objects for each link. .EXAMPLE Get-LinksFromXML -GPOOutput $xmlData -Splitter "/" Retrieves links from the XML data $xmlData and joins them with a forward slash. #> [cmdletBinding()] param( [System.Xml.XmlElement[]] $GPOOutput, [string] $Splitter, [switch] $FullObjects ) $Links = [ordered] @{ Linked = $null LinksCount = $null Links = $null } if ($GPOOutput.LinksTo) { $Links.Linked = $true $Links.LinksCount = ([Array] $GPOOutput.LinksTo).Count $Links.Links = foreach ($Link in $GPOOutput.LinksTo) { if ($FullObjects) { [PSCustomObject] @{ Path = $Link.SOMPath Enabled = if ($Link.Enabled -eq 'true') { $true } else { $false } NoOverride = if ($Link.NoOverride -eq 'true') { $true } else { $false } } } else { if ($Link.Enabled) { $Link.SOMPath } } } if ($Splitter) { $Links.Links = $Links.Links -join $Splitter } } else { $Links.Linked = $false $Links.LinksCount = 0 $Links.Links = $null } [PSCustomObject] $Links } function Get-PermissionsAnalysis { <# .SYNOPSIS Analyzes permissions for a specified Group Policy Object (GPO) based on provided criteria. .DESCRIPTION This function analyzes permissions for a specified Group Policy Object (GPO) based on the given criteria. It checks for specific administrative groups, standard users, and well-known administrative groups to determine the type of permissions assigned. .PARAMETER GPOPermissions An array of GPO permissions to analyze. .PARAMETER Type Specifies the type of permissions to analyze. Valid values are 'WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', or 'Default'. .PARAMETER PermissionType The specific permission type to include in the analysis. .PARAMETER Forest Target different Forest, by default current forest is used. .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned. .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned. .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing. .PARAMETER ADAdministrativeGroups Specifies the Active Directory administrative groups to consider. .EXAMPLE Get-PermissionsAnalysis -GPOPermissions $GPOPermissions -Type 'Administrative' -PermissionType 'Read' -Forest 'Contoso' -IncludeDomains 'DomainA', 'DomainB' Description: Analyzes permissions for the specified GPOPermissions array, focusing on administrative groups with 'Read' permission in the 'Contoso' forest for 'DomainA' and 'DomainB'. .EXAMPLE Get-PermissionsAnalysis -GPOPermissions $GPOPermissions -Type 'WellKnownAdministrative' -PermissionType 'Write' Description: Analyzes permissions for the specified GPOPermissions array, targeting well-known administrative groups with 'Write' permission. #> [cmdletBinding()] param( [PSCustomObject] $GPOPermissions, [validateset('WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', 'Default')][string] $Type = 'Default', [Parameter(Mandatory)][alias('IncludePermissionType')][Microsoft.GroupPolicy.GPPermissionType] $PermissionType, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [System.Collections.IDictionary] $ADAdministrativeGroups ) if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } $AdministrativeExists = [ordered] @{ DisplayName = $GPOPermissions[0].DisplayName DomainName = $GPOPermissions[0].DomainName GUID = $GPOPermissions[0].GUID Skip = $false DomainAdmins = $false EnterpriseAdmins = $false } if ($GPOPermissions.GPOSecurityPermissionItem) { foreach ($GPOPermission in $GPOPermissions) { if ($Type -eq 'Default') { $AdministrativeExists['Skip'] = $true break } elseif ($Type -eq 'Administrative') { if ($GPOPermission.Permission -eq $PermissionType) { $AdministrativeGroup = $ADAdministrativeGroups['BySID'][$GPOPermission.PrincipalSid] if ($AdministrativeGroup.SID -like '*-519') { $AdministrativeExists['EnterpriseAdmins'] = $true } elseif ($AdministrativeGroup.SID -like '*-512') { $AdministrativeExists['DomainAdmins'] = $true } } if ($AdministrativeExists['DomainAdmins'] -and $AdministrativeExists['EnterpriseAdmins']) { $AdministrativeExists['Skip'] = $true break } } elseif ($Type -eq 'WellKnownAdministrative') { $AdministrativeExists['Skip'] = $true break } elseif ($Type -eq 'AuthenticatedUsers') { $AdministrativeExists['Skip'] = $true break } } } [PSCustomObject] $AdministrativeExists } function Get-PrivGPOZaurrLink { <# .SYNOPSIS Retrieves and processes Group Policy Object (GPO) links associated with a given Active Directory object. .DESCRIPTION This function retrieves and processes the GPO links associated with a specified Active Directory object. It parses the GPO links to extract relevant information such as distinguished name, canonical name, GUID, enforcement status, and enablement status. .PARAMETER Object The Active Directory object for which GPO links will be retrieved and processed. .PARAMETER Limited Indicates whether to provide limited information about the GPO links. .PARAMETER GPOCache A dictionary cache of Group Policy Objects for efficient processing. .EXAMPLE Get-PrivGPOZaurrLink -Object $ADObject Description: Retrieves and processes the GPO links associated with the specified Active Directory object. .EXAMPLE Get-PrivGPOZaurrLink -Object $ADObject -Limited Description: Retrieves limited information about the GPO links associated with the specified Active Directory object. #> [cmdletBinding()] param( [Microsoft.ActiveDirectory.Management.ADObject] $Object, [switch] $Limited, [System.Collections.IDictionary] $GPOCache ) if ($Object.GpLink -and $Object.GpLink.Trim() -ne '') { $ObjectsToProcess = $Object.GpLink -split '\]\[' } elseif ($Object.LinkedGroupPolicyObjects -and $Object.LinkedGroupPolicyObjects.Trim() -ne '') { $ObjectsToProcess = $Object.LinkedGroupPolicyObjects -split '\]\[' } else { $ObjectsToProcess = $null } $ObjectsToProcess | ForEach-Object -Process { $Link = $_ -replace 'LDAP://' -replace '\]' -replace '\[' if ($Link.Length -gt 10) { $SplitGPLink = $Link -split ';' $DN = $SplitGPLink[0] $Option = $SplitGPLink[1] if ($Option -eq '0') { $Enforced = $false $Enabled = $true } elseif ($Option -eq '1') { $Enabled = $false $Enforced = $false } elseif ($Option -eq '2') { $Enabled = $true $Enforced = $true } elseif ($Option -eq '3') { $Enabled = $false $Enforced = $true } else { if ($Object.GpLink) { Write-Warning "Get-PrivGPOZaurrLink - This should't happen. Please investigate - Option: $Option" } else { Write-Warning "Get-PrivGPOZaurrLink - Property GPLink is required to be able to tell if Enabled/Enforced is added. Skipping those settings." } $Enabled = $null $Enforced = $null } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDomainCN $Output = [ordered] @{ DistinguishedName = $Object.DistinguishedName CanonicalName = if ($Object.CanonicalName) { $Object.CanonicalName.TrimEnd('/') } else { $Object.CanonicalName } Guid = [Regex]::Match($DN, '(?={)(.*)(?<=})').Value -replace '{' -replace '}' Enforced = $Enforced Enabled = $Enabled ObjectClass = $Object.ObjectClass } $Search = -join ($DomainCN, $Output['Guid']) if ($GPOCache -and -not $Limited) { if ($GPOCache[$Search]) { $Output['DisplayName'] = $GPOCache[$Search].DisplayName $Output['DomainName'] = $GPOCache[$Search].DomainName $Output['Owner'] = $GPOCache[$Search].Owner $Output['GpoStatus'] = $GPOCache[$Search].GpoStatus $Output['Description'] = $GPOCache[$Search].Description $Output['CreationTime'] = $GPOCache[$Search].CreationTime $Output['ModificationTime'] = $GPOCache[$Search].ModificationTime $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDC $Output['GPODistinguishedName'] = $DN $Output['Name'] = $Object.Name [PSCustomObject] $Output } else { Write-Warning "Get-PrivGPOZaurrLink - Couldn't find link $Search in a GPO Cache. Lack of permissions for given GPO? Are you running as admin? Skipping." } } else { $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDC $Output['GPODistinguishedName'] = $DN [PSCustomObject] $Output } } } } function Get-PrivPermission { <# .SYNOPSIS Retrieves permissions for a specified Group Policy Object (GPO) based on various criteria. .DESCRIPTION This function retrieves permissions for a specified Group Policy Object (GPO) based on the provided parameters. It allows filtering by principal, permission type, and other criteria. .PARAMETER GPO Specifies the Group Policy Object (GPO) for which permissions will be retrieved. .PARAMETER SecurityRights Specifies the security rights to be evaluated for the GPO. .PARAMETER Principal Specifies the principal for which permissions will be retrieved. .PARAMETER PrincipalType Specifies the type of principal to filter by. Valid values are 'DistinguishedName', 'Name', 'NetbiosName', or 'Sid'. .PARAMETER SkipWellKnown Skips well-known principals when evaluating permissions. .PARAMETER SkipAdministrative Skips administrative principals when evaluating permissions. .PARAMETER IncludeOwner Includes the owner of the GPO in the permission results. .PARAMETER IncludePermissionType Specifies the permission types to include in the results. .PARAMETER ExcludePermissionType Specifies the permission types to exclude from the results. .PARAMETER PermitType Specifies the type of permissions to include. Valid values are 'Allow', 'Deny', or 'All'. .PARAMETER ExcludePrincipal Specifies principals to exclude from the results. .PARAMETER ExcludePrincipalType Specifies the type of principal to exclude. Valid values are 'DistinguishedName', 'Name', or 'Sid'. .PARAMETER IncludeGPOObject Includes the GPO object in the permission results. .PARAMETER ADAdministrativeGroups Specifies the Active Directory administrative groups to consider. .PARAMETER Type Specifies the type of principals to include. Valid values are 'AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', or 'All'. .PARAMETER ExtendedForestInformation Specifies extended forest information to be used in evaluation. .EXAMPLE Get-PrivPermission -GPO $GPO -SecurityRights $SecurityRights -Principal 'Domain Admins' -PrincipalType 'Name' -PermitType 'Allow' Retrieves permissions for the specified GPO where 'Domain Admins' have 'Allow' permissions. .EXAMPLE Get-PrivPermission -GPO $GPO -SecurityRights $SecurityRights -Principal 'S-1-5-21-3623811015-3361044348-30300820-1013' -PrincipalType 'Sid' -PermitType 'Deny' Retrieves permissions for the specified GPO where the principal with the SID 'S-1-5-21-3623811015-3361044348-30300820-1013' has 'Deny' permissions. #> [cmdletBinding()] param( [Microsoft.GroupPolicy.Gpo] $GPO, [Object] $SecurityRights, [string[]] $Principal, [validateset('DistinguishedName', 'Name', 'NetbiosName', 'Sid')][string] $PrincipalType = 'Sid', [switch] $SkipWellKnown, [switch] $SkipAdministrative, [switch] $IncludeOwner, [Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType, [Microsoft.GroupPolicy.GPPermissionType[]] $ExcludePermissionType, [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'All', [string[]] $ExcludePrincipal, [validateset('DistinguishedName', 'Name', 'Sid')][string] $ExcludePrincipalType = 'Sid', [switch] $IncludeGPOObject, [System.Collections.IDictionary] $ADAdministrativeGroups, [validateSet('AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', 'All')][string[]] $Type = 'All', #[System.Collections.IDictionary] $Accounts, [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { Write-Verbose "Get-PrivPermission - Processing $($GPO.DisplayName) from $($GPO.DomainName)" } Process { $SecurityRights | ForEach-Object -Process { $GPOPermission = $_ if ($PermitType -ne 'All') { if ($PermitType -eq 'Deny') { if ($GPOPermission.Denied -eq $false) { return } } else { if ($GPOPermission.Denied -eq $true) { return } } } if ($ExcludePermissionType -contains $GPOPermission.Permission) { return } if ($IncludePermissionType) { if ($IncludePermissionType -notcontains $GPOPermission.Permission) { if ($IncludePermissionType -eq 'GpoRead' -and $GPOPermission.Permission -eq 'GpoApply') { } else { return } } } if ($SkipWellKnown.IsPresent -or $Type -contains 'NotWellKnown') { if ($GPOPermission.Trustee.SidType -eq 'WellKnownGroup') { return } } if ($SkipAdministrative.IsPresent -or $Type -contains 'NotAdministrative') { $IsAdministrative = $ADAdministrativeGroups['BySID'][$GPOPermission.Trustee.Sid.Value] if ($IsAdministrative) { return } } if ($Type -contains 'Administrative' -and $Type -notcontains 'All') { $IsAdministrative = $ADAdministrativeGroups['BySID'][$GPOPermission.Trustee.Sid.Value] if (-not $IsAdministrative) { return } } if ($Type -contains 'NotWellKnownAdministrative' -and $Type -notcontains 'All') { if ($GPOPermission.Trustee.Sid -eq 'S-1-5-18') { return } } if ($Type -contains 'WellKnownAdministrative' -and $Type -notcontains 'All') { if ($GPOPermission.Trustee.Sid -ne 'S-1-5-18') { return } } if ($Type -contains 'Unknown' -and $Type -notcontains 'All') { if ($GPOPermission.Trustee.SidType -ne 'Unknown') { return } } if ($Type -contains 'AuthenticatedUsers' -and $Type -notcontains 'All') { if ($GPOPermission.Trustee.Sid -ne 'S-1-5-11') { return } } if ($Type -contains 'DomainComputers' -and $Type -notcontains 'All') { $DomainComputersSID = -join ($ExtendedForestInformation['DomainsExtended'][$GPO.DomainName].DomainSID, '-515') if ($GPOPermission.Trustee.Sid -ne $DomainComputersSID) { return } } if ($GPOPermission.Trustee.Domain) { $UserMerge = -join ($GPOPermission.Trustee.Domain, '\', $GPOPermission.Trustee.Name) } else { $UserMerge = $null } if ($Principal) { if ($PrincipalType -eq 'Sid') { if ($Principal -notcontains $GPOPermission.Trustee.Sid.Value) { return } } elseif ($PrincipalType -eq 'DistinguishedName') { if ($Principal -notcontains $GPOPermission.Trustee.DSPath) { return } } elseif ($PrincipalType -eq 'Name') { if ($Principal -notcontains $GPOPermission.Trustee.Name) { return } } elseif ($PrincipalType -eq 'NetbiosName') { if ($Principal -notcontains $UserMerge) { return } } } if ($ExcludePrincipal) { if ($ExcludePrincipalType -eq 'Sid') { if ($ExcludePrincipal -contains $GPOPermission.Trustee.Sid.Value) { return } } elseif ($ExcludePrincipalType -eq 'DistinguishedName') { if ($ExcludePrincipal -contains $GPOPermission.Trustee.DSPath) { return } } elseif ($ExcludePrincipalType -eq 'Name') { if ($ExcludePrincipal -contains $GPOPermission.Trustee.Name) { return } } elseif ($ExcludePrincipalType -eq 'NetbiosName') { if ($ExcludePrincipal -contains $UserMerge) { return } } } $PermissionAccount = Get-WinADObject -Identity $GPOPermission.Trustee.Sid.Value -AddType -Cache -Verbose:$false if ($PermissionAccount) { $UserNameDomain = $PermissionAccount.DomainName $UserName = $PermissionAccount.Name $SidType = $PermissionAccount.Type $ObjectClass = $PermissionAccount.ObjectClass } else { $ConvertFromSID = ConvertFrom-SID -SID $GPOPermission.Trustee.Sid.Value $UserNameDomain = '' $Username = $ConvertFromSID.Name $SidType = $ConvertFromSID.Type if ($SidType -eq 'Unknown') { $ObjectClass = 'unknown' } else { $ObjectClass = 'foreignSecurityPrincipal' } } $ReturnObject = [ordered] @{ DisplayName = $GPO.DisplayName GUID = $GPO.ID DomainName = $GPO.DomainName Enabled = $GPO.GpoStatus Description = $GPO.Description CreationDate = $GPO.CreationTime ModificationTime = $GPO.ModificationTime PermissionType = if ($GPOPermission.Denied -eq $true) { 'Deny' } else { 'Allow' } Permission = $GPOPermission.Permission Inherited = $GPOPermission.Inherited PrincipalNetBiosName = $UserMerge PrincipalDistinguishedName = $GPOPermission.Trustee.DSPath PrincipalDomainName = $UserNameDomain PrincipalName = $UserName PrincipalSid = $GPOPermission.Trustee.Sid.Value PrincipalSidType = $SidType PrincipalObjectClass = $ObjectClass } if ($IncludeGPOObject) { $ReturnObject['GPOObject'] = $GPO $ReturnObject['GPOSecurity'] = $SecurityRights $ReturnObject['GPOSecurityPermissionItem'] = $GPOPermission } [PSCustomObject] $ReturnObject } if ($IncludeOwner) { if ($GPO.Owner) { $OwnerAccount = Get-WinADObject -Identity $GPO.Owner -AddType -Cache -Verbose:$false if ($OwnerAccount) { $UserNameDomain = $OwnerAccount.DomainName $UserName = $OwnerAccount.Name $SidType = $OwnerAccount.Type $OwnerObjectClass = $OwnerAccount.ObjectClass $SID = $OwnerAccount.ObjectSID } else { $ConvertFromSID = ConvertFrom-SID -SID $GPO.Owner $UserNameDomain = '' $Username = $ConvertFromSID.Name $SidType = $ConvertFromSID.Type if ($SidType -eq 'Unknown') { $OwnerObjectClass = 'unknown' } else { $OwnerObjectClass = 'foreignSecurityPrincipal' } $SID = $ConvertFromSID.SID } } else { $UserName = '' $UserNameDomain = '' $SID = '' $SIDType = 'Unknown' $DistinguishedName = '' $OwnerObjectClass = 'unknown' } if ($Type -contains 'Administrative' -and $Type -notcontains 'All') { if ($SID) { $IsAdministrative = $ADAdministrativeGroups['BySID'][$SID] if (-not $IsAdministrative) { return } } else { return } } if ($Type -contains 'NotWellKnownAdministrative' -and $Type -notcontains 'All') { if ($SID -eq 'S-1-5-18') { return } } if ($Type -contains 'WellKnownAdministrative' -and $Type -notcontains 'All') { if ($SID -ne 'S-1-5-18') { return } } if ($Type -contains 'Unknown' -and $Type -notcontains 'All') { if ($SidType -ne 'Unknown') { return } } if ($Type -contains 'AuthenticatedUsers' -and $Type -notcontains 'All') { if ($SID -ne 'S-1-5-11') { return } } if ($Type -contains 'DomainComputers' -and $Type -notcontains 'All') { $DomainComputersSID = -join ($ExtendedForestInformation['DomainsExtended'][$GPO.DomainName].DomainSID, '-515') if ($SID -ne $DomainComputersSID) { return } } if ($Principal) { if ($PrincipalType -eq 'Sid') { if ($Principal -notcontains $SID) { return } } elseif ($PrincipalType -eq 'DistinguishedName') { if ($Principal -notcontains $DistinguishedName) { return } } elseif ($PrincipalType -eq 'Name') { if ($Principal -notcontains $UserName) { return } } elseif ($PrincipalType -eq 'NetbiosName') { if ($Principal -notcontains $GPO.Owner) { return } } } if ($ExcludePrincipal) { if ($ExcludePrincipalType -eq 'Sid') { if ($ExcludePrincipal -contains $SID) { return } } elseif ($ExcludePrincipalType -eq 'DistinguishedName') { if ($ExcludePrincipal -contains $DistinguishedName) { return } } elseif ($ExcludePrincipalType -eq 'Name') { if ($ExcludePrincipal -contains $UserName) { return } } elseif ($ExcludePrincipalType -eq 'NetbiosName') { if ($ExcludePrincipal -contains $GPO.Owner) { return } } } $ReturnObject = [ordered] @{ DisplayName = $GPO.DisplayName GUID = $GPO.Id DomainName = $GPO.DomainName Enabled = $GPO.GpoStatus Description = $GPO.Description CreationDate = $GPO.CreationTime ModificationTime = $GPO.ModificationTime PermissionType = 'Allow' Permission = 'GpoOwner' Inherited = $false PrincipalNetBiosName = $GPO.Owner PrincipalDistinguishedName = $DistinguishedName PrincipalDomainName = $UserNameDomain PrincipalName = $UserName PrincipalSid = $SID PrincipalSidType = $SIDType PrincipalObjectClass = $OwnerObjectClass } if ($IncludeGPOObject) { $ReturnObject['GPOObject'] = $GPO $ReturnObject['GPOSecurity'] = $SecurityRights $ReturnObject['GPOSecurityPermissionItem'] = $null } [PSCustomObject] $ReturnObject } } End { } } function Get-WellKnownFolders { <# .SYNOPSIS Gets users and computers well known folders for a forest .DESCRIPTION Gets users and computers well known folders for a forest .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE Get-WellKnownFolders .NOTES General notes #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended foreach ($Domain in $ForestInformation.Domains) { $ForestInformation.DomainsExtended[$Domain].ComputersContainer $ForestInformation.DomainsExtended[$Domain].UsersContainer } } function Get-XMLGPO { <# .SYNOPSIS Retrieves information from an XML representation of a Group Policy Object (GPO). .DESCRIPTION This function retrieves various details from an XML representation of a GPO, such as GPO name, domain name, links information, etc. .PARAMETER XMLContent The XML content representing the GPO. .PARAMETER GPO The Microsoft.GroupPolicy.Gpo object representing the GPO. .PARAMETER PermissionsOnly Indicates whether to retrieve only permissions information. .PARAMETER OwnerOnly Indicates whether to retrieve only owner information. .PARAMETER ADAdministrativeGroups A dictionary of Active Directory administrative groups. .PARAMETER Splitter The string used to split values in the output. .PARAMETER ExcludeGroupPolicies A dictionary of group policies to exclude. .PARAMETER Type An array of types to filter the output. .PARAMETER LinksSummaryCache A cache of links summary information. .EXAMPLE Get-XMLGPO -XMLContent $xml -GPO $gpo Description: Retrieves information from the XML content of a specific GPO. .EXAMPLE Get-XMLGPO -XMLContent $xml -GPO $gpo -PermissionsOnly Description: Retrieves only the permissions information from the XML content of a specific GPO. #> [cmdletBinding()] param( [XML] $XMLContent, [Microsoft.GroupPolicy.Gpo] $GPO, [switch] $PermissionsOnly, [switch] $OwnerOnly, [System.Collections.IDictionary] $ADAdministrativeGroups, [string] $Splitter = [System.Environment]::NewLine, [System.Collections.IDictionary] $ExcludeGroupPolicies, [string[]] $Type, [System.Collections.IDictionary] $LinksSummaryCache ) $SysvolGpoPath = "\\$($GPO.DomainName)\SYSVOL\$($GPO.DomainName)\Policies\{$($GPO.ID)}" $DisplayName = $XMLContent.GPO.Name $DomainName = $XMLContent.GPO.Identifier.Domain.'#text' if ($LinksSummaryCache) { $SearchGUID = -join ($XMLContent.GPO.Identifier.Domain.'#text', $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}') if ($LinksSummaryCache[$SearchGUID]) { $Linked = $LinksSummaryCache[$SearchGUID].Linked $LinksEnabledCount = $LinksSummaryCache[$SearchGUID].LinksEnabledCount $LinksDisabledCount = $LinksSummaryCache[$SearchGUID].LinksDisabledCount $LinksTotalCount = $LinksSummaryCache[$SearchGUID].LinksCount $Links = $LinksSummaryCache[$SearchGUID].Links $LinksObjects = $LinksSummaryCache[$SearchGUID].LinksObjects } else { $Linked = $false $LinksEnabledCount = 0 $LinksDisabledCount = 0 $LinksTotalCount = 0 $Links = $null $LinksObjects = $null } } else { if ($XMLContent.GPO.LinksTo) { $LinkSplit = ([Array] $XMLContent.GPO.LinksTo).Where( { $_.Enabled -eq $true }, 'Split') [Array] $LinksEnabled = $LinkSplit[0] [Array] $LinksDisabled = $LinkSplit[1] $LinksEnabledCount = $LinksEnabled.Count $LinksDisabledCount = $LinksDisabled.Count $LinksTotalCount = ([Array] $XMLContent.GPO.LinksTo).Count if ($LinksEnabledCount -eq 0) { $Linked = $false } else { $Linked = $true } $Links = @( $XMLContent.GPO.LinksTo | ForEach-Object -Process { if ($_) { $_.SOMPath } } ) -join $Splitter $LinksObjects = $XMLContent.GPO.LinksTo | ForEach-Object -Process { if ($_) { [PSCustomObject] @{ CanonicalName = $_.SOMPath Enabled = $_.Enabled NoOverride = $_.NoOverride } } } } else { $Linked = $false $LinksEnabledCount = 0 $LinksDisabledCount = 0 $LinksTotalCount = 0 $Links = $null $LinksObjects = $null } } if ($XMLContent.GPO.Computer.Enabled -eq 'False') { $ComputerEnabled = $false } elseif ($XMLContent.GPO.Computer.Enabled -eq 'True') { $ComputerEnabled = $true } else { Write-Warning "Get-XMLGPO - Computer enabled not set to true or false [$DisplayName/$DomainName]. Weird." $ComputerEnabled = $null } if ($XMLContent.GPO.User.Enabled -eq 'False') { $UserEnabled = $false } elseif ($XMLContent.GPO.User.Enabled -eq 'True') { $UserEnabled = $true } else { Write-Warning "Get-XMLGPO - User enabled not set to true or false [$DisplayName/$DomainName] . Weird." $UserEnabled = $null } if ($UserEnabled -eq $True -and $ComputerEnabled -eq $true) { $EnabledBool = $true $Enabled = 'Enabled' } elseif ($UserEnabled -eq $false -and $ComputerEnabled -eq $false) { $EnabledBool = $false $Enabled = 'All settings disabled' } elseif ($UserEnabled -eq $true -and $ComputerEnabled -eq $false) { $EnabledBool = $True $Enabled = 'Computer configuration settings disabled' } elseif ($UserEnabled -eq $false -and $ComputerEnabled -eq $true) { $EnabledBool = $True $Enabled = 'User configuration settings disabled' } [bool] $ComputerSettingsAvailable = if ($null -eq $XMLContent.GPO.Computer.ExtensionData) { $false } else { $true } [bool] $UserSettingsAvailable = if ($null -eq $XMLContent.GPO.User.ExtensionData) { $false } else { $true } if ($ComputerSettingsAvailable -eq $false -and $UserSettingsAvailable -eq $false) { $NoSettings = $true } else { $NoSettings = $false } $PreCheckOutputUser = $false $PreCheckOutputComputer = $false foreach ($Extension in $XMLContent.GPO.User.ExtensionData) { if ($Extension.Error) { $PreCheckOutputUser = $true } } foreach ($Extension in $XMLContent.GPO.Computer.ExtensionData) { if ($Extension.Error) { $PreCheckOutputComputer = $true } } if ($PreCheckOutputComputer -eq $true -or $PreCheckOutputUser -eq $true) { Write-Warning "Get-XMLGPO - Reading GPO content [$DisplayName/$DomainName] returned an error. This may be because of non-english language. Assessing EMPTY using old method which can report false positives. Be careful please." $OutputUser = @() $OutputComputer = @() } else { [Array] $OutputUser = foreach ($ExtensionType in $XMLContent.GPO.User.ExtensionData.Extension) { if ($ExtensionType) { $GPOSettingTypeSplit = ($ExtensionType.type -split ':') try { $KeysToLoop = $ExtensionType | Get-Member -MemberType Properties -ErrorAction Stop | Where-Object { $_.Name -notin 'type', $GPOSettingTypeSplit[0] -and $_.Name -notin @('Blocked') } } catch { Write-Warning "Get-XMLGPO - things went sideways [$DisplayName/$DomainName]. Error $($_.Exception.Message)" continue } } $KeysToLoop } [Array] $OutputComputer = foreach ($ExtensionType in $XMLContent.GPO.Computer.ExtensionData.Extension) { if ($ExtensionType) { $GPOSettingTypeSplit = ($ExtensionType.type -split ':') try { $KeysToLoop = $ExtensionType | Get-Member -MemberType Properties -ErrorAction Stop | Where-Object { $_.Name -notin 'type', $GPOSettingTypeSplit[0] -and $_.Name -notin @('Blocked') } } catch { Write-Warning "Get-XMLGPO - things went sideways [$DisplayName/$DomainName]. Error $($_.Exception.Message)" continue } } $KeysToLoop } [bool] $ComputerSettingsAvailable = if ($OutputComputer.Count -gt 0) { $true } else { $false } [bool] $UserSettingsAvailable = if ($OutputUser.Count -gt 0) { $true } else { $false } } $GPFFile = $false $FilesCount = 0 $TotalSize = 0 Get-ChildItem -LiteralPath $SysvolGpoPath -Recurse -ErrorAction SilentlyContinue -File | ForEach-Object { if ($_.Extension -eq '.gpf') { $GPFFile = $true } $FilesCount++ $TotalSize += $_.Length } if ($ComputerSettingsAvailable -eq $false -and $UserSettingsAvailable -eq $false -and $GPFFile -eq $false) { $Empty = $true } else { $Empty = $false } $ComputerProblem = $false if ($ComputerEnabled -eq $true -and $ComputerSettingsAvailable -eq $true) { $ComputerOptimized = $true } elseif ($ComputerEnabled -eq $true -and $ComputerSettingsAvailable -eq $false) { $ComputerOptimized = $false } elseif ($ComputerEnabled -eq $false -and $ComputerSettingsAvailable -eq $false) { $ComputerOptimized = $true } else { $ComputerOptimized = $false $ComputerProblem = $true } $UserProblem = $false if ($UserEnabled -eq $true -and $UserSettingsAvailable -eq $true) { $UserOptimized = $true } elseif ($UserEnabled -eq $true -and $UserSettingsAvailable -eq $false) { $UserOptimized = $false } elseif ($UserEnabled -eq $false -and $UserSettingsAvailable -eq $false) { $UserOptimized = $true } else { $UserOptimized = $false $UserProblem = $true } if ($UserProblem -or $ComputerProblem) { $Problem = $true } else { $Problem = $false } if ($UserOptimized -and $ComputerOptimized) { $Optimized = $true } else { $Optimized = $false } if (-not $PermissionsOnly) { if ($ADAdministrativeGroups -and $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text') { $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text')"] $WellKnown = ConvertFrom-SID -SID $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text' -OnlyWellKnown if ($AdministrativeGroup) { $OwnerType = 'Administrative' } elseif ($WellKnown.Name) { $OwnerType = 'WellKnown' } else { $OwnerType = 'NotAdministrative' } } elseif ($ADAdministrativeGroups) { $OwnerType = 'Unknown' } else { $OwnerType = 'Unable to assess (local files?)' } } $Exclude = $false if ($ExcludeGroupPolicies) { $GUID = $XMLContent.GPO.Identifier.Identifier.'#text' $GUIDWithOutBrackets = $GUID.Replace('{', '').Replace('}', '') $PolicyWithDomain = -join ($XMLContent.GPO.Identifier.Domain.'#text', $XMLContent.GPO.Name) $PolicyWithDomainID = -join ($XMLContent.GPO.Identifier.Domain.'#text', $GUID) $PolicyWithDomainIDWithoutBrackets = -join ($XMLContent.GPO.Identifier.Domain.'#text', $GUIDWithOutBrackets) if ($ExcludeGroupPolicies[$XMLContent.GPO.Name] -or $ExcludeGroupPolicies[$PolicyWithDomain] -or $ExcludeGroupPolicies[$PolicyWithDomainID] -or $ExcludeGroupPolicies[$GUID] -or $ExcludeGroupPolicies[$GUIDWithOutBrackets] -or $ExcludeGroupPolicies[$PolicyWithDomainIDWithoutBrackets] ) { $Exclude = $true } } if ($PermissionsOnly) { $GPOOutput = [PsCustomObject] @{ 'DisplayName' = $XMLContent.GPO.Name 'DomainName' = $XMLContent.GPO.Identifier.Domain.'#text' 'GUID' = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}' 'Enabled' = $Enabled 'Name' = $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text' 'Sid' = $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text' 'PermissionType' = 'Allow' 'Inherited' = $false 'Permissions' = 'Owner' 'GPODistinguishedName' = $GPO.Path 'GPOSysvolPath' = $SysvolGpoPath } $XMLContent.GPO.SecurityDescriptor.Permissions.TrusteePermissions | ForEach-Object -Process { if ($_) { [PsCustomObject] @{ 'DisplayName' = $XMLContent.GPO.Name 'DomainName' = $XMLContent.GPO.Identifier.Domain.'#text' 'GUID' = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}' 'Enabled' = $Enabled 'Name' = $_.trustee.name.'#Text' 'Sid' = $_.trustee.SID.'#Text' 'PermissionType' = $_.type.PermissionType 'Inherited' = if ($_.Inherited -eq 'false') { $false } else { $true } 'Permissions' = $_.Standard.GPOGroupedAccessEnum 'GPODistinguishedName' = $GPO.Path } } } } elseif ($OwnerOnly) { $GPOOutput = [PsCustomObject] @{ 'DisplayName' = $XMLContent.GPO.Name 'DomainName' = $XMLContent.GPO.Identifier.Domain.'#text' 'GUID' = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}' 'Enabled' = $Enabled 'Owner' = $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text' 'OwnerSID' = $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text' 'OwnerType' = $OwnerType 'GPODistinguishedName' = $GPO.Path 'GPOSysvolPath' = $SysvolGpoPath } } else { $GPOOutput = [PsCustomObject] @{ 'DisplayName' = $XMLContent.GPO.Name 'DomainName' = $XMLContent.GPO.Identifier.Domain.'#text' 'GUID' = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}' 'Days' = (New-TimeSpan -Start ([DateTime] $XMLContent.GPO.ModifiedTime) -End (Get-Date)).Days 'Empty' = $Empty 'Linked' = $Linked 'Enabled' = $EnabledBool 'Optimized' = $Optimized 'Problem' = $Problem 'ApplyPermission' = $null 'Exclude' = $Exclude 'SizeMB' = [Math]::Round($TotalSize / 1MB, 2) 'Size' = $TotalSize 'Description' = $GPO.Description 'ComputerPolicies' = $XMLContent.GPO.Computer.ExtensionData.Name -join ", " 'UserPolicies' = $XMLContent.GPO.User.ExtensionData.Name -join ", " 'FilesCount' = $FilesCount 'LinksCount' = $LinksTotalCount 'LinksEnabledCount' = $LinksEnabledCount 'LinksDisabledCount' = $LinksDisabledCount 'EnabledDetails' = $Enabled 'ComputerProblem' = $ComputerProblem 'ComputerOptimized' = $ComputerOptimized 'UserProblem' = $UserProblem 'UserOptimized' = $UserOptimized 'ComputerSettingsAvailable' = $ComputerSettingsAvailable 'UserSettingsAvailable' = $UserSettingsAvailable 'ComputerSettingsTypes' = $OutputComputer.Name -join ", " 'UserSettingsTypes' = $OutputUser.Name -join ", " 'ComputerEnabled' = $ComputerEnabled 'UserEnabled' = $UserEnabled 'ComputerSettingsStatus' = if ($XMLContent.GPO.Computer.VersionDirectory -eq 0 -and $XMLContent.GPO.Computer.VersionSysvol -eq 0) { "NeverModified" } else { "Modified" } 'ComputerSetttingsVersionIdentical' = if ($XMLContent.GPO.Computer.VersionDirectory -eq $XMLContent.GPO.Computer.VersionSysvol) { $true } else { $false } 'ComputerSettings' = $XMLContent.GPO.Computer.ExtensionData.Extension 'UserSettingsStatus' = if ($XMLContent.GPO.User.VersionDirectory -eq 0 -and $XMLContent.GPO.User.VersionSysvol -eq 0) { "NeverModified" } else { "Modified" } 'UserSettingsVersionIdentical' = if ($XMLContent.GPO.User.VersionDirectory -eq $XMLContent.GPO.User.VersionSysvol) { $true } else { $false } 'UserSettings' = $XMLContent.GPO.User.ExtensionData.Extension 'NoSettings' = $NoSettings 'CreationTime' = [DateTime] $XMLContent.GPO.CreatedTime 'ModificationTime' = [DateTime] $XMLContent.GPO.ModifiedTime 'ReadTime' = [DateTime] $XMLContent.GPO.ReadTime 'WMIFilter' = $GPO.WmiFilter.name 'WMIFilterDescription' = $GPO.WmiFilter.Description 'GPODistinguishedName' = $GPO.Path 'GPOSysvolPath' = $SysvolGpoPath 'SDDL' = if ($Splitter -ne '') { $XMLContent.GPO.SecurityDescriptor.SDDL.'#text' -join $Splitter } else { $XMLContent.GPO.SecurityDescriptor.SDDL.'#text' } 'Owner' = $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text' 'OwnerSID' = $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text' 'OwnerType' = $OwnerType 'ACL' = @( [PsCustomObject] @{ 'Name' = $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text' 'Sid' = $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text' 'PermissionType' = 'Allow' 'Inherited' = $false 'Permissions' = 'Owner' } $XMLContent.GPO.SecurityDescriptor.Permissions.TrusteePermissions | ForEach-Object -Process { if ($_) { [PsCustomObject] @{ 'Name' = $_.trustee.name.'#Text' 'Sid' = $_.trustee.SID.'#Text' 'PermissionType' = $_.type.PermissionType 'Inherited' = if ($_.Inherited -eq 'false') { $false } else { $true } 'Permissions' = $_.Standard.GPOGroupedAccessEnum } } } ) 'Auditing' = if ($XMLContent.GPO.SecurityDescriptor.AuditingPresent.'#text' -eq 'true') { $true } else { $false } 'Links' = $Links 'LinksObjects' = $LinksObjects 'GPOObject' = $GPO } if ($GPOOutput.ACL) { $GPOOutput.ApplyPermission = $false foreach ($Permission in $GPOOutput.ACL) { if ($Permission.Permissions -eq 'Apply Group Policy') { $GPOOutput.ApplyPermission = $true } } } } if ($PermissionsOnly -or $OwnerOnly) { $GPOOutput } else { if (-not $Type -or $Type -contains 'All') { $GPOOutput } else { if ($Type -contains 'Empty') { if ($GPOOutput.Empty -eq $true) { $GPOOutput } } if ($Type -contains 'Unlinked') { if ($GPOOutput.Linked -eq $false) { $GPOOutput } } if ($Type -contains 'Disabled') { if ($GPOOutput.Enabled -eq $false) { $GPOOutput } } if ($Type -contains 'NoApplyPermission') { if ($GPOOutput.ApplyPermission -eq $false) { $GPOOutput } } } } } function Get-XMLNestedRegistry { <# .SYNOPSIS Retrieves registry settings from XML data and formats them for output. .DESCRIPTION This function retrieves registry settings from XML data and formats them for output. It can either provide a limited set of information or the full details of each registry setting. .PARAMETER GPO The Group Policy Object (GPO) associated with the registry settings. .PARAMETER DataSet An array of XML elements containing the registry settings. .PARAMETER Collection The name of the collection of registry settings. .PARAMETER Limited Indicates whether to provide limited information about each registry setting. .EXAMPLE Get-XMLNestedRegistry -GPO $GPO -DataSet $DataSet -Collection "SoftwareSettings" -Limited Retrieves limited information about the registry settings in the "SoftwareSettings" collection. .EXAMPLE Get-XMLNestedRegistry -GPO $GPO -DataSet $DataSet -Collection "WindowsSettings" Retrieves full details of the registry settings in the "WindowsSettings" collection. #> [cmdletBinding()] param( [PSCustomObject] $GPO, [System.Xml.XmlElement[]] $DataSet, [string] $Collection, [switch] $Limited ) if ($DataSet.Properties) { $Registry = $DataSet foreach ($Registry in $DataSet) { if ($Registry.Properties) { if ($Limited) { [PSCustomObject] @{ Collection = $Collection Description = $Registry.descr Changed = try { [DateTime] $Registry.changed } catch { $Registry.changed }; Disabled = if ($Registry.disabled -eq '1') { $true } else { $false }; GPOSettingOrder = [int] $Registry.GPOSettingOrder Action = $Script:Actions[$Registry.Properties.action] DisplayDecimal = if ($Registry.Properties.displayDecimal -eq '1') { $true } else { $false }; Default = if ($Registry.Properties.default -eq '1') { $true } else { $false }; Hive = $Registry.Properties.hive Key = $Registry.Properties.key Name = $Registry.Properties.name Type = $Registry.Properties.type Value = $Registry.Properties.value Filters = $Registry.Filters BypassErrors = if ($Registry.bypassErrors -eq '1') { $true } else { $false }; } } else { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Collection = $Collection Description = $Registry.descr Changed = try { [DateTime] $Registry.changed } catch { $Registry.changed }; Disabled = if ($Registry.disabled -eq '1') { $true } else { $false }; GPOSettingOrder = [int] $Registry.GPOSettingOrder Action = $Script:Actions[$Registry.Properties.action] DisplayDecimal = if ($Registry.Properties.displayDecimal -eq '1') { $true } else { $false }; Default = if ($Registry.Properties.default -eq '1') { $true } else { $false }; Hive = $Registry.Properties.hive Key = $Registry.Properties.key Name = $Registry.Properties.name Type = $Registry.Properties.type Value = $Registry.Properties.value Filters = $Registry.Filters BypassErrors = if ($Registry.bypassErrors -eq '1') { $true } else { $false }; } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } } } foreach ($Name in @('Registry', 'Collection')) { foreach ($Registry in $DataSet.$Name) { if ($Registry.Properties) { if ($Limited) { [PSCustomObject] @{ Collection = $Collection Description = $Registry.descr Changed = try { [DateTime] $Registry.changed } catch { $Registry.changed }; Disabled = if ($Registry.disabled -eq '1') { $true } else { $false }; GPOSettingOrder = [int] $Registry.GPOSettingOrder Action = $Script:Actions[$Registry.Properties.action] DisplayDecimal = if ($Registry.Properties.displayDecimal -eq '1') { $true } else { $false }; Default = if ($Registry.Properties.default -eq '1') { $true } else { $false }; Hive = $Registry.Properties.hive Key = $Registry.Properties.key Name = $Registry.Properties.name Type = $Registry.Properties.type Value = $Registry.Properties.value Filters = $Registry.Filters BypassErrors = if ($Registry.bypassErrors -eq '1') { $true } else { $false }; } } else { $CreateGPO = [ordered]@{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID GpoType = $GPO.GpoType Collection = $Collection Description = $Registry.descr Changed = try { [DateTime] $Registry.changed } catch { $Registry.changed }; Disabled = if ($Registry.disabled -eq '1') { $true } else { $false }; GPOSettingOrder = [int] $Registry.GPOSettingOrder Action = $Script:Actions[$Registry.Properties.action] DisplayDecimal = if ($Registry.Properties.displayDecimal -eq '1') { $true } else { $false }; ; Default = if ($Registry.Properties.default -eq '1') { $true } else { $false }; Hive = $Registry.Properties.hive Key = $Registry.Properties.key Name = $Registry.Properties.name Type = $Registry.Properties.type Value = $Registry.Properties.value Filters = $Registry.Filters BypassErrors = if ($Registry.bypassErrors -eq '1') { $true } else { $false }; } $CreateGPO['Linked'] = $GPO.Linked $CreateGPO['LinksCount'] = $GPO.LinksCount $CreateGPO['Links'] = $GPO.Links [PSCustomObject] $CreateGPO } } else { if ($Registry.Registry) { $TempCollection = $Collection if ($Collection) { $Collection = "$Collection/$($Registry.name)" } else { $Collection = $Registry.name } Get-XMLNestedRegistry -GPO $GPO -DataSet $Registry.Registry -Collection $Collection $Collection = $TempCollection } if ($Registry.Collection) { $TempCollection = $Collection foreach ($MyCollection in $Registry.Collection) { if ($Collection) { $Collection = "$Collection/$($Registry.name)/$($MyCollection.name)" } else { $Collection = "$($Registry.name)/$($MyCollection.name)" } Get-XMLNestedRegistry -GPO $GPO -DataSet $MyCollection -Collection $Collection $Collection = $TempCollection } } } } } } $GPOZaurrAnalysis = [ordered] @{ Name = 'Group Policy Content' Enabled = $true ActionRequired = $null Data = $null Execute = { Invoke-GPOZaurrContent -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -GPOName $GPOName -GPOGUID $GPOGUID } Processing = { } Variables = @{ } Overview = { } Solution = { foreach ($Key in $Script:Reporting['GPOAnalysis']['Data'].Keys) { New-HTMLTab -Name $Key { New-HTMLTable -DataTable $Script:Reporting['GPOAnalysis']['Data'][$Key] -Filtering -Title $Key -ScrollX } } if ($Script:Reporting['GPOAnalysis']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOAnalysis']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrBlockedInheritance = [ordered] @{ Name = 'Group Policy Blocked Inhertiance' Enabled = $true ActionRequired = $null Data = $null Execute = { if ($Script:Reporting['GPOBlockedInheritance']['Exclusions']) { Get-GPOZaurrInheritance -IncludeBlockedObjects -IncludeExcludedObjects -OnlyBlockedInheritance -IncludeGroupPoliciesForBlockedObjects -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Exclusions $Script:Reporting['GPOBlockedInheritance']['Exclusions'] } else { Get-GPOZaurrInheritance -IncludeBlockedObjects -IncludeExcludedObjects -OnlyBlockedInheritance -IncludeGroupPoliciesForBlockedObjects -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } } Processing = { foreach ($GPO in $Script:Reporting['GPOBlockedInheritance']['Data']) { if (-not $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmlessPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmlessPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'][$GPO.DomainName] = 0 } if ($GPO.Exclude -eq $true) { $Script:Reporting['GPOBlockedInheritance']['Variables']['Exclude']++ $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedExclude'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedExclude'] + $GPO.UsersCount $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedExclude'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedExclude'] + $GPO.ComputersCount } else { $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffected'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffected'] + $GPO.UsersCount $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffected'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffected'] + $GPO.ComputersCount } $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedIncludingExclude'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedIncludingExclude'] + $GPO.UsersCount $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedIncludingExclude'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedIncludingExclude'] + $GPO.ComputersCount if ($GPO.Exclude -eq $false -and ($GPO.UsersCount -gt 0 -or $GPO.ComputersCount -gt 0)) { $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigation']++ $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'][$GPO.DomainName]++ } if ($GPO.Exclude -eq $false -and ($GPO.UsersCount -eq 0 -and $GPO.ComputersCount -eq 0)) { $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmless']++ $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmlessPerDomain'][$GPO.DomainName]++ } } if ($Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigation'] -gt 0 -or $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmless'] -gt 0) { $Script:Reporting['GPOBlockedInheritance']['ActionRequired'] = $true } else { $Script:Reporting['GPOBlockedInheritance']['ActionRequired'] = $false } } Resources = @( 'http://www.firewall.cx/microsoft-knowledgebase/windows-2012/1056-windows-2012-group-policy-enforcement.html' ) Variables = @{ Total = 0 Exclude = 0 RequiresInvesigation = 0 RequiresInvesigationPerDomain = [ordered] @{} DeletionHarmless = 0 DeletionHarmlessPerDomain = [ordered] @{} UsersAffected = 0 UsersAffectedExclude = 0 UsersAffectedIncludingExclude = 0 ComputersAffected = 0 ComputersAffectedIncludingExclude = 0 ComputersAffectedExclude = 0 GroupPolicies = [System.Collections.Generic.List[PSCustomObject]]::new() } Overview = { } Summary = { New-HTMLText -FontSize 10pt -TextBlock { "By default, group policy settings that are linked to parent objects are inherited to the child objects in the active directory hierarchy. " "By default, Default Domain Policy is linked to the domain and is inherited to all the child objects of the domain hierarchy. " "So does any other policies linked to the top level OU's. " } New-HTMLText -Text "Blocked Inheritance" -FontSize 10pt -FontWeight bold New-HTMLText -FontSize 10pt -Text @( "As GPOs can be inherited by default, they can also be blocked, if required using the Block Inheritance. " "If the Block Inheritance setting is enabled, the inheritance of group policy setting is blocked. " "This setting is mostly used when the OU contains users or computers that require different settings than what is applied to the domain level. " "Unfortunately blocking inheritance can have serious security consequences. " ) New-HTMLText -Text @( 'As it stands currently there are ', $Script:Reporting['GPOBlockedInheritance']['Data'].Count, ' organisational units with ' 'GPO Inheritance Block' ' out of which ' $Script:Reporting['GPOBlockedInheritance']['Variables']['Exclude'], ' are marked as Excluded ' '(approved by IT). ' ) -FontSize 10pt -FontWeight normal, bold, normal, bold, normal, bold, normal, bold -LineBreak if ($Script:Reporting['GPOBlockedInheritance']['Data'].Count -ne 0) { New-HTMLText -Text 'Users & Computers affected by inheritance blocks:' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffected'], ' users affected due to inheritance blocks' -FontWeight bold, normal New-HTMLListItem -Text $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedExclude'], ' users affected, but approved/excluded, due to inheritance blocks' -FontWeight bold, normal New-HTMLListItem -Text $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffected'], ' computers affected due to inheritance blocks' -FontWeight bold, normal New-HTMLListItem -Text $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedExclude'], ' computers affected, but approved/excluded, due to inheritance blocks' -FontWeight bold, normal } -FontSize 10pt New-HTMLText -Text 'Following domains require:' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'].Keys) { New-HTMLListItem -Text "$Domain proposes ", $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'][$Domain], " investigation (computers or users inside)." -FontWeight normal, bold, normal New-HTMLListItem -Text "$Domain proposes ", $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmlessPerDomain'][$Domain], " removal (mostly harmless due to no computers or users inside)." -FontWeight normal, bold, normal } } -FontSize 10pt } New-HTMLText -FontSize 10pt -Text "Please review output in table and follow the steps below table to get Active Directory Group Policies in healthy state." if ($Script:Reporting['GPOBlockedInheritance']['Exclusions']) { New-HTMLText -LineBreak New-HTMLText -Text @( "While preparing this report following exclusions were defined. " "Please make sure that when you execute your steps to include those exclusions to prevent any issues. " ) -FontSize 10pt -FontWeight bold, normal -Color Red, None -LineBreak New-HTMLText -Text "Code to use for exclusions: " -FontSize 10pt -FontWeight bold -LineBreak $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOBlockedInheritance']['Exclusions'] if ($Code) { New-HTMLCodeBlock -Code $Code -Style powershell } } } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOBlockedInheritance']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartLegend -Names 'Affected', 'Affected, but excluded' -Color Salmon, PaleGreen New-ChartBarOptions -Type barStacked New-ChartBar -Name 'Users' -Value $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffected'], $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedExclude'] New-ChartBar -Name 'Computers' -Value $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffected'], $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedExclude'] } -Title 'Users & Computers affected due to blocked inheritance' -TitleAlignment center } } New-HTMLSection -Name 'Organizational Units with Group Policy Blocked Inheritance' { New-HTMLTable -DataTable $Script:Reporting['GPOBlockedInheritance']['Data'] -Filtering { New-TableEvent -TableID 'TableWithGroupPoliciesBlockedInheritance' -SourceColumnID 8 -TargetColumnID 9 New-HTMLTableCondition -Name 'Exclude' -Value $true -BackgroundColor DeepSkyBlue -ComparisonType string -Row New-TableConditionGroup { New-TableCondition -Name 'BlockedInheritance' -Value $true New-TableCondition -Name 'Exclude' -Value $false } -BackgroundColor Salmon -FailBackgroundColor SpringGreen -HighlightHeaders 'BlockedInheritance', 'Exclude' New-TableConditionGroup { New-TableCondition -Name 'UsersCount' -Value 0 New-TableCondition -Name 'ComputersCount' -Value 0 } -BackgroundColor Salmon -FailBackgroundColor Amber -HighlightHeaders 'UsersCount', 'ComputersCount' New-TableColumnOption -Hidden $true -ColumnIndex 8 } -PagingOptions 5, 10, 20, 30, 40, 50 -ExcludeProperty GroupPolicies -ScrollX } New-HTMLSection -Name 'Group Policies affecting objects in Organizational Units with Blocked Inheritance' { New-HTMLTable -DataTable $Script:Reporting['GPOBlockedInheritance']['Data'].GroupPolicies -Filtering { New-TableCondition -Name 'Enabled' -Value $true -BackgroundColor SpringGreen -FailBackgroundColor Salmon New-TableCondition -Name 'Enforced' -Value $true -BackgroundColor Amber -FailBackgroundColor AirForceBlue New-TableCondition -Name 'LinkedDirectly' -Value $true -BackgroundColor Amber -FailBackgroundColor AirForceBlue } -PagingOptions 5, 10, 20, 30, 40, 50 -DataTableID 'TableWithGroupPoliciesBlockedInheritance' -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix - Organizational Units with Group Policy Blocked Inheritance' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } if ($Script:Reporting['GPOBlockedInheritance']['Exclusions']) { New-HTMLWizardStep -Name 'Required exclusions' { New-HTMLText -Text @( "While preparing this report following exclusions were defined. " "Please make sure that when you execute your steps to include those exclusions to prevent any issues. " ) $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOBlockedInheritance']['Exclusions'] if ($Code) { New-HTMLCodeBlock -Code $Code -Style powershell } } } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text @( "Depending when this report was run you may want to prepare new report before proceeding removing Group Policy Inheritance Blocks. " "Please keep in mind that if exclusions for some Organizational OU's were defined you need to pass them to cmdlet below to not remove approved GPO Inheritance Blocks. " "To generate new report please use:" ) New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBlockedGPOInheritanceBefore.html -Verbose -Type GPOBlockedInheritance } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step. " } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $GPOOutput = Get-GPOZaurrInheritance -IncludeBlockedObjects -IncludeExcludedObjects -OnlyBlockedInheritance $GPOOutput | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Remove OU GPO Inheritance Blocks' { New-HTMLText -Text @( "Removing inheritance blocks is quite trivial and can be done from GPO GUI. However knowing when to remove is the important part. " "Please consult other Domain Admins before removing any inheritance blocks, and either approve exclusion or remove blocking inheritance. " ) } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBlockedGPOInheritanceAfter.html -Verbose -Type GPOBlockedInheritance } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOBlockedInheritance']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOBlockedInheritance']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrBrokenLink = [ordered] @{ Name = 'Group Policy Broken Links' Enabled = $true ActionRequired = $null Data = $null Execute = { Get-GPOZaurrBrokenLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { $Script:Reporting['GPOBrokenLink']['Variables']['RequireDeletion'] = $Script:Reporting['GPOBrokenLink']['Data'].Count $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'] = @{} $Script:Reporting['GPOBrokenLink']['Variables']['Unique'] = @{} foreach ($Link In $Script:Reporting['GPOBrokenLink']['Data']) { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Link.DistinguishedName if (-not $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName]) { $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName] = 0 } $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName]++ $Script:Reporting['GPOBrokenLink']['Variables']['Unique'][$Link.CanonicalName] = $Link } $Script:Reporting['GPOBrokenLink']['Variables']['UniqueObjects'] = $Script:Reporting['GPOBrokenLink']['Variables']['Unique'].Keys if ($Script:Reporting['GPOBrokenLink']['Data'].Count -gt 0) { $Script:Reporting['GPOBrokenLink']['ActionRequired'] = $true } else { $Script:Reporting['GPOBrokenLink']['ActionRequired'] = $false } } Variables = @{ RequireDeletion = 0 WillFixPerDomain = $null UniqueObjects = $null Unique = $null } Overview = { } Summary = { New-HTMLText -FontSize 10pt -TextBlock { "When GPO is deleted correctly, it usually is removed from AD, SYSVOL, and any link to it is also discarded. " "Unfortunately, this is true only if the GPO is created and linked within the same domain. " "If GPO is linked in another domain, this leaves a broken link hanging on before it was linked. " "Additionally, the Remove-GPO cmdlet doesn't handle site link deletions, which causes dead links to be stuck on sites until those are manually deleted. " "This means that any GPOs deleted using PowerShell may leave a trail." } New-HTMLText -Text @( 'As it stands currently there are ', $Script:Reporting['GPOBrokenLink']['Data'].Count, ' broken links that need to be deleted over ' $Script:Reporting['GPOBrokenLink']['Variables']['UniqueObjects'].Count, ' unique objects. ' ) -FontSize 10pt -FontWeight normal, bold, normal, bold, normal -LineBreak if ($Script:Reporting['GPOBrokenLink']['Data'].Count -ne 0) { New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt } } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOBrokenLink']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartLegend -Names 'Bad' -Color Salmon New-ChartBar -Name 'Broken Links' -Value $Script:Reporting['GPOBrokenLink']['Data'].Count } -Title 'Broken Links' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Broken Links' { New-HTMLTable -DataTable $Script:Reporting['GPOBrokenLink']['Data'] -Filtering { } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to remove Broken Links' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing GPO links. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenLinkBefore.html -Verbose -Type GPOBrokenLink } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step. " } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $GPOOutput = Get-GPOZaurrBrokenLink -Verbose $GPOOutput | Format-Table * } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Remove Broken Links' { New-HTMLText -Text "Following command when executed, runs internally command that lists all broken links. After finding them all it delets them according to given criteria. " New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black New-HTMLCodeBlock -Code { Repair-GPOZaurrBrokenLink -WhatIf -Verbose } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data. Once happy with results please follow with command: " } New-HTMLCodeBlock -Code { Repair-GPOZaurrBrokenLink -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "This command when executed removes only first X number of links. Keep in mind that 5 broken links on a single Organizational Unit are treated as one. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenLinkAfter.html -Verbose -Type GPOBrokenLink } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOBrokenLink']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOBrokenLink']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrConsistency = [ordered] @{ Name = 'GPO Permissions Consistency' Enabled = $true ActionRequired = $null Data = $null Execute = { Get-GPOZaurrPermissionConsistency -Type All -VerifyInheritance -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { foreach ($GPO in $Script:Reporting['GPOConsistency']['Data']) { if ($GPO.ACLConsistent -eq $true) { $Script:Reporting['GPOConsistency']['Variables']['Consistent']++ } else { $Script:Reporting['GPOConsistency']['Variables']['Inconsistent']++ } if ($GPO.ACLConsistentInside -eq $true) { $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside']++ } else { $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside']++ } } if ($Script:Reporting['GPOConsistency']['Variables']['Inconsistent'] -gt 0 -or $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside'] -gt 0 ) { $Script:Reporting['GPOConsistency']['ActionRequired'] = $true } else { $Script:Reporting['GPOConsistency']['ActionRequired'] = $false } } Variables = @{ Consistent = 0 Inconsistent = 0 ConsistentInside = 0 InconsistentInside = 0 } Overview = { New-HTMLPanel { New-HTMLText -Text 'Following chart presents ', 'permissions consistency between Active Directory and SYSVOL for Group Policies' -FontSize 10pt -FontWeight normal, bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Top level permissions consistency: ', $Script:Reporting['GPOConsistency']['Variables']['Consistent'] -FontWeight normal, bold New-HTMLListItem -Text 'Inherited permissions consistency: ', $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside'] -FontWeight normal, bold New-HTMLListItem -Text 'Inconsistent top level permissions: ', $Script:Reporting['GPOConsistency']['Variables']['Inconsistent'] -FontWeight normal, bold New-HTMLListItem -Text "Inconsistent inherited permissions: ", $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -FontSize 10pt -Text 'Having inconsistent permissions on AD in comparison to those on SYSVOL can lead to uncontrolled ability to modify them.' New-HTMLChart { New-ChartLegend -Names 'Bad', 'Good' -Color PaleGreen, Salmon New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'Consistent', 'Inconsistent' New-ChartBar -Name 'TopLevel' -Value $Script:Reporting['GPOConsistency']['Variables']['Consistent'], $Script:Reporting['GPOConsistency']['Variables']['Inconsistent'] New-ChartBar -Name 'Inherited' -Value $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside'], $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside'] } -Title 'Permissions Consistency' -TitleAlignment center } } Summary = { New-HTMLText -FontSize 10pt -TextBlock { "When GPO is created, it creates an entry in Active Directory (metadata) and SYSVOL (content). " "Two different places mean two different sets of permissions. " "The group Policy module is making sure the data in both places is correct. " "However, it's not necessarily the case for different reasons, and often permissions go out of sync between AD and SYSVOL. " "This test verifies the consistency of policies between AD and SYSVOL in two ways. " "It checks top-level permissions for a GPO and then checks if all files within said GPO is inheriting permissions or have different permissions in place." } New-HTMLText -Text 'Following list presents ', 'permissions consistency between Active Directory and SYSVOL for Group Policies' -FontSize 10pt -FontWeight normal, bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Top level permissions consistency: ', $Script:Reporting['GPOConsistency']['Variables']['Consistent'] -FontWeight normal, bold New-HTMLListItem -Text 'Inherited permissions consistency: ', $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside'] -FontWeight normal, bold New-HTMLListItem -Text 'Inconsistent top level permissions: ', $Script:Reporting['GPOConsistency']['Variables']['Inconsistent'] -FontWeight normal, bold New-HTMLListItem -Text "Inconsistent inherited permissions: ", $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -FontSize 10pt -Text 'Having inconsistent permissions on AD in comparison to those on SYSVOL can lead to uncontrolled ability to modify them. Please notice that if ', ` ' Not available ', 'is visible in the table you should first fix related, more pressing issue, before fixing permissions inconsistency.' -FontWeight normal, bold, normal } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOConsistency']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'Consistent', 'Inconsistent' -Color PaleGreen, Salmon New-ChartBar -Name 'TopLevel' -Value $Script:Reporting['GPOConsistency']['Variables']['Consistent'], $Script:Reporting['GPOConsistency']['Variables']['Inconsistent'] New-ChartBar -Name 'Inherited' -Value $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside'], $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside'] } -Title 'Permissions Consistency' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Permissions Consistency' { New-HTMLTable -DataTable $Script:Reporting['GPOConsistency']['Data'] -Filtering { New-HTMLTableCondition -Name 'ACLConsistent' -Value $false -BackgroundColor Salmon -TextTransform capitalize -ComparisonType string New-HTMLTableCondition -Name 'ACLConsistentInside' -Value $false -BackgroundColor Salmon -TextTransform capitalize -ComparisonType string New-HTMLTableCondition -Name 'ACLConsistent' -Value $true -BackgroundColor PaleGreen -TextTransform capitalize -ComparisonType string New-HTMLTableCondition -Name 'ACLConsistentInside' -Value $true -BackgroundColor PaleGreen -TextTransform capitalize -ComparisonType string New-HTMLTableCondition -Name 'ACLConsistent' -Value 'Not available' -BackgroundColor Crimson -ComparisonType string New-HTMLTableCondition -Name 'ACLConsistentInside' -Value 'Not available' -BackgroundColor Crimson -ComparisonType string } -PagingOptions 7, 15, 30, 45, 60 -ScrollX -ExcludeProperty 'UserVersion', 'ComputerVersion', 'WmiFilter', 'ACLConsistentInsideDetails' } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix - Permissions Consistency' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLText -Text 'Following steps will guide you how to fix permissions consistency' New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing permissions inconsistencies. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrPermissionsInconsistentBefore.html -Verbose -Type GPOConsistency } New-HTMLText -Text @( "When executed it will take a while to generate all data and provide you with new report depending on size of environment." "Once confirmed that data is still showing issues and requires fixing please proceed with next step." ) New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $GPOOutput = Get-GPOZaurrPermissionConsistency $GPOOutput | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Fix inconsistent permissions' { New-HTMLText -Text "Following command when executed fixes inconsistent permissions." New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black New-HTMLText -Text "Make sure to fill in TargetDomain to match your Domain Admin permission account" New-HTMLCodeBlock -Code { Repair-GPOZaurrPermissionConsistency -IncludeDomains "TargetDomain" -Verbose -WhatIf } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be deleted matches expected data. Once happy with results please follow with command: " } New-HTMLCodeBlock -Code { Repair-GPOZaurrPermissionConsistency -LimitProcessing 2 -IncludeDomains "TargetDomain" } New-HTMLText -TextBlock { "This command when executed repairs only first X inconsistent permissions. Use LimitProcessing parameter to prevent mass fixing and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " } New-HTMLText -Text "If there's nothing else to be fixed, we can skip to next step step" } New-HTMLWizardStep -Name 'Fix inconsistent downlevel permissions' { New-HTMLText -Text @( "Unfortunately this step is manual until automation is developed. " "If there are inconsistent permissions found inside GPO one has to fix them manually by going into SYSVOL and making sure inheritance is enabled, and that permissions are consistent across all files." "Please keep in mind that it's possible inconsistent downlevel permissions fix will not be required once the top level fix is applied. " "Rerun report to find out if you've just fixed top-level permissions. " ) } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrPermissionsInconsistentAfter.html -Verbose -Type GPOConsistency } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOConsistency']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOConsistency']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrDuplicates = [ordered] @{ Name = 'Duplicate (CNF) Group Policies' Enabled = $true Action = $null Data = $null Execute = { Get-GPOZaurrDuplicateObject -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { $Script:Reporting['GPODuplicates']['Variables']['RequireDeletion'] = $Script:Reporting['GPODuplicates']['Data'].Count if ($Script:Reporting['GPODuplicates']['Data'].Count -gt 0) { $Script:Reporting['GPODuplicates']['ActionRequired'] = $true } else { $Script:Reporting['GPODuplicates']['ActionRequired'] = $false } } Variables = @{ RequireDeletion = 0 } Overview = { } Resources = @( 'https://social.technet.microsoft.com/wiki/contents/articles/15435.active-directory-duplicate-object-name-resolution.aspx' 'https://kickthatcomputer.wordpress.com/2014/11/22/seek-and-destroy-duplicate-ad-objects-with-cnf-in-the-name/' ) Summary = { New-HTMLText -FontSize 10pt -TextBlock { "CNF objects, Conflict objects or Duplicate Objects are created in Active Directory when there is simultaneous creation of an AD object under the same container " "on two separate Domain Controllers near about the same time or before the replication occurs. " "This results in a conflict and the same is exhibited by a CNF (Duplicate) object. " "While it doesn't nessecarily have a huge impact on Active Directory, it's important to keep Active Directory in proper, healthy state. " } New-HTMLText -Text 'As it stands currently there are ', $Script:Reporting['GPODuplicates']['Data'].Count, ' CNF (Duplicate) Group Policy objects to be deleted.' -FontSize 10pt -FontWeight normal, bold, normal } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPODuplicates']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartLegend -Names 'Bad' -Color Salmon New-ChartBar -Name 'Duplicate (CNF) object' -Value $Script:Reporting['GPODuplicates']['Data'].Count } -Title 'Duplicate (CNF) Objects' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy CNF (Duplicate) Objects' { New-HTMLTable -DataTable $Script:Reporting['GPODuplicates']['Data'] -Filtering { } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix - Remove duplicate (CNF) objects' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing duplicate GPO objects. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrDuplicateObjectsBefore.html -Verbose -Type GPODuplicates } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step. " } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $GPOOutput = Get-GPOZaurrDuplicateObject $GPOOutput | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Remove CNF objects' { New-HTMLText -Text "Following command when executed, runs internally command that lists all duplicate objects." New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurrDuplicateObject -WhatIf -Verbose } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data. Once happy with results please follow with command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrDuplicateObject -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "This command when executed removes only first X duplicate objects. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrDuplicateObjectsAfter.html -Verbose -Type GPODuplicates } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPODuplicates']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPODuplicates']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrFiles = [ordered] @{ Name = 'SYSVOL (NetLogon) Files List' Enabled = $true ActionRequired = $null Data = $null Execute = { Get-GPOZaurrFiles -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { } Variables = @{ } Overview = { } Solution = { New-HTMLTable -DataTable $Script:Reporting['GPOFiles']['Data'] -Filtering -ScrollX -PagingOptions 7, 15, 30, 45, 60 { New-HTMLTableCondition -Name 'SuggestedAction' -Value 'Requires verification' -BackgroundColor YellowOrange -ComparisonType string New-HTMLTableCondition -Name 'SuggestedAction' -Value 'Consider deleting' -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'SuggestedAction' -Value 'GPO requires cleanup' -BackgroundColor RedRobin -ComparisonType string New-HTMLTableCondition -Name 'SuggestedAction' -Value 'Skip assesment' -BackgroundColor LightGreen -ComparisonType string } if ($Script:Reporting['GPOFiles']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOFiles']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrGPORedirects = [ordered] @{ Name = 'Group Policies With Redirected SYSVOL' Enabled = $false ActionRequired = $null Data = $null Execute = { Get-GPOZaurrRedirect -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { foreach ($GPO in $Script:Reporting['GPORedirect']['Data']) { $Script:Reporting['GPORedirect']['Variables']['GPOTotal']++ if ($GPO.IsCorrect -eq $true) { $Script:Reporting['GPORedirect']['Variables']['GPOIsCorrect']++ } else { $Script:Reporting['GPORedirect']['Variables']['GPOIsNotCorrect']++ } } if ($Script:Reporting['GPORedirect']['Variables']['GPOIsNotCorrect'] -gt 0) { $Script:Reporting['GPORedirect']['ActionRequired'] = $true } else { $Script:Reporting['GPORedirect']['ActionRequired'] = $false } } Variables = @{ GPOTotal = 0 GPOIsCorrect = 0 GPOIsNotCorrect = 0 } Overview = { } Summary = { New-HTMLText -TextBlock { "Group Policies are stored in Active Directory and SYSVOL. SYSVOL is a folder shared by the domain controllers to hold its logon scripts, " "group policy data, and other domain-wide data which needs to be available anywhere there is a domain controller. " "SYSVOL provides a default location for files that must be shared for common access throughout a domain. " "However it is possible to redirect SYSVOL to a different location by modifying " "gPCFileSysPath " "attribute of a GPO. " "This is not recommended and should be avoided, but it can also be a sign of compromise." "This report shows which GPOs are redirected and which are not. " } -FontSize 10pt -LineBreak -FontWeight normal, normal, normal, normal, bold, normal, normal, normal -Color None, None, None, None, RedBerry, None, None, None New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies in total: ', $Script:Reporting['GPORedirect']['Variables']['GPOTotal'] -FontWeight normal, bold New-HTMLListItem -Text 'Group Policies without redirects: ', $Script:Reporting['GPORedirect']['Variables']['GPOIsCorrect'] -FontWeight normal, bold -Color None, MintGreen New-HTMLListItem -Text 'Group Policies with redirects: ', $Script:Reporting['GPORedirect']['Variables']['GPOIsNotCorrect'] -FontWeight normal, bold -Color None, RedBerry } -FontSize 10pt New-HTMLText -TextBlock { "If you notice any GPO with redirect, you should investigate it. " } -FontSize 10pt -LineBreak } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPORedirect']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'No redirects', 'With redirects' -Color MintGreen, MediumOrchid New-ChartBar -Name 'No redirection' -Value $Script:Reporting['GPORedirect']['Variables']['GPOIsCorrect'], $Script:Reporting['GPORedirect']['Variables']['GPOIsNotCorrect'] } -Title 'Group Policies with redirects' -TitleAlignment center } } New-HTMLSection -Name 'Group Policies showing redirects (if any)' { New-HTMLTable -DataTable $Script:Reporting['GPORedirect']['Data'] -Filtering { New-HTMLTableCondition -Name 'IsCorrect' -Value $false -BackgroundColor Salmon -ComparisonType bool -FailBackgroundColor MintGreen -HighlightHeaders 'IsCorrect', 'Path', 'ExpectedPath' } -ScrollX -PagingOptions 7, 15, 30, 45, 60 } if ($Script:Reporting['GPORedirect']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPORedirect']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrGPOUpdates = [ordered] @{ Name = 'Group Policies added last 7 days' Enabled = $false Action = $null Data = $null Execute = { Get-GPOZaurrUpdates -DateRange Last7Days -DateProperty WhenCreated -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { foreach ($GPO in $Script:Reporting['GPOUpdates']['Data']) { $Script:Reporting['GPOUpdates']['Variables']['GPOTotal']++ if ($GPO.LinksEnabledCount -eq 0) { $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutEnabledLinks']++ } else { $Script:Reporting['GPOUpdates']['Variables']['GPOWithEnabledLinks']++ } if ($GPO.AffectedCount -eq 0) { $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutAffectedObjects']++ } } } Variables = @{ GPOTotal = 0 GPOWithoutEnabledLinks = 0 GPOWithEnabledLinks = 0 GPOWithoutAffectedObjects = 0 } Overview = { } Summary = { New-HTMLText -TextBlock { "Group Policies are important part of Active Directory. Knowing when those are created and what they affect is important part of admins work." "This report shows which GPOs were created in last 7 days and how many objects those are affecting." } -FontSize 10pt -LineBreak New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies added in last 7 days: ', $Script:Reporting['GPOUpdates']['Variables']['GPOTotal'] -FontWeight normal, bold New-HTMLListItem -Text 'Group Policies without enabled links: ', $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutEnabledLinks'] -FontWeight normal, bold New-HTMLListItem -Text 'Group Policies with enabled links: ', $Script:Reporting['GPOUpdates']['Variables']['GPOWithEnabledLinks'] -FontWeight normal, bold New-HTMLListItem -Text 'Group Policies without affected objects: ', $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutAffectedObjects'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -TextBlock { "If you notice any GPO that is not working or against best practices please reach out to your collegues to confirm whether this is as expected." } -FontSize 10pt -LineBreak } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOUpdates']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'No enabled links', 'Enabled links' -Color Crimson, MediumOrchid New-ChartBar -Name 'Links enabled' -Value $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutEnabledLinks'], $Script:Reporting['GPOUpdates']['Variables']['GPOWithEnabledLinks'] } -Title 'Group Policies created last 7 days' -TitleAlignment center } } New-HTMLSection -Name 'Group Policies added in last 7 days' { New-HTMLTable -DataTable $Script:Reporting['GPOUpdates']['Data'] -Filtering { New-HTMLTableCondition -Name 'AffectedCount' -Value 0 -BackgroundColor Salmon -ComparisonType number -FailBackgroundColor Goldenrod New-HTMLTableConditionGroup { New-HTMLTableCondition -Name 'LinksCount' -Value 0 -ComparisonType number New-HTMLTableCondition -Name 'LinksEnabledCount' -Value 0 -ComparisonType number } -BackgroundColor Salmon -FailBackgroundColor Goldenrod -Logic OR -HighlightHeaders 'LinksCount', 'LinksEnabledCount', 'DisplayName', 'DomainName' } -ScrollX -PagingOptions 7, 15, 30, 45, 60 } if ($Script:Reporting['GPOUpdates']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOUpdates']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrLinks = [ordered] @{ Name = 'Group Policy Links' Enabled = $true ActionRequired = $null Data = $null Execute = { Get-GPOZaurrLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Summary } Processing = { } Variables = @{ } Overview = { } Solution = { New-HTMLTable -DataTable $Script:Reporting['GPOLinks']['Data'] -Filtering -ScrollX -PagingOptions 7, 15, 30, 45, 60 -ExcludeProperty 'LinksObjects' { New-HTMLTableCondition -Name 'Linked' -Value 'True' -BackgroundColor PaleGreen -ComparisonType string -FailBackgroundColor Salmon } if ($Script:Reporting['GPOLinks']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOLinks']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrList = [ordered] @{ Name = 'Group Policy Summary' Enabled = $true Action = $null Data = $null Execute = { if ($Script:Reporting['GPOList']['Exclusions']) { Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeGroupPolicies $Script:Reporting['GPOList']['Exclusions'] } else { Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } } Processing = { $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'] = @{} $Script:Reporting['GPOList']['Variables']['GPOValidPerDomain'] = @{} $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'] = @{} $Script:Reporting['GPOList']['Variables']['GPOOptimizedPerDomain'] = @{} $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'] = @{} $Script:Reporting['GPOList']['Variables']['GPONoProblemPerDomain'] = @{} $Script:Reporting['GPOList']['Variables']['GPOApplyPermissionYesPerDomain'] = @{} $Script:Reporting['GPOList']['Variables']['GPOApplypermissionNoPerDomain'] = @{} foreach ($GPO in $Script:Reporting['GPOList']['Data']) { if (-not $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOList']['Variables']['GPOValidPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOList']['Variables']['GPOValidPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOList']['Variables']['GPOOptimizedPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOList']['Variables']['GPOOptimizedPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOList']['Variables']['GPONoProblemPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOList']['Variables']['GPONoProblemPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOList']['Variables']['GPOApplyPermissionYesPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOList']['Variables']['GPOApplyPermissionYesPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOList']['Variables']['GPOApplypermissionNoPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOList']['Variables']['GPOApplypermissionNoPerDomain'][$GPO.DomainName] = 0 } if ($GPO.Days -le $Script:Reporting['GPOList']['Variables']['GPOOlderThan']) { $Script:Reporting['GPOList']['Variables']['GPOSkip']++ } if ($GPO.Exclude -eq $true) { $Script:Reporting['GPOList']['Variables']['GPOSkipExcluded']++ } if (($GPO.Enabled -eq $false -or $GPO.Empty -eq $true -or $GPO.Linked -eq $false -or $GPO.ApplyPermission -eq $false) -and $GPO.Exclude -eq $true) { $Script:Reporting['GPOList']['Variables']['GPONotValidButExcluded']++ $Script:Reporting['GPOList']['Variables']['GPONotValidButSkippedOrExcluded']++ } elseif (($GPO.Enabled -eq $false -or $GPO.Empty -eq $true -or $GPO.Linked -eq $false -or $GPO.ApplyPermission -eq $false) -and $GPO.Days -le $Script:Reporting['GPOList']['Variables']['GPOOlderThan']) { $Script:Reporting['GPOList']['Variables']['GPONotValidButSkip']++ $Script:Reporting['GPOList']['Variables']['GPONotValidButSkippedOrExcluded']++ } if (($GPO.Enabled -eq $false -or $GPO.Empty -eq $true -or $GPO.Linked -eq $false -or $GPO.ApplyPermission -eq $false) -and $GPO.Days) { $Script:Reporting['GPOList']['Variables']['GPONotValid']++ $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOList']['Variables']['GPOValid']++ $Script:Reporting['GPOList']['Variables']['GPOValidPerDomain'][$GPO.DomainName]++ } if ($GPO.Linked -eq $false -and $GPO.Empty -eq $true) { $Script:Reporting['GPOList']['Variables']['GPOEmptyAndUnlinked']++ $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']++ $Script:Reporting['GPOList']['Variables']['GPONotLinked']++ $Script:Reporting['GPOList']['Variables']['GPOEmpty']++ } elseif ($GPO.Linked -eq $true -and $GPO.Empty -eq $true) { $Script:Reporting['GPOList']['Variables']['GPOLinkedButEmpty']++ $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']++ $Script:Reporting['GPOList']['Variables']['GPOEmpty']++ $Script:Reporting['GPOList']['Variables']['GPOLinked']++ } elseif ($GPO.Linked -eq $false) { $Script:Reporting['GPOList']['Variables']['GPONotLinked']++ $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']++ $Script:Reporting['GPOList']['Variables']['GPONotEmpty']++ } elseif ($GPO.Empty -eq $true) { $Script:Reporting['GPOList']['Variables']['GPOEmpty']++ $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']++ $Script:Reporting['GPOList']['Variables']['GPOLinked']++ } else { $Script:Reporting['GPOList']['Variables']['GPOLinked']++ $Script:Reporting['GPOList']['Variables']['GPONotEmpty']++ } if ($GPO.Enabled -eq $true) { $Script:Reporting['GPOList']['Variables']['GPOEnabled']++ } else { $Script:Reporting['GPOList']['Variables']['GPODisabled']++ } if ($GPO.ApplyPermission -eq $true) { $Script:Reporting['GPOList']['Variables']['ApplyPermissionYes']++ } else { $Script:Reporting['GPOList']['Variables']['ApplyPermissionNo']++ } if ($GPO.LinksDisabledCount -eq $GPO.LinksCount -and $GPO.LinksCount -gt 0) { $Script:Reporting['GPOList']['Variables']['GPOLinkedButLinkDisabled']++ } if ($GPO.ComputerOptimized -eq $true) { $Script:Reporting['GPOList']['Variables']['ComputerOptimizedYes']++ } else { $Script:Reporting['GPOList']['Variables']['ComputerOptimizedNo']++ } if ($GPO.ComputerProblem -eq $true) { $Script:Reporting['GPOList']['Variables']['ComputerProblemYes']++ } else { $Script:Reporting['GPOList']['Variables']['ComputerProblemNo']++ } if ($GPO.UserOptimized -eq $true) { $Script:Reporting['GPOList']['Variables']['UserOptimizedYes']++ } else { $Script:Reporting['GPOList']['Variables']['UserOptimizedNo']++ } if ($GPO.UserProblem -eq $true) { $Script:Reporting['GPOList']['Variables']['UserProblemYes']++ } else { $Script:Reporting['GPOList']['Variables']['UserProblemNo']++ } if ($GPO.UserProblem -or $GPO.ComputerProblem) { $Script:Reporting['GPOList']['Variables']['GPOWithProblems']++ } if ($GPO.Problem -eq $true) { $Script:Reporting['GPOList']['Variables']['GPOProblem']++ $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOList']['Variables']['GPONoProblem']++ $Script:Reporting['GPOList']['Variables']['GPONoProblemPerDomain'][$GPO.DomainName]++ } if ($GPO.Optimized -eq $true) { $Script:Reporting['GPOList']['Variables']['GPOOptimized']++ $Script:Reporting['GPOList']['Variables']['GPOOptimizedPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOList']['Variables']['GPONotOptimized']++ $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'][$GPO.DomainName]++ } } $Script:Reporting['GPOList']['Variables']['GPOTotal'] = $Script:Reporting['GPOList']['Data'].Count if ($Script:Reporting['GPOList']['Variables']['GPONotValid'] -gt 0 -and $Script:Reporting['GPOList']['Variables']['GPONotValidButSkippedOrExcluded'] -ne $Script:Reporting['GPOList']['Variables']['GPONotValid']) { $Script:Reporting['GPOList']['ActionRequired'] = $true } else { $Script:Reporting['GPOList']['ActionRequired'] = $false } } Variables = @{ GPOOlderThan = 30 GPONotValidPerDomain = $null GPOValidPerDomain = $null GPONotOptimizedPerDomain = $null GPOOptimizedPerDomain = $null GPOProblemPerDomain = $null GPONoProblemPerDomain = $null GPOApplyPermissionYesPerDomain = $null GPOApplyPermissionNoPerDomain = $null GPOWithProblems = 0 ComputerOptimizedYes = 0 ComputerOptimizedNo = 0 ComputerProblemYes = 0 ComputerProblemNo = 0 UserOptimizedYes = 0 UserOptimizedNo = 0 UserProblemYes = 0 UserProblemNo = 0 GPOOptimized = 0 GPONotOptimized = 0 GPOProblem = 0 GPONoProblem = 0 GPONotLinked = 0 GPOLinked = 0 GPOEmpty = 0 GPONotEmpty = 0 GPOEmptyAndUnlinked = 0 GPOEmptyOrUnlinked = 0 GPOLinkedButEmpty = 0 GPOEnabled = 0 GPODisabled = 0 GPOSkip = 0 GPOSkipExcluded = 0 GPOValid = 0 GPONotValid = 0 GPONotValidButSkip = 0 GPONotValidButExcluded = 0 GPONotValidButSkippedOrExcluded = 0 GPOLinkedButLinkDisabled = 0 GPOTotal = 0 ApplyPermissionYes = 0 ApplyPermissionNo = 0 } Overview = { } Summary = { New-HTMLText -TextBlock { "Over time Administrators add more and more group policies, as business requirements change. " "Due to neglection or thinking it may serve it's purpose later on a lot of Group Policies often have no value at all. " "Either the Group Policy is not linked to anything and just stays unlinked forever, or GPO is linked, but the link (links) are disabled or GPO is totally disabled. " "Then there are Group Policies that are targetting certain group or person and that group is removed leaving Group Policy doing nothing. " "Additionally sometimes new GPO is created without any settings or the settings are removed over time, but GPO stays in place. " } -FontSize 10pt New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies total: ', $Script:Reporting['GPOList']['Variables']['GPOTotal'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies valid: ", $Script:Reporting['GPOList']['Variables']['GPOValid'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies exclusions defined: ", $Script:Reporting['GPOList']['Variables']['GPOSkipExcluded'] -FontWeight normal, bold -Color None, DeepSkyBlue New-HTMLListItem -Text "Group Policies ", "NOT", " valid: ", $Script:Reporting['GPOList']['Variables']['GPONotValid'] -FontWeight normal, bold, normal, bold { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies that are unlinked (are not doing anything currently): ', $Script:Reporting['GPOList']['Variables']['GPONotLinked'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies that are empty (have no settings): ", $Script:Reporting['GPOList']['Variables']['GPOEmpty'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies that are linked, but empty: ", $Script:Reporting['GPOList']['Variables']['GPOLinkedButEmpty'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies that are linked, but link disabled: ", $Script:Reporting['GPOList']['Variables']['GPOLinkedButLinkDisabled'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies that are disabled (both user/computer sections): ", $Script:Reporting['GPOList']['Variables']['GPODisabled'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies that have no Apply Permission: ", $Script:Reporting['GPOList']['Variables']['ApplyPermissionNo'] -FontWeight normal, bold } } -Color Black, Red, Black, Red, Black New-HTMLListItem -Text @( "Group Policies ", "NOT", " valid, to skip (because of age): ", $Script:Reporting['GPOList']['Variables']['GPONotValidButSkip'], " (modified less than $($Script:Reporting['GPOList']['Variables']['GPOOlderThan']) days ago)" ) -FontWeight 'normal', 'bold', 'normal', 'bold', 'normal' -Color 'Black', 'Red', 'Black', 'Red', 'Black' New-HTMLListItem -Text @( "Group Policies ", "NOT", " valid, to skip (because of exclusions): ", $Script:Reporting['GPOList']['Variables']['GPONotValidButExcluded'] ) -FontWeight 'normal', 'bold', 'normal', 'bold', 'normal' -Color 'Black', 'Red', 'Black', 'Red', 'Black' New-HTMLListItem -Text "Group Policies recently modified: ", $Script:Reporting['GPOList']['Variables']['GPOSkip'], " (modified less than $($Script:Reporting['GPOList']['Variables']['GPOOlderThan']) days ago)" -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text "Keep in mind that each GPO can match multiple conditions such as being empty and unlinked and disabled at the same time. We're only deleting GPO once." -FontSize 10pt New-HTMLText -Text @( 'All ', 'empty', ' or ', 'unlinked', ' or ', 'disabled', ' Group Policies can be automatically deleted. Please review output in the table and follow steps below table to cleanup Group Policies. ', 'GPOs that have content, but are disabled require manual intervention. ', "If performance is an issue you should consider disabling user or computer sections of GPO when those are not used. " ) -FontSize 10pt -FontWeight normal, bold, normal, bold, normal, bold, normal, normal, normal, normal New-HTMLText -LineBreak New-HTMLText -Text "Additionally, we're reviewing Group Policies that have their section disabled, but contain data." -FontSize 10pt New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies with problems: ', $Script:Reporting['GPOList']['Variables']['GPOWithProblems'] -FontWeight normal, bold { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies that have content (computer), but are disabled: ', $Script:Reporting['GPOList']['Variables']['ComputerProblemYes'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies that have content (user), but are disabled: ", $Script:Reporting['GPOList']['Variables']['UserProblemYes'] -FontWeight normal, bold } } } -FontSize 10pt New-HTMLText -Text @( "Such policies require manual review from whoever owns them. " "It could be a mistake tha section was disabled while containing data or that content is no longer needed in which case it should be deleted. " "This can't be auto-handled and is INFORMATIONAL only. " ) -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -LineBreak New-HTMLText -Text "Moreover, for best performance it's recommended that if there are no settings of certain kind (Computer or User settings) it's best to disable whole section. " -FontSize 10pt New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies with optimization: ' -FontWeight normal, bold { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies that are optimized (computer) ', $Script:Reporting['GPOList']['Variables']['ComputerOptimizedYes'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies that are optimized (user): ", $Script:Reporting['GPOList']['Variables']['UserOptimizedYes'] -FontWeight normal, bold } } New-HTMLListItem -Text 'Group Policies without optimization: ' -FontWeight normal, bold { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies that are not optimized (computer): ', $Script:Reporting['GPOList']['Variables']['ComputerOptimizedNo'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies that are not optimized (user): ", $Script:Reporting['GPOList']['Variables']['UserOptimizedNo'] -FontWeight normal, bold } } } -FontSize 10pt New-HTMLText -Text @( "This means " $Script:Reporting['GPOList']['Variables']['GPONotOptimized'] " could be optimized for performance reasons. " ) -FontSize 10pt -FontWeight normal, bold, normal New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -FontSize 10pt -Text "To generate up to date report please execute: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Install-Module GPOZaurr -Force', ' or ', ' install module manually.' -Color RoyalBlue, None, None New-HTMLListItem -Text 'Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPListBefore.html -Verbose -Type GPOList' -Color RoyalBlue } -FontSize 10pt New-HTMLText -FontSize 10pt -Text 'Steps above will generate above summary with more details allowing you to get up to date report and steps on how to fix it.' if ($Script:Reporting['GPOList']['Exclusions']) { New-HTMLText -LineBreak New-HTMLText -Text @( "While preparing this report following exclusions were defined. " "Please make sure that when you execute your steps to include those exclusions to prevent any issues. " ) -FontSize 10pt -FontWeight bold, normal -Color Red, None -LineBreak New-HTMLText -Text "Code to use for exclusions: " -FontSize 10pt -FontWeight bold -LineBreak $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOList']['Exclusions'] if ($Code) { New-HTMLCodeBlock -Code $Code -Style powershell } } } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOList']['Summary'] } New-HTMLPanel { New-HTMLChart -Title 'Group Policies Empty & Unlinked' { New-ChartBarOptions -Type barStacked New-ChartLegend -Names 'Yes', 'No' -Color SpringGreen, Salmon New-ChartBar -Name 'Linked' -Value $Script:Reporting['GPOList']['Variables']['GPOLinked'], $Script:Reporting['GPOList']['Variables']['GPONotLinked'] New-ChartBar -Name 'Not Empty' -Value $Script:Reporting['GPOList']['Variables']['GPONotEmpty'], $Script:Reporting['GPOList']['Variables']['GPOEmpty'] New-ChartBar -Name 'Enabled' -Value $Script:Reporting['GPOList']['Variables']['GPOEnabled'], $Script:Reporting['GPOList']['Variables']['GPODisabled'] New-ChartBar -Name 'Apply Permission' -Value $Script:Reporting['GPOList']['Variables']['ApplyPermissionYes'], $Script:Reporting['GPOList']['Variables']['ApplyPermissionNo'] New-ChartBar -Name 'Valid' -Value $Script:Reporting['GPOList']['Variables']['GPOValid'], $Script:Reporting['GPOList']['Variables']['GPONotValid'] New-ChartBar -Name 'Optimized (for speed)' -Value $Script:Reporting['GPOList']['Variables']['GPOOptimized'], $Script:Reporting['GPOList']['Variables']['GPONotOptimized'] New-ChartBar -Name 'No problem' -Value $Script:Reporting['GPOList']['Variables']['GPONoProblem'], $Script:Reporting['GPOList']['Variables']['GPOProblem'] New-ChartBar -Name 'No problem (computers)' -Value $Script:Reporting['GPOList']['Variables']['ComputerProblemNo'], $Script:Reporting['GPOList']['Variables']['ComputerProblemYes'] New-ChartBar -Name 'No problem (users)' -Value $Script:Reporting['GPOList']['Variables']['UserProblemNo'], $Script:Reporting['GPOList']['Variables']['UserProblemYes'] New-ChartBar -Name 'Optimized Computers' -Value $Script:Reporting['GPOList']['Variables']['ComputerOptimizedYes'], $Script:Reporting['GPOList']['Variables']['ComputerOptimizedNo'] New-ChartBar -Name 'Optimized Users' -Value $Script:Reporting['GPOList']['Variables']['UserOptimizedYes'], $Script:Reporting['GPOList']['Variables']['UserOptimizedNo'] } -TitleAlignment center } } New-HTMLSection -Name 'Group Policies List' { New-HTMLContainer { New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt New-HTMLList { New-HTMLListItem -FontWeight bold, normal -Text "Empty", " - means GPO has currently no content. It could be there was content, but it was removed, or that it never had content. " New-HTMLListItem -FontWeight bold, normal -Text "Linked", " - means GPO is linked or unlinked. We need at least one link that is enabled to mark it as linked. If GPO is linked, but all links are disabled, it's not linked. " New-HTMLListItem -FontWeight bold, normal -Text "Enabled", " - means GPO has at least one section enabled. If enabled is set to false that means both sections are disabled, and therefore GPO is not active. " New-HTMLListItem -FontWeight bold, normal -Text "Optimized", " - means GPO section that is not in use is disabled. If section (user or computer) is enabled and there is no content, it's not optimized. " New-HTMLListItem -FontWeight bold, normal -Text "Problem", " - means GPO has one or more section (user or computer) that is disabled, yet there is content in it. " New-HTMLListItem -FontWeight bold, normal -Text "ApplyPermission", " - means GPO has no Apply Permission. This means there's no user/computer/group it's applicable to. " } -FontSize 10pt New-HTMLTable -DataTable $Script:Reporting['GPOList']['Data'] -Filtering { New-HTMLTableCondition -Name 'Exclude' -Value $true -BackgroundColor DeepSkyBlue -ComparisonType string -Row New-HTMLTableCondition -Name 'Empty' -Value $true -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'Linked' -Value $false -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'Enabled' -Value $false -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'Optimized' -Value $false -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'Problem' -Value $true -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'ApplyPermission' -Value $false -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'ComputerProblem' -Value $true -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'UserProblem' -Value $true -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'ComputerOptimized' -Value $false -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'UserOptimized' -Value $false -BackgroundColor Salmon -TextTransform capitalize -ComparisonType string New-HTMLTableCondition -Name 'Empty' -Value $false -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'Linked' -Value $true -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'Enabled' -Value $true -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'Optimized' -Value $true -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'Problem' -Value $false -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'ApplyPermission' -Value $true -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'ComputerProblem' -Value $false -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'UserProblem' -Value $false -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'ComputerOptimized' -Value $true -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'UserOptimized' -Value $true -BackgroundColor SpringGreen -TextTransform capitalize -ComparisonType string } -PagingOptions 7, 15, 30, 45, 60 -ScrollX -ExcludeProperty 'LinksObjects', 'GPOObject', 'ACL', 'Size' } } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix - Empty & Unlinked & Disabled Group Policies' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLText -Text 'Following steps will guide you how to remove empty or unlinked group policies' New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } if ($Script:Reporting['GPOList']['Exclusions']) { New-HTMLWizardStep -Name 'Required exclusions' { New-HTMLText -Text @( "While preparing this report following exclusions were defined. " "Please make sure that when you execute your steps to include those exclusions to prevent any issues. " ) $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOList']['Exclusions'] if ($Code) { New-HTMLCodeBlock -Code $Code -Style powershell } } } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with removal. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrEmptyUnlinked.html -Verbose -Type GPOList } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment." "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $GPOOutput = Get-GPOZaurr $GPOOutput | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Make a backup' { New-HTMLText -TextBlock { "The process of deleting Group Policies is final. Once GPO is removed - it's gone. " "To make sure you can recover deleted GPO please make sure to back them up. " } New-HTMLCodeBlock -Code { $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All $GPOSummary | Format-Table } New-HTMLText -TextBlock { "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. " "Keep in mind that Remove-GPOZaurr command also has a backup feature built-in. " "It's possible to skip this backup and use the backup provided as part of Remove-GPOZaurr command. " } } New-HTMLWizardStep -Name 'Excluding Group Policies' { New-HTMLText -Text @( "Remove-GPOZaurr", " cmdlet that you will use in next steps is pretty advanced in what it can do. It can remove one or multiple types of problems at the same time. " "That means you can pick just EMPTY, just UNLINKED or just DISABLED but also a mix of them if you want. " "It also provides a way to exclude some GPOs from being removed even though they match condition. " "You would do so using following approach " ) -FontSize 10pt -FontWeight bold, normal New-HTMLCodeBlock -Code { $Exclusions = { Skip-GroupPolicy -Name 'TEST | Drive Mapping' Skip-GroupPolicy -Name 'Default Domain Policy' Skip-GroupPolicy -Name 'Default Domain Controllers Policy' -DomaiName 'JustOneDomain' '{D39BF08A-87BF-4662-BFA0-E56240EBD5A2}' 'COMPUTERS | Enable Sets' } Remove-GPOZaurr -RequireDays 30 -Type Empty, Unlinked, Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' -WhatIf -ExcludeGroupPolicies $Exclusions } New-HTMLText -Text @( "Code above when executed will scan YourDomainYouHavePermissionsFor, find all empty, unlinked, disabled group policies, backup any GPO just before it's to be deleted to `$Env:UserProfile\Desktop\GPO. " "If it's not able to make a backup it will terminate. Additionally it will skip all 3 group policies that have been shown above. " "You can one or multiple group policies to be skipped. " "Now go ahead and find what's there" ) } New-HTMLWizardStep -Name 'Remove GPOs (Manual)' { New-HTMLText -Text @( "Please condider deleting GPOs manually if the amount of GPOs to delete is small enough. " "Deleting 1-5-30 GPOs manually on domain of 4000 GPOs will be much faster than doing it in controlled manner with automated steps mentioned on next steps. " "What can take 30 minutes manually, can take 8 hours using automated script, because of amount of checks required by the script over and over to delete a single GPO. " ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black } New-HTMLWizardStep -Name 'Remove GPOs that are EMPTY' { New-HTMLText -Text @( "Following command when executed removes every ", "EMPTY" " Group Policy. Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal.", "Make sure to use BackupPath which will make sure that for each GPO that is about to be deleted a backup is made to folder on a desktop." "You can skip parameters related to backup if you did backup all GPOs prior to running remove command. " ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Empty -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Empty -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be deleted matches expected data. " } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Empty -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Empty -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed deletes only first X empty GPOs. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur." "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly." "Please make sure to check if backup is made as well before going all in." } New-HTMLText -Text "If there's nothing else to be deleted, we can skip to next step step" } New-HTMLWizardStep -Name 'Remove GPOs that are UNLINKED' { New-HTMLText -Text @( "Following command when executed removes every ", "NOT LINKED" " Group Policy. Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal.", "Make sure to use BackupPath which will make sure that for each GPO that is about to be deleted a backup is made to folder on a desktop." "You can skip parameters related to backup if you did backup all GPOs prior to running remove command. " ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be deleted matches expected data. " } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed deletes only first X unlinked GPOs. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur." "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly." "Please make sure to check if backup is made as well before going all in." } New-HTMLText -Text "If there's nothing else to be deleted, we can skip to next step step" } New-HTMLWizardStep -Name 'Remove GPOs that are DISABLED' { New-HTMLText -Text @( "Following command when executed removes every ", "DISABLED" " Group Policy. Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal.", "Make sure to use BackupPath which will make sure that for each GPO that is about to be deleted a backup is made to folder on a desktop." "You can skip parameters related to backup if you did backup all GPOs prior to running remove command. " ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black New-HTMLText -TextBlock { "" } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be deleted matches expected data. " } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed deletes only first X disabled GPOs. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " "Please make sure to check if backup is made as well before going all in." } New-HTMLText -Text "If there's nothing else to be deleted, we can skip to next step step." } New-HTMLWizardStep -Name 'Remove GPOs that do not APPLY' { New-HTMLText -Text @( "Following command when executed removes every ", "NoApplyPermission" " Group Policy. Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal.", "Make sure to use BackupPath which will make sure that for each GPO that is about to be deleted a backup is made to folder on a desktop." "You can skip parameters related to backup if you did backup all GPOs prior to running remove command. " ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black New-HTMLText -TextBlock { "" } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type NoApplyPermission -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type NoApplyPermission -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be deleted matches expected data. " } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type NoApplyPermission -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurr -RequireDays 30 -Type NoApplyPermission -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed deletes only first X NoApplyPermission GPOs. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " "Please make sure to check if backup is made as well before going all in." } New-HTMLText -Text "If there's nothing else to be deleted, we can skip to next step step." } New-HTMLWizardStep -Name 'Optimize GPOs (optional)' { New-HTMLText -Text @( "Following command when executed disables user or computer section when there's no content for given type. ", "This makes sure that when GPO is processed for application it's empty section is ignored. Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental disabling of sections." ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black New-HTMLCodeBlock -Code { Optimize-GPOZaurr -All -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Optimize-GPOZaurr -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be optimized matches expected data. " } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Optimize-GPOZaurr -All -LimitProcessing 2 -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Optimize-GPOZaurr -All -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed optimizes only first X not optimized GPOs. Use LimitProcessing parameter to prevent mass changes and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " "Please make sure to check if backup is made as well before going all in." } New-HTMLText -Text "If there's nothing else to be optimized, we can skip to next step step." } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrEmptyUnlinkedAfter.html -Verbose -Type GPOList } New-HTMLText -Text "If there are no more problems to solve, GPOs to optimize in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOList']['WarningsAndErrors']) { New-HTMLSection -Name 'Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOList']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrMissingFiles = [ordered] @{ Name = 'Group Policies with missing files' Enabled = $true Action = $null Data = $null Execute = { Get-GPOZaurrMissingFiles -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -GPOName $GPOName -GPOGUID $GPOGUID | Sort-Object -Property ErrorCount -Descending } Processing = { foreach ($GPO in $Script:Reporting['GPOBrokenPartially']['Data']) { if ($GPO.ErrorCount -gt 0) { $Script:Reporting['GPOBrokenPartially']['Variables']['RequireFixing']++ } } if ($Script:Reporting['GPOBrokenPartially']['Variables']['RequireFixing'] -gt 0) { $Script:Reporting['GPOBrokenPartially']['ActionRequired'] = $true } else { $Script:Reporting['GPOBrokenPartially']['ActionRequired'] = $false } } Variables = @{ RequireFixing = 0 } Overview = { } Resources = @( ) Summary = { New-HTMLText -FontSize 10pt -TextBlock { "Group Policies can become broken for various reasons. One of the common reasons is when GPOs are created and then deleted without being properly removed from Active Directory. " "In other cases, it can be due to replication issues, or simply due to corruption. " "This can lead to GPOs not being applied as expected, or not being applied at all. " "If random files are missing from GPOs, it's important to fix them to ensure that GPOs are applied as expected. " "This report provides you with list of GPOs that have missing files. " "Once files are missing, it's usually best to restore them from backup (if available) or remove the given section completely. " "It's not possible to restore missing files from Active Directory directly or manually. " } -LineBreak New-HTMLText -Text 'As it stands currently there are ', $Script:Reporting['GPOBrokenPartially']['Variables']['RequireFixing'], ' error requring fixing. ' -FontSize 10pt -FontWeight normal, bold, normal } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOBrokenPartially']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartLegend -Names 'Bad' -Color Salmon New-ChartBar -Name 'Missing Files' -Value $Script:Reporting['GPOBrokenPartially']['Variables']['RequireFixing'] } -Title 'Group Policies with Missing Files' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Missing Files' { New-HTMLTable -DataTable $Script:Reporting['GPOBrokenPartially']['Data'] -Filtering { New-HTMLTableCondition -Name 'ErrorCount' -Value 0 -BackgroundColor LightGreen -ComparisonType number -FailBackgroundColor Salmon -HighlightHeaders 'ErrorCount', 'ErrorCategory', 'ErrorDetails' } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix - Remove duplicate (CNF) objects' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing duplicate GPO objects. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrMissingFilesBefore.html -Verbose -Type GPOMissingFiles } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step. " } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $GPOOutput = Get-GPOZaurrMissingFiles -BrokenOnly $GPOOutput | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Remove Broken objects' { New-HTMLText -Text "There is no automated way to fix missing files. You need to manually fix them. " New-HTMLText -Text "You can do so by restoring files from backup or removing section completly. " } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrMissingFilesAfter.html -Verbose -Type GPOMissingFiles } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOBrokenPartially']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOZaurrMissingFiles']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrNetLogonOwners = [ordered] @{ Name = 'NetLogon Owners' Enabled = $true ActionRequired = $null Data = $null Execute = { Get-GPOZaurrNetLogon -OwnerOnly -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { foreach ($File in $Script:Reporting['NetLogonOwners']['Data']) { $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwners']++ if ($File.OwnerType -eq 'WellKnownAdministrative') { $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrative']++ } elseif ($File.OwnerType -eq 'Administrative') { $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrative']++ } else { $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersNotAdministrative']++ } if ($File.OwnerSid -eq 'S-1-5-32-544') { $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrators']++ } elseif ($File.OwnerType -in 'WellKnownAdministrative', 'Administrative') { $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrativeNotAdministrators']++ $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix']++ } else { $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix']++ } } if ($Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix'] -gt 0) { $Script:Reporting['NetLogonOwners']['ActionRequired'] = $true } else { $Script:Reporting['NetLogonOwners']['ActionRequired'] = $false } } Variables = @{ NetLogonOwners = 0 NetLogonOwnersAdministrators = 0 NetLogonOwnersNotAdministrative = 0 NetLogonOwnersAdministrative = 0 NetLogonOwnersAdministrativeNotAdministrators = 0 NetLogonOwnersToFix = 0 } Overview = { } Summary = { New-HTMLText -TextBlock { "NetLogon is crucial part of Active Directory. Files stored there are available on each and every computer or server in the company. " "Keeping those files clean and secure is very important task. " "It's important that NetLogon file owners are set to BUILTIN\Administrators (SID: S-1-5-32-544). " "Owners have full control over the file object. Current owner of the file may be an Administrator but it doesn't guarentee that he/she will be in the future. " "That's why as a best-practice it's recommended to change any non-administrative owners to BUILTIN\Administrators, and even Administrative accounts should be replaced with it. " } -FontSize 10pt New-HTMLList -Type Unordered { New-HTMLListItem -Text 'NetLogon Files in Total: ', $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwners'] -FontWeight normal, bold New-HTMLListItem -Text 'NetLogon BUILTIN\Administrators as Owner: ', $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrators'] -FontWeight normal, bold New-HTMLListItem -Text "NetLogon Owners requiring change: ", $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix'] -FontWeight normal, bold { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Not Administrative: ', $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersNotAdministrative'] -FontWeight normal, bold New-HTMLListItem -Text 'Administrative, but not BUILTIN\Administrators: ', $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrativeNotAdministrators'] -FontWeight normal, bold } } } -FontSize 10pt New-HTMLText -Text "Follow the steps below table to get NetLogon Owners into compliant state." -FontSize 10pt } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['NetLogonOwners']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Correct Owners' -Value $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrators'] -Color SpringGreen New-ChartPie -Name 'Incorrect Owners' -Value $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix'] -Color Salmon } -Title 'NetLogon Owners' -TitleAlignment center } } New-HTMLSection -Name 'NetLogon File Owners' { New-HTMLTable -DataTable $Script:Reporting['NetLogonOwners']['Data'] -Filtering { New-HTMLTableCondition -Name 'OwnerSid' -Value "S-1-5-32-544" -BackgroundColor LightGreen -ComparisonType string New-HTMLTableCondition -Name 'OwnerSid' -Value "S-1-5-32-544" -BackgroundColor Salmon -ComparisonType string -Operator ne New-HTMLTableCondition -Name 'OwnerType' -Value "WellKnownAdministrative" -BackgroundColor LightGreen -ComparisonType string -Operator eq New-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor LightGreen -ComparisonType string -Operator eq New-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor Salmon -ComparisonType string -Operator ne } -ScrollX -PagingOptions 7, 15, 30, 45, 60 } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix NetLogon Owners ' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with removal. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrNetLogonBefore.html -Verbose -Type NetLogonOwners } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment." "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $NetLogonOutput = Get-GPOZaurrNetLogon -OwnerOnly -Verbose $NetLogonOutput | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Set non-compliant file owners to BUILTIN\Administrators' { New-HTMLText -Text "Following command when executed runs internally command that lists all file owners and if it doesn't match changes it BUILTIN\Administrators. It doesn't change compliant owners." New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black New-HTMLCodeBlock -Code { Repair-GPOZaurrNetLogonOwner -Verbose -WhatIf } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Repair-GPOZaurrNetLogonOwner -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data. " } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start replacement of owners process): " -LineBreak -FontWeight bold New-HTMLText -TextBlock { "This command when executed sets new owner only on first X non-compliant NetLogon files. Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " } New-HTMLCodeBlock -Code { Repair-GPOZaurrNetLogonOwner -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Repair-GPOZaurrNetLogonOwner -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrNetLogonAfter.html -Verbose -Type NetLogonOwners } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['NetLogonOwners']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['NetLogonOwners']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrNetLogonPermissions = [ordered] @{ Name = 'NetLogon Permissions' Enabled = $true ActionRequired = $null Data = $null Execute = { Get-GPOZaurrNetLogon -SkipOwner -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'] = @{} $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'] = @{} $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'] = @{} $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'] = @{} foreach ($File in $Script:Reporting['NetLogonPermissions']['Data']) { if (-not $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'][$File.DomainName]) { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'][$File.DomainName] = 0 } if (-not $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'][$File.DomainName]) { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'][$File.DomainName] = 0 } if (-not $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'][$File.DomainName]) { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'][$File.DomainName] = 0 } if (-not $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'][$File.DomainName]) { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'][$File.DomainName] = 0 } if ($File.Status -eq 'Review permission required') { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionReviewRequired']++ if ($File.FileSystemRights -like '*Modify*') { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReview']++ $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'][$File.DomainName]++ } elseif ($File.FileSystemRights -like '*Write*') { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReview']++ $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'][$File.DomainName]++ } elseif ($File.FileSystemRights -like '*FullControl*') { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReview']++ $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'][$File.DomainName]++ } else { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOtherReview']++ } } elseif ($File.Status -eq 'Removal permission required') { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequired']++ $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'][$File.DomainName]++ if ($File.PrincipalObjectClass -in 'user', 'computer') { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredBecauseObject']++ } else { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredBecauseUnknown']++ } } elseif ($File.Status -eq 'Not assesed') { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionNotAssesed']++ } else { $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOK']++ } } if ($Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequired'] -gt 0 -or $Script:Reporting['NetLogonPermissions']['Variables']['PermissionReviewRequired'] -gt 0) { $Script:Reporting['NetLogonPermissions']['ActionRequired'] = $true } else { $Script:Reporting['NetLogonPermissions']['ActionRequired'] = $false } } Variables = @{ PermissionReviewRequired = 0 PermissionRemovalRequired = 0 PermissionOK = 0 PermissionNotAssesed = 0 PermissionWriteReview = 0 PermissionFullControlReview = 0 PermissionModifyReview = 0 PermissionOtherReview = 0 PermissionRemovalRequiredBecauseObject = 0 PermissionRemovalRequiredBecauseUnknown = 0 PermissionWriteReviewPerDomain = $null PermissionFullControlReviewPerDomain = $null PermissionModifyReviewPerDomain = $null PermissionRemovalRequiredPerDomain = $null } Overview = { } Summary = { New-HTMLText -TextBlock { "NetLogon is crucial part of Active Directory. Files stored there are available on each and every computer or server in the company. " "Keeping those files clean and secure is very important task. " "Each file stored on NETLOGON has it's own permissions. " "It's important that crucial permissions such as FullControl, Modify or Write permissions are only applied to proper, trusted groups of users. " "Additionally permissions for FullControl, Modify or Write should not be granted to direct users or computers. Only groups are allowed! " "" } -FontSize 10pt New-HTMLText -Text 'Assesment overall: ' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Permissions that look ok: ' { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Assesed and as expected ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOK'] -FontWeight normal, bold New-HTMLListItem -Text 'Not assesed, but not critical (read/execute only) ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionNotAssesed'] -FontWeight normal, bold } } New-HTMLListItem -Text 'Permissions requiring review:' { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Full control permissions ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReview'] -FontWeight normal, bold New-HTMLListItem -Text 'Modify permissions ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReview'] -FontWeight normal, bold New-HTMLListItem -Text 'Write permissions ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReview'] -FontWeight normal, bold } } New-HTMLListItem -Text 'Permissions requiring removal: ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequired'] -FontWeight normal, bold { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Because of object type (user/computer) ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredBecauseObject'] -FontWeight normal, bold New-HTMLListItem -Text 'Because of unknown permissions ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredBecauseUnknown'] -FontWeight normal, bold } } } -FontSize 10pt -LineBreak New-HTMLText -Text 'Assesment split per domain (will require permissions to fix): ' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires review of ", $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'][$Domain], " full control" -FontWeight normal, bold, normal New-HTMLListItem -Text "$Domain requires review of ", $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'][$Domain], " modify permission" -FontWeight normal, bold, normal New-HTMLListItem -Text "$Domain requires review of ", $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'][$Domain], " write permission" -FontWeight normal, bold, normal New-HTMLListItem -Text "$Domain requires removal of ", $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'][$Domain], " permissions" -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text "Please review output in table and follow the steps below table to get NetLogon permissions in order." -FontSize 10pt } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['NetLogonPermissions']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Full Control requiring review' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReview'] -Color Crimson New-ChartPie -Name 'Modify requiring review' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReview'] -Color Plum New-ChartPie -Name 'Write requiring review' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReview'] -Color LightCoral New-ChartPie -Name 'Permissions OK' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOK'] -Color LightGreen New-ChartPie -Name 'Permissions ReadOnly/Execute' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionNotAssesed'] -Color Aqua } -Title 'NetLogon Permissions' -TitleAlignment center } } New-HTMLSection -Name 'NetLogon Files List' { New-HTMLTable -DataTable $Script:Reporting['NetLogonPermissions']['Data'] -Filtering { New-HTMLTableCondition -Name 'PrincipalType' -Value "Unknown" -BackgroundColor Salmon -ComparisonType string -Operator eq -Row New-HTMLTableCondition -Name 'PrincipalType' -Value "WellKnownAdministrative" -BackgroundColor LightGreen -ComparisonType string -Operator eq -Row New-HTMLTableCondition -Name 'Status' -Value "Review permission required" -BackgroundColor PaleGoldenrod -ComparisonType string -Operator eq New-HTMLTableCondition -Name 'Status' -Value "Removal permission required" -BackgroundColor Salmon -ComparisonType string -Operator eq -Row New-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor LightGreen -ComparisonType string -Operator eq } -ScrollX -PagingOptions 7, 15, 30, 45, 60 } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix NetLogon Permissions ' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with removal. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrNetLogonBefore.html -Verbose -Type NetLogonPermissions } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step. " } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $NetLogonOutput = Get-GPOZaurrNetLogon -SkipOwner -Verbose $NetLogonOutput | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Remove permissions manually for non-compliant users/groups' { New-HTMLText -Text @( "In case of NETLOGON permissions it's impossible to tell what in a given moment for given domain should be automatically removed except for the very obvious ", "unknown ", 'permissions. Domain Admins have to make their assesment on and remove permissions from users or groups that ' "they think do not belong. " ) -FontWeight normal, bold, normal } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrNetLogonAfter.html -Verbose -Type NetLogonPermissions } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['NetLogonPermissions']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['NetLogonPermissions']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrOrganizationalUnit = [ordered] @{ Name = 'Group Policy Organizational Units' Enabled = $true ActionRequired = $null Data = $null Execute = { if ($Script:Reporting['GPOOrganizationalUnit']['Exclusions']) { Get-GPOZaurrOrganizationalUnit -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeOrganizationalUnit $Script:Reporting['GPOOrganizationalUnit']['Exclusions'] } else { Get-GPOZaurrOrganizationalUnit -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } } Processing = { $Script:Reporting['GPOOrganizationalUnit']['Variables']['RequiresDiffFixPerDomain'] = @{} $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'] = @{} foreach ($OU in $Script:Reporting['GPOOrganizationalUnit']['Data']) { $Script:Reporting['GPOOrganizationalUnit']['Variables']['TotalOU']++ if (-not $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName]) { $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName] = 0 } if ($OU.Status -contains 'Unlink GPO' -and $OU.Status -contains 'Delete OU') { $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPOEmpty']++ $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFix']++ $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName]++ } elseif ($OU.Status -contains 'Unlink GPO') { $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPO']++ $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFix']++ $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName]++ } elseif ($OU.Status -contains 'Delete OU') { $Script:Reporting['GPOOrganizationalUnit']['Variables']['DeleteOU']++ } elseif ($OU.Status -contains 'Excluded' -or $OU.Status -contains 'Excluded, Default OU') { $Script:Reporting['GPOOrganizationalUnit']['Variables']['Excluded']++ $null = $Script:Reporting['GPOOrganizationalUnit']['Variables']['ExcludedOU'].Add($OU.Organizationalunit) } else { $Script:Reporting['GPOOrganizationalUnit']['Variables']['Legitimate']++ } } if ($Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFix'] -gt 0) { $Script:Reporting['GPOOrganizationalUnit']['ActionRequired'] = $true } else { $Script:Reporting['GPOOrganizationalUnit']['ActionRequired'] = $false } } Variables = @{ TotalOU = 0 UnlinkGPO = 0 UnlinkGPOEmpty = 0 DeleteOU = 0 Legitimate = 0 Excluded = 0 ExcludedOU = [System.Collections.Generic.List[string]]::new() WillFix = 0 WillFixPerDomain = $null } Overview = { } Summary = { New-HTMLText -FontSize 10pt -Text @( "In most Active Directories there are a lot of Organizational Units that have different use cases to store different type of objects. " "As Active Directories change over time you can often find Organizational Units with linked GPOs and no objects inside. " "In some cases thats's expected, but in some cases it's totally unnessecary, and for very large AD can be a problem. " "Additionally only User and Computer objects can have GPO applied to them, so having GPO applied to a any other object type won't really work. " ) New-HTMLText -FontSize 10pt -Text "Following can happen: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Organizational Units that can have Group Policies unlinked (objects exists): ', $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPO'] -FontWeight normal, bold New-HTMLListItem -Text 'Organizational Units that can have Group Policies unlinked (no applicable objects): ', $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPOEmpty'] -FontWeight normal, bold New-HTMLListItem -Text "Organizational Units that can be deleted (no objects/no gpos) - ", "optional", ": ", $Script:Reporting['GPOOrganizationalUnit']['Variables']['DeleteOU'] -FontWeight normal, bold, normal, bold -Color None, red, None, None } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt if ($Script:Reporting['GPOOrganizationalUnit']['Variables']['ExcludedOU'].Count -gt 0) { New-HTMLText -Text @( 'There are ', $Script:Reporting['GPOOrganizationalUnit']['Variables']['ExcludedOU'].Count, " Organizational Units that are excluded.", " Please make sure to exclude those when executing unlinking/removal procedures. " ) -FontSize 10pt -FontWeight normal, bold, normal, bold -Color None, Red, None, Red } New-HTMLText -Text @( "Please make sure that you really want to unlink GPO or delete Organizational Unit before executing changes. Sometimes it's completly valid to keep one or the other. " "Unlinking GPO from OU that has no Computer or User objects is fairly safe exercise. Removing OU requires a bit more dive in, and should only be executed if you know what you're doing. " ) -FontWeight normal, bold -Color None, Red -FontSize 10pt if ($Script:Reporting['GPOOrganizationalUnit']['Exclusions']) { New-HTMLText -LineBreak New-HTMLText -Text @( "While preparing this report following exclusions were defined. " "Please make sure that when you execute your steps to include those exclusions to prevent any issues. " ) -FontSize 10pt -FontWeight bold, normal -Color Red, None -LineBreak New-HTMLText -Text "Code to use for exclusions: " -FontSize 10pt -FontWeight bold -LineBreak $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOOrganizationalUnit']['Exclusions'] if ($Code) { New-HTMLCodeBlock -Code $Code -Style powershell } } } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOOrganizationalUnit']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type bar -Distributed New-ChartAxisY -LabelMaxWidth 200 -LabelAlign left -Show New-ChartBar -Name "Unlink GPO ($($Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPO']))" -Value $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPO'] New-ChartBar -Name "Unlink GPO Delete OU ($($Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPOEmpty']))" -Value $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPOEmpty'] New-ChartBar -Name "Delete OU ($($Script:Reporting['GPOOrganizationalUnit']['Variables']['DeleteOU']))" -Value $Script:Reporting['GPOOrganizationalUnit']['Variables']['DeleteOU'] } -Title 'Organizational Units' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Organizational Units' { New-HTMLTable -DataTable $Script:Reporting['GPOOrganizationalUnit']['Data'] -Filtering { New-TableHeader -ResponsiveOperations none -Names 'GPONames', 'Objects' New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Unlink GPO, Delete OU' -BackgroundColor Salmon -Row New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Unlink GPO' -BackgroundColor YellowOrange -Row New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Delete OU' -BackgroundColor Red -Row New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'OK' -BackgroundColor LightGreen -Row New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Excluded' -BackgroundColor DeepSkyBlue -Row New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Excluded, Default OU' -BackgroundColor DeepSkyBlue -Row } -PagingOptions 7, 15, 30, 45, 60 -ExcludeProperty GPO } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix Group Organizational Units' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } if ($Script:Reporting['GPOOrganizationalUnit']['Exclusions']) { New-HTMLWizardStep -Name 'Required exclusions' { New-HTMLText -Text @( "While preparing this report following exclusions were defined. " "Please make sure that when you execute your steps to include those exclusions to prevent any issues. " ) $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOOrganizationalUnit']['Exclusions'] if ($Code) { New-HTMLCodeBlock -Code $Code -Style powershell } } } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with unlinking unused Group Policies. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOOrganizationalUnitBefore.html -Verbose -Type GPOOrganizationalUnit } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment." "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $OwnersGPO = Get-GPOZaurrOrganizationalUnit -Verbose $OwnersGPO | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Unlink unused Group Policies' { New-HTMLText -Text @( "Following command when executed runs cleanup procedure that unlinks all Group Policies from Organizational Units that have no user or computer objects. " "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental unlinking." 'When run it will remove any GPO links from Organizational Units that have no objects applicable for GPOs.' ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurrLinkEmptyOU -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrLinkEmptyOU -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be removed matches expected data. " "Keep in mind that there is no backup for this, and if link is removed you would need to relink it yourself." "Once you remove it, it's gone. " } -LineBreak New-HTMLText -Text 'Once happy with results please follow with command (this will start removal process): ' -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurrLinkEmptyOU -LimitProcessing 2 -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrLinkEmptyOU -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " } -LineBreak New-HTMLText -TextBlock { "It's possible to exclude certain OU's from having GPO's unlinked using follwing method: " } -FontWeight bold New-HTMLCodeBlock -Code { $Exclude = @( "OU=Groups,OU=Production,DC=ad,DC=evotec,DC=pl" "OU=Test \, OU,OU=ITR02,DC=ad,DC=evotec,DC=xyz" ) Remove-GPOZaurrLinkEmptyOU -Verbose -LimitProcessing 3 -WhatIf -ExcludeOrganizationalUnit $Exclude } } New-HTMLWizardStep -Name 'Delete unused Organizational Units' { New-HTMLText -Text @( "Following automation is not yet implemented. Requires more testing as potentially it could do more damage than help." ) } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOOrganizationalUnitAfter.html -Verbose -Type GPOOrganizationalUnit } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOOrganizationalUnit']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOOrganizationalUnit']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 } } } } $GPOZaurrOrphans = [ordered] @{ Name = 'Broken Group Policies' Enabled = $true ActionRequired = $null Data = $null Execute = { Get-GPOZaurrBroken -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'] = @{} $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'] = @{} $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'] = @{} foreach ($GPO in $Script:Reporting['GPOBroken']['Data']) { if (-not $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName] = 0 } if ($GPO.Status -eq 'Not available in AD') { $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD']++ $Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++ $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++ } elseif ($GPO.Status -eq 'Not available on SYSVOL') { $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol']++ $Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++ $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++ } elseif ($GPO.Status -eq 'Permissions issue') { $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue']++ $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]++ } elseif ($GPO.Status -eq 'ObjectClass issue') { $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue']++ $Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++ $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName]++ $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++ } } if ($Script:Reporting['GPOBroken']['Variables']['ToBeDeleted'] -gt 0) { $Script:Reporting['GPOBroken']['ActionRequired'] = $true } else { $Script:Reporting['GPOBroken']['ActionRequired'] = $false } } Variables = @{ NotAvailableInAD = 0 NotAvailableOnSysvol = 0 NotAvailablePermissionIssue = 0 NotAvailablePermissionIssuePerDomain = $null ToBeDeleted = 0 ToBeDeletedPerDomain = $null NotAvailableObjectClassIssue = 0 NotAvailableObjectClassIssuePerDomain = $null } Overview = { } Summary = { New-HTMLText -TextBlock { "Group Policies are stored in two places - Active Directory (metadata) and SYSVOL (content)." "Since those are managed in different ways, replicated in different ways it's possible because of different issues they get out of sync." } -FontSize 10pt -LineBreak New-HTMLText -Text "For example:" -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'USN Rollback in AD could cause already deleted Group Policies to reapper in Active Directory, yet SYSVOL data would be unavailable' New-HTMLListItem -Text 'Group Policy deletion failing to delete GPO content' New-HTMLListItem -Text 'Permission issue preventing deletion of GPO content' New-HTMLListItem -Text 'Failing DFSR replication between DCs' } -FontSize 10pt New-HTMLText -Text 'Following problems were detected:' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies on SYSVOL, but no details in AD: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'] -FontWeight normal, bold New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold New-HTMLListItem -Text 'Group Policies which exists, but have wrong ObjectClass: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies which couldn't be assessed due to permissions issue: ", $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text "Please review output in table and follow the steps below table to get Active Directory Group Policies in healthy state." -FontSize 10pt } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOBroken']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'Not in AD', 'Not on SYSVOL', 'ObjectClass Issue', 'Permissions Issue' -Color Crimson, LightCoral, MediumOrchid, IndianRed New-ChartBar -Name 'Broken' -Value $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue'], $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue'] } -Title 'Broken / Orphaned Group Policies' -TitleAlignment center } } New-HTMLSection -Name 'Health State of Group Policies' { New-HTMLTable -DataTable $Script:Reporting['GPOBroken']['Data'] -Filtering { New-HTMLTableCondition -Name 'Status' -Value "Not available in AD" -BackgroundColor Salmon -ComparisonType string New-HTMLTableCondition -Name 'Status' -Value "Not available on SYSVOL" -BackgroundColor LightCoral -ComparisonType string New-HTMLTableCondition -Name 'Status' -Value "ObjectClass issue" -BackgroundColor MediumOrchid -ComparisonType string New-HTMLTableCondition -Name 'Status' -Value "Permissions issue" -BackgroundColor MediumVioletRed -ComparisonType string -Color White New-HTMLTableCondition -Name 'Status' -Value "Exists" -BackgroundColor LightGreen -ComparisonType string } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix - Not available on SYSVOL / Active Directory / ObjectClass issue' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with removal. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenGpoBefore.html -Verbose -Type GPOBroken } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment." "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $GPOOutput = Get-GPOZaurrBroken -Verbose $GPOOutput | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Make a backup (optional)' { New-HTMLText -TextBlock { "The process fixing broken GPOs will delete AD or SYSVOL content depending on type of a problem. " "While it's always useful to have a backup, this backup won't actually backup those broken group policies" " for a simple reason that those are not backupable. You can't back up GPO if there is no SYSVOL content" " and you can't backup GPO if there's only SYSVOL content. " "However, since the script does make changes to GPOs it's advised to have a backup anyways! " } New-HTMLCodeBlock -Code { $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All $GPOSummary | Format-Table } New-HTMLText -TextBlock { "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. " } } New-HTMLWizardStep -Name 'Fix GPOs not available in AD' { New-HTMLText -Text @( "Following command when executed runs cleanup procedure that removes all broken GPOs on SYSVOL side. ", "Make sure when running it for the first time to run it with ", "WhatIf ", "parameter as shown below to prevent accidental removal. ", 'When run it will remove any GPO remains from SYSVOL, that should not be there, as AD metadata is already gone.' "Please notice I'm using SYSVOL as a type, because the removal will happen on SYSVOL. " ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type SYSVOL -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type SYSVOL -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be deleted matches expected data. " "Keep in mind that what backup command does is simply copy SYSVOL content to given place. " "Since there is no GPO metadata in AD there's no real restore process for this step. " "It's there to make sure if someone kept some data in there and wants to get access to it, he/she can. " } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start deletion process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type SYSVOL -LimitProcessing 2 -BackupPath $Env:UserProfile\Desktop\GPOSYSVOLBackup -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type SYSVOL -LimitProcessing 2 -BackupPath $Env:UserProfile\Desktop\GPOSYSVOLBackup -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose } New-HTMLText -TextBlock { "This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " "If there's nothing else to be deleted on SYSVOL side, we can skip to next step step. " } } New-HTMLWizardStep -Name 'Fix GPOs not available on SYSVOL' { New-HTMLText -Text @( "Following command when executed runs cleanup procedure that removes all broken GPOs on Active Directory side." "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." 'When run it will remove any GPO remains from AD, that should not be there, as SYSVOL content is already gone.' "Please notice I'm using AD as a type, because the removal will happen on AD side. " ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type AD -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type AD -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be deleted matches expected data. " "Keep in mind that there is no backup for this. " "Since there is no SYSVOL data, and only AD object is there there's no real restore process for this step. " "Once you delete it, it's gone. " } -LineBreak New-HTMLText -Text 'Once happy with results please follow with command (this will start deletion process): ' -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type AD -LimitProcessing 2 -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type AD -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose } New-HTMLText -TextBlock { "This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " "If there's nothing else to be deleted on AD side, we can skip to next step step. " } } New-HTMLWizardStep -Name 'Fix GPOs of wrong ObjectClass' { New-HTMLText -Text @( "Following command when executed runs cleanup procedure that removes all GPOs which have ObjectClass of Container, rather than required groupPolicyContainer. " "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." 'When run it will remove GPO metadata from AD, and any files/folders from SYSVOL.' ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type ObjectClass -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type ObjectClass -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be deleted matches expected data. " "Keep in mind that there is no backup for this as backup process doesn't see GPOs that are of wrong ObjectClass. " "Once you delete it, it's gone. " } -LineBreak New-HTMLText -Text 'Once happy with results please follow with command (this will start deletion process): ' -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type ObjectClass -LimitProcessing 2 -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrBroken -Type ObjectClass -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose } New-HTMLText -TextBlock { "This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " "If there's nothing else to be deleted, we can skip to next step step. " } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenGpoAfter.html -Verbose -Type GPOBroken } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOBroken']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOBroken']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrOwners = [ordered] @{ Name = 'Group Policy Owners' Enabled = $true ActionRequired = $null Data = $null Execute = { if ($Script:Reporting['GPOOwners']['Exclusions']) { Get-GPOZaurrOwner -IncludeSysvol -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ApprovedOwner $Script:Reporting['GPOOwners']['Exclusions'] } else { Get-GPOZaurrOwner -IncludeSysvol -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } } Processing = { $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'] = @{} $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'] = @{} foreach ($GPO in $Script:Reporting['GPOOwners']['Data']) { if (-not $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName] = 0 } if ($GPO.Status -contains 'Consistent') { $Script:Reporting['GPOOwners']['Variables']['IsConsistent']++ } elseif ($GPO.Status -contains 'Inconsistent') { $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent']++ } if ($GPO.Status -contains 'Administrative') { $Script:Reporting['GPOOwners']['Variables']['IsAdministrative']++ } elseif ($GPO.Status -contains 'Approved') { $Script:Reporting['GPOOwners']['Variables']['IsApproved']++ } else { $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative']++ } if ($GPO.SysvolExists -eq $false) { $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFix']++ $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$GPO.DomainName]++ } else { if ($GPO.Status -contains 'Inconsistent') { $Script:Reporting['GPOOwners']['Variables']['WillFix']++ $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName]++ } elseif ($GPO.Status -contains 'NotAdministrative' -and $GPO.Status -notcontains 'Approved') { $Script:Reporting['GPOOwners']['Variables']['WillFix']++ $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOOwners']['Variables']['WillNotTouch']++ } } } if ($Script:Reporting['GPOOwners']['Variables']['WillFix'] -gt 0) { $Script:Reporting['GPOOwners']['ActionRequired'] = $true } else { $Script:Reporting['GPOOwners']['ActionRequired'] = $false } } Variables = @{ IsAdministrative = 0 IsApproved = 0 IsNotAdministrative = 0 IsConsistent = 0 IsNotConsistent = 0 WillFix = 0 RequiresDiffFix = 0 WillNotTouch = 0 RequiresDiffFixPerDomain = $null WillFixPerDomain = $null } Overview = { } Summary = { New-HTMLText -FontSize 10pt -Text @( "By default, GPO creation is usually maintained by Domain Admins or Enterprise Admins. " "When GPO is created by Domain Admins or Enterprise Admins group members, the GPO Owner is set to Domain Admins. " "When GPO is created by a member of Group Policy Creator Owners or other group has delegated rights to create a GPO, " "the owner of said GPO is not Domain Admins group but is assigned to the relevant user. " "GPO Owners should be Domain Admins or Enterprise Admins to prevent abuse. " "If that isn't so, it means the owner can fully control GPO and potentially change its settings in an uncontrolled way. " "While at the moment of creation of new GPO, it's not a problem, in the long term, it's possible such a person may no longer be admin, yet keep their rights over GPO. " "As your aware, Group Policies are stored in 2 places. In Active Directory (metadata) and SYSVOL (settings). " "This means that there are 2 places where GPO Owners exists. " "This also means that for multiple reasons, AD and SYSVOL can be out of sync when it comes to their permissions, which can lead to uncontrolled ability to modify them. " "Ownership in Active Directory and Ownership of SYSVOL for said GPO is required to be the same." ) New-HTMLText -Text "Here's a short summary of ", "Group Policy Owners", ": " -FontSize 10pt -FontWeight normal, bold, normal New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Administrative Owners: ', $Script:Reporting['GPOOwners']['Variables']['IsAdministrative'] -FontWeight normal, bold New-HTMLListItem -Text 'Non-Administrative, but approved Owners (for example AGPM): ', $Script:Reporting['GPOOwners']['Variables']['IsApproved'] -FontWeight normal, bold New-HTMLListItem -Text 'Non-Administrative Owners: ', $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative'] -FontWeight normal, bold New-HTMLListItem -Text "Owners consistent in AD and SYSVOL: ", $Script:Reporting['GPOOwners']['Variables']['IsConsistent'] -FontWeight normal, bold New-HTMLListItem -Text "Owners not-consistent in AD and SYSVOL: ", $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -FontSize 10pt -Text "Following will need to happen: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies requiring owner change: ', $Script:Reporting['GPOOwners']['Variables']['WillFix'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies which can't be fixed (no SYSVOL?): ", $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFix'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies unaffected: ", $Script:Reporting['GPOOwners']['Variables']['WillNotTouch'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text 'Following domains require fixing using, ', 'different methods:' -FontSize 10pt -FontWeight bold, bold -Color Black, RedRobin -TextDecoration none, underline New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt if ($Script:Reporting['GPOOwners']['Exclusions']) { New-HTMLText -LineBreak New-HTMLText -Text @( "While preparing this report following exclusions were defined. " "Please make sure that when you execute your steps to include those exclusions to prevent any issues. " ) -FontSize 10pt -FontWeight bold, normal -Color Red, None -LineBreak New-HTMLText -Text "Code to use for exclusions: " -FontSize 10pt -FontWeight bold -LineBreak $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOOwners']['Exclusions'] if ($Code) { New-HTMLCodeBlock -Code $Code -Style powershell } } } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOOwners']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'Yes', 'No', 'Approved' -Color LightGreen, Salmon, DeepSkyBlue New-ChartBar -Name 'Is administrative' -Value $Script:Reporting['GPOOwners']['Variables']['IsAdministrative'], $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative'], $Script:Reporting['GPOOwners']['Variables']['IsApproved'] New-ChartBar -Name 'Is consistent' -Value $Script:Reporting['GPOOwners']['Variables']['IsConsistent'], $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent'] } -Title 'Group Policy Owners' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Owners' { New-HTMLTable -DataTable $Script:Reporting['GPOOwners']['Data'] -Filtering { New-HTMLTableCondition -Name 'Status' -Value 'Administrative, Consistent' -BackgroundColor LightGreen -ComparisonType string -Row New-HTMLTableCondition -Name 'Status' -Value 'NotAdministrative, Consistent, Approved' -BackgroundColor DeepSkyBlue -ComparisonType string -Row New-HTMLTableCondition -Name 'Status' -Value 'Administrative, Inconsistent' -BackgroundColor Salmon -ComparisonType string -Row New-HTMLTableCondition -Name 'Status' -Value 'NotAdministrative, Inconsistent' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix Group Policy Owners' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } if ($Script:Reporting['GPOOwners']['Exclusions']) { New-HTMLWizardStep -Name 'Required exclusions' { New-HTMLText -Text @( "While preparing this report following exclusions were defined. " "Please make sure that when you execute your steps to include those exclusions to prevent any issues. " ) $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOOwners']['Exclusions'] if ($Code) { New-HTMLCodeBlock -Code $Code -Style powershell } } } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with fixing Group Policy Owners. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOOwnersBefore.html -Verbose -Type GPOOwners } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment." "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $OwnersGPO = Get-GPOZaurrOwner -IncludeSysvol -Verbose $OwnersGPO | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Make a backup (optional)' { New-HTMLText -TextBlock { "The process of fixing GPO Owner does NOT touch GPO content. It simply changes owners on AD and SYSVOL at the same time. " "However, it's always good to have a backup before executing changes that may impact Active Directory. " } New-HTMLCodeBlock -Code { $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All $GPOSummary | Format-Table } New-HTMLText -TextBlock { "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. " } } New-HTMLWizardStep -Name 'Set GPO Owners to Administrative (Domain Admins)' { New-HTMLText -Text "Following command will find any GPO which doesn't have proper GPO Owner (be it due to inconsistency or not being Domain Admin) and will enforce new GPO Owner. " New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black New-HTMLCodeBlock -Code { Set-GPOZaurrOwner -Type All -Verbose -WhatIf } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Set-GPOZaurrOwner -Type All -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data." } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Set-GPOZaurrOwner -Type All -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Set-GPOZaurrOwner -Type All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed sets new owner only on first X non-compliant GPO Owners for AD/SYSVOL. Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " } -LineBreak New-HTMLText -TextBlock { "It's possible to define certain owners as being approved (for example with domain that have AGPM). " "Make sure to verify if excluded/approved owners were provided in Required Exclusions tab, or add your own when nessecary. " "You can approve owners with following code: " } -FontWeight bold New-HTMLCodeBlock -Code { $Approved = @( 'EVOTEC\przemyslaw.klys' 'EVOTEC\green.b' ) Set-GPOZaurrOwner -Type All -Verbose -LimitProcessing 2 -ApprovedOwner $Approved } New-HTMLText -TextBlock { "Please keep in mind that ApprovedOwner is only applicable to Non-Administrative permissions to provide a way to approve special use cases. " "It won't do anything for inconsistent, unknown permissions as those are still treated as wrong. " } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOOwnersAfter.html -Verbose -Type GPOOwners } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOOwners']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOOwners']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 } } } } $GPOZaurrPassword = [ordered] @{ Name = 'Group Policy Passwords' Enabled = $true Action = $null Data = $null Execute = { Get-GPOZaurrPassword -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { } Variables = @{ } Overview = { } Solution = { New-HTMLTable -DataTable $Script:Reporting['GPOPassword']['Data'] -Filtering -ScrollX -PagingOptions 7, 15, 30, 45, 60 if ($Script:Reporting['GPOPassword']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOPassword']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrPermissionsAdministrative = [ordered] @{ Name = 'Group Policy Administrative Permissions' Enabled = $false Action = $null Data = $null Execute = { $Object = [ordered] @{ Permissions = Get-GPOZaurrPermission -Type Administrative -IncludePermissionType GpoEditDeleteModifySecurity -ReturnSecurityWhenNoData -IncludeGPOObject -ReturnSingleObject -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } $Object['PermissionsPerRow'] = $Object['Permissions'] | ForEach-Object { $_ } $Object['PermissionsAnalysis'] = [System.Collections.Generic.List[PSCustomObject]]::new() $Object } Processing = { $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'] = @{} $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouchPerDomain'] = @{} foreach ($GPO in $Script:Reporting['GPOPermissionsAdministrative']['Data'].Permissions) { if (-not $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'][$GPO[0].DomainName]) { $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'][$GPO[0].DomainName] = 0 } if (-not $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouchPerDomain'][$GPO[0].DomainName]) { $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouchPerDomain'][$GPO[0].DomainName] = 0 } $Analysis = Get-PermissionsAnalysis -GPOPermissions $GPO -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Type Administrative -PermissionType GpoEditDeleteModifySecurity if ($Analysis.Skip) { $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouch']++ $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouchPerDomain'][$GPO[0].DomainName]++ } else { $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFix']++ $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'][$GPO[0].DomainName]++ } $Script:Reporting['GPOPermissionsAdministrative']['Data'].'PermissionsAnalysis'.Add($Analysis) } if ($Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFix'] -gt 0) { $Script:Reporting['GPOPermissionsAdministrative']['ActionRequired'] = $true } else { $Script:Reporting['GPOPermissionsAdministrative']['ActionRequired'] = $false } } Variables = @{ WillFix = 0 WillNotTouch = 0 WillFixPerDomain = $null WillNotTouchPerDomain = $null } Overview = { } Summary = { New-HTMLText -FontSize 10pt -TextBlock { "When GPO is created by default it gets Domain Admins and Enterprise Admins with Edit/Delete/Modify Security permissions. " "For some reason, some Administrators remove those permissions or modify them when they shouldn't touch those at all. " "Since having Edit/Delete/Modify Security permissions doesn't affect GPOApply permissions there's no reason to remove Domain Admins or Enterprise Admins from permissions, or limit their rights. " } -LineBreak New-HTMLText -FontSize 10pt -Text "Assesment results: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies requiring adding Domain Admins or Enterprise Admins: ', $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFix'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouch'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text @( "That means we need to fix permissions on: " $($Script:Reporting['GPOPermissionsAdministrative']['Variables'].WillFix) " out of " $($Script:Reporting['GPOPermissionsAdministrative']['Data'].Permissions).Count " Group Policies. " ) -FontSize 10pt -FontWeight bold, bold, normal, bold, normal -Color Black, FreeSpeechRed, Black, Black -LineBreak -TextDecoration none, underline, underline, underline, none } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOPermissionsAdministrative']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'Yes', 'No' -Color SpringGreen, Salmon New-ChartBar -Name 'Administrative Users Present' -Value $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouch'], $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFix'] } -Title 'Group Policy Permissions' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Administrative Users Permissions Summary' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsAdministrative']['Data'].PermissionsPerRow -Filtering { New-HTMLTableCondition -Name 'Permission' -Value '' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 } New-HTMLSection -Name 'Group Policy Administrative Users Analysis' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsAdministrative']['Data'].PermissionsAnalysis -Filtering { } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix Group Policy Administrative Users' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with fixing Group Policy Authenticated Users. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsAdministrativeBefore.html -Verbose -Type GPOPermissionsAdministrative } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "GPOs with problems will be those not having any value for Permission/PermissionType columns. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $AdministrativeUsers = Get-GPOZaurrPermission -Type Administrative -IncludePermissionType GpoEditDeleteModifySecurity -ReturnSecurityWhenNoData $AdministrativeUsers | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Make a backup (optional)' { New-HTMLText -TextBlock { "The process of fixing GPO Permissions does NOT touch GPO content. It simply adds permissionss on AD and SYSVOL at the same time for given GPO. " "However, it's always good to have a backup before executing changes that may impact Active Directory. " } New-HTMLCodeBlock -Code { $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All $GPOSummary | Format-Table } New-HTMLText -TextBlock { "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. " } } New-HTMLWizardStep -Name 'Add Administrative Groups proper permissions GPO' { New-HTMLText -Text @( "Following command will find any GPO which doesn't have Domain Admins and Enterprise Admins added with GpoEditDeleteModifySecurity and will add it as GpoEditDeleteModifySecurity. ", "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ", "It ensures that Domain Admins and Enterprise Admins can manage GPO. ", "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental adding of permissions." ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data." } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed adds Enterprise Admins or/and Domain Admins (GpoEditDeleteModifySecurity permission) only on first X non-compliant Group Policies. " "Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. " "In case of any issues please review and action accordingly. " } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsAdministrativeAfter.html -Verbose -Type GPOPermissionsAdministrative } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOPermissionsAdministrative']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsAdministrative']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 } } } } $GPOZaurrPermissionsAnalysis = [ordered] @{ Name = 'Group Policy Permissions Analysis' Enabled = $true Action = $null Data = $null Execute = { $Object = [ordered] @{ Permissions = Get-GPOZaurrPermission -ReturnSecurityWhenNoData -IncludeGPOObject -ReturnSingleObject -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } $Object['PermissionsPerRow'] = $Object['Permissions'] | ForEach-Object { $_ } $Object['PermissionsAnalysis'] = Get-GPOZaurrPermissionAnalysis -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Permissions $Object.Permissions $Object['PermissionsIssues'] = Get-GPOZaurrPermissionIssue -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains $Object } Processing = { $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrativePerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAuthenticatedUsersPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystemPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknownPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillNotFixPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['WillFixPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['ReadPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'] = @{} $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'] = @{} foreach ($GPO in $Script:Reporting['GPOPermissions']['Data'].PermissionsIssues) { if (-not $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['ReadPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['ReadPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'][$GPO.DomainName] = 0 } if ($GPO.PermissionIssue) { $Script:Reporting['GPOPermissions']['Variables']['CouldNotRead']++ $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOPermissions']['Variables']['Read']++ $Script:Reporting['GPOPermissions']['Variables']['ReadPerDomain'][$GPO.DomainName]++ } $Script:Reporting['GPOPermissions']['Variables']['Total']++ $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'][$GPO.DomainName]++ } foreach ($GPO in $Script:Reporting['GPOPermissions']['Data'].PermissionsAnalysis) { if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillFixPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['WillNotFixPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillNotFixPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrativePerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrativePerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystemPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystemPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknownPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknownPerDomain'][$GPO.DomainName] = 0 } if ($GPO.Administrative -eq $true) { $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrative']++ $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrativePerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrative']++ $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'][$GPO.DomainName]++ } if ($GPO.AuthenticatedUsers -eq $true) { $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAuthenticatedUsers']++ $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAuthenticatedUsersPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsers']++ $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'][$GPO.DomainName]++ } if ($GPO.System -eq $true) { $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystem']++ $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystemPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOPermissions']['Variables']['WillFixSystem']++ $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'][$GPO.DomainName]++ } if ($GPO.Unknown -eq $false) { $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknown']++ $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknownPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknown']++ $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'][$GPO.DomainName]++ } if ($GPO.Status -eq $false) { $Script:Reporting['GPOPermissions']['Variables']['WillFix']++ $Script:Reporting['GPOPermissions']['Variables']['WillFixPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOPermissions']['Variables']['WillNotFix']++ $Script:Reporting['GPOPermissions']['Variables']['WillNotFixPerDomain'][$GPO.DomainName]++ } } if ($Script:Reporting['GPOPermissions']['Variables']['WillFix'] -gt 0 -or $Script:Reporting['GPOPermissions']['Variables']['CouldNotRead'] -gt 0) { $Script:Reporting['GPOPermissions']['ActionRequired'] = $true } else { $Script:Reporting['GPOPermissions']['ActionRequired'] = $false } } Variables = @{ Read = 0 ReadPerDomain = $null CouldNotRead = 0 CouldNotReadPerDomain = $null TotalPerDomain = $null Total = 0 WillFix = 0 WillNotFix = 0 WillFixAdministrative = 0 WillNotTouchAdministrative = 0 WillFixUnknown = 0 WillNotTouchUnknown = 0 WillNotTouchSystem = 0 WillFixSystem = 0 WillNotTouchAuthenticatedUsers = 0 WillFixAuthenticatedUsers = 0 WillNotTouchAuthenticatedUsersPerDomain = $null WillFixAuthenticatedUsersPerDomain = $null WillNotTouchSystemPerDomain = $null WillFixSystemPerDomain = $null WillFixAdministrativePerDomain = $null WillNotTouchAdministrativePerDomain = $null WillNotFixPerDomain = $null WillFixPerDomain = $null WillFixUnknownPerDomain = $null WillNotTouchUnknownPerDomain = $null } Summary = { New-HTMLText -FontSize 10pt -Text "When GPO is created it gets a handful of standard permissions. Those are:" New-HTMLList { New-HTMLListItem -Text "NT AUTHORITY\Authenticated Users with GpoApply permissions" New-HTMLListItem -Text "Domain Admins and Enterprise Admins with Edit/Delete/Modify permissions" New-HTMLListItem -Text "SYSTEM account with Edit/Delete/Modify permissions" } -FontSize 10pt New-HTMLText -FontSize 10pt -Text "But then IT people change those permissions to their own needs. While most changes make sense and are required to be able to target proper groups of people, some changes are not required or even bad. " New-HTMLText -Text "First problem relates to NT AUTHORITY\Authenticated Users" -FontSize 10pt -FontWeight bold -TextDecoration underline -Alignment center New-HTMLText -FontSize 10pt -TextBlock { "When GPO is created one of the permissions that are required for proper functioning of Group Policies is NT AUTHORITY\Authenticated Users. " "Some Administrators don't follow best practices and trying to remove GpoApply permission, remove also GpoRead permission from a GPO which can have consequences. " "On June 14th, 2016 Microsoft released [HotFix](https://support.microsoft.com/en-gb/help/3159398/ms16-072-description-of-the-security-update-for-group-policy-june-14-2) that requires Authenticated Users to be present on all Group Policies to function properly. " "MS16-072 changes the security context with which user group policies are retrieved. " "This by-design behavior change protects customers’ computers from a security vulnerability. " } New-HTMLList { New-HTMLListItem -Text "Before MS16-072 is installed, user group policies were retrieved by using the user’s security context. " New-HTMLListItem -Text "After MS16-072 is installed, user group policies are retrieved by using the computer's security context." } -FontSize 10pt New-HTMLText -FontSize 10pt -Text @( "Unfortunately it's not as simple as it sounds. While checking for permissions mostly works fine, it's possible a Group Policy has totally removed account permissions from being able to asses any of it. ", "The account we're using " "$($Env:USERDOMAIN)\$($Env:USERNAME.ToUpper())", " may simply not have enough permisions to properly asses permissions for a GPO. " "Therefore we're using dual assesment for this situation, where first assesment is checking for GPO visibility or lack of it, and second assesment is checking for direct permissions assignement. " "We just were able to detect the problem, but hopefully higher level account (Domain Admin) should be able to provide full assesment. " ) -FontWeight normal, normal, bold, normal -Color None, None, BlueDiamond, none, none New-HTMLText -FontSize 10pt -Text "First assesment results: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text "Group Policies couldn't read at all: ", $Script:Reporting['GPOPermissions']['Variables']['CouldNotRead'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies with permissions allowing read: ", $Script:Reporting['GPOPermissions']['Variables']['Read'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'].Keys) { New-HTMLListItem -Text @( "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'][$Domain], " changes out of ", $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'][$Domain], "." ) -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -FontSize 10pt -Text "Second Assesment results " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies requiring Authenticated Users with GpoRead permission: ', $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsers'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAuthenticatedUsers'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text "Second problem relates to Domain Admins and Enterprise Admins" -FontSize 10pt -FontWeight bold -TextDecoration underline -Alignment center New-HTMLText -FontSize 10pt -TextBlock { "When GPO is created by default it gets Domain Admins and Enterprise Admins with Edit/Delete/Modify Security permissions. " "For some reason, some Administrators remove those permissions or modify them when they shouldn't touch those at all. " "Since having Edit/Delete/Modify Security permissions doesn't affect GPOApply permissions there's no reason to remove Domain Admins or Enterprise Admins from permissions, or limit their rights. " "Domain Admins and Enterprise Admins have to have either GPOEditModify permissions or at the very least GPOCustom. " "When GPOCustom is set it usually means there's a mix of Allow and Deny permission in place (for example deny GPOApply). " "In such case we're assuming you know what you're doing. However it's always possible to review those permissions, as those are marked in the table for review. " } -LineBreak New-HTMLText -FontSize 10pt -Text "Assesment results " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies requiring Administrative permission fix: ', $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrative'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrative'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text "Third problem relates to SYSTEM account" -FontSize 10pt -FontWeight bold -TextDecoration underline -Alignment center New-HTMLText -FontSize 10pt -TextBlock { "When GPO is created by default it gets SYSTEM account with Edit/Delete/Modify Security permissions. " "For some reason, some Administrators remove those permissions or modify them when they shouldn't touch those at all. " "Since having Edit/Delete/Modify Security permissions doesn't affect GPOApply permissions there's no reason to remove SYSTEM from permissions, or limit their rights. " } -LineBreak New-HTMLText -FontSize 10pt -Text "Assesment results " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies requiring SYSTEM permission fix: ', $Script:Reporting['GPOPermissions']['Variables']['WillFixSystem'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystem'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text "Fourth problem relates to UNKNOWN SID" -FontSize 10pt -FontWeight bold -TextDecoration underline -Alignment center New-HTMLText -FontSize 10pt -TextBlock { "Sometimes groups or users are deleted in Active Directory and Unfortunately their permissions are not cleaned automatically. " "Those are left in-place and stay there forever until removed. " } -LineBreak New-HTMLText -FontSize 10pt -Text "Assesment results " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies requiring Unknown permission removal: ', $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknown'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknown'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt -LineBreak New-HTMLText -FontSize 10pt -Text "To generate up to date report please execute: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Install-Module GPOZaurr -Force', ' or ', ' install module manually.' -Color RoyalBlue, None, None New-HTMLListItem -Text 'Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsBefore.html -Verbose -Type GPOPermissions' -Color RoyalBlue } -FontSize 10pt New-HTMLText -FontSize 10pt -Text 'Steps above will generate above summary with more details allowing you to get up to date report and steps on how to fix it.' } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOPermissions']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'Yes', 'No' -Color SpringGreen, Salmon New-ChartBar -Name 'Visible Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['Read'], $Script:Reporting['GPOPermissions']['Variables']['CouldNotRead'] New-ChartBar -Name 'Overall Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotFix'], $Script:Reporting['GPOPermissions']['Variables']['WillFix'] New-ChartBar -Name 'Administrative Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrative'], $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrative'] New-ChartBar -Name 'Authenticated Users Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrative'], $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsers'] New-ChartBar -Name 'System Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystem'], $Script:Reporting['GPOPermissions']['Variables']['WillFixSystem'] New-ChartBar -Name 'Unknown Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknown'], $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknown'] } -Title 'Group Policy Permissions' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Visibility Analysis' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['Data'].PermissionsIssues -Filtering { New-HTMLTableCondition -Name 'PermissionIssue' -Value $true -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 -DefaultSortColumn PermissionIssue -DefaultSortOrder Descending -ScrollX } New-HTMLSection -Name 'Group Policy Permissions Analysis' { New-HTMLContainer { New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt New-HTMLList { New-HTMLListItem -FontWeight bold, normal -Text "Status", " - means GPO has at least one problem with permissions. " New-HTMLListItem -FontWeight bold, normal -Text "Administrative", " - means GPO has problem with either Domain Admins or Enterprise Admins not having proper permissions. " New-HTMLListItem -FontWeight bold, normal -Text "AuthenticatedUsers", " - means GPO has Authenticated Users missing either as GPOApply or GPORead. " New-HTMLListItem -FontWeight bold, normal -Text "System", " - means GPO has SYSTEM permission missing or lacking proper permissions. " New-HTMLListItem -FontWeight bold, normal -Text "DomainAdmins", " - means GPO has Domain Admins missing or having wrong permissions. " New-HTMLListItem -FontWeight bold, normal -Text "EnterpriseAdmins", " - means GPO has Enterprise Admins missing or having wrong permissions. " } -FontSize 10pt New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['Data'].PermissionsAnalysis -Filtering { New-HTMLTableCondition -Name 'Status' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'Administrative' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'AuthenticatedUsers' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'System' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'Unknown' -Value 'False' -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'DomainAdmins' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'EnterpriseAdmins' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string New-HTMLTableCondition -Name 'DomainAdminsPermission' -Value 'GpoCustom' -BackgroundColor Moccasin -ComparisonType string New-HTMLTableCondition -Name 'EnterpriseAdminsPermission' -Value 'GpoCustom' -BackgroundColor Moccasin -ComparisonType string New-HTMLTableCondition -Name 'Status' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne New-HTMLTableCondition -Name 'Administrative' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne New-HTMLTableCondition -Name 'AuthenticatedUsers' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne New-HTMLTableCondition -Name 'System' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne New-HTMLTableCondition -Name 'Unknown' -Value 'False' -BackgroundColor Salmon -ComparisonType string -Operator ne New-HTMLTableCondition -Name 'DomainAdmins' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne New-HTMLTableCondition -Name 'EnterpriseAdmins' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne New-TableEvent -TableID 'GPOPermissionsAll' -SourceColumnName 'GUID' -TargetColumnID 1 } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } } New-HTMLSection -Name 'All Permissions' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['Data'].PermissionsPerRow -Filtering { New-HTMLTableHeader -Names 'PrincipalNetBiosName', 'PrincipalDistinguishedName', 'PrincipalDomainName', 'PrincipalName', 'PrincipalSid', 'PrincipalSidType' -Title 'Account Information' New-HTMLTableCondition -Name 'Permission' -Value 'GpoEditDeleteModifySecurity' -BackgroundColor HotPink -ComparisonType string -Operator eq New-HTMLTableCondition -Name 'Permission' -Value 'GpoCustom' -BackgroundColor Moccasin -ComparisonType string New-HTMLTableCondition -Name 'Permission' -Value 'GpoApply' -BackgroundColor Orange -ComparisonType string New-HTMLTableCondition -Name 'Permission' -Value 'GpoRead' -BackgroundColor MediumSpringGreen -ComparisonType string -Operator eq New-HTMLTableCondition -Name 'PrincipalSidType' -Value 'Unknown' -BackgroundColor Salmon -ComparisonType string -Operator eq } -PagingOptions 7, 15, 30, 45, 60 -DataTableID 'GPOPermissionsAll' -ScrollX -ExcludeProperty 'GPOSecurity', 'GPOSecurityPermissionItem', 'GPOObject' } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix Group Policy Administrative Users' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with fixing Group Policy Authenticated Users. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsBefore.html -Verbose -Type GPOPermissions } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "GPOs with problems will be those not having any value for Permission/PermissionType columns. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $AllPermissions = Get-GPOZaurrPermission $AllPermissions | Format-Table $PermissionsAnalysis = Get-GPOZaurrPermissionAnalysis $PermissionsAnalysis | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Make a backup (optional)' { New-HTMLText -TextBlock { "The process of fixing GPO Permissions does NOT touch GPO content. It simply adds permissionss on AD and SYSVOL at the same time for given GPO. " "However, it's always good to have a backup before executing changes that may impact Active Directory. " } New-HTMLCodeBlock -Code { $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All $GPOSummary | Format-Table } New-HTMLText -TextBlock { "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. " } } New-HTMLWizardStep -Name 'Add Authenticated Users permissions' { New-HTMLText -Text @( "Following command will find any GPO which doesn't have Authenticated User as GpoRead or GpoApply and will add it as GpoRead. ", "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ", "It ensures that COMPUTERS can read GPO properly to be able to Apply it. ", "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental adding of permissions." ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data." } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed adds Authenticated Users (GpoRead permission) only on first X non-compliant Group Policies. " "Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. " "In case of any issues please review and action accordingly." } } New-HTMLWizardStep -Name 'Add Administrative Groups permissions' { New-HTMLText -Text @( "Following command will find any GPO which doesn't have Domain Admins and Enterprise Admins added with GpoEditDeleteModifySecurity and will add it as GpoEditDeleteModifySecurity. ", "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ", "It ensures that Domain Admins and Enterprise Admins can manage GPO. ", "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental adding of permissions." ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data." } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed adds Enterprise Admins or/and Domain Admins (GpoEditDeleteModifySecurity permission) only on first X non-compliant Group Policies. " "Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. " "In case of any issues please review and action accordingly. " } } New-HTMLWizardStep -Name 'Add SYSTEM permissions' { New-HTMLText -Text @( "Following command will find any GPO which doesn't have SYSTEM account added with GpoEditDeleteModifySecurity and will add it as GpoEditDeleteModifySecurity. ", "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ", "It ensures that SYSTEM can manage GPO. ", "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental adding of permissions." ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data." } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed adds SYSTEM account (GpoEditDeleteModifySecurity permission) only on first X non-compliant Group Policies. " "Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. " "In case of any issues please review and action accordingly. " } } New-HTMLWizardStep -Name 'Remove UNKNOWN permissions' { New-HTMLText -Text @( "Following command will find any GPO which has an unknown SID and will remove it. ", "This change doesn't change any other permissions. ", "It ensures that GPOs have no unknown permissions present. ", "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental adding of permissions." ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurrPermission -Verbose -Type Unknown -WhatIf } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrPermission -Verbose -Type Unknown -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data." } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurrPermission -Verbose -Type Unknown -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrPermission -Verbose -Type Unknown -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed removes only first X unknwon permissions from Group Policies. " "Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. " "In case of any issues please review and action accordingly. " } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsAfter.html -Verbose -Type GPOPermissions } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOPermissions']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrPermissionsRead = [ordered] @{ Name = 'Group Policy Authenticated Users Permissions' Enabled = $false Action = $null Data = $null Execute = { [ordered] @{ Permissions = Get-GPOZaurrPermission -Type AuthenticatedUsers -ReturnSecurityWhenNoData -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains Issues = Get-GPOZaurrPermissionIssue -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } } Processing = { $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'] = @{} $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouchPerDomain'] = @{} $Script:Reporting['GPOPermissionsRead']['Variables']['ReadPerDomain'] = @{} $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'] = @{} $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'] = @{} foreach ($GPO in $Script:Reporting['GPOPermissionsRead']['Data'].Issues) { if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['ReadPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissionsRead']['Variables']['ReadPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'][$GPO.DomainName] = 0 } if ($GPO.PermissionIssue) { $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead']++ $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOPermissionsRead']['Variables']['Read']++ $Script:Reporting['GPOPermissionsRead']['Variables']['ReadPerDomain'][$GPO.DomainName]++ } $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'][$GPO.DomainName]++ } foreach ($GPO in $Script:Reporting['GPOPermissionsRead']['Data'].Permissions) { if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'][$GPO.DomainName] = 0 } if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouchPerDomain'][$GPO.DomainName]) { $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouchPerDomain'][$GPO.DomainName] = 0 } if ($GPO.Permission -in 'GpoApply', 'GpoRead') { $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouch']++ $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouchPerDomain'][$GPO.DomainName]++ } else { $Script:Reporting['GPOPermissionsRead']['Variables']['WillFix']++ $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'][$GPO.DomainName]++ } } if ($Script:Reporting['GPOPermissionsRead']['Variables']['WillFix'] -gt 0 -or $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead'] -gt 0) { $Script:Reporting['GPOPermissionsRead']['ActionRequired'] = $true } else { $Script:Reporting['GPOPermissionsRead']['ActionRequired'] = $false } $Script:Reporting['GPOPermissionsRead']['Variables']['TotalToFix'] = $Script:Reporting['GPOPermissionsRead']['Variables']['WillFix'] + $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead'] } Variables = @{ WillFix = 0 WillNotTouch = 0 WillFixPerDomain = $null WillNotTouchPerDomain = $null CouldNotRead = 0 CouldNotReadPerDomain = $null Read = 0 ReadPerDomain = $null TotalToFix = 0 TotalPerDomain = $null } Overview = { } Summary = { New-HTMLText -FontSize 10pt -TextBlock { "When GPO is created one of the permissions that are required for proper functioning of Group Policies is NT AUTHORITY\Authenticated Users. " "Some Administrators don't follow best practices and trying to remove GpoApply permission, remove also GpoRead permission from a GPO which can have consequences. " } -LineBreak New-HTMLText -Text "On June 14th, 2016 Microsoft released [HotFix](https://support.microsoft.com/en-gb/help/3159398/ms16-072-description-of-the-security-update-for-group-policy-june-14-2) that requires Authenticated Users to be present on all Group Policies to function properly: " -FontSize 10pt New-HTMLText -TextBlock { "MS16-072 changes the security context with which user group policies are retrieved. " "This by-design behavior change protects customers’ computers from a security vulnerability. " "Before MS16-072 is installed, user group policies were retrieved by using the user’s security context. " "After MS16-072 is installed, user group policies are retrieved by using the computer's security context." } -FontStyle italic -FontSize 10pt -FontWeight bold -LineBreak New-HTMLText -FontSize 10pt -Text @( "There are two parts to this assesment. Reading all Group Policies Permissions that account ", "$($Env:USERDOMAIN)\$($Env:USERNAME.ToUpper())", " has permissions to read and provide detailed assesment about permissions. ", "Second assesment checks for permissions that this account is not able to read at all, and therefore it has no visibility about permissions set on it. " "We just were able to detect the problem, but hopefully higher level account (Domain Admin) should be able to provide full assesment. " ) -FontWeight normal, bold, normal New-HTMLText -FontSize 10pt -Text "First assesment results: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies requiring Authenticated Users with GpoRead permission: ', $Script:Reporting['GPOPermissionsRead']['Variables']['WillFix'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouch'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -FontSize 10pt -Text "Secondary assesment results: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text "Group Policies couldn't read at all: ", $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead'] -FontWeight normal, bold New-HTMLListItem -Text "Group Policies with permissions allowing read: ", $Script:Reporting['GPOPermissionsRead']['Variables']['Read'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'With split per domain (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'].Keys) { New-HTMLListItem -Text @( "$Domain requires ", $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'][$Domain], " changes out of ", $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'][$Domain], "." ) -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text @( "That means we need to fix permissions on: " $($Script:Reporting['GPOPermissionsRead']['Variables']['TotalToFix']) " out of " $($Script:Reporting['GPOPermissionsRead']['Data'].Issues).Count " Group Policies. " ) -FontSize 10pt -FontWeight bold, bold, normal, bold, normal -Color Black, FreeSpeechRed, Black, Black -LineBreak -TextDecoration none, underline, underline, underline, none } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOPermissionsRead']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'Yes', 'No' -Color SpringGreen, Salmon New-ChartBar -Name 'Authenticated Users Available' -Value $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouch'], $Script:Reporting['GPOPermissionsRead']['Variables']['WillFix'] New-ChartBar -Name 'Accessible Group Policies' -Value $Script:Reporting['GPOPermissionsRead']['Variables']['Read'], $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead'] } -Title 'Group Policy Permissions' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Authenticated Users Analysis' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRead']['Data'].Permissions -Filtering { New-HTMLTableCondition -Name 'Permission' -Value '' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } New-HTMLSection -Name 'Group Policy Issues Assesment' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRead']['Data'].Issues -Filtering { New-HTMLTableCondition -Name 'PermissionIssue' -Value $true -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 -DefaultSortColumn PermissionIssue -DefaultSortOrder Descending -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix Group Policy Authenticated Users' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with fixing Group Policy Authenticated Users. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsReadBefore.html -Verbose -Type GPOPermissionsRead } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "GPOs with problems will be those not having any value for Permission/PermissionType columns. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $AuthenticatedUsers = Get-GPOZaurrPermission -Type AuthenticatedUsers -ReturnSecurityWhenNoData $AuthenticatedUsers | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Make a backup (optional)' { New-HTMLText -TextBlock { "The process of fixing GPO Permissions does NOT touch GPO content. It simply adds permissionss on AD and SYSVOL at the same time for given GPO. " "However, it's always good to have a backup before executing changes that may impact Active Directory. " } New-HTMLCodeBlock -Code { $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All $GPOSummary | Format-Table } New-HTMLText -TextBlock { "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. " } } New-HTMLWizardStep -Name 'Add Authenticated Users ability to read all GPO' { New-HTMLText -Text @( "Following command will find any GPO which doesn't have Authenticated User as GpoRead or GpoApply and will add it as GpoRead. ", "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ", "It ensures that COMPUTERS can read GPO properly to be able to Apply it. ", "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental adding of permissions." ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -WhatIf -Verbose } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data." } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -Verbose -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed adds Authenticated Users (GpoRead permission) only on first X non-compliant Group Policies. " "Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. " "In case of any issues please review and action accordingly." } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsReadAfter.html -Verbose -Type GPOPermissionsRead } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOPermissionsRead']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRead']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 } } } } $GPOZaurrPermissionsRoot = [ordered] @{ Name = 'Group Policies Root Permissions' Enabled = $false Action = $null Data = $null Execute = { Get-GPOZaurrPermissionRoot -SkipNames -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { } Variables = @{ } Overview = { } Solution = { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRoot']['Data'] -Filtering -PagingOptions 7, 15, 30, 45, 60 -ScrollX if ($Script:Reporting['GPOPermissionsRoot']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRoot']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $GPOZaurrPermissionsUnknown = [ordered] @{ Name = 'Group Policy Unknown Permissions' Enabled = $false Action = $null Data = $null Execute = { Get-GPOZaurrPermission -Type Unknown -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'] = @{} foreach ($GPO in $Script:Reporting['GPOPermissionsUnknown']['Data']) { if (-not $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'][$GPO[0].DomainName]) { $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'][$GPO[0].DomainName] = 0 } $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFix']++ $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'][$GPO[0].DomainName]++ } if ($Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFix'] -gt 0) { $Script:Reporting['GPOPermissionsUnknown']['ActionRequired'] = $true } else { $Script:Reporting['GPOPermissionsUnknown']['ActionRequired'] = $false } } Variables = @{ WillFix = 0 WillFixPerDomain = $null } Overview = { } Summary = { New-HTMLText -FontSize 10pt -TextBlock { "Group Policies contain multiple permissions for different level of access. " "Be it adminstrative permissions, read permissions or apply permissions. " "Over time some users or groups get deleted for different reasons and such permission in Group Policies leave a trace in form of Unknown SID. " "Unknown SIDs can also be remains of Active Directory Trusts, that have been deleted or are otherwise unavailable. " "Following assesment detects all unknown permissions and provides them for review & deletion. " } -LineBreak New-HTMLText -FontSize 10pt -Text "Assesment results: " -FontWeight bold New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Group Policies requiring removal of unknown SIDs: ', $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFix'] -FontWeight normal, bold } -FontSize 10pt New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold New-HTMLList -Type Unordered { foreach ($Domain in $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'].Keys) { New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal } } -FontSize 10pt New-HTMLText -Text @( "That means we need to remove " $($Script:Reporting['GPOPermissionsUnknown']['Variables'].WillFix) " unknown permissions from Group Policies. " ) -FontSize 10pt -FontWeight normal, bold, normal -Color Black, FreeSpeechRed, Black -LineBreak -TextDecoration none, underline, none } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['GPOPermissionsUnknown']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Name 'Yes' -Color Salmon New-ChartBar -Name 'Unknown Permissions Present' -Value $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFix'] } -Title 'Group Policy Permissions' -TitleAlignment center } } New-HTMLSection -Name 'Group Policy Unknown Permissions Analysis' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsUnknown']['Data'] -Filtering { New-HTMLTableCondition -Name 'Permission' -Value '' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 -ScrollX } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix Group Policy Unknown Permissions' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module GPOZaurr -Force Import-Module GPOZaurr -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare report' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with removing unknown permissions. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsUnknownBefore.html -Verbose -Type GPOPermissionsUnknown } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment. " "The table only shows GPO and their unknown permissions. " "It doesn't show permissions that are not subject of this investigation. " "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $UnknownPermissions = Get-GPOZaurrPermission -Type Unknown $UnknownPermissions | Format-Table } New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you." } New-HTMLWizardStep -Name 'Make a backup (optional)' { New-HTMLText -TextBlock { "The process of fixing GPO Permissions does NOT touch GPO content. It simply removes permissionss on AD and SYSVOL at the same time for given GPO. " "However, it's always good to have a backup before executing changes that may impact Active Directory. " } New-HTMLCodeBlock -Code { $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All $GPOSummary | Format-Table } New-HTMLText -TextBlock { "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. " } } New-HTMLWizardStep -Name 'Remove Unknown Permissions' { New-HTMLText -Text @( "Following command will find any GPO which has an unknown SID and will remove it. ", "This change doesn't change any other permissions. ", "It ensures that GPOs have no unknown permissions present. ", "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental adding of permissions." ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black New-HTMLCodeBlock -Code { Remove-GPOZaurrPermission -Verbose -Type Unknown -WhatIf } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrPermission -Verbose -Type Unknown -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data." } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold New-HTMLCodeBlock -Code { Remove-GPOZaurrPermission -Verbose -Type Unknown -LimitProcessing 2 } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Remove-GPOZaurrPermission -Verbose -Type Unknown -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "This command when executed removes only first X unknwon permissions from Group Policies. " "Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. " "In case of any issues please review and action accordingly. " } } New-HTMLWizardStep -Name 'Verification report' { New-HTMLText -TextBlock { "Once cleanup task was executed properly, we need to verify that report now shows no problems." } New-HTMLCodeBlock -Code { Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsUnknownAfter.html -Verbose -Type GPOPermissionsUnknown } New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['GPOPermissionsUnknown']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsUnknown']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 7, 15, 30, 45, 60 } } } } $GPOZaurrSysVolLegacyFiles = [ordered] @{ Name = 'SYSVOL Legacy ADM Files' Enabled = $false Action = $null Data = $null Execute = { Get-GPOZaurrLegacyFiles -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { } Variables = @{ } Overview = { } Summary = { New-HTMLText -TextBlock { "This report shows legacy ADM files in SYSVOL. These files are no longer used and can be safely removed. " "Before 'adm' files were replaced by 'admx' files, they were stored in the SYSVOL share, directly per each GPO. " "This report will help you identify and remove these files. " } } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:GPOConfiguration['SysVolLegacyFiles']['Summary'] } } New-HTMLSection -Name "Legacy ADM Files in SYSVOL" { New-HTMLTable -DataTable $Script:Reporting['SysVolLegacyFiles']['Data'] -Filtering -PagingOptions 7, 15, 30, 45, 60 -ScrollX } if ($Script:Reporting['SysVolLegacyFiles']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['SysVolLegacyFiles']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } function New-GPOZaurrExclusions { <# .SYNOPSIS Creates exclusion code for Group Policy Objects (GPOs) in Zaurr format. .DESCRIPTION The New-GPOZaurrExclusions function generates exclusion code for GPOs in Zaurr format based on the provided exclusions. It supports both script block and array types of exclusions. .PARAMETER Exclusions Specifies the exclusions to be included in the generated code. This parameter accepts script blocks or arrays of exclusion items. .EXAMPLE $exclusions = { # Exclude specific settings 'Setting1', 'Setting2' } New-GPOZaurrExclusions -Exclusions $exclusions .EXAMPLE $exclusions = @('Setting1', 'Setting2') New-GPOZaurrExclusions -Exclusions $exclusions #> [cmdletBinding()] param( [alias('ExcludeGroupPolicies', 'ExclusionsCode', 'ExclusionsArray')][Parameter(Position = 1)][object] $Exclusions ) if ($Exclusions) { if ($Exclusions -is [scriptblock]) { [string] $Code = @( "`$Exclusions = {" " " + $Exclusions.ToString() "}" ) $Code } if ($Exclusions -is [Array]) { [string] $Code = @( '$Exclusions = @(' [System.Environment]::NewLine foreach ($Exclusion in $Exclusions) { " `"$Exclusion`"" + [System.Environment]::NewLine } [System.Environment]::NewLine ')' ) $Code } } } function New-GPOZaurrReportConsole { <# .SYNOPSIS Generates a detailed report of Group Policy Objects (GPO) applied to a computer and user. .DESCRIPTION This function provides a comprehensive overview of the Group Policy Objects (GPO) applied to a specific computer and user. It includes information such as last applied time, computer details, domain name, organizational unit, site, GPO types, slow link status, applied GPOs, and denied GPOs. .PARAMETER Results An IDictionary containing the results of Group Policy Object queries for both computer and user. .PARAMETER ComputerName The name of the computer for which the GPO report is generated. .EXAMPLE New-GPOZaurrReportConsole -Results $Results -ComputerName "MyComputer" Generates a detailed report of Group Policy Objects applied to the computer named "MyComputer". #> [cmdletBinding()] param( [System.Collections.IDictionary] $Results, [string] $ComputerName ) Begin { $GPODeny = @{ Color = 'Yellow', 'Red', 'Yellow', 'Red' StartSpaces = 6 } $GPOSuccess = @{ Color = 'Yellow', 'Green', 'Yellow', 'Green' StartSpaces = 6 } $WriteSummary = @{ Color = 'Yellow', 'Blue' StartSpaces = 3 } $ComputerWhereApplied = ($Results.ComputerResults.GroupPolicies | Sort-Object -Property DomainName, Name).Where( { $_.Status -eq 'Applied' }, 'split') $UserWhereApplied = ($Results.UserResults.GroupPolicies | Sort-Object -Property Name).Where( { $_.Status -eq 'Applied' }, 'split') } Process { if ($Results.ComputerResults) { Write-Color -Text 'Computer Settings' -Color Yellow -LinesBefore 1 Write-Color -Text '[>] Last time Group Policy was applied: ', $Results.ComputerResults.Summary.ReadTime @WriteSummary Write-Color -Text '[>] Computer Name: ', $Results.ComputerResults.Summary.ComputerName @WriteSummary Write-Color -Text '[>] Domain Name: ', $Results.ComputerResults.Summary.DomainName @WriteSummary Write-Color -Text '[>] Organizational Unit: ', $Results.ComputerResults.Summary.OrganizationalUnit @WriteSummary Write-Color -Text '[>] Site: ', $Results.ComputerResults.Summary.Site @WriteSummary Write-Color -Text '[>] GPO Types: ', ($Results.ComputerResults.Summary.GPOTypes -replace [System.Environment]::NewLine, ', ') @WriteSummary Write-Color -Text '[>] Slow link: ', ($Results.ComputerResults.Summary.SlowLink) @WriteSummary Write-Color -Text 'Applied Group Policy Objects' -StartSpaces 3 -LinesBefore 1 foreach ($GPO in $ComputerWhereApplied[0]) { Write-Color -Text '[+] [', $GPO.DomainName, '] ', $GPO.Name @GPOSuccess } Write-Color -Text 'Denied Group Policy Objects' -StartSpaces 3 foreach ($GPO in $ComputerWhereApplied[1]) { Write-Color -Text '[-] [', $GPO.DomainName, '] ', $GPO.Name @GPODeny } } else { Write-Color -Text 'Computer Settings' -Color Yellow -LinesBefore 1 Write-Color -Text '[>] Last time Group Policy was applied: ', 'Unable to get? No administrative permission?' @WriteSummary Write-Color -Text '[>] Computer Name: ', $ComputerName @WriteSummary } Write-Color -Text 'User Settings' -Color Yellow -LinesBefore 1 Write-Color -Text '[>] Last time Group Policy was applied: ', $Results.UserResults.Summary.ReadTime @WriteSummary Write-Color -Text '[>] Computer Name: ', $Results.UserResults.Summary.ComputerName @WriteSummary Write-Color -Text '[>] Domain Name: ', $Results.UserResults.Summary.DomainName @WriteSummary Write-Color -Text '[>] Organizational Unit: ', $Results.UserResults.Summary.OrganizationalUnit @WriteSummary Write-Color -Text '[>] Site: ', $Results.UserResults.Summary.Site @WriteSummary Write-Color -Text '[>] GPO Types: ', ($Results.UserResults.Summary.GPOTypes -replace [System.Environment]::NewLine, ', ') @WriteSummary Write-Color -Text '[>] Slow link: ', ($Results.UserResults.Summary.SlowLink) @WriteSummary Write-Color -Text 'Applied Group Policy Objects' -StartSpaces 3 foreach ($GPO in $UserWhereApplied[0] ) { Write-Color -Text '[+] [', $GPO.DomainName, '] ', $GPO.Name @GPOSuccess } Write-Color -Text 'Denied Group Policy Objects' -StartSpaces 3 foreach ($GPO in $UserWhereApplied[1]) { Write-Color -Text '[-] [', $GPO.DomainName, '] ', $GPO.Name @GPODeny } } } function New-GPOZaurrReportHTML { <# .SYNOPSIS Generates an HTML report based on Group Policy information. .DESCRIPTION The New-GPOZaurrReportHTML function generates an HTML report based on the Group Policy information provided. It includes detailed sections for general computer information, CPU and RAM details, operating system information, and disk information. .PARAMETER Support Specifies the Group Policy support information to be included in the report. .PARAMETER Path Specifies the path where the HTML report will be saved. If not provided, a temporary file will be created. .PARAMETER Online Indicates whether to generate an online report. .PARAMETER Open Indicates whether to open the generated report after creation. .EXAMPLE New-GPOZaurrReportHTML -Support $GPOInfo -Path "C:\Reports\GPOReport.html" Generates an HTML report based on the Group Policy information in $GPOInfo and saves it to the specified path. .EXAMPLE New-GPOZaurrReportHTML -Support $GPOInfo -Online Generates an online HTML report based on the Group Policy information in $GPOInfo. .EXAMPLE New-GPOZaurrReportHTML -Support $GPOInfo -Path "C:\Reports\GPOReport.html" -Open Generates an HTML report based on the Group Policy information in $GPOInfo, saves it to the specified path, and opens the report after creation. #> [cmdletBinding()] param( [System.Collections.IDictionary] $Support, [string] $Path, [switch] $Online, [switch] $Open ) $PSDefaultParameterValues = @{ "New-HTMLTable:WarningAction" = 'SilentlyContinue' } if (-not $Path) { $Path = [io.path]::GetTempFileName().Replace('.tmp', ".html") } $ComputerName = $($Support.ResultantSetPolicy.LoggingComputer) New-HTML -TitleText "Group Policy Report - $ComputerName" { New-HTMLTableOption -DataStore JavaScript -BoolAsString New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLPanelStyle -BorderRadius 0px New-HTMLTabOptions -SlimTabs ` -BorderBottomStyleActive solid -BorderBottomColorActive LightSkyBlue -BackgroundColorActive none ` -TextColorActive Black -Align left -BorderRadius 0px -RemoveShadow -TextColor Grey -TextTransform capitalize New-HTMLTab -Name 'Information' { New-HTMLSection { New-HTMLSection -HeaderText 'General Information' { New-HTMLTable -DataTable $Support.ComputerInformation.Time -Filtering -Transpose { New-TableHeader -Names 'Name', 'Value' -Title 'Time Information' } New-HTMLTable -DataTable $Support.ComputerInformation.BIOS -Filtering -Transpose { New-TableHeader -Names 'Name', 'Value' -Title 'BIOS Information' } } New-HTMLContainer { New-HTMLSection -HeaderText 'CPU Information' { New-HTMLTable -DataTable $Support.ComputerInformation.CPU -Filtering } New-HTMLSection -HeaderText 'RAM Information' { New-HTMLTable -DataTable $Support.ComputerInformation.RAM -Filtering } } } New-HTMLSection -HeaderText 'Operating System Information' { New-HTMLTable -DataTable $Support.ComputerInformation.OperatingSystem -Filtering New-HTMLTable -DataTable $Support.ComputerInformation.System -Filtering } New-HTMLSection -HeaderText 'Disk Information' { New-HTMLTable -DataTable $Support.ComputerInformation.Disk -Filtering New-HTMLTable -DataTable $Support.ComputerInformation.DiskLogical -Filtering } New-HTMLSection -HeaderText 'Services Information' { New-HTMLTable -DataTable $Support.ComputerInformation.Services -Filtering } } foreach ($Key in $Support.Keys) { if ($Key -in 'ResultantSetPolicy', 'ComputerInformation') { continue } New-HTMLTab -Name $Key { New-HTMLTab -Name 'Summary' { New-HTMLSection -Invisible { New-HTMLSection -HeaderText 'Summary' { New-HTMLTable -DataTable $Support.$Key.Summary -Filtering -PagingOptions @(7, 14 ) New-HTMLTable -DataTable $Support.$Key.SummaryDetails -Filtering -PagingOptions @(7, 14) -Transpose } New-HTMLSection -HeaderText 'Part of Security Groups' { New-HTMLTable -DataTable $Support.$Key.SecurityGroups -Filtering -PagingOptions @(7, 14) } } } New-HTMLTab -Name 'Group Policies' { New-HTMLSection -Invisible { New-HTMLSection -HeaderText 'ExtensionStatus' { New-HTMLPanel { New-HTMLTable -DataTable $Support.$Key.ExtensionStatus -Filtering } New-HTMLPanel { New-HTMLChart -Title 'Extension TimeLine' -TitleAlignment center { foreach ($Extension in $Support.$Key.ExtensionStatus) { New-ChartTimeLine -DateFrom ([DateTime] $Extension.BeginTime) -DateTo ([DateTime] $Extension.EndTime) -Name $Extension.Name } } } } } New-HTMLSection -HeaderText 'Group Policies' { New-HTMLTable -DataTable $Support.$Key.GroupPolicies -Filtering { New-TableCondition -Name 'Status' -Value 'Applied' -BackgroundColor BrightGreen -Row New-TableCondition -Name 'Status' -Value 'Denied' -BackgroundColor Salmon -Row New-TableCondition -Name 'IsValid' -Value $true -BackgroundColor BrightGreen New-TableCondition -Name 'IsValid' -Value $false -BackgroundColor Salmon New-TableCondition -Name 'FilterAllowed' -Value $true -BackgroundColor BrightGreen New-TableCondition -Name 'FilterAllowed' -Value $false -BackgroundColor Salmon New-TableCondition -Name 'AccessAllowed' -Value $true -BackgroundColor BrightGreen New-TableCondition -Name 'AccessAllowed' -Value $false -BackgroundColor Salmon } } New-HTMLSection -HeaderText 'Group Policies Links' { New-HTMLTable -DataTable $Support.$Key.GroupPoliciesLinks -Filtering } } New-HTMLTab -Name 'Extension Data' { New-HTMLSection -HeaderText 'Extension Data' { New-HTMLTable -DataTable $Support.$Key.ExtensionData -Filtering } } New-HTMLTab -Name 'Scope of Management' { New-HTMLSection -HeaderText 'Scope of Management' { New-HTMLTable -DataTable $Support.$Key.ScopeOfManagement -Filtering } } } } if ($Support.ComputerResults.Results) { New-HTMLTab -Name 'Details' { foreach ($Detail in $Support.ComputerResults.Results.Keys) { $ShortDetails = $Support.ComputerResults.Results[$Detail] New-HTMLTab -Name $Detail { New-HTMLTab -Name 'Test' { New-HTMLSection -HeaderText 'Summary Downloads' { New-HTMLTable -DataTable $ShortDetails.SummaryDownload -HideFooter } New-HTMLSection -HeaderText 'Processing Time' { New-HTMLTable -DataTable $ShortDetails.ProcessingTime -Filtering } New-HTMLSection -HeaderText 'Group Policies Applied' { New-HTMLTable -DataTable $ShortDetails.GroupPoliciesApplied -Filtering } New-HTMLSection -HeaderText 'Group Policies Denied' { New-HTMLTable -DataTable $ShortDetails.GroupPoliciesDenied -Filtering } } New-HTMLTab -Name 'Events By ID' { foreach ($ID in $ShortDetails.EventsByID.Keys) { New-HTMLSection -HeaderText "Event ID $ID" { New-HTMLTable -DataTable $ShortDetails.EventsByID[$ID] -Filtering -AllProperties } } } New-HTMLTab -Name 'Events' { New-HTMLSection -HeaderText 'Events' { New-HTMLTable -DataTable $ShortDetails.Events -Filtering -AllProperties } } } } } } } -Online:$Online.IsPresent -Open:$Open.IsPresent -FilePath $Path } function New-HTMLReportAll { <# .SYNOPSIS Generates an HTML report based on specified parameters. .DESCRIPTION This function generates an HTML report with customizable content based on the provided parameters. It supports generating standard reports and can be used for various reporting purposes. .PARAMETER FilePath Specifies the file path where the HTML report will be saved. .PARAMETER Online Indicates whether the report should be generated online. .PARAMETER HideHTML Hides the HTML content of the report if specified. .PARAMETER Type Specifies the type of report to generate. .EXAMPLE New-HTMLReportAll -FilePath "C:\Reports\Report.html" -Online -Type "Summary" Generates an HTML report with a summary of data and saves it to the specified file path. .EXAMPLE New-HTMLReportAll -FilePath "C:\Reports\FullReport.html" -Type "Detailed" Generates a detailed HTML report and saves it to the specified file path. #> [CmdletBinding()] param( [string] $FilePath, [switch] $Online, [switch] $HideHTML, [Array] $Type ) Write-Color -Text '[i]', '[HTML ] ', "Generating HTML report ($FilePath)" -Color Yellow, DarkGray, Yellow New-HTML -Author 'Przemysław Kłys @ Evotec' -TitleText 'GPOZaurr Report' { New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLPanelStyle -BorderRadius 0px New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin 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 "GPOZaurr - $($Script:Reporting['Version'])" -Color Blue } -JustifyContent flex-end -Invisible } } if ($Type.Count -eq 1) { foreach ($T in $Script:GPOConfiguration.Keys) { if ($Script:GPOConfiguration[$T].Enabled -eq $true) { if ($Script:GPOConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Summary'] } & $Script:GPOConfiguration[$T]['Solution'] } } } else { foreach ($T in $Script:GPOConfiguration.Keys) { if ($Script:GPOConfiguration[$T].Enabled -eq $true) { if ($Script:GPOConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Summary'] } New-HTMLTab -Name $Script:GPOConfiguration[$T]['Name'] { & $Script:GPOConfiguration[$T]['Solution'] } } } } } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath } function New-HTMLReportWithSplit { <# .SYNOPSIS Creates an HTML report with the option to split it into multiple files for easier viewing. .DESCRIPTION This function generates an HTML report based on the provided parameters. It also allows splitting the report into multiple files for better organization and readability. .PARAMETER FilePath Specifies the path where the HTML report will be saved. .PARAMETER Online Indicates whether the report should be generated for online viewing. .PARAMETER HideHTML Hides the HTML report file after generation. .PARAMETER CurrentReport Specifies the type of the current report to generate. .EXAMPLE New-HTMLReportWithSplit -FilePath "C:\Reports\GPO_Report.html" -Online -HideHTML -CurrentReport "Security" Generates an HTML report for the "Security" report type and saves it to the specified file path. The report is optimized for online viewing and the HTML file is hidden after generation. .EXAMPLE New-HTMLReportWithSplit -FilePath "C:\Reports\All_Reports.html" -CurrentReport "All" Generates an HTML report for all available report types and saves it to the specified file path. #> [cmdletBinding()] param( [string] $FilePath, [switch] $Online, [switch] $HideHTML, [string] $CurrentReport ) $DateName = $(Get-Date -f yyyy-MM-dd_HHmmss) $FileName = [io.path]::GetFileNameWithoutExtension($FilePath) $DirectoryName = [io.path]::GetDirectoryName($FilePath) foreach ($T in $Script:GPOConfiguration.Keys) { $NewFileName = $FileName + '_' + $T + "_" + $DateName + '.html' $FilePath = [io.path]::Combine($DirectoryName, $NewFileName) if ($Script:GPOConfiguration[$T].Enabled -eq $true -and ((-not $CurrentReport) -or ($CurrentReport -and $CurrentReport -eq $T))) { Write-Color -Text '[i]', '[HTML ] ', "Generating HTML report ($FilePath) for $T with split reports" -Color Yellow, DarkGray, Yellow New-HTML -Author 'Przemysław Kłys @ Evotec' -TitleText 'GPOZaurr Report' { New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLPanelStyle -BorderRadius 0px New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin 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 "GPOZaurr - $($Script:Reporting['Version'])" -Color Blue } -JustifyContent flex-end -Invisible } } if ($Script:GPOConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Summary'] } New-HTMLTab -Name $Script:GPOConfiguration[$T]['Name'] { & $Script:GPOConfiguration[$T]['Solution'] } } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath } } } function Remove-PrivPermission { <# .SYNOPSIS Removes a specified permission from a Group Policy Object (GPO). .DESCRIPTION This function removes a specified permission from a GPO based on the provided Principal and PrincipalType. It supports removing permissions by DistinguishedName or SID. It also provides the option to include specific permission types. .PARAMETER Principal Specifies the principal for which the permission needs to be removed. .PARAMETER PrincipalType Specifies the type of the principal. Valid values are 'DistinguishedName', 'Name', or 'Sid'. .PARAMETER GPOPermission Specifies the GPO permission object to remove. .PARAMETER IncludePermissionType Specifies the permission type to include in the removal process. .EXAMPLE Remove-PrivPermission -Principal "CN=User1,OU=Users,DC=Contoso,DC=com" -PrincipalType "DistinguishedName" -GPOPermission $PermissionObject -IncludePermissionType "Read" This example removes the "Read" permission for the user with the specified DistinguishedName from the GPO. .EXAMPLE Remove-PrivPermission -Principal "S-1-5-21-3623811015-3361044348-30300820-1013" -PrincipalType "Sid" -GPOPermission $PermissionObject -IncludePermissionType "Write" This example removes the "Write" permission for the user with the specified SID from the GPO. #> [cmdletBinding(SupportsShouldProcess)] param( [string] $Principal, [validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'DistinguishedName', [PSCustomObject] $GPOPermission, [alias('PermissionType')][Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType ) if ($GPOPermission.PrincipalName) { $Text = "Removing SID: $($GPOPermission.PrincipalSid), Name: $($GPOPermission.PrincipalDomainName)\$($GPOPermission.PrincipalName), SidType: $($GPOPermission.PrincipalSidType) from domain $($GPOPermission.DomainName)" } else { $Text = "Removing SID: $($GPOPermission.PrincipalSid), Name: EMPTY, SidType: $($GPOPermission.PrincipalSidType) from domain $($GPOPermission.DomainName)" } if ($PrincipalType -eq 'DistinguishedName') { if ($GPOPermission.DistinguishedName -eq $Principal -and $GPOPermission.Permission -eq $IncludePermissionType) { if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, $Text)) { try { Write-Verbose "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType for $($Principal) / $($GPOPermission.PrincipalName) / Type: $($GPOPermission.PermissionType)" $GPOPermission.GPOSecurity.Remove($GPOPermission.GPOSecurityPermissionItem) $GPOPermission.GPOObject.SetSecurityInfo($GPOPermission.GPOSecurity) } catch { Write-Warning "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType failed for $($Principal) with error: $($_.Exception.Message)" } } } } elseif ($PrincipalType -eq 'Sid') { if ($GPOPermission.PrincipalSid -eq $Principal -and $GPOPermission.Permission -eq $IncludePermissionType) { if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, $Text)) { try { Write-Verbose "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType for $($Principal) / $($GPOPermission.PrincipalName) / Type: $($GPOPermission.PermissionType)" $GPOPermission.GPOSecurity.Remove($GPOPermission.GPOSecurityPermissionItem) $GPOPermission.GPOObject.SetSecurityInfo($GPOPermission.GPOSecurity) } catch { if ($_.Exception.Message -like '*The request is not supported. (Exception from HRESULT: 0x80070032)*') { Write-Warning "Remove-GPOZaurrPermission - Bummer! The request is not supported, but lets fix it differently." $ACL = Get-ADACL -ADObject $GPOPermission.GPOObject.Path -Bundle if ($ACL) { Remove-ADACL -ACL $ACL -Principal $Principal -AccessControlType Deny -Verbose:$VerbosePreference } $ACL = Get-ADACL -ADObject $GPOPermission.GPOObject.Path -Bundle if ($ACL) { Remove-ADACL -ACL $ACL -Principal $Principal -AccessControlType Allow -Verbose:$VerbosePreference } } else { Write-Warning "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType failed for $($Principal) with error: $($_.Exception.Message)" } } } } } elseif ($PrincipalType -eq 'Name') { if ($GPOPermission.PrincipalName -eq $Principal -and $GPOPermission.Permission -eq $IncludePermissionType) { if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, $Text)) { try { Write-Verbose "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType for $($Principal) / $($GPOPermission.PrincipalName) / Type: $($GPOPermission.PermissionType)" $GPOPermission.GPOSecurity.Remove($GPOPermission.GPOSecurityPermissionItem) $GPOPermission.GPOObject.SetSecurityInfo($GPOPermission.GPOSecurity) } catch { Write-Warning "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType failed for $($Principal) with error: $($_.Exception.Message)" } } } } } function Reset-GPOZaurrStatus { <# .SYNOPSIS Resets the status of GPO configurations. .DESCRIPTION This function resets the status of GPO configurations by enabling the default types and disabling all other types. .EXAMPLE Reset-GPOZaurrStatus Resets the status of GPO configurations to default. #> param( ) if (-not $Script:DefaultTypes) { $Script:DefaultTypes = foreach ($T in $Script:GPOConfiguration.Keys) { if ($Script:GPOConfiguration[$T].Enabled) { $T } } } else { foreach ($T in $Script:GPOConfiguration.Keys) { $Script:GPOConfiguration[$T]['Enabled'] = $false } foreach ($T in $Script:DefaultTypes) { $Script:GPOConfiguration[$T]['Enabled'] = $true } } } $Script:Actions = @{ C = 'Create' D = 'Delete' U = 'Update' R = 'Replace' } $Script:GPOConfiguration = [ordered] @{ GPOBroken = $GPOZaurrOrphans GPOBrokenLink = $GPOZaurrBrokenLink GPOBrokenPartially = $GPOZaurrMissingFiles GPOOwners = $GPOZaurrOwners GPOConsistency = $GPOZaurrConsistency GPODuplicates = $GPOZaurrDuplicates GPOOrganizationalUnit = $GPOZaurrOrganizationalUnit GPOList = $GPOZaurrList GPOLinks = $GPOZaurrLinks GPOPassword = $GPOZaurrPassword GPOPermissions = $GPOZaurrPermissionsAnalysis GPOPermissionsAdministrative = $GPOZaurrPermissionsAdministrative GPOPermissionsRead = $GPOZaurrPermissionsRead GPOPermissionsRoot = $GPOZaurrPermissionsRoot GPOPermissionsUnknown = $GPOZaurrPermissionsUnknown GPORedirect = $GPOZaurrGPORedirects GPOFiles = $GPOZaurrFiles GPOBlockedInheritance = $GPOZaurrBlockedInheritance GPOAnalysis = $GPOZaurrAnalysis GPOUpdates = $GPOZaurrGPOUpdates NetLogonOwners = $GPOZaurrNetLogonOwners NetLogonPermissions = $GPOZaurrNetLogonPermissions SysVolLegacyFiles = $GPOZaurrSysVolLegacyFiles } $Script:GPODitionary = [ordered] @{ AccountPolicies = [ordered] @{ Types = @( @{ Category = 'SecuritySettings' Settings = 'Account' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Account Policies' Code = { ConvertTo-XMLAccountPolicy -GPO $GPO } CodeSingle = { ConvertTo-XMLAccountPolicy -GPO $GPO -SingleObject } } Audit = [ordered] @{ Types = @( @{ Category = 'SecuritySettings' Settings = 'Audit' } @{ Category = 'AuditSettings' Settings = 'AuditSetting' } ) GPOPath = @( 'Policies -> Windows Settings -> Security Settings -> Advanced Audit Policy Configuration -> Audit Policies' 'Policies -> Windows Settings -> Security Settings -> Local Policies -> Audit Policy' ) Code = { ConvertTo-XMLAudit -GPO $GPO } CodeSingle = { ConvertTo-XMLAudit -GPO $GPO -SingleObject } } Autologon = [ordered] @{ ByReports = @( @{ Report = 'RegistrySettings' } ) GPOPath = 'Preferences -> Windows Settings -> Registry' CodeReport = { ConvertTo-XMLRegistryAutologonOnReport -GPO $GPO } } AutoPlay = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/AutoPlay Policies' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/AutoPlay Policies*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/AutoPlay Policies*' -SingleObject } } Biometrics = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Biometrics' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Biometrics*' } } Bitlocker = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/BitLocker Drive Encryption' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/BitLocker Drive Encryption*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/BitLocker Drive Encryption*' -SingleObject } } ControlPanel = [ordered]@{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Controol Panel' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel' -SingleObject } } ControlPanelAddRemove = [ordered]@{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Control Panel/Add or Remove Programs' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Add or Remove Programs' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Add or Remove Programs' -SingleObject } } ControlPanelDisplay = [ordered]@{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Control Panel/Display' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Display' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Display' -SingleObject } } ControlPanelPersonalization = [ordered]@{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Control Panel/Personalization' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Personalization' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Personalization' -SingleObject } } ControlPanelPrinters = [ordered]@{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Control Panel/Printers' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Printers' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Printers' -SingleObject } } ControlPanelPrograms = [ordered]@{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Control Panel/Programs' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Programs' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Programs' -SingleObject } } ControlPanelRegional = [ordered]@{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Control Panel/Regional and Language Options' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Regional and Language Options' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Regional and Language Options' -SingleObject } } CredentialsDelegation = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> System/Credentials Delegation' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Credentials Delegation*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Credentials Delegation*' -SingleObject } } CustomInternationalSettings = [ordered]@{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Custom International Settings' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Custom International Settings*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Custom International Settings*' -SingleObject } } Desktop = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Desktop' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Desktop*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Desktop*' -SingleObject } } DnsClient = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Network/DNS Client' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Network/DNS Client*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Network/DNS Client*' -SingleObject } } DriveMapping = [ordered] @{ Types = @( @{ Category = 'DriveMapSettings' Settings = 'DriveMapSettings' } ) GPOPath = 'Preferences -> Windows Settings -> Drive Maps' Code = { ConvertTo-XMLDriveMapSettings -GPO $GPO } CodeSingle = { ConvertTo-XMLDriveMapSettings -GPO $GPO -SingleObject } } EventLog = [ordered] @{ Types = @( @{ Category = 'SecuritySettings' Settings = 'EventLog' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Event Log' Code = { ConvertTo-XMLEventLog -GPO $GPO } CodeSingle = { ConvertTo-XMLEventLog -GPO $GPO } } EventForwarding = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Event Forwarding' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Event Forwarding*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Event Forwarding*' -SingleObject } } EventLogService = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Event Log Service' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Event Log Service*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Event Log Service*' -SingleObject } } FileExplorer = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/File Explorer' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/File Explorer*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/File Explorer*' -SingleObject } } FolderRedirection = @{ Types = @( @{ Category = 'FolderRedirectionSettings' Settings = 'Folder' } ) GPOPath = 'Windows Settings -> Folder Redirection' Code = { ConvertTo-XMLFolderRedirection -GPO $GPO } CodeSingle = { ConvertTo-XMLFolderRedirection -GPO $GPO -SingleObject } } FolderRedirectionPolicy = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> System/Folder Redirection' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Folder Redirection' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Folder Redirection' -SingleObject } } FSLogix = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> FSLogix' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'FSLogix' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'FSLogix' -SingleObject } } Firefox = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = @( 'Policies -> Administrative Templates -> Mozilla -> Firefox' ) Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Mozilla\Firefox*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Mozilla\Firefox*' -SingleObject } } GoogleChrome = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = @( 'Policies -> Administrative Templates -> Google Chrome' 'Policies -> Administrative Templates -> Google/Google Chrome' 'Policies -> Administrative Templates -> Google Chrome - Default Settings (users can override)' ) Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Google Chrome*', 'Google/Google Chrome*', 'Google Chrome - Default Settings (users can override)' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Google Chrome*', 'Google/Google Chrome*', 'Google Chrome - Default Settings (users can override)' -SingleObject } } GroupPolicy = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> System/Group Policy' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Group Policy*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Group Policy*' -SingleObject } } InternetCommunicationManagement = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> System/Internet Communication Management' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Internet Communication Management*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Internet Communication Management*' -SingleObject } } InternetExplorer = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Internet Explorer' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Internet Explorer*', 'Composants Windows/Celle Internet Explorer' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Internet Explorer*', 'Composants Windows/Celle Internet Explorer' -SingleObject } } InternetExplorerZones = [ordered] @{ ByReports = @( @{ Report = 'RegistrySettings' } ) GPOPath = 'Preferences -> Windows Settings -> Registry' CodeReport = { ConvertTo-XMLRegistryInternetExplorerZones -GPO $GPO } } KDC = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> System/KDC' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/KDC' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/KDC' -SingleObject } } LAPS = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> LAPS' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'LAPS' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'LAPS' -SingleObject } } Lithnet = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Lithnet/Password Protection for Active Directory' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Lithnet/Password Protection for Active Directory*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Lithnet/Password Protection for Active Directory*' -SingleObject } } LocalUsers = [ordered] @{ Types = @( @{ Category = 'LugsSettings' Settings = 'LocalUsersAndGroups' } ) GPOPath = 'Preferences -> Control Panel Settings -> Local Users and Groups' Code = { ConvertTo-XMLLocalUser -GPO $GPO } CodeSingle = { ConvertTo-XMLLocalUser -GPO $GPO -SingleObject } } LocalGroups = [ordered] @{ Types = @( @{ Category = 'LugsSettings' Settings = 'LocalUsersAndGroups' } ) GPOPath = 'Preferences -> Control Panel Settings -> Local Users and Groups' Code = { ConvertTo-XMLLocalGroups -GPO $GPO } CodeSingle = { ConvertTo-XMLLocalGroups -GPO $GPO -SingleObject } } Logon = @{ Types = @( @{ Category = 'RegistrySettings'; Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> System/Logon' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Logon*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Logon*' -SingleObject } } MicrosoftOutlook2002 = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Microsoft Outlook 2002' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2002*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2002*' -SingleObject } } MicrosoftEdge = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = @( 'Policies -> Administrative Templates -> Microsoft Edge' 'Policies -> Administrative Templates -> Windows Components/Edge UI' 'Policies -> Administrative Templates -> Windows Components/Microsoft Edge' ) Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Edge*', 'Windows Components/Microsoft Edge', 'Windows Components/Edge UI' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Edge*', 'Windows Components/Microsoft Edge', 'Windows Components/Edge UI' -SingleObject } } MicrosoftOutlook2003 = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = @( 'Policies -> Administrative Templates -> Microsoft Office Outlook 2003' 'Policies -> Administrative Templates -> Outlook 2003 RPC Encryption' ) Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Office Outlook 2003*', 'Outlook 2003 RPC Encryption' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Office Outlook 2003*', 'Outlook 2003 RPC Encryption' -SingleObject } } MicrosoftOutlook2010 = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Microsoft Outlook 2010' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2010*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2010*' -SingleObject } } MicrosoftOutlook2013 = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Microsoft Outlook 2013' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2013*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2013*' -SingleObject } } MicrosoftOutlook2016 = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Microsoft Outlook 2016' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2016*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2016*' -SingleObject } } MicrosoftManagementConsole = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Microsoft Management Console' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Management Console*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Management Console*' -SingleObject } } NetMeeting = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/NetMeeting' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/NetMeeting*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/NetMeeting*' -SingleObject } } MSSLegacy = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> MSS (Legacy)' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'MSS (Legacy)' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'MSS (Legacy)' -SingleObject } } MSSecurityGuide = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> MS Security Guide' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'MS Security Guide' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'MS Security Guide' -SingleObject } } OneDrive = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/OneDrive' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/OneDrive*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/OneDrive*' -SingleObject } } Policies = @{ Comment = "This isn't really translated" Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates' Code = { ConvertTo-XMLPolicies -GPO $GPO } CodeSingle = { ConvertTo-XMLPolicies -GPO $GPO -SingleObject } } Printers = @{ Types = @( @{ Category = 'PrintersSettings' Settings = 'Printers' } @{ Category = 'PrinterConnectionSettings' Settings = 'PrinterConnection' } ) GPOPath = 'Preferences -> Control Panel Settings -> Printers' Code = { ConvertTo-XMLPrinter -GPO $GPO } CodeSingle = { ConvertTo-XMLPrinter -GPO $GPO -SingleObject } } PrintersPolicies = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = @( 'Policies -> Administrative Templates -> Printers' 'Policies -> Administrative Templates -> Control Panel/Printers' ) Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Printers*', 'Control Panel/Printers*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Printers*', 'Control Panel/Printers*' -SingleObject } } PublicKeyPoliciesCertificates = [ordered] @{ Types = @( @{ Category = 'PublicKeySettings' Settings = 'RootCertificate' } @{ Category = 'PublicKeySettings' Settings = 'IntermediateCACertificate' } @{ Category = 'PublicKeySettings' Settings = 'TrustedPeopleCertificate' } @{ Category = 'PublicKeySettings' Settings = 'UntrustedCertificate' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies' Code = { ConvertTo-XMLCertificates -GPO $GPO } CodeSingle = { ConvertTo-XMLCertificates -GPO $GPO -SingleObject } } PublicKeyPoliciesAutoEnrollment = [ordered] @{ Types = @( @{ Category = 'PublicKeySettings' Settings = 'AutoEnrollmentSettings' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies' Code = { ConvertTo-XMLGenericPublicKey -GPO $GPO } CodeSingle = { ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject } } PublicKeyPoliciesEFS = [ordered] @{ Types = @( @{ Category = 'PublicKeySettings' Settings = 'EFSSettings' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies' Code = { ConvertTo-XMLGenericPublicKey -GPO $GPO } CodeSingle = { ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject } } PublicKeyPoliciesRootCA = [ordered] @{ Types = @( @{ Category = 'PublicKeySettings' Settings = 'RootCertificateSettings' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies' Code = { ConvertTo-XMLGenericPublicKey -GPO $GPO } CodeSingle = { ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject } } PublicKeyPoliciesEnrollmentPolicy = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = @( 'Policies -> Windows Settings -> Security Settings -> Public Key Policies' ) Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Internet Communication Management*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Internet Communication Management*' -SingleObject } } RegistrySetting = [ordered] @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'RegistrySetting' } ) GPOPath = "Mixed - missing ADMX?" Code = { ConvertTo-XMLGenericPublicKey -GPO $GPO } CodeSingle = { ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject } } RegistrySettings = [ordered] @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'RegistrySettings' } ) GPOPath = 'Preferences -> Windows Settings -> Registry' Code = { ConvertTo-XMLRegistrySettings -GPO $GPO } CodeSingle = { ConvertTo-XMLRegistrySettings -GPO $GPO -SingleObject } } OnlineAssistance = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Online Assistance' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Online Assistance*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Online Assistance*' -SingleObject } } RemoteAssistance = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> System/Remote Assistance' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Remote Assistance*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Remote Assistance*' -SingleObject } } RemoteDesktopServices = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Remote Desktop Services' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Remote Desktop Services*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Remote Desktop Services*' -SingleObject } } RSSFeeds = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/RSS Feeds' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/RSS Feeds*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/RSS Feeds*' -SingleObject } } Scripts = [ordered] @{ Types = @( @{ Category = 'Scripts' Settings = 'Script' } ) GPOPath = 'Policies -> Windows Settings -> Scripts' Code = { ConvertTo-XMLScripts -GPO $GPO } CodeSingle = { ConvertTo-XMLScripts -GPO $GPO -SingleObject } } SecurityOptions = [ordered] @{ Types = @( @{ Category = 'SecuritySettings' Settings = 'SecurityOptions' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Local Policies -> Security Options' Code = { ConvertTo-XMLSecurityOptions -GPO $GPO } CodeSingle = { ConvertTo-XMLSecurityOptions -GPO $GPO -SingleObject } } SoftwareInstallation = [ordered] @{ Types = @( @{ Category = 'SoftwareInstallationSettings' Settings = 'MsiApplication' } ) GPOPath = 'Policies -> Software Settings -> Software Installation' Code = { ConvertTo-XMLSoftwareInstallation -GPO $GPO } CodeSingle = { ConvertTo-XMLSoftwareInstallation -GPO $GPO -SingleObject } } SystemServices = [ordered] @{ Types = @( @{ Category = 'SecuritySettings' Settings = 'SystemServices' } ) Description = '' GPOPath = 'Policies -> Windows Settings -> Security Settings -> System Services' Code = { ConvertTo-XMLSystemServices -GPO $GPO } CodeSingle = { ConvertTo-XMLSystemServices -GPO $GPO -SingleObject } } SystemServicesNT = [ordered] @{ Types = @( @{ Category = 'ServiceSettings' Settings = 'NTServices' } ) Description = '' GPOPath = 'Preferences -> Control Pannel Settings -> Services' Code = { ConvertTo-XMLSystemServicesNT -GPO $GPO } CodeSingle = { ConvertTo-XMLSystemServicesNT -GPO $GPO -SingleObject } } TaskScheduler = [ordered] @{ Types = @( @{ Category = 'ScheduledTasksSettings' Settings = 'ScheduledTasks' } ) Description = '' GPOPath = 'Preferences -> Control Pannel Settings -> Scheduled Tasks' Code = { ConvertTo-XMLTaskScheduler -GPO $GPO } CodeSingle = { ConvertTo-XMLTaskScheduler -GPO $GPO -SingleObject } } TaskSchedulerPolicies = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Task Scheduler' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Task Scheduler*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Task Scheduler*' -SingleObject } } UserRightsAssignment = [ordered] @{ Types = @( @{ Category = 'SecuritySettings' Settings = 'UserRightsAssignment' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Local Policies -> User Rights Assignment' Code = { ConvertTo-XMLUserRightsAssignment -GPO $GPO } CodeSingle = { ConvertTo-XMLUserRightsAssignment -GPO $GPO -SingleObject } } WindowsDefender = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows Defender' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*' -SingleObject } } WindowsDefenderExploitGuard = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard*' -SingleObject } } WindowsFirewallConnectionSecurityRules = @{ Types = @( @{ Category = 'WindowsFirewallSettings' Settings = 'ConnectionSecurityRules' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security' Code = { ConvertTo-XMLWindowsFirewallSecurityRules -GPO $GPO } CodeSingle = { ConvertTo-XMLWindowsFirewallSecurityRules -GPO $GPO -SingleObject } } WindowsFirewallConnectionSecurityAuthentication = @{ Types = @( @{ Category = 'WindowsFirewallSettings' Settings = 'Phase1AuthenticationSets' } @{ Category = 'WindowsFirewallSettings' Settings = 'Phase2AuthenticationSets' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security' Code = { ConvertTo-XMLWindowsFirewallConnectionSecurityAuthentiation -GPO $GPO } CodeSingle = { ConvertTo-XMLWindowsFirewallConnectionSecurityAuthentiation -GPO $GPO -SingleObject } } WindowsFirewallProfiles = @{ Types = @( @{ Category = 'WindowsFirewallSettings' Settings = 'DomainProfile' } @{ Category = 'WindowsFirewallSettings' Settings = 'PublicProfile' } @{ Category = 'WindowsFirewallSettings' Settings = 'PrivateProfile' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security' Code = { ConvertTo-XMLWindowsFirewallProfile -GPO $GPO } CodeSingle = { ConvertTo-XMLWindowsFirewallProfile -GPO $GPO -SingleObject } } WindowsFirewallRules = @{ Types = @( @{ Category = 'WindowsFirewallSettings' Settings = 'InboundFirewallRules' } @{ Category = 'WindowsFirewallSettings' Settings = 'OutboundFirewallRules' } ) GPOPath = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security -> ' Code = { ConvertTo-XMLWindowsFirewallRules -GPO $GPO } CodeSingle = { ConvertTo-XMLWindowsFirewallRules -GPO $GPO -SingleObject } } WindowsHelloForBusiness = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows Hello For Business' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Hello For Business*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Hello For Business*' -SingleObject } } WindowsInstaller = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows Installer' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Installer*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Installer*' -SingleObject } } WindowsLogon = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows Logon Options' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Logon Options*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Logon Options*' -SingleObject } } WindowsMediaPlayer = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows Media Player' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Media Player*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Media Player*' -SingleObject } } WindowsMessenger = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows Messenger' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Messenger*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Messenger*' -SingleObject } } WindowsPowerShell = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows PowerShell' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows PowerShell*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows PowerShell*' -SingleObject } } WindowsRemoteManagement = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> Windows Components/Windows Remote Management (WinRM)' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Remote Management (WinRM)*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Remote Management (WinRM)*' -SingleObject } } WindowsTimeService = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = 'Policies -> Administrative Templates -> System/Windows Time Service' Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Windows Time Service*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Windows Time Service*' -SingleObject } } WindowsUpdate = @{ Types = @( @{ Category = 'RegistrySettings' Settings = 'Policy' } ) GPOPath = @( 'Policies -> Administrative Templates -> Windows Components/Windows Update' ) Code = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Update*', 'Windows Components/Delivery Optimization*' } CodeSingle = { ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Update*', 'Windows Components/Delivery Optimization*' -SingleObject } } } function Test-SysVolFolders { <# .SYNOPSIS Tests the SYSVOL folders for discrepancies between Group Policy Objects (GPOs) in Active Directory and SYSVOL. .DESCRIPTION The Test-SysVolFolders function compares the GPOs in Active Directory with the GPOs stored in the SYSVOL folder to identify any discrepancies. It checks for missing GPOs, GPOs not available in SYSVOL, and orphaned GPOs. .PARAMETER GPOs An array of Group Policy Objects to be compared. .PARAMETER Server The server name where the SYSVOL folder is located. .PARAMETER Domain The domain name where the SYSVOL folder is located. .PARAMETER PoliciesAD A dictionary containing the GPOs from Active Directory. .PARAMETER PoliciesSearchBase The search base for the GPOs in Active Directory. .EXAMPLE Test-SysVolFolders -GPOs $GPOs -Server "DC01" -Domain "contoso.com" -PoliciesAD $PoliciesAD -PoliciesSearchBase "OU=Group Policies,DC=contoso,DC=com" Description: This example tests the SYSVOL folders for discrepancies using the specified parameters. #> [cmdletBinding()] param( [Array] $GPOs, [string] $Server, [string] $Domain, [System.Collections.IDictionary] $PoliciesAD, [string] $PoliciesSearchBase ) $Differences = @{ } $SysvolHash = @{ } $GPOGUIDS = ConvertFrom-DistinguishedName -DistinguishedName $GPOs.DistinguishedName $SysVolPath = "\\$($Server)\SYSVOL\$Domain\Policies" Write-Verbose "Get-GPOZaurrBroken - Processing SYSVOL from \\$($Server)\SYSVOL\$Domain\Policies" try { $SYSVOL = Get-ChildItem -Path "\\$($Server)\SYSVOL\$Domain\Policies" -Exclude 'PolicyDefinitions' -ErrorAction Stop -Verbose:$false } catch { $Sysvol = $Null } foreach ($_ in $SYSVOL) { $GUID = $_.Name $SysvolHash[$GUID] = $_ } $Files = $SYSVOL.Name if ($Files) { $Comparing = Compare-Object -ReferenceObject $GPOGUIDS -DifferenceObject $Files -IncludeEqual foreach ($_ in $Comparing) { if ($_.InputObject -eq 'PolicyDefinitions') { continue } $ADStatus = $PoliciesAD[$_.InputObject] if ($_.SideIndicator -eq '==') { $Found = $ADStatus } elseif ($_.SideIndicator -eq '<=') { $Found = 'Not available on SYSVOL' } elseif ($_.SideIndicator -eq '=>') { if ($PoliciesAD[$_.InputObject]) { $Found = $PoliciesAD[$_.InputObject] } else { $Found = 'Not available in AD' } } else { $Found = 'Orphaned GPO' } $Differences[$_.InputObject] = $Found } } $GPOSummary = @( $Count = 0 foreach ($GPO in $GPOS) { $Count++ $GPOGuid = ConvertFrom-DistinguishedName -DistinguishedName $GPO.DistinguishedName if ($GPO.DisplayName) { $GPODisplayName = $GPO.DisplayName $GPOName = $GPO.Name Write-Verbose "Get-GPOZaurrBroken - Processing [$($Domain)]($Count/$($GPOS.Count)) $($GPO.DisplayName)" } else { $GPOName = $GPOGuid $GPODisplayName = $GPOGuid Write-Verbose "Get-GPOZaurrBroken - Processing [$($Domain)]($Count/$($GPOS.Count)) $($GPOGuid)" } if ($null -ne $SysvolHash[$GPOGuid].FullName) { $FullPath = $SysvolHash[$GPOGuid].FullName $ErrorMessage = '' } else { $FullPath = -join ($SysVolPath, "\$($GPOGuid)") $ErrorMessage = 'Not found on SYSVOL' } if ($null -eq $Differences[$GPOGuid]) { $SysVolStatus = 'Unknown issue' } else { $SysVolStatus = $Differences[$GPOGuid] } [PSCustomObject] @{ DisplayName = $GPODisplayName Status = $SysVolStatus DomainName = $Domain SysvolServer = $Server ObjectClass = $GPO.ObjectClass Id = $GPOName Path = $FullPath DistinguishedName = -join ("CN=", $GPOGuid, ",", $PoliciesSearchBase) Description = $GPO.Description CreationTime = $GPO.Created ModificationTime = $GPO.Modified Error = $ErrorMessage } } Write-Verbose "Get-GPOZaurrBroken - Processing SYSVOL differences" foreach ($_ in $Differences.Keys) { if ($Differences[$_] -in 'Not available in AD') { $FullPath = $SysvolHash[$_].FullName [PSCustomObject] @{ DisplayName = $SysvolHash[$_].BaseName Status = $Differences[$_] DomainName = $Domain SysvolServer = $Server ObjectClass = '' Id = $_ Path = $FullPath DistinguishedName = -join ("CN=", $_, ",", $PoliciesSearchBase) Description = $null CreationTime = $SysvolHash[$_].CreationTime ModificationTime = $SysvolHash[$_].LastWriteTime Error = $ErrorMessage } } } ) $GPOSummary | Sort-Object -Property DisplayName } function Add-GPOPermission { <# .SYNOPSIS Adds a permission to a Group Policy Object (GPO). .DESCRIPTION This function adds a permission to a specified Group Policy Object (GPO) based on the provided parameters. .PARAMETER Type Specifies the type of permission to add. Valid values are 'WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', and 'Default'. .PARAMETER IncludePermissionType Specifies the permission type to include. .PARAMETER Principal Specifies the principal to which the permission is granted. .PARAMETER PrincipalType Specifies the type of the principal. Valid values are 'DistinguishedName', 'Name', and 'Sid'. .PARAMETER PermitType Specifies whether to allow or deny the permission. Valid values are 'Allow' and 'Deny'. .EXAMPLE Add-GPOPermission -Type Default -IncludePermissionType Read -Principal "Domain Admins" -PrincipalType DistinguishedName -PermitType Allow Adds a permission to the GPO with default settings allowing 'Domain Admins' to read. .EXAMPLE Add-GPOPermission -Type Administrative -IncludePermissionType Write -Principal "Finance Group" -PrincipalType Name -PermitType Allow Adds a permission to the GPO for the 'Finance Group' allowing write access. .EXAMPLE Add-GPOPermission -Type AuthenticatedUsers -IncludePermissionType Modify -PermitType Deny Adds a permission to the GPO for all authenticated users denying modification. .EXAMPLE Add-GPOPermission -Type WellKnownAdministrative -IncludePermissionType FullControl -Principal "Enterprise Admins" -PrincipalType Sid -PermitType Allow Adds a permission to the GPO for 'Enterprise Admins' with full control. #> [cmdletBinding()] param( [validateset('WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', 'Default')][string] $Type = 'Default', [Microsoft.GroupPolicy.GPPermissionType] $IncludePermissionType, [alias('Trustee')][string] $Principal, [alias('TrusteeType')][validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'DistinguishedName', [validateSet('Allow', 'Deny')][string] $PermitType = 'Allow' ) if ($Type -eq 'Default') { @{ Action = 'Add' Type = 'Default' Principal = $Principal IncludePermissionType = $IncludePermissionType PrincipalType = $PrincipalType PermitType = $PermitType } } elseif ($Type -eq 'AuthenticatedUsers') { @{ Action = 'Add' Type = 'AuthenticatedUsers' IncludePermissionType = $IncludePermissionType PermitType = $PermitType } } elseif ($Type -eq 'Administrative') { @{ Action = 'Add' Type = 'Administrative' IncludePermissionType = $IncludePermissionType PermitType = $PermitType } } elseif ($Type -eq 'WellKnownAdministrative') { @{ Action = 'Add' Type = 'WellKnownAdministrative' IncludePermissionType = $IncludePermissionType PermitType = $PermitType } } } function Add-GPOZaurrPermission { <# .SYNOPSIS Adds permissions to a Group Policy Object (GPO) in Active Directory. .DESCRIPTION This function allows you to add permissions to a specified GPO in Active Directory. You can specify the GPO by name, GUID, or apply permissions to all GPOs. Various parameters allow you to customize the permission settings. .PARAMETER GPOName Specifies the name of the GPO to which permissions will be added. .PARAMETER GPOGuid Specifies the GUID of the GPO to which permissions will be added. .PARAMETER All Indicates that permissions should be added to all GPOs. .PARAMETER ADObject Specifies the Active Directory object to which permissions will be applied. .PARAMETER Type Specifies the type of permissions to be added. Valid values are 'WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', and 'Default'. .PARAMETER Principal Specifies the trustee to which permissions will be granted. .PARAMETER PrincipalType Specifies the type of the trustee. Valid values are 'DistinguishedName', 'Name', and 'Sid'. .PARAMETER PermissionType Specifies the type of permission to be added. .PARAMETER Inheritable Indicates whether permissions should be inheritable. .PARAMETER PermitType Specifies the type of permission to be granted. Valid values are 'Allow', 'Deny', and 'All'. .PARAMETER Forest Specifies the forest in which the GPO resides. .PARAMETER ExcludeDomains Specifies the domains to exclude when applying permissions. .PARAMETER IncludeDomains Specifies the domains to include when applying permissions. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER ADAdministrativeGroups Specifies the administrative groups in Active Directory. .PARAMETER LimitProcessing Specifies the maximum number of processing steps. .EXAMPLE Add-GPOZaurrPermission -GPOName "TestGPO" -Principal "User1" -PermissionType Read -PermitType Allow Adds read permission to "User1" for the GPO named "TestGPO". .EXAMPLE Add-GPOZaurrPermission -GPOGuid "12345678-1234-1234-1234-1234567890AB" -Principal "Group1" -PermissionType Write -PermitType Deny Denies write permission to "Group1" for the GPO with the specified GUID. #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'GPOName')] param( [Parameter(ParameterSetName = 'GPOName', Mandatory)][string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID', Mandatory)][alias('GUID', 'GPOID')][string] $GPOGuid, [Parameter(ParameterSetName = 'All', Mandatory)][switch] $All, [Parameter(ParameterSetName = 'ADObject', Mandatory)] [alias('OrganizationalUnit', 'DistinguishedName')][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, [validateset('WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', 'Default')][string] $Type = 'Default', [alias('Trustee')][string] $Principal, [alias('TrusteeType')][validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'DistinguishedName', [Parameter(Mandatory)][alias('IncludePermissionType')][Microsoft.GroupPolicy.GPPermissionType] $PermissionType, [switch] $Inheritable, [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'All', [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [System.Collections.IDictionary] $ADAdministrativeGroups, [int] $LimitProcessing = [int32]::MaxValue ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } if ($GPOName) { $Splat = @{ GPOName = $GPOName } } elseif ($GPOGUID) { $Splat = @{ GPOGUID = $GPOGUID } } else { $Splat = @{} } $Splat['IncludeGPOObject'] = $true $Splat['Forest'] = $Forest $Splat['IncludeDomains'] = $IncludeDomains if ($Type -ne 'Default') { $Splat['Type'] = $Type } $Splat['PermitType'] = $PermitType $Splat['Principal'] = $Principal if ($PrincipalType) { $Splat.PrincipalType = $PrincipalType } $Splat['ExcludeDomains'] = $ExcludeDomains $Splat['ExtendedForestInformation'] = $ExtendedForestInformation $Splat['IncludePermissionType'] = $PermissionType $Splat['SkipWellKnown'] = $SkipWellKnown.IsPresent $Splat['SkipAdministrative'] = $SkipAdministrative.IsPresent $CountFixed = 0 Do { Get-GPOZaurrPermission @Splat -ReturnSecurityWhenNoData -ReturnSingleObject | ForEach-Object { $GPOPermissions = $_ $PermissionsAnalysis = Get-PermissionsAnalysis -GPOPermissions $GPOPermissions -ADAdministrativeGroups $ADAdministrativeGroups -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Type $Type -PermissionType $PermissionType if (-not $PermissionsAnalysis.'Skip') { if (-not $GPOPermissions) { Write-Warning "Add-GPOZaurrPermission - Couldn't get permissions for GPO. Things aren't what they should be. Skipping!" } else { $GPO = $GPOPermissions[0] if ($GPOPermissions.GPOSecurityPermissionItem) { if ($Type -eq 'Administrative') { if ($PermissionsAnalysis.'DomainAdmins' -eq $false) { $Principal = $ADAdministrativeGroups[$GPO.DomainName]['DomainAdmins'] Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)" $CountFixed++ if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) { try { $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent) $GPO.GPOSecurity.Add($AddPermission) $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity) } catch { Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)" } } } if ($PermissionsAnalysis.'EnterpriseAdmins' -eq $false) { $Principal = $ADAdministrativeGroups[$ForestInformation.Forest.RootDomain]['EnterpriseAdmins'] Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)" $CountFixed++ if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) { try { $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent) $GPO.GPOSecurity.Add($AddPermission) $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity) } catch { Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)" } } } } elseif ($Type -eq 'Default') { Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType skipped for $($Principal). This shouldn't even happen!" } } else { if ($Type -eq 'Default') { Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)" $CountFixed++ if ($PrincipalType -eq 'DistinguishedName') { $ADIdentity = Get-WinADObject -Identity $Principal if ($ADIdentity) { Write-Verbose "Add-GPOZaurrPermission - Need to convert DN $Principal to SID $($ADIdentity.ObjectSID) to $($GPO.DisplayName) at $($GPO.DomainName)" $Principal = $ADIdentity.ObjectSID } } if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) { try { Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal)" $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent) $GPO.GPOSecurity.Add($AddPermission) $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity) } catch { Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)" } } } elseif ($Type -eq 'Administrative') { $Principal = $ADAdministrativeGroups[$GPO.DomainName]['DomainAdmins'] Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)" $CountFixed++ if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) { try { $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent) $GPO.GPOSecurity.Add($AddPermission) $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity) } catch { Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)" } } $Principal = $ADAdministrativeGroups[$ForestInformation.Forest.RootDomain]['EnterpriseAdmins'] Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)" $CountFixed++ if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) { try { $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent) $GPO.GPOSecurity.Add($AddPermission) $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity) } catch { Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)" } } } elseif ($Type -eq 'WellKnownAdministrative') { $Principal = 'S-1-5-18' Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)" $CountFixed++ if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal (SYSTEM) / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) { try { $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent) $GPO.GPOSecurity.Add($AddPermission) $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity) } catch { Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) (SYSTEM) with error: $($_.Exception.Message)" } } } elseif ($Type -eq 'AuthenticatedUsers') { $Principal = 'S-1-5-11' Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)" $CountFixed++ if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal (Authenticated Users) / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) { try { $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent) $GPO.GPOSecurity.Add($AddPermission) $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity) } catch { Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) (Authenticated Users) with error: $($_.Exception.Message)" } } } } } } if ($CountFixed -ge $LimitProcessing) { break } } } while ($false) } function Backup-GPOZaurr { <# .SYNOPSIS Provides Backup functionality to Group Policies .DESCRIPTION Provides Backup functionality to Group Policies .PARAMETER LimitProcessing Limits amount of GPOs that are backed up .PARAMETER Type Provides a way to backup only Empty or Unlinked GPOs. The default is All. .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER BackupPath Path where to keep the backup .PARAMETER BackupDated Whether cmdlet should created Dated folders for executed backup or not. Keep in mind it's not nessecary and two backups made to same folder have their dates properly tagged .EXAMPLE $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All $GPOSummary | Format-Table # only if you want to display output of backup .EXAMPLE $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All -BackupDated $GPOSummary | Format-Table # only if you want to display output of backup .NOTES General notes #> [cmdletBinding(SupportsShouldProcess)] param( [int] $LimitProcessing, [validateset('Empty', 'Unlinked', 'Disabled', 'All')][string[]] $Type = 'All', [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string] $BackupPath, [switch] $BackupDated ) Begin { if ($BackupDated) { $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))" } else { $BackupFinalPath = $BackupPath } Write-Verbose "Backup-GPOZaurr - Backing up to $BackupFinalPath" $null = New-Item -ItemType Directory -Path $BackupFinalPath -Force $Count = 0 } Process { Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Type $Type | ForEach-Object { $GPO = $_ Write-Verbose "Backup-GPOZaurr - Backing up GPO $($GPO.DisplayName) from $($GPO.DomainName)" $Count++ try { $BackupInfo = Backup-GPO -Guid $GPO.GUID -Domain $GPO.DomainName -Path $BackupFinalPath -ErrorAction Stop $BackupInfo } catch { Write-Warning "Backup-GPOZaurr - Backing up GPO $($GPO.DisplayName) from $($GPO.DomainName) failed: $($_.Exception.Message)" } if ($LimitProcessing -eq $Count) { break } } } End { } } function Clear-GPOZaurrSysvolDFSR { <# .SYNOPSIS Clears the ConflictAndDeleted folder in DFSR for specified GPOs. .DESCRIPTION This function clears the ConflictAndDeleted folder in DFSR for specified Group Policy Objects (GPOs) within a given forest. It allows excluding specific domains and domain controllers if needed. .PARAMETER Forest Specifies the forest name where the GPOs are located. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the cleanup process. .PARAMETER ExcludeDomainControllers Specifies an array of domain controllers to exclude from the cleanup process. .PARAMETER IncludeDomains Specifies an array of domains to include in the cleanup process. .PARAMETER IncludeDomainControllers Specifies an array of domain controllers to include in the cleanup process. .PARAMETER SkipRODC Indicates whether Read-Only Domain Controllers (RODCs) should be skipped during cleanup. .PARAMETER ExtendedForestInformation Specifies additional forest information if needed. .PARAMETER LimitProcessing Specifies the maximum number of GPOs to process. .EXAMPLE Clear-GPOZaurrSysvolDFSR -Forest "contoso.com" -IncludeDomains "child.contoso.com" -ExcludeDomainControllers "dc1.contoso.com" -SkipRODC Clears the ConflictAndDeleted folder in DFSR for GPOs in the "contoso.com" forest, including only the "child.contoso.com" domain and excluding the "dc1.contoso.com" domain controller. .EXAMPLE Clear-GPOZaurrSysvolDFSR -Forest "contoso.com" -IncludeDomains "child.contoso.com" -LimitProcessing 5 Clears the ConflictAndDeleted folder in DFSR for GPOs in the "contoso.com" forest, including only the "child.contoso.com" domain, and processes a maximum of 5 GPOs. #> [cmdletBinding(SupportsShouldProcess)] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation, [int] $LimitProcessing = [int32]::MaxValue ) $StatusCodes = @{ '0' = 'Success' '1' = 'Generic database error' '2' = 'ID record not found' '3' = 'Volume not found' '4' = 'Access denied' '5' = 'Generic error' } $getGPOZaurrSysvolDFSRSplat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation ExcludeDomainControllers = $ExcludeDomainControllers IncludeDomainControllers = $IncludeDomainControllers SkipRODC = $SkipRODC } Get-GPOZaurrSysvolDFSR @getGPOZaurrSysvolDFSRSplat | Select-Object -First $LimitProcessing | ForEach-Object { $Executed = Invoke-CimMethod -InputObject $_.DFSR -MethodName 'cleanupconflictdirectory' -CimSession $_.ComputerName if ($Executed) { [PSCustomObject] @{ Status = $StatusCodes["$($Executed.ReturnValue)"] ComputerName = $Executed.PSComputerName } } } } function ConvertFrom-CSExtension { <# .SYNOPSIS Converts Client-side Extension (CSE) GUIDs to their corresponding names. .DESCRIPTION This function takes an array of CSE GUIDs and returns their corresponding names. It can be used to easily identify the purpose of each CSE GUID. .PARAMETER CSE Specifies an array of Client-side Extension (CSE) GUIDs to be converted to names. .PARAMETER Limited Indicates whether the conversion should be limited to a predefined set of CSE GUIDs. .EXAMPLE ConvertFrom-CSExtension -CSE '{35378EAC-683F-11D2-A89A-00C04FBBCFA2}', '{0F6B957E-509E-11D1-A7CC-0000F87571E3}' -Limited Converts the specified CSE GUIDs to their corresponding names, limited to a predefined set. .EXAMPLE ConvertFrom-CSExtension -CSE '{D02B1F73-3407-48AE-BA88-E8213C6761F1}', '{0ACDD40C-75AC-47ab-BAA0-BF6DE7E7FE63}' Converts the specified CSE GUIDs to their corresponding names without any limitations. #> [cmdletBinding()] param( [string[]] $CSE, [switch] $Limited ) $GUIDs = @{ '{35378EAC-683F-11D2-A89A-00C04FBBCFA2}' = 'Client-side extension GUID (CSE GUID)' '{0F6B957E-509E-11D1-A7CC-0000F87571E3}' = 'Tool Extension GUID (User Policy Settings)' '{D02B1F73-3407-48AE-BA88-E8213C6761F1}' = 'Tool Extension GUID (User Policy Settings)' '{0F6B957D-509E-11D1-A7CC-0000F87571E3}' = 'Tool Extension GUID (Computer Policy Settings)' '{D02B1F72-3407-48AE-BA88-E8213C6761F1}' = 'Tool Extension GUID (Computer Policy Settings)' '{0ACDD40C-75AC-47ab-BAA0-BF6DE7E7FE63}' = 'Wireless Group Policy' '{0E28E245-9368-4853-AD84-6DA3BA35BB75}' = 'Group Policy Environment' '{16be69fa-4209-4250-88cb-716cf41954e0}' = 'Central Access Policy Configuration' '{17D89FEC-5C44-4972-B12D-241CAEF74509}' = 'Group Policy Local Users and Groups' '{1A6364EB-776B-4120-ADE1-B63A406A76B5}' = 'Group Policy Device Settings' '{25537BA6-77A8-11D2-9B6C-0000F8080861}' = 'Folder Redirection' '{2A8FDC61-2347-4C87-92F6-B05EB91A201A}' = 'MitigationOptions' '{346193F5-F2FD-4DBD-860C-B88843475FD3}' = 'ConfigMgr User State Management Extension.' '{3610eda5-77ef-11d2-8dc5-00c04fa31a66}' = 'Microsoft Disk Quota' '{3A0DBA37-F8B2-4356-83DE-3E90BD5C261F}' = 'Group Policy Network Options' '{426031c0-0b47-4852-b0ca-ac3d37bfcb39}' = 'QoS Packet Scheduler' '{42B5FAAE-6536-11d2-AE5A-0000F87571E3}' = 'Scripts' '{4bcd6cde-777b-48b6-9804-43568e23545d}' = 'Remote Desktop USB Redirection' '{4CFB60C1-FAA6-47f1-89AA-0B18730C9FD3}' = 'Internet Explorer Zonemapping' '{4D2F9B6F-1E52-4711-A382-6A8B1A003DE6}' = 'RADCProcessGroupPolicyEx' '{4d968b55-cac2-4ff5-983f-0a54603781a3}' = 'Work Folders' '{5794DAFD-BE60-433f-88A2-1A31939AC01F}' = 'Group Policy Drive Maps' '{6232C319-91AC-4931-9385-E70C2B099F0E}' = 'Group Policy Folders' '{6A4C88C6-C502-4f74-8F60-2CB23EDC24E2}' = 'Group Policy Network Shares' '{7150F9BF-48AD-4da4-A49C-29EF4A8369BA}' = 'Group Policy Files' '{728EE579-943C-4519-9EF7-AB56765798ED}' = 'Group Policy Data Sources' '{74EE6C03-5363-4554-B161-627540339CAB}' = 'Group Policy Ini Files' '{7933F41E-56F8-41d6-A31C-4148A711EE93}' = 'Windows Search Group Policy Extension' '{7B849a69-220F-451E-B3FE-2CB811AF94AE}' = 'Internet Explorer User Accelerators' '{827D319E-6EAC-11D2-A4EA-00C04F79F83A}' = 'Security' '{8A28E2C5-8D06-49A4-A08C-632DAA493E17}' = 'Deployed Printer Connections' '{91FBB303-0CD5-4055-BF42-E512A681B325}' = 'Group Policy Services' '{A3F3E39B-5D83-4940-B954-28315B82F0A8}' = 'Group Policy Folder Options' '{AADCED64-746C-4633-A97C-D61349046527}' = 'Group Policy Scheduled Tasks' '{B087BE9D-ED37-454f-AF9C-04291E351182}' = 'Group Policy Registry' '{B587E2B1-4D59-4e7e-AED9-22B9DF11D053}' = '802.3 Group Policy' '{BA649533-0AAC-4E04-B9BC-4DBAE0325B12}' = 'Windows To Go Startup Options' '{BC75B1ED-5833-4858-9BB8-CBF0B166DF9D}' = 'Group Policy Printers' '{C34B2751-1CF4-44F5-9262-C3FC39666591}' = 'Windows To Go Hibernate Options' '{C418DD9D-0D14-4efb-8FBF-CFE535C8FAC7}' = 'Group Policy Shortcuts' '{C631DF4C-088F-4156-B058-4375F0853CD8}' = 'Microsoft Offline Files' '{c6dc5466-785a-11d2-84d0-00c04fb169f7}' = 'Software Installation' '{cdeafc3d-948d-49dd-ab12-e578ba4af7aa}' = 'TCPIP' '{CF7639F3-ABA2-41DB-97F2-81E2C5DBFC5D}' = 'Internet Explorer Machine Accelerators' '{e437bc1c-aa7d-11d2-a382-00c04f991e27}' = 'IP Security' '{E47248BA-94CC-49c4-BBB5-9EB7F05183D0}' = 'Group Policy Internet Settings' '{E4F48E54-F38D-4884-BFB9-D4D2E5729C18}' = 'Group Policy Start Menu Settings' '{E5094040-C46C-4115-B030-04FB2E545B00}' = 'Group Policy Regional Options' '{E62688F0-25FD-4c90-BFF5-F508B9D2E31F}' = 'Group Policy Power Options' '{F312195E-3D9D-447A-A3F5-08DFFA24735E}' = 'ProcessVirtualizationBasedSecurityGroupPolicy' '{f3ccc681-b74c-4060-9f26-cd84525dca2a}' = 'Audit Policy Configuration' '{F9C77450-3A41-477E-9310-9ACD617BD9E3}' = 'Group Policy Applications' '{FB2CA36D-0B40-4307-821B-A13B252DE56C}' = 'Enterprise QoS' '{fbf687e6-f063-4d9f-9f4f-fd9a26acdd5f}' = 'CP' '{FC491EF1-C4AA-4CE1-B329-414B101DB823}' = 'ProcessConfigCIPolicyGroupPolicy' '{169EBF44-942F-4C43-87CE-13C93996EBBE}' = 'UEV Policy' '{2BFCC077-22D2-48DE-BDE1-2F618D9B476D}' = 'AppV Policy' '{4B7C3B0F-E993-4E06-A241-3FBE06943684}' = 'Per-process Mitigation Options' '{7909AD9E-09EE-4247-BAB9-7029D5F0A278}' = 'MDM Policy' '{CFF649BD-601D-4361-AD3D-0FC365DB4DB7}' = 'Delivery Optimization GP extension' '{D76B9641-3288-4f75-942D-087DE603E3EA}' = 'AdmPwd' '{9650FDBC-053A-4715-AD14-FC2DC65E8330}' = 'Unknown' '{B1BE8D72-6EAC-11D2-A4EA-00C04F79F83A}' = 'EFS Recovery' '{A2E30F80-D7DE-11d2-BBDE-00C04F86AE3B}' = 'Internet Explorer Maintenance Policy Processing' '{FC715823-C5FB-11D1-9EEF-00A0C90347FF}' = 'Internet Explorer Maintenance Extension Protocol' } foreach ($C in $CSE) { if (-not $Limited) { if ($GUIDs[$C]) { [PSCustomObject] @{ Name = $C; Description = $GUIDs[$C] } } else { [PSCustomObject] @{ Name = $C; Description = $C } } } else { if ($GUIDs[$C]) { $GUIDs[$C] } else { $CSE } } } } function Export-GPOZaurrContent { <# .SYNOPSIS Exports Group Policy Objects (GPOs) to XML or HTML files. .DESCRIPTION This function exports GPOs to either XML or HTML files based on the specified parameters. .PARAMETER FolderOutput Specifies the folder path where the exported GPO files will be saved. .PARAMETER ReportType Specifies the type of report to generate. Valid values are XML or HTML. The default value is XML. .EXAMPLE Export-GPOZaurrContent -FolderOutput "C:\ExportedGPOs" -ReportType HTML Exports all GPOs to HTML format and saves them in the "C:\ExportedGPOs" folder. .NOTES This function exports GPOs to XML or HTML files for further analysis or backup purposes. #> [CmdletBinding()] param( [Parameter(Mandatory)][alias('Path')][string] $FolderOutput, [ValidateSet('XML', 'HTML')][string] $ReportType = 'XML' ) if ($FolderOutput) { if (-not (Test-Path -LiteralPath $FolderOutput)) { $null = New-Item -Path $FolderOutput -ItemType Directory -Force } $Forest = Get-ADForest $Count = 0 foreach ($Domain in $Forest.Domains) { $GPOs = Get-GPO -All -Domain $Domain foreach ($GPO in $GPOS) { $Count++ Write-Verbose -Message "Export-GPOZaurr - Exporting ($Count / $($GPOs.Count)) - $($GPO.DisplayName) to $ReportType" $Name = "$($GPO.DomainName)_$($GPO.Id)_$($GPO.DisplayName).xml".Replace(" ", "_").Replace("|", "_") $FullName = [io.path]::Combine($GPOOutput, $Name) Get-GPOReport -Guid $GPO.Id -Domain $GPO.DomainName -ReportType $ReportType -Path $FullName } } } } function Find-CSExtension { <# .SYNOPSIS This function retrieves Group Policy Client Side Extensions (CSEs) from a specified Windows computer. .DESCRIPTION The Find-CSExtension function lists Group Policy Client Side Extensions (CSEs) configured on a Windows computer. It queries the Windows Registry to retrieve information about the CSEs. .PARAMETER CSE Specifies an array of CSE names to filter the results. If not provided, all CSEs will be listed. .PARAMETER ComputerName Specifies the name of the computer from which to retrieve the CSE information. .EXAMPLE Find-CSExtension -ComputerName "Computer01" Retrieves all CSEs configured on the computer named "Computer01". .EXAMPLE Find-CSExtension -CSE "CSE1", "CSE2" -ComputerName "Computer02" Retrieves information about CSEs named "CSE1" and "CSE2" on the computer named "Computer02". #> [cmdletBinding()] param( [string[]] $CSE, [string] $ComputerName ) $Keys = Get-PSRegistry -RegistryPath "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\GPExtensions" -ComputerName $ComputerName foreach ($Key in $Keys.PSSubKeys) { $RegistryKey = Get-PSRegistry -RegistryPath "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\GPExtensions\$Key" -ComputerName $ComputerName if ($CSE) { foreach ($C in $CSE) { if ($RegistryKey.DefaultKey -eq $Key) { [PSCustomObject] @{ Name = $Key; Description = $RegistryKey.DefaultKey } } } } else { [PSCustomObject] @{ CSE = $Key; Description = $RegistryKey.DefaultKey } } } } function Get-GPOZaurr { <# .SYNOPSIS Gets information about all Group Policies. Similar to what Get-GPO provides by default. .DESCRIPTION Gets information about all Group Policies. Similar to what Get-GPO provides by default. .PARAMETER ExcludeGroupPolicies Marks the GPO as excluded from the list. .PARAMETER GPOName Provide a GPOName to get information about a specific GPO. .PARAMETER GPOGuid Provide a GPOGuid to get information about a specific GPO. .PARAMETER Type Choose a specific type of GPO. Options are: 'Empty', 'Unlinked', 'Disabled', 'NoApplyPermission', 'All'. Default is All. .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER GPOPath Define GPOPath where the XML files are located to be analyzed instead of asking Active Directory .PARAMETER PermissionsOnly Only show permissions, by default all information is shown .PARAMETER OwnerOnly only show owner information, by default all information is shown .PARAMETER Limited Provide limited output without analyzing XML data .PARAMETER ADAdministrativeGroups Ability to provide ADAdministrativeGroups from different function to speed up processing .EXAMPLE $GPOs = Get-GPOZaurr $GPOs | Format-Table DisplayName, Owner, OwnerSID, OwnerType .EXAMPLE $GPO = Get-GPOZaurr -GPOName 'ALL | Allow use of biometrics' $GPO | Format-List * .EXAMPLE $GPOS = Get-GPOZaurr -ExcludeGroupPolicies { Skip-GroupPolicy -Name 'de14_usr_std' Skip-GroupPolicy -Name 'de14_usr_std' -DomaiName 'ad.evotec.xyz' Skip-GroupPolicy -Name 'All | Trusted Websites' #-DomaiName 'ad.evotec.xyz' '{D39BF08A-87BF-4662-BFA0-E56240EBD5A2}' 'COMPUTERS | Enable Sets' } $GPOS | Format-Table -AutoSize * .NOTES General notes #> [cmdletBinding()] param( [scriptblock] $ExcludeGroupPolicies, [string] $GPOName, [alias('GUID', 'GPOID')][string] $GPOGuid, [validateset('Empty', 'Unlinked', 'Disabled', 'NoApplyPermission', 'All')][string[]] $Type, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string[]] $GPOPath, [switch] $PermissionsOnly, [switch] $OwnerOnly, [switch] $Limited, [System.Collections.IDictionary] $ADAdministrativeGroups ) Begin { if (-not $ADAdministrativeGroups) { Write-Verbose "Get-GPOZaurr - Getting ADAdministrativeGroups" $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } if (-not $GPOPath) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } $ExcludeGPO = [ordered] @{} if ($ExcludeGroupPolicies) { $ExExecution = Invoke-Command -ScriptBlock $ExcludeGroupPolicies foreach ($GroupPolicy in $ExExecution) { if ($GroupPolicy -is [string]) { $ExcludeGPO[$GroupPolicy] = $true } elseif ($GroupPolicy.Name -and $GroupPolicy.DomainName) { $PolicyName = -join ($GroupPolicy.DomainName, $GroupPolicy.Name) $ExcludeGPO[$PolicyName] = $true } elseif ($GroupPolicy.Name) { $ExcludeGPO[$GroupPolicy.Name] = $true } else { Write-Warning "Get-GPOZaurr - Exclusion takes only Group Policy Name as string, or as hashtable with domain name @{ Name = 'Group Policy Name'; DomainName = 'Domain' }." continue } } } if ($OwnerOnly -or $PermissionsOnly -and $Type) { Write-Warning "Get-GPOZaurr - Using PermissionOnly or OwnerOnly with Type is not supported. " } if (-not $GPOPath) { $LinksSummaryCache = Get-GPOZaurrLink -AsHashTable -Summary -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } } Process { if (-not $GPOPath) { foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation.QueryServers[$Domain]['HostName'][0] $Count = 0 if ($GPOName) { $getGPOSplat = @{ Name = $GPOName Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } } elseif ($GPOGuid) { $getGPOSplat = @{ Guid = $GPOGuid Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } } else { $getGPOSplat = @{ All = $true Server = $QueryServer Domain = $Domain ErrorAction = 'SilentlyContinue' } } $GroupPolicies = Get-GPO @getGPOSplat foreach ($GPO in $GroupPolicies) { $Count++ Write-Verbose "Get-GPOZaurr - Processing [$($GPO.DomainName)]($Count/$($GroupPolicies.Count)) $($_.DisplayName)" if (-not $Limited) { try { $XMLContent = Get-GPOReport -ID $GPO.ID -ReportType XML -Server $ForestInformation.QueryServers[$Domain].HostName[0] -Domain $Domain -ErrorAction Stop } catch { Write-Warning "Get-GPOZaurr - Failed to get [$($GPO.DomainName)]($Count/$($GroupPolicies.Count)) $($GPO.DisplayName) GPOReport: $($_.Exception.Message). Skipping." continue } Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -GPO $GPO -PermissionsOnly:$PermissionsOnly.IsPresent -ADAdministrativeGroups $ADAdministrativeGroups -ExcludeGroupPolicies $ExcludeGPO -Type $Type -LinksSummaryCache $LinksSummaryCache } else { $GPO } } } } else { foreach ($Path in $GPOPath) { Write-Verbose "Get-GPOZaurr - Getting GPO content from XML files" Get-ChildItem -LiteralPath $Path -Recurse -Filter *.xml -ErrorAction SilentlyContinue | ForEach-Object { if ($_.Name -ne 'GPOList.xml') { $XMLContent = [XML]::new() $XMLContent.Load($_.FullName) Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -PermissionsOnly:$PermissionsOnly.IsPresent -ExcludeGroupPolicies $ExcludeGPO -Type $Type } } Write-Verbose "Get-GPOZaurr - Finished GPO content from XML files" } } } End { } } function Get-GPOZaurrAD { <# .SYNOPSIS Retrieves Group Policy Objects (GPOs) information from Active Directory. .DESCRIPTION This function retrieves information about Group Policy Objects (GPOs) from Active Directory based on specified criteria such as GPO name, GPO GUID, date range, and forest details. .PARAMETER GPOName Specifies the name of the GPO to retrieve. .PARAMETER GPOGuid Specifies the GUID of the GPO to retrieve. .PARAMETER Forest Specifies the forest name to search for GPOs. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the search. .PARAMETER IncludeDomains Specifies an array of domains to include in the search. .PARAMETER DateFrom Specifies the start date for filtering GPOs based on creation or modification date. .PARAMETER DateTo Specifies the end date for filtering GPOs based on creation or modification date. .PARAMETER DateRange Specifies a predefined date range for filtering GPOs based on creation or modification date. .PARAMETER DateProperty Specifies the property (WhenCreated or WhenChanged) to use for filtering GPOs based on date. .PARAMETER ExtendedForestInformation Specifies additional forest information to include in the output. .EXAMPLE Get-GPOZaurrAD -GPOName "ExampleGPO" Description: Retrieves information about a GPO with the name "ExampleGPO". .EXAMPLE Get-GPOZaurrAD -GPOGuid "{12345678-1234-1234-1234-123456789012}" Description: Retrieves information about a GPO with the specified GUID. .EXAMPLE Get-GPOZaurrAD -Forest "example.com" -IncludeDomains "domain1", "domain2" -DateRange "Last30Days" Description: Retrieves GPO information from the forest "example.com" for domains "domain1" and "domain2" created or modified in the last 30 days. #> [cmdletbinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'GPOName')] [string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID')] [alias('GUID', 'GPOID')][string] $GPOGuid, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [DateTime] $DateFrom, [DateTime] $DateTo, [ValidateSet('PastHour', 'CurrentHour', 'PastDay', 'CurrentDay', 'PastMonth', 'CurrentMonth', 'PastQuarter', 'CurrentQuarter', 'Last14Days', 'Last21Days', 'Last30Days', 'Last7Days', 'Last3Days', 'Last1Days')][string] $DateRange, [ValidateSet('WhenCreated', 'WhenChanged')][string[]] $DateProperty = 'WhenCreated', [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } Process { foreach ($Domain in $ForestInformation.Domains) { if ($PSCmdlet.ParameterSetName -eq 'GPOGUID') { if ($GPOGuid) { if ($GPOGUID -notlike '*{*') { $GUID = -join ("{", $GPOGUID, '}') } else { $GUID = $GPOGUID } $Splat = @{ Filter = "(objectClass -eq 'groupPolicyContainer') -and (Name -eq '$GUID')" Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } } else { Write-Warning "Get-GPOZaurrAD - GPOGUID parameter is empty. Provide name and try again." continue } } elseif ($PSCmdlet.ParameterSetName -eq 'GPOName') { if ($GPOName) { $Splat = @{ Filter = "(objectClass -eq 'groupPolicyContainer') -and (DisplayName -eq '$GPOName')" Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } } else { Write-Warning "Get-GPOZaurrAD - GPOName parameter is empty. Provide name and try again." continue } } else { $Splat = @{ Filter = "(objectClass -eq 'groupPolicyContainer')" Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } } if ($PSBoundParameters.ContainsKey('DateRange')) { $Dates = Get-ChoosenDates -DateRange $DateRange $DateFrom = $($Dates.DateFrom) $DateTo = $($Dates.DateTo) if ($DateProperty -contains 'WhenChanged' -and $DateProperty -contains 'WhenCreated') { $Splat['Filter'] = -join ($Splat['Filter'], ' -and ((WhenChanged -ge $DateFrom -and WhenChanged -le $DateTo) -or (WhenCreated -ge $DateFrom -and WhenCreated -le $DateTo))') } elseif ($DateProperty -eq 'WhenChanged' -or $DateProperty -eq 'WhenCreated') { $Property = $DateProperty[0] $Splat['Filter'] = -join ($Splat['Filter'], " -and ($Property -ge $DateFrom -and $Property -le $DateTo)") } else { Write-Warning -Message "Get-GPOZaurrAD - DateProperty parameter is empty. Provide name and try again." continue } } elseif ($PSBoundParameters.ContainsKey('DateFrom') -and $PSBoundParameters.ContainsKey('DateTo')) { if ($DateProperty -contains 'WhenChanged' -and $DateProperty -contains 'WhenCreated') { $Splat['Filter'] = -join ($Splat['Filter'], ' -and ((WhenChanged -ge $DateFrom -and WhenChanged -le $DateTo) -or (WhenCreated -ge $DateFrom -and WhenCreated -le $DateTo))') } elseif ($DateProperty -eq 'WhenChanged' -or $DateProperty -eq 'WhenCreated') { $Property = $DateProperty[0] $Splat['Filter'] = -join ($Splat['Filter'], " -and ($Property -ge $DateFrom -and $Property -le $DateTo)") } else { Write-Warning -Message "Get-GPOZaurrAD - DateProperty parameter is empty. Provide name and try again." continue } } else { } Write-Verbose -Message "Get-GPOZaurrAD - Searching domain $Domain with filter $($Splat['Filter'])" $Objects = Get-ADObject @Splat -Properties DisplayName, Name, Created, Modified, ntSecurityDescriptor, gPCFileSysPath, gPCFunctionalityVersion, gPCWQLFilter, gPCMachineExtensionNames, Description, CanonicalName, DistinguishedName foreach ($Object in $Objects) { $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Object.DistinguishedName -ToDomainCN $GUID = $Object.Name -replace '{' -replace '}' if (($GUID).Length -ne 36) { Write-Warning "Get-GPOZaurrAD - GPO GUID ($($($GUID.Replace("`n",' ')))) is incorrect. Skipping $($Object.DisplayName) / Domain: $($DomainCN)" } else { [PSCustomObject]@{ 'DisplayName' = $Object.DisplayName 'DomainName' = $DomainCN 'Description' = $Object.Description 'GUID' = $GUID 'Path' = $Object.gPCFileSysPath 'Created' = $Object.Created 'Modified' = $Object.Modified 'Owner' = $Object.ntSecurityDescriptor.Owner 'GPOCanonicalName' = $Object.CanonicalName 'GPODomainDistinguishedName' = ConvertFrom-DistinguishedName -DistinguishedName $Object.DistinguishedName -ToDC 'GPODistinguishedName' = $Object.DistinguishedName } } } } } End { } } function Get-GPOZaurrBackupInformation { <# .SYNOPSIS Retrieves backup information from GPOZaurr manifest files. .DESCRIPTION This function retrieves backup information from GPOZaurr manifest files located in the specified BackupFolder(s). .PARAMETER BackupFolder Specifies the path(s) to the folder containing GPOZaurr manifest files. .EXAMPLE Get-GPOZaurrBackupInformation -BackupFolder "C:\Backups" Description: Retrieves backup information from GPOZaurr manifest files located in the "C:\Backups" folder. .EXAMPLE Get-GPOZaurrBackupInformation -BackupFolder "C:\Backups", "D:\Archives" Description: Retrieves backup information from GPOZaurr manifest files located in both "C:\Backups" and "D:\Archives" folders. #> [cmdletBinding()] param( [string[]] $BackupFolder ) Begin { } Process { foreach ($Folder in $BackupFolder) { if ($Folder) { if ((Test-Path -LiteralPath "$Folder\manifest.xml")) { [xml] $Xml = Get-Content -LiteralPath "$Folder\manifest.xml" $Xml.Backups.BackupInst | ForEach-Object -Process { [PSCustomObject] @{ DisplayName = $_.GPODisplayName.'#cdata-section' DomainName = $_.GPODomain.'#cdata-section' Guid = $_.GPOGUid.'#cdata-section' -replace '{' -replace '}' DomainGuid = $_.GPODomainGuid.'#cdata-section' -replace '{' -replace '}' DomainController = $_.GPODomainController.'#cdata-section' BackupTime = [DateTime]::Parse($_.BackupTime.'#cdata-section') ID = $_.ID.'#cdata-section' -replace '{' -replace '}' Comment = $_.Comment.'#cdata-section' } } } else { Write-Warning "Get-GPOZaurrBackupInformation - No backup information available" } } } } End { } } function Get-GPOZaurrBroken { <# .SYNOPSIS Detects broken or otherwise damaged Group Policies .DESCRIPTION Detects broken or otherwise damaged Group Policies providing insight whether GPO exists in both AD and SYSVOL. It provides few statuses: - Permissions issue - means account couldn't read GPO due to permissions - ObjectClass issue - means that ObjectClass is of type Container, rather than expected groupPolicyContainer - Not available on SYSVOL - means SYSVOL data is missing, yet AD metadata is available - Not available in AD - means AD metadata is missing, yet SYSVOL data is available - Exists - means AD metadata and SYSVOL data are available .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER ExcludeDomainControllers Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER IncludeDomainControllers Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER SkipRODC Skip Read-Only Domain Controllers. By default all domain controllers are included. .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER VerifyDomainControllers Forces cmdlet to check GPO Existance on Domain Controllers rather then per domain .EXAMPLE Get-GPOZaurrBroken -Verbose | Format-Table .NOTES General notes #> [alias('Get-GPOZaurrSysvol')] [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $VerifyDomainControllers ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended foreach ($Domain in $ForestInformation.Domains) { $TimeLog = Start-TimeLog Write-Verbose "Get-GPOZaurrBroken - Starting process for $Domain" $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer $PoliciesAD = @{} if ($SystemsContainer) { $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer) $PoliciesInAD = Get-ADObject -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties Name, gPCFileSysPath, DisplayName, DistinguishedName, Description, Created, Modified, ObjectClass, ObjectGUID foreach ($Policy in $PoliciesInAD) { $GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName if ($Policy.ObjectClass -eq 'Container') { $PoliciesAD[$GUIDFromDN] = 'ObjectClass issue' } else { $GUID = $Policy.Name if ($GUID -and $GUIDFromDN) { $PoliciesAD[$GUIDFromDN] = 'Exists' } else { $PoliciesAD[$GUIDFromDN] = 'Permissions issue' } } } } else { Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Skipping" } if ($PoliciesInAD.Count -ge 2) { if (-not $VerifyDomainControllers) { Test-SysVolFolders -GPOs $PoliciesInAD -Server $Domain -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase } else { foreach ($Server in $ForestInformation['DomainDomainControllers']["$Domain"]) { Write-Verbose "Get-GPOZaurrBroken - Processing $Domain \ $($Server.HostName.Trim())" Test-SysVolFolders -GPOs $PoliciesInAD -Server $Server.Hostname -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase } } } else { Write-Warning "Get-GPOZaurrBroken - GPO count for $Domain is less then 2. This is not expected for fully functioning domain. Skipping processing SYSVOL folder." } $TimeEnd = Stop-TimeLog -Time $TimeLog -Option OneLiner Write-Verbose "Get-GPOZaurrBroken - Finishing process for $Domain (Time to process: $TimeEnd)" } } function Get-GPOZaurrBrokenLink { <# .SYNOPSIS Finds any GPO link that doesn't have a matching GPO (already removed GPO). .DESCRIPTION Finds any GPO link that doesn't have a matching GPO (already removed GPO). .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE Get-GPOZaurrBrokenLink -Verbose | Format-Table -AutoSize * .EXAMPLE Get-GPOZaurrBrokenLink -Verbose -IncludeDomains ad.evotec.pl | Format-Table -AutoSize * .NOTES General notes #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $ErrorFound = $false $PoliciesAD = @{} $ForestInformation = Get-WinADForestDetails -Forest $Forest -Extended foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer if ($SystemsContainer) { $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer) try { $PoliciesInAD = Get-ADObject -ErrorAction Stop -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties Name, gPCFileSysPath, DisplayName, DistinguishedName, Description, Created, Modified, ObjectClass, ObjectGUID } catch { Write-Warning "Get-GPOZaurrBrokenLink - An error occured while searching $PoliciesSearchBase. Error $($_.Exception.Message). Please resolve this before continuing." $ErrorFound = $true break } foreach ($Policy in $PoliciesInAD) { $GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName $Key = $Policy.DistinguishedName if ($Policy.ObjectClass -eq 'Container') { $PoliciesAD[$Key] = 'ObjectClass issue' } else { $GUID = $Policy.Name if ($GUID -and $GUIDFromDN) { $PoliciesAD[$Key] = 'Exists' } else { $PoliciesAD[$Key] = 'Permissions issue' } } } } else { Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Skipping" } } if ($ErrorFound) { return } $ForestInformation = Get-WinADForestDetails -Forest $Forest -Extended -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $Links = Get-GPOZaurrLinkLoop -Linked 'All' -ForestInformation $ForestInformation foreach ($Link in $Links) { if (-not $PoliciesAD[$Link.GPODistinguishedName]) { $Link } } } function Get-GPOZaurrDictionary { <# .SYNOPSIS Retrieves a dictionary of Group Policy Objects (GPOs) with their associated types and paths. .DESCRIPTION This function retrieves a dictionary of Group Policy Objects (GPOs) along with their associated types and paths. It iterates through the GPOs stored in the $Script:GPODitionary variable and constructs a custom object for each GPO containing its name, types, and path. .PARAMETER Splitter Specifies the delimiter used to separate multiple types or paths. Default value is [System.Environment]::NewLine. .EXAMPLE Get-GPOZaurrDictionary Retrieves the dictionary of GPOs with their types and paths using the default newline delimiter. .EXAMPLE Get-GPOZaurrDictionary -Splitter "," Retrieves the dictionary of GPOs with their types and paths using a comma as the delimiter. #> [cmdletBinding()] param( [string] $Splitter = [System.Environment]::NewLine ) foreach ($Policy in $Script:GPODitionary.Keys) { if ($Script:GPODitionary[$Policy].ByReports) { [Array] $Type = foreach ($T in $Script:GPODitionary[$Policy].ByReports ) { $T.Report } } else { [Array]$Type = foreach ($T in $Script:GPODitionary[$Policy].Types) { ( -join ($T.Category, '/', $T.Settings)) } } [PSCustomObject] @{ Name = $Policy Types = $Type -join $Splitter Path = $Script:GPODitionary[$Policy].GPOPath -join $Splitter } } } function Get-GPOZaurrDuplicateObject { <# .SYNOPSIS Retrieves duplicate Group Policy Objects (GPOs) within a specified forest. .DESCRIPTION This function retrieves duplicate Group Policy Objects (GPOs) within a specified forest by comparing GPOs based on partial distinguished name matching. .PARAMETER Forest Specifies the name of the forest to search for duplicate GPOs. .PARAMETER IncludeDomains Specifies an array of domain names to include in the search for duplicate GPOs. .PARAMETER ExcludeDomains Specifies an array of domain names to exclude from the search for duplicate GPOs. .PARAMETER ExtendedForestInformation Specifies additional information about the forest to aid in the search for duplicate GPOs. .EXAMPLE Get-GPOZaurrDuplicateObject -Forest "contoso.com" -IncludeDomains "child1.contoso.com", "child2.contoso.com" -ExcludeDomains "child3.contoso.com" -ExtendedForestInformation $additionalInfo Description ----------- Retrieves duplicate GPOs within the "contoso.com" forest, including domains "child1.contoso.com" and "child2.contoso.com" while excluding "child3.contoso.com". Additional forest information is provided for the search. #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $getWinADDuplicateObjectSplat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation PartialMatchDistinguishedName = "*,CN=Policies,CN=System,DC=*" Extended = $true } $DuplicateObjects = Get-WinADDuplicateObject @getWinADDuplicateObjectSplat $DuplicateObjects } function Get-GPOZaurrFiles { <# .SYNOPSIS Retrieves information about Group Policy Objects (GPOs) stored in SYSVOL and NETLOGON folders. .DESCRIPTION This function retrieves information about GPOs stored in SYSVOL and NETLOGON folders of specified domains. It can filter by type of files and hash algorithms used for verification. .PARAMETER Type Specifies the type of files to retrieve. Valid values are 'All', 'Netlogon', and 'Sysvol'. .PARAMETER HashAlgorithm Specifies the hash algorithm to use for file verification. Valid values are 'None', 'MACTripleDES', 'MD5', 'RIPEMD160', 'SHA1', 'SHA256', 'SHA384', 'SHA512'. .PARAMETER Signature Indicates whether to include file signatures for verification. .PARAMETER AsHashTable Indicates whether to return the results as a hashtable. .PARAMETER Extended Indicates whether to include extended information about the forest. .PARAMETER ExtendedMetaData Indicates whether to include extended metadata information. .PARAMETER Forest Specifies the forest name to retrieve GPO information from. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the search. .PARAMETER IncludeDomains Specifies an array of domains to include in the search. .PARAMETER ExtendedForestInformation Specifies additional forest information to include. .EXAMPLE Get-GPOZaurrFiles -Type 'All' -HashAlgorithm 'SHA256' -Signature Retrieves all files from SYSVOL and NETLOGON folders with SHA256 hash algorithm and includes file signatures. .EXAMPLE Get-GPOZaurrFiles -Type 'Sysvol' -HashAlgorithm 'MD5' -AsHashTable Retrieves only SYSVOL files with MD5 hash algorithm and returns the results as a hashtable. #> [cmdletbinding()] param( [ValidateSet('All', 'Netlogon', 'Sysvol')][string[]] $Type = 'All', [ValidateSet('None', 'MACTripleDES', 'MD5', 'RIPEMD160', 'SHA1', 'SHA256', 'SHA384', 'SHA512')][string] $HashAlgorithm = 'None', [switch] $Signature, [switch] $AsHashTable, [switch] $Extended, [switch] $ExtendedMetaData, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $GPOCache = @{} $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $GPOList = Get-GPOZaurrAD -ExtendedForestInformation $ForestInformation foreach ($GPO in $GPOList) { if (-not $GPOCache[$GPO.DomainName]) { $GPOCache[$GPO.DomainName] = @{} } $GPOCache[$($GPO.DomainName)][($GPO.GUID)] = $GPO } foreach ($Domain in $ForestInformation.Domains) { $Path = @( if ($Type -contains 'All') { "\\$Domain\SYSVOL\$Domain" } if ($Type -contains 'Sysvol') { "\\$Domain\SYSVOL\$Domain\policies" } if ($Type -contains 'NetLogon') { "\\$Domain\NETLOGON" } ) $Folders = [ordered] @{ "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" = @{ Name = 'SYSVOL PolicyDefinitions' } "\\$Domain\SYSVOL\$Domain\policies" = @{ Name = 'SYSVOL Policies' } "\\$Domain\SYSVOL\$Domain\scripts" = @{ Name = 'NETLOGON Scripts' } "\\$Domain\SYSVOL\$Domain\StarterGPOs" = @{ Name = 'SYSVOL GPO Starters' } "\\$Domain\NETLOGON" = @{ Name = 'NETLOGON Scripts' } "\\$Domain\SYSVOL\$Domain\DfsrPrivate" = @{ Name = 'DfsrPrivate' } "\\$Domain\SYSVOL\$Domain" = @{ Name = 'SYSVOL Root' } } Get-ChildItem -Path $Path -ErrorAction SilentlyContinue -Recurse -ErrorVariable err -File -Force | ForEach-Object { $GPO = $null $BelongsToGPO = $false $GPODisplayName = $null $SuggestedAction = $null $SuggestedActionComment = $null $FileType = foreach ($Key in $Folders.Keys) { if ($_.FullName -like "$Key*") { $Folders[$Key] break } } if ($FileType.Name -eq 'SYSVOL Policies') { $FoundGUID = $_.FullName -match '[\da-zA-Z]{8}-([\da-zA-Z]{4}-){3}[\da-zA-Z]{12}' if ($FoundGUID) { $GPO = $GPOCache[$Domain][$matches[0]] if ($GPO) { $BelongsToGPO = $true $GPODisplayName = $GPO.DisplayName } } $Correct = @( [System.IO.Path]::Combine($GPO.Path, 'GPT.INI') [System.IO.Path]::Combine($GPO.Path, 'GPO.cmt') [System.IO.Path]::Combine($GPO.Path, 'Group Policy', 'GPE.ini') foreach ($TypeM in @('Machine', 'User')) { [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Registry.pol') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'comment.cmtx') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Registry\Registry.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Printers\Printers.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\ScheduledTasks\ScheduledTasks.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Services\Services.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Groups\Groups.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\RegionalOptions\RegionalOptions.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\FolderOptions\FolderOptions.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Drives\Drives.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\InternetSettings\InternetSettings.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Folders\Folders.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\PowerOptions\PowerOptions.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Shortcuts\Shortcuts.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Files\Files.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\EnvironmentVariables\EnvironmentVariables.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\NetworkOptions\NetworkOptions.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\DataSources\DataSources.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\NetworkShares\NetworkShares.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\StartMenuTaskbar\StartMenuTaskbar.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Applications\Microsoft\TBLayout.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Applications\Microsoft\DefaultApps.xml') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Applications\ADE.CFG') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Scripts\scripts.ini') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Scripts\psscripts.ini') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Documents & Settings\fdeploy.ini') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Documents & Settings\fdeploy1.ini') [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Documents & Settings\fdeploy2.ini') if ($_.Extension -eq '.aas') { [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Applications', $_.Name) } } [System.IO.Path]::Combine($GPO.Path, 'Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf') [System.IO.Path]::Combine($GPO.Path, 'Machine\Microsoft\Windows NT\Audit\audit.csv') ) if ($GPO) { if ($_.FullName -in $Correct) { $SuggestedAction = 'Skip assesment' $SuggestedActionComment = 'Correctly placed in SYSVOL' } elseif ($_.FullName -like '*_NTFRS_*') { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Most likely replication error' } elseif ($_.Extension -eq '.adm') { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Most likely legacy ADM files' } elseif ($_.Name -eq 'Thumbs.db') { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Most likely database files to store image thumbnails on Windows systems.' } if (-not $SuggestedAction) { $FullPathAdmFiles = [System.IO.Path]::Combine($GPO.Path, 'Adm\admfiles.ini') if ($_.FullName -eq $FullPathAdmFiles) { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Most likely legacy ADM files settings file' } } if (-not $SuggestedAction) { foreach ($Ext in @('*old*', '*bak*', '*bck', '.new')) { if ($_.Extension -like $Ext) { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Most likely backup files' break } } } if (-not $SuggestedAction) { if ($_.FullName -like '*microsoft\IEAK*') { $SuggestedAction = 'GPO requires cleanup' $SuggestedActionComment = 'Internet Explorer Maintenance (IEM) is deprecated for IE 11' } } } else { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Most likely orphaned SYSVOL GPO' } } elseif ($FileType.Name -eq 'NETLOGON Scripts') { foreach ($Ext in @('*old*', '*bak*', '*bck', '.new')) { if ($_.Extension -like $Ext) { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Most likely backup files' break } } if (-not $SuggestedAction) { if ($_.Extension.Length -gt 6 -and $_.Extension -notin @('.config', '.sites', '.ipsec')) { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Extension longer then 5 chars' } elseif ($_.Extension -eq '') { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'No extension' } } if (-not $SuggestedAction) { foreach ($Name in @('*old*', '*bak*', '*bck*', '*Copy', '*backup*')) { if ($_.BaseName -like $Name) { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = "FileName contains backup related names ($Name)" break } } } if (-not $SuggestedAction) { if ($_.Name -eq 'Thumbs.db') { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'Most likely database files to store image thumbnails on Windows systems.' } } if (-not $SuggestedAction) { foreach ($FullName in @('*backup*', '*Delete*', '*Obsoleet*', '*Obsolete*', '*Archive*')) { if ($_.FullName -like $FullName) { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = "Fullname contains backup related names ($FullName)" break } } } if (-not $SuggestedAction) { $StrippedNumbers = $_.Name -replace "[^0-9]" , '' if ($StrippedNumbers.Length -gt 5) { $SuggestedAction = 'Consider deleting' $SuggestedActionComment = 'FileName contains over 5 numbers (date?)' } } } elseif ($FileType.Name -eq 'SYSVOL PolicyDefinitions') { if ($_.Extension -in @('.admx', '.adml')) { $SuggestedAction = 'Skip assesment' $SuggestedActionComment = 'Most likely ADMX templates' } } elseif ($FileType.Name -eq 'SYSVOL GPO Starters') { $FoundGUID = $_.FullName -match '[\da-zA-Z]{8}-([\da-zA-Z]{4}-){3}[\da-zA-Z]{12}' if ($FoundGUID) { $GUID = $matches[0] $TemporaryStarterPath = "\\$Domain\SYSVOL\$Domain\StarterGPOs\{$GUID}" $Correct = @( [System.IO.Path]::Combine($TemporaryStarterPath, 'StarterGPO.tmplx') [System.IO.Path]::Combine($TemporaryStarterPath, 'en-US', 'StarterGPO.tmpll') foreach ($TypeM in @('Machine', 'User')) { [System.IO.Path]::Combine($TemporaryStarterPath, $TypeM, 'Registry.pol') [System.IO.Path]::Combine($TemporaryStarterPath, $TypeM, 'comment.cmtx') } ) if ($_.FullName -in $Correct) { $SuggestedAction = 'Skip assesment' $SuggestedActionComment = 'Correctly placed in SYSVOL' } } } else { } if (-not $SuggestedAction) { $SuggestedAction = 'Requires verification' $SuggestedActionComment = 'Not able to auto asses' } if (-not $ExtendedMetaData) { $MetaData = [ordered] @{ LocationType = $FileType.Name FullName = $_.FullName Extension = $_.Extension SuggestedAction = $SuggestedAction SuggestedActionComment = $SuggestedActionComment BelongsToGPO = $BelongsToGPO GPODisplayName = $GPODisplayName SizeMB = [math]::Round(($_.Length / 1MB), 2) Size = $_.Length Attributes = $_.Attributes CreationTime = $_.CreationTime LastAccessTime = $_.LastAccessTime LastWriteTime = $_.LastWriteTime } } else { $MetaData = Get-FileMetaData -File $_ -AsHashTable $MetaData['SuggestedAction'] = $SuggestedAction $MetaData['SuggestedActionComment'] = $SuggestedActionComment $MetaData['BelongsToGPO'] = $BelongsToGPO $MetaData['GPODisplayName'] = $GPODisplayName $MetaData['SizeMB'] = [math]::Round(($_.Length / 1MB), 2) $MetaData['Size'] = $_.Length } if ($Signature) { try { $DigitalSignature = Get-AuthenticodeSignature -FilePath $_.Fullname -ErrorAction Stop } catch { Write-Warning "Get-GPOZaurrFiles - Error when reading signature: $($_.Exception.Message)" } if ($DigitalSignature) { $MetaData['SignatureStatus'] = $DigitalSignature.Status $MetaData['IsOSBinary'] = $DigitalSignature.IsOSBinary $MetaData['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject if ($Extended) { $MetaData['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer $MetaData['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber $MetaData['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore $MetaData['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter $MetaData['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint } } else { $MetaData['SignatureStatus'] = 'Not available' $MetaData['IsOSBinary'] = $null $MetaData['SignatureCertificateSubject'] = $null if ($Extended) { $MetaData['SignatureCertificateIssuer'] = $null $MetaData['SignatureCertificateSerialNumber'] = $null $MetaData['SignatureCertificateNotBefore'] = $null $MetaData['SignatureCertificateNotAfter'] = $null $MetaData['SignatureCertificateThumbprint'] = $null } } } if ($HashAlgorithm -ne 'None') { $MetaData['ChecksumSHA256'] = (Get-FileHash -LiteralPath $_.FullName -Algorithm $HashAlgorithm).Hash } if ($AsHashTable) { $MetaData } else { [PSCustomObject] $MetaData } } foreach ($e in $err) { Write-Warning "Get-GPOZaurrFiles - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } } } function Get-GPOZaurrFilesPolicyDefinition { <# .SYNOPSIS Retrieves policy definitions for Group Policy Objects (GPOs) within specified domains. .DESCRIPTION This function retrieves policy definitions for GPOs within specified domains. It collects information about policy files, including their attributes and digital signatures. .PARAMETER Forest Specifies the forest name to retrieve GPO policy definitions from. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the search. .PARAMETER IncludeDomains Specifies an array of domains to include in the search. .PARAMETER ExtendedForestInformation Specifies additional forest information to include in the output. .PARAMETER Signature Indicates whether to retrieve digital signature information for policy files. .EXAMPLE Get-GPOZaurrFilesPolicyDefinition -Forest "contoso.com" -IncludeDomains "domain1", "domain2" -ExcludeDomains "domain3" -Signature Retrieves policy definitions for GPOs within the "contoso.com" forest, including domains "domain1" and "domain2" while excluding "domain3". Digital signature information is also retrieved. #> [alias('Get-GPOZaurrFilesPolicyDefinitions')] [cmdletbinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $Signature ) $Output = [ordered] @{ FilesToDelete = [System.Collections.Generic.List[Object]]::new() } $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $FilesCache = @{} foreach ($Domain in $ForestInformation.Domains) { $Output[$Domain] = [ordered] @{} $FilesCache[$Domain] = [ordered] @{} $Directories = Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" -Directory -ErrorAction SilentlyContinue -ErrorVariable err [Array] $Languages = foreach ($Directory in $Directories) { if ($Directory.BaseName.Length -eq 5) { $Directory.BaseName } } $Files = Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" -ErrorAction SilentlyContinue -ErrorVariable +err -File foreach ($File in $Files) { $FilesCache[$Domain][$($File.BaseName)] = [ordered] @{ Name = $File.BaseName FullName = $File.FullName IsReadOnly = $File.IsReadOnly CreationTime = $File.CreationTime LastAccessTime = $File.LastAccessTime LastWriteTime = $File.LastWriteTime IsConsistent = $false } foreach ($Language in $Languages) { $FilesCache[$Domain][$($File.BaseName)][$Language] = $false } if ($Signature) { $DigitalSignature = Get-AuthenticodeSignature -FilePath $File.FullName $FilesCache[$Domain][$($File.BaseName)]['SignatureStatus'] = $DigitalSignature.Status $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint $FilesCache[$Domain][$($File.BaseName)]['IsOSBinary'] = $DigitalSignature.IsOSBinary } } foreach ($Directory in $Directories) { $FilesLanguage = Get-ChildItem -Path $Directory.FullName -ErrorAction SilentlyContinue -ErrorVariable +err foreach ($FileLanguage in $FilesLanguage) { if ($FileLanguage.Extension -eq '.adml') { if ($FilesCache[$Domain][$FileLanguage.BaseName]) { $FilesCache[$Domain][$FileLanguage.BaseName][$Directory.Name] = $true } else { $Output.FilesToDelete.Add( [PSCustomobject] @{ Name = $FileLanguage.BaseName FullName = $FileLanguage.FullName IsReadOnly = $FileLanguage.IsReadOnly CreationTime = $FileLanguage.CreationTime LastAccessTime = $FileLanguage.LastAccessTime LastWriteTime = $FileLanguage.LastWriteTime } ) } } else { } } } foreach ($e in $err) { Write-Warning "Get-GPOZaurrLegacy - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } $ExcludeProperty = @( 'Name', 'FullName', 'IsReadOnly', 'CreationTime', 'LastAccessTime', 'LastWriteTime', 'IsConsistent' 'SignatureCertificateSubject', 'SignatureCertificateIssuer', 'SignatureCertificateSerialNumber', 'SignatureCertificateNotBefore' 'SignatureCertificateNotAfter', 'SignatureCertificateThumbprint', 'SignatureStatus', 'IsOSBinary' ) $Properties = Select-Properties -Objects $FilesCache[$Domain][0] -ExcludeProperty $ExcludeProperty $Output[$Domain] = foreach ($File in $FilesCache[$Domain].Keys) { $Values = foreach ($Property in $Properties) { $FilesCache[$Domain][$File][$Property] } if ($Values -notcontains $false) { $FilesCache[$Domain][$File]['IsConsistent'] = $true } [PSCustomObject] $FilesCache[$Domain][$File] } } $Output } function Get-GPOZaurrFolders { <# .SYNOPSIS Retrieves information about GPO folders within specified domains. .DESCRIPTION This function retrieves information about various GPO folders within specified domains, such as PolicyDefinitions, Policies, Scripts, GPO Starters, NETLOGON Scripts, DfsrPrivate, and SYSVOL Root. .PARAMETER Type Specifies the type of folders to retrieve. Valid values are 'All', 'Netlogon', 'Sysvol'. .PARAMETER FolderType Specifies the type of folders to retrieve. Valid values are 'All', 'NTFRS', 'Empty'. .PARAMETER Forest Specifies the forest name to retrieve information for. .PARAMETER ExcludeDomains Specifies domains to exclude from the retrieval. .PARAMETER IncludeDomains Specifies domains to include in the retrieval. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER AsHashTable Indicates whether to return the output as a hashtable. .EXAMPLE Get-GPOZaurrFolders -Type All -FolderType All -Forest 'example.com' -IncludeDomains 'domain1', 'domain2' -ExcludeDomains 'domain3' -ExtendedForestInformation $info -AsHashTable Retrieves information about all types of GPO folders within the specified domains in the forest 'example.com', excluding 'domain3', and including 'domain1' and 'domain2', with extended forest information. .EXAMPLE Get-GPOZaurrFolders -Type Sysvol -FolderType NTFRS -Forest 'example.com' -IncludeDomains 'domain1' -AsHashTable Retrieves information about Sysvol folders using NTFRS type within the specified domain 'domain1' in the forest 'example.com' and returns the output as a hashtable. #> [cmdletBinding()] param( [ValidateSet('All', 'Netlogon', 'Sysvol')][string[]] $Type = 'All', [ValidateSet('All', 'NTFRS', 'Empty')][string] $FolderType = 'All', [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $AsHashTable ) $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $Path = @( if ($Type -contains 'All') { "\\$Domain\SYSVOL\$Domain" } if ($Type -contains 'Sysvol') { "\\$Domain\SYSVOL\$Domain\policies" } if ($Type -contains 'NetLogon') { "\\$Domain\NETLOGON" } ) $Folders = [ordered] @{ "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" = @{ Name = 'SYSVOL PolicyDefinitions' } "\\$Domain\SYSVOL\$Domain\policies" = @{ Name = 'SYSVOL Policies' } "\\$Domain\SYSVOL\$Domain\scripts" = @{ Name = 'NETLOGON Scripts' } "\\$Domain\SYSVOL\$Domain\StarterGPOs" = @{ Name = 'SYSVOL GPO Starters' } "\\$Domain\NETLOGON" = @{ Name = 'NETLOGON Scripts' } "\\$Domain\SYSVOL\$Domain\DfsrPrivate" = @{ Name = 'DfsrPrivate' } "\\$Domain\SYSVOL\$Domain" = @{ Name = 'SYSVOL Root' } } $Exclusions = @{ DfsrPrivate = @{ ConflictAndDeleted = $true Deleted = $true Installing = $true } 'SYSVOL Policies' = @{ User = $true Machine = $true } 'NETLOGON Scripts' = @{ } 'SYSVOL Root' = @{ } 'SYSVOL GPO Starters' = @{ } 'SYSVOL PolicyDefinitions' = @{ } } Get-ChildItem -Path $Path -ErrorAction SilentlyContinue -Recurse -ErrorVariable +err -Force -Directory | ForEach-Object { $FileType = foreach ($Key in $Folders.Keys) { if ($_.FullName -like "$Key*") { $Folders[$Key] break } } $RootFolder = $Folders["$($_.FullName)"] if ($RootFolder) { $IsRootFolder = $true } else { $IsRootFolder = $false } $IsExcluded = $Exclusions["$($FileType.Name)"]["$($_.Name)"] -is [bool] if ($IsRootFolder -and $IsExcluded -eq $false) { $IsExcluded = $true } $FullFolder = Test-Path -Path "$($_.FullName)\*" $BrokenReplicationRoot = $_.Name -like '*_NTFRS_*' $BrokenReplicationChild = $_.FullName -like '*_NTFRS_*' -and $_.Name -notlike '*_NTFRS_*' $BrokenReplication = $_.FullName -like '*_NTFRS_*' $Object = [ordered] @{ FolderType = $FileType.Name FullName = $_.FullName IsEmptyFolder = -not $FullFolder IsBrokenReplication = $BrokenReplication IsBrokenReplicationRoot = $BrokenReplicationRoot IsBrokenReplicationChild = $BrokenReplicationChild IsRootFolder = $IsRootFolder IsExcluded = $IsExcluded Name = $_.Name Root = $_.Root Parent = $_.Parent CreationTime = $_.CreationTime LastWriteTime = $_.LastWriteTime Attributes = $_.Attributes DomainName = $Domain } if (-not $Object.IsExcluded) { if ($FolderType -eq 'Empty' -and $Object.IsEmptyFolder -eq $true) { if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } elseif ($FolderType -eq 'NTFRS' -and $Object.IsBrokenReplicationRoot -eq $true) { if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } elseif ($FolderType -eq 'All') { if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } } } } foreach ($e in $err) { Write-Warning "Get-GPOZaurrFolders - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } } function Get-GPOZaurrInheritance { <# .SYNOPSIS Retrieves inheritance information for Group Policy Objects (GPOs) within specified Organizational Units (OUs). .DESCRIPTION This function retrieves and displays inheritance information for GPOs within specified OUs. It provides details on blocked inheritance, excluded objects, and group policies associated with blocked objects. .PARAMETER IncludeBlockedObjects Specifies whether to include OUs with blocked inheritance. By default, this is disabled. .PARAMETER OnlyBlockedInheritance Specifies whether to show only OUs with blocked inheritance. .PARAMETER IncludeExcludedObjects Specifies whether to show excluded objects. By default, this is disabled. .PARAMETER IncludeGroupPoliciesForBlockedObjects Specifies whether to include Group Policies for blocked objects. By default, this is disabled. .PARAMETER Exclusions Specifies the OUs approved by IT to be excluded. You can provide OUs by canonical name or distinguishedName. .PARAMETER Forest Specifies the target forest. By default, the current forest is used. .PARAMETER ExcludeDomains Specifies the domain to exclude from the search. By default, the entire forest is scanned. .PARAMETER IncludeDomains Specifies specific domains to include. By default, the entire forest is scanned. .PARAMETER ExtendedForestInformation Allows providing Forest Information from another command to speed up processing. .EXAMPLE $Objects = Get-GPOZaurrInheritance -IncludeBlockedObjects -IncludeExcludedObjects -OnlyBlockedInheritance -Exclusions $ExcludedOU $Objects | Format-Table .NOTES These are general notes about the function. #> [cmdletBinding()] param( [switch] $IncludeBlockedObjects, [switch] $OnlyBlockedInheritance, [switch] $IncludeExcludedObjects, [switch] $IncludeGroupPoliciesForBlockedObjects, [string[]] $Exclusions, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { $ExclusionsCache = @{} $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Exclusion in $Exclusions) { $ExclusionsCache[$Exclusion] = $true } } Process { foreach ($Domain in $ForestInformation.Domains) { $OrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties gpOptions, canonicalName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0] foreach ($OU in $OrganizationalUnits) { $InheritanceInformation = [Ordered] @{ CanonicalName = $OU.canonicalName BlockedInheritance = if ($OU.gpOptions -eq 1) { $true } else { $false } Exclude = $false DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $OU.DistinguishedName } if ($InheritanceInformation.BlockedInheritance -and $IncludeGroupPoliciesForBlockedObjects.IsPresent) { try { $GPInheritance = Get-GPInheritance -Target $OU.distinguishedName -ErrorAction Stop -Domain $InheritanceInformation.DomainName } catch { Write-Warning -Message "Get-GPOZaurrInheritance - Can't get GPInheritance for $($OU.distinguishedName). Error: $($_.Exception.Message)" continue } $ActiveGroupPolicies = foreach ($GPO in $GPInheritance.InheritedGpoLinks) { [PSCustomObject] @{ OrganizationalUnit = $OU.canonicalName DisplayName = $GPO.DisplayName DomainName = $GPO.GpoDomainName LinkedDirectly = if ($OU.DistinguishedName -eq $GPO.Target) { $true } else { $false } GPOID = $GPO.GPOID Enabled = $GPO.Enabled Enforced = $GPO.Enforced Order = $GPO.Order LinkedTo = $GPO.Target OrganizationalUnitDN = $OU.DistinguishedName } } } else { $ActiveGroupPolicies = $null } if ($Exclusions) { if ($ExclusionsCache[$OU.canonicalName]) { $InheritanceInformation['Exclude'] = $true } elseif ($ExclusionsCache[$OU.DistinguishedName]) { $InheritanceInformation['Exclude'] = $true } } if (-not $IncludeExcludedObjects -and $InheritanceInformation['Exclude']) { continue } if (-not $IncludeBlockedObjects) { if ($OnlyBlockedInheritance) { if ($InheritanceInformation.BlockedInheritance -eq $true) { [PSCustomObject] $InheritanceInformation } } else { [PSCustomObject] $InheritanceInformation } } else { if ($InheritanceInformation) { if ($InheritanceInformation.BlockedInheritance -eq $true) { $InheritanceInformation['UsersCount'] = $null $InheritanceInformation['ComputersCount'] = $null [Array] $InheritanceInformation['Users'] = (Get-ADUser -SearchBase $OU.DistinguishedName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0] -Filter *).SamAccountName [Array] $InheritanceInformation['Computers'] = (Get-ADComputer -SearchBase $OU.DistinguishedName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0] -Filter *).SamAccountName $InheritanceInformation['UsersCount'] = $InheritanceInformation['Users'].Count $InheritanceInformation['ComputersCount'] = $InheritanceInformation['Computers'].Count } else { $InheritanceInformation['UsersCount'] = $null $InheritanceInformation['ComputersCount'] = $null $InheritanceInformation['Users'] = $null $InheritanceInformation['Computers'] = $null } } $InheritanceInformation['DistinguishedName'] = $OU.DistinguishedName $InheritanceInformation['GroupPolicies'] = $ActiveGroupPolicies if ($OnlyBlockedInheritance) { if ($InheritanceInformation.BlockedInheritance -eq $true) { [PSCustomObject] $InheritanceInformation } } else { [PSCustomObject] $InheritanceInformation } } } } } } function Get-GPOZaurrLegacyFiles { <# .SYNOPSIS Retrieves legacy Group Policy Object (GPO) files from the SYSVOL directory of specified domains within a forest. .DESCRIPTION The Get-GPOZaurrLegacyFiles function retrieves legacy GPO files, such as '*.adm' and 'admfiles.ini', from the SYSVOL directory of specified domains within a forest. It provides detailed information about these files including their name, full path, creation time, last write time, attributes, associated domain name, and directory name. .PARAMETER Forest Specifies the name of the forest from which to retrieve legacy GPO files. .PARAMETER ExcludeDomains Specifies an array of domain names to exclude from the search for legacy GPO files. .PARAMETER IncludeDomains Specifies an array of domain names to include in the search for legacy GPO files. .PARAMETER ExtendedForestInformation Specifies additional information about the forest to enhance the retrieval process. .EXAMPLE Get-GPOZaurrLegacyFiles -Forest "contoso.com" -IncludeDomains "domain1", "domain2" -ExcludeDomains "domain3" -ExtendedForestInformation $additionalInfo Retrieves legacy GPO files from the "contoso.com" forest for "domain1" and "domain2" domains while excluding "domain3", using additional forest information. #> [cmdletbinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies" -ErrorAction SilentlyContinue -Recurse -Include '*.adm', 'admfiles.ini' -ErrorVariable err -Force | ForEach-Object { [PSCustomObject] @{ Name = $_.Name FullName = $_.FullName CreationTime = $_.CreationTime LastWriteTime = $_.LastWriteTime Attributes = $_.Attributes DomainName = $Domain DirectoryName = $_.DirectoryName } } foreach ($e in $err) { Write-Warning "Get-GPOZaurrLegacyFiles - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } } } function Get-GPOZaurrLink { <# .SYNOPSIS Retrieves Group Policy Object (GPO) links based on specified criteria. .DESCRIPTION This function retrieves GPO links based on various parameters such as ADObject, Filter, Linked, Site, etc. It provides flexibility in searching for GPO links within Active Directory. .PARAMETER ADObject Specifies the Active Directory object(s) to search for GPO links. .PARAMETER Filter Specifies the filter criteria to search for GPO links. .PARAMETER SearchBase Specifies the search base for filtering GPO links. .PARAMETER SearchScope Specifies the search scope for filtering GPO links. .PARAMETER Linked Specifies the type of linked GPOs to retrieve. Valid values are 'All', 'Root', 'DomainControllers', 'Site', and 'OrganizationalUnit'. .PARAMETER Site Specifies the site(s) to search for GPO links. .PARAMETER Limited Indicates whether to limit the search results. .PARAMETER SkipDuplicates Indicates whether to skip duplicate search results. .PARAMETER GPOCache Specifies a cache for storing GPO information. .PARAMETER Forest Specifies the forest name for filtering GPO links. .PARAMETER ExcludeDomains Specifies the domains to exclude from the search. .PARAMETER IncludeDomains Specifies the domains to include in the search. .EXAMPLE Get-GPOZaurrLink -ADObject $ADObject -Linked 'All' Description ----------- Retrieves all linked GPOZaurr links for the specified Active Directory object(s). .EXAMPLE Get-GPOZaurrLink -Filter "(objectClass -eq 'organizationalUnit')" -SearchBase 'CN=Configuration,DC=ad,DC=evotec,DC=xyz' Description ----------- Retrieves GPOZaurr links based on the specified filter and search base. #> [cmdletbinding(DefaultParameterSetName = 'Linked')] param( [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, # site doesn't really work this way unless you give it 'CN=Configuration,DC=ad,DC=evotec,DC=xyz' as SearchBase [parameter(ParameterSetName = 'Filter')][string] $Filter, # "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')" [parameter(ParameterSetName = 'Filter')][string] $SearchBase, [parameter(ParameterSetName = 'Filter')][Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope, [parameter(ParameterSetName = 'Linked')][validateset('All', 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string[]] $Linked, [parameter(ParameterSetName = 'Site')][string[]] $Site, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $Limited, [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $SkipDuplicates, [parameter(ParameterSetName = 'Site')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [System.Collections.IDictionary] $GPOCache, [parameter(ParameterSetName = 'Site')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [alias('ForestName')][string] $Forest, [parameter(ParameterSetName = 'Site')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [string[]] $ExcludeDomains, [parameter(ParameterSetName = 'Site')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [alias('Domain', 'Domains')][string[]] $IncludeDomains, [parameter(ParameterSetName = 'Site')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [System.Collections.IDictionary] $ExtendedForestInformation, [parameter(ParameterSetName = 'Site')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $AsHashTable, [parameter(ParameterSetName = 'Site')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $Summary ) Begin { $CacheReturnedGPOs = [ordered] @{} $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation if (-not $GPOCache -and -not $Limited) { $GPOCache = @{ } foreach ($Domain in $ForestInformation.Forest.Domains) { Write-Verbose "Get-GPOZaurrLink - Building GPO cache for domain $Domain" if ($ForestInformation['QueryServers'][$Domain]) { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] Get-GPO -All -DomainName $Domain -Server $QueryServer | ForEach-Object { $GPOCache["$Domain$($_.ID.Guid)"] = $_ } } else { Write-Warning -Message "Get-GPOZaurrLink - Couldn't get query server for $Domain. Skipped." } } } } Process { $getGPOLoopSplat = @{ Linked = $Linked ForestInformation = $ForestInformation SearchScope = $SearchScope SearchBase = $SearchBase ADObject = $ADObject Filter = $Filter SkipDuplicates = $SkipDuplicates Site = $Site } Remove-EmptyValue -Hashtable $getGPOLoopSplat -Recursive $getGPOLoopSplat['CacheReturnedGPOs'] = $CacheReturnedGPOs if ($AsHashTable -or $Summary) { $HashTable = [ordered] @{} $SummaryHashtable = [ordered] @{} $Links = Get-GPOZaurrLinkLoop @getGPOLoopSplat foreach ($Link in $Links) { $Key = -join ($Link.DomainName, $Link.GUID) if (-not $HashTable[$Key]) { $HashTable[$Key] = [System.Collections.Generic.List[PSCustomObject]]::new() } $HashTable[$Key].Add($Link) } foreach ($Key in $HashTable.Keys) { [Array] $Link = $HashTable[$Key] $EnabledLinks = $Link.Enabled.Where( { $_ -eq $true }, 'split') $LinkedRoot = $false $LinkedRootPlaces = [System.Collections.Generic.List[string]]::new() $LinkedSite = $false $LinkedSitePlaces = [System.Collections.Generic.List[string]]::new() $LinkedOU = $false $LinkedCrossDomain = $false $LinkedCrossDomainPlaces = [System.Collections.Generic.List[string]]::new() foreach ($InternalLink in $Link) { if ($InternalLink.ObjectClass -eq 'domainDNS') { $LinkedRoot = $true $LinkedRootPlaces.Add($InternalLink.DistinguishedName) } elseif ($InternalLink.ObjectClass -eq 'site') { $LinkedSite = $true $LinkedSitePlaces.Add($InternalLink.Name) } else { $LinkedOU = $true } $CN = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $InternalLink.distinguishedName $GPOCN = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $InternalLink.GPODomainDistinguishedName if ($CN -ne $GPOCN) { $LinkedCrossDomain = $true $LinkedCrossDomainPlaces.Add($InternalLink.DistinguishedName) } } if ($EnabledLinks[0].Count -gt 0) { $IsLinked = $true } else { $IsLinked = $false } $SummaryLink = [PSCustomObject] @{ DisplayName = $Link[0].DisplayName DomainName = $Link[0].DomainName GUID = $Link[0].GUID Linked = $IsLinked LinksCount = $Link.Count LinksEnabledCount = $EnabledLinks[0].Count LinksDisabledCount = $EnabledLinks[1].Count LinkedCrossDomain = $LinkedCrossDomain LinkedRoot = $LinkedRoot LinkedSite = $LinkedSite LinkedOU = $LinkedOU LinkedSitePlaces = $LinkedSitePlaces LinkedRootPlaces = $LinkedRootPlaces LinkedCrossDomainPlaces = $LinkedCrossDomainPlaces Links = $Link.DistinguishedName LinksObjects = $Link } $SummaryHashtable[$Key] = $SummaryLink } if ($AsHashTable -and $Summary) { $SummaryHashtable } elseif ($AsHashTable) { $HashTable } elseif ($Summary) { $SummaryHashtable.Values } } else { Get-GPOZaurrLinkLoop @getGPOLoopSplat } } End { } } function Get-GPOZaurrLinkSummary { <# .SYNOPSIS Retrieves a summary of GPO links based on specified criteria. .DESCRIPTION This function retrieves a summary of GPO links based on the provided parameters. It categorizes the links into different types and provides detailed information about each link. .PARAMETER Report Specifies the type of report to generate. Valid values are 'All', 'MultipleLinks', 'OneLink', and 'LinksSummary'. Default is 'All'. .PARAMETER UnlimitedProperties Indicates whether to include unlimited properties in the report. .PARAMETER Forest Specifies the forest name to retrieve GPO links from. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the report. .PARAMETER IncludeDomains Specifies an array of domains to include in the report. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .EXAMPLE Get-GPOZaurrLinkSummary -Forest "Contoso" -IncludeDomains "Domain1", "Domain2" -Report "MultipleLinks" Retrieves a summary of GPO links for the specified forest and included domains, focusing on multiple links. .EXAMPLE Get-GPOZaurrLinkSummary -Forest "Fabrikam" -ExcludeDomains "Domain3" -Report "OneLink" Retrieves a summary of GPO links for the specified forest excluding Domain3, focusing on a single link. #> [cmdletBinding()] param( [ValidateSet('All', 'MultipleLinks', 'OneLink', 'LinksSummary')][string[]] $Report = 'All', [switch] $UnlimitedProperties, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $HighestCount = 0 $CacheSummaryLinks = [ordered] @{} $Links = Get-GPOZaurrLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Linked Root, DomainControllers, OrganizationalUnit foreach ($Link in $Links) { if (-not $CacheSummaryLinks["$($Link.DomainName)$($Link.Guid)"]) { $CacheSummaryLinks["$($Link.DomainName)$($Link.Guid)"] = [System.Collections.Generic.List[System.Object]]::new() } $CacheSummaryLinks["$($Link.DomainName)$($Link.Guid)"].Add($Link) } $ReturnObject = [ordered] @{ MultipleLinks = [System.Collections.Generic.List[System.Object]]::new() OneLink = [System.Collections.Generic.List[System.Object]]::new() LinksSummary = [System.Collections.Generic.List[System.Object]]::new() } foreach ($Key in $CacheSummaryLinks.Keys) { $GPOs = $CacheSummaryLinks[$Key] [Array] $LinkingSummary = foreach ($GPO in $GPOs) { $SplitttedOU = ($GPO.DistinguishedName -split ',') [Array] $Clean = foreach ($_ in $SplitttedOU) { if ($_ -notlike 'DC=*') { $_ -replace 'OU=' } } if ($Clean.Count -gt $HighestCount) { $HighestCount = $Clean.Count } if ($Clean) { $Test = [ordered] @{ DisplayName = $GPO.DisplayName Guid = $GPO.Guid DomainName = $GPO.DomainName Level0 = ConvertFrom-DistinguishedName -DistinguishedName $GPO.DistinguishedName -ToDomainCN } for ($i = 1; $i -le 10; $i++) { $Test["Level$i"] = $Clean[ - $i] } [PSCustomobject] $Test } else { $Test = [ordered] @{ DisplayName = $GPO.DisplayName Guid = $GPO.Guid DomainName = $GPO.DomainName Level0 = $GPO.CanonicalName } for ($i = 1; $i -le 10; $i++) { $Test["Level$i"] = $null } [PSCustomobject] $Test } } if ($Report -contains 'MultipleLinks' -or $Report -contains 'All') { foreach ($Link in $LinkingSummary) { $ReturnObject.MultipleLinks.Add($Link) } } if ($Report -eq 'OneLink' -or $Report -contains 'All') { $List = [ordered] @{ DisplayName = $GPOs[0].DisplayName Guid = $GPOs[0].Guid DomainName = $GPOs[0].DomainName LinksCount = $GPOs.Count } for ($i = 0; $i -le 10; $i++) { $List["Level$i"] = ($LinkingSummary."Level$i" | Select-Object -Unique).Count $List["Level$($i)List"] = ($LinkingSummary."Level$i" | Select-Object -Unique) } $List.LinksDistinguishedName = $GPOs.DistinguishedName $List.LinksCanonicalName = $GPOs.CanonicalName $List.Owner = $GPOs[0].Owner $List.GpoStatus = $GPOs[0].GpoStatus $List.Description = $GPOs[0].Description $List.CreationTime = $GPOs[0].CreationTime $List.ModificationTime = $GPOs[0].ModificationTime $List.GPODomainDistinguishedName = $GPOs[0].GPODomainDistinguishedName $List.GPODistinguishedName = $GPOs[0].GPODistinguishedName $ReturnObject.OneLink.Add( [PSCustomObject] $List) } if ($Report -eq 'LinksSummary' -or $Report -contains 'All') { $Output = [PSCustomObject] @{ DisplayName = $GPOs[0].DisplayName Guid = $GPOs[0].Guid DomainName = $GPOs[0].DomainName LinksCount = $GPOs.Count LinksDistinguishedName = $GPOs.DistinguishedName LinksCanonicalName = $GPOs.CanonicalName Owner = $GPOs[0].Owner GpoStatus = $GPOs[0].GpoStatus Description = $GPOs[0].Description CreationTime = $GPOs[0].CreationTime ModificationTime = $GPOs[0].ModificationTime GPODomainDistinguishedName = $GPOs[0].GPODomainDistinguishedName GPODistinguishedName = $GPOs[0].GPODistinguishedName } $ReturnObject.LinksSummary.Add($Output) } } if (-not $UnlimitedProperties) { if ($Report -contains 'MultipleLinks' -or $Report -contains 'All') { $Properties = @( 'DisplayName' 'DomainName' 'GUID' for ($i = 0; $i -le $HighestCount; $i++) { "Level$i" } ) $ReturnObject.MultipleLinks = $ReturnObject.MultipleLinks | Select-Object -Property $Properties } if ($Report -contains 'OneLink' -or $Report -contains 'All') { $Properties = @( 'DisplayName' 'DomainName' 'GUID' for ($i = 0; $i -le $HighestCount; $i++) { "Level$i" "Level$($i)List" } 'LinksDistinguishedName' 'LinksCanonicalName' 'Owner' 'GpoStatus' 'Description' 'CreationTime' 'ModificationTime' 'GPODomainDistinguishedName' 'GPODistinguishedName' ) $ReturnObject.OneLink = $ReturnObject.OneLink | Select-Object -Property $Properties } } if ($Report.Count -eq 1 -and $Report -notcontains 'All') { $ReturnObject["$Report"] } else { $ReturnObject } } function Get-GPOZaurrMissingFiles { <# .SYNOPSIS Retrieves information about missing files in Group Policy Objects (GPOs) within a specified forest. .DESCRIPTION This function queries Active Directory for GPOs and checks for missing files within them. It provides detailed information about any errors found. .PARAMETER Forest Specifies the name of the forest to query for GPO information. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the query. .PARAMETER IncludeDomains Specifies an array of domains to include in the query. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER GPOName Specifies the name of the GPO to retrieve information for. .PARAMETER GPOGUID Specifies the GUID of the GPO to retrieve information for. .PARAMETER BrokenOnly Indicates whether to only display GPOs with missing files. .EXAMPLE Get-GPOZaurrMissingFiles -Forest "example.com" -IncludeDomains "domain1", "domain2" -ExcludeDomains "domain3" -GPOName "GPO1" Retrieves information about missing files in the GPO named "GPO1" within the "example.com" forest, including only domains "domain1" and "domain2" while excluding "domain3". .EXAMPLE Get-GPOZaurrMissingFiles -Forest "example.com" -IncludeDomains "domain1", "domain2" -GPOGUID "12345678-1234-1234-1234-1234567890AB" -BrokenOnly Retrieves information about GPOs with missing files in the "example.com" forest, including only domains "domain1" and "domain2" for the GPO with the specified GUID, displaying only GPOs with missing files. #> [cmdletBinding()] param( [Parameter()][alias('ForestName')][string] $Forest, [Parameter()][string[]] $ExcludeDomains, [Parameter()][alias('Domain', 'Domains')][string[]] $IncludeDomains, [Parameter()][System.Collections.IDictionary] $ExtendedForestInformation, [Parameter()][Alias('Name')][string[]] $GPOName, [Parameter()][string[]] $GPOGUID, [switch] $BrokenOnly ) Write-Verbose "Get-GPOZaurrMissingFiles - Query AD for GPOs" if ($GPOName -or $GPOGUID) { [Array] $GPOs = @( foreach ($Name in $GPOName) { Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -GPOName $Name } foreach ($GUID in $GPOGUID) { Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -GPOGuid $GUID } ) } else { [Array] $GPOs = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } $Count = 0 foreach ($GPO in $GPOS) { $Count++ Write-Verbose -Message "Get-GPOZaurrMissingFiles - Processing ($Count / $($GPOs.Count)) - $($GPO.DisplayName)" [xml] $GPOOutput = Get-GPOReport -Guid $GPO.GUID -Domain $GPO.DomainName -ReportType Xml [Array] $ErrorsFound = foreach ($Type in @('User', 'Computer')) { foreach ($Extension in $GPOOutput.GPO.$Type.ExtensionData) { if ($Extension.Error) { Write-Warning -Message "Get-GPOZaurrMissingFiles - $($GPO.DisplayName) - $($Extension.Name) - $($Extension.Error.Details)" [ordered] @{ Category = $Extension.Name; Error = $Extension.Error.Details } } } } if ($BrokenOnly -and $ErrorsFound.Count -eq 0) { continue } [PSCustomObject] @{ GPOName = $GPO.DisplayName GPOGuid = $GPO.GUID DomainName = $GPO.DomainName ErrorCount = $ErrorsFound.Count ErrorCategory = $ErrorsFound.Category ErrorDetails = $ErrorsFound.Error } } } function Get-GPOZaurrNetLogon { <# .SYNOPSIS Retrieves information about Group Policy Objects (GPO) stored in the Netlogon and SYSVOL directories. .DESCRIPTION The Get-GPOZaurrNetLogon function retrieves details about GPOs stored in the Netlogon and SYSVOL directories of specified domains within a forest. It provides information about file ownership, status, domain, extension, creation time, and more. .PARAMETER OwnerOnly Specifies whether to include only GPOs with identified owners. .PARAMETER SkipOwner Specifies whether to skip checking the owner of GPOs. .PARAMETER Forest Specifies the forest name to retrieve GPO information from. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from GPO retrieval. .PARAMETER IncludeDomains Specifies an array of domains to include in GPO retrieval. .PARAMETER ExtendedForestInformation Specifies additional forest information to include in the output. .EXAMPLE Get-GPOZaurrNetLogon -Forest "contoso.com" -IncludeDomains "domain1", "domain2" Retrieves GPO information for the specified forest and domains. .EXAMPLE Get-GPOZaurrNetLogon -OwnerOnly Retrieves GPO information only for GPOs with identified owners. .EXAMPLE Get-GPOZaurrNetLogon -SkipOwner Retrieves GPO information while skipping the owner check. #> [cmdletBinding(DefaultParameterSetName = 'Default')] param( [parameter(ParameterSetName = 'OwnerOnly')][switch] $OwnerOnly, [parameter(ParameterSetName = 'SkipOwner')][switch] $SkipOwner, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $FilesAll = foreach ($Domain in $ForestInformation.Domains) { $Path = -join ("\\", $Domain, '\Netlogon') $PathOnSysvol = -join ("\\", $Domain, "\SYSVOL\", $Domain, "\Scripts") [Array] $Files = Get-ChildItem -LiteralPath $Path -Recurse -Force -ErrorVariable Err -ErrorAction SilentlyContinue foreach ($e in $err) { Write-Warning "Get-GPOZaurrNetLogon - Listing file failed with error $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } $Count = 0 foreach ($File in $Files) { $Count++ Write-Verbose "GPOZaurrNetLogon - Processing [$($Domain)]($Count/$($Files.Count)) $($File.FullName)" try { $ACL = Get-Acl -Path $File.FullName -ErrorAction Stop } catch { Write-Warning "Get-GPOZaurrNetLogon - ACL reading failed for $($File.FullName) with error $($_.Exception.Message) ($($_.CategoryInfo.Reason))" } if ($ACL.Owner) { $IdentityOwner = Convert-Identity -Identity $ACL.Owner -Verbose:$false $IdentityOwnerAdvanced = Get-WinADObject -Identity $ACL.Owner -Cache -Verbose:$false } else { $IdentityOwner = [PSCustomObject] @{ Name = ''; SID = ''; Type = 'Unknown' } $IdentityOwnerAdvanced = [PSCustomObject] @{ ObjectClass = '' } } if (-not $OwnerOnly) { if (-not $SkipOwner) { if ($IdentityOwner.SID -eq "S-1-5-32-544") { $Status = 'OK' } else { $Status = 'Replace owner required' } [PSCustomObject] @{ FullName = $File.FullName Status = $Status DomainName = $Domain Extension = $File.Extension CreationTime = $File.CreationTime LastAccessTime = $File.LastAccessTime LastWriteTime = $File.LastWriteTime Attributes = $File.Attributes SizeMB = [math]::Round(($File.Length / 1MB), 2) AccessControlType = 'Allow' Principal = $IdentityOwner.Name PrincipalSid = $IdentityOwner.SID PrincipalType = $IdentityOwner.Type PrincipalObjectClass = $IdentityOwnerAdvanced.ObjectClass FileSystemRights = 'Owner' IsInherited = $false FullNameOnSysVol = $File.FullName.Replace($Path, $PathOnSysvol) Size = $File.Length } } $FilePermission = Get-FilePermissions -Path $File.FullName -ACLS $ACL -Verbose:$false foreach ($Perm in $FilePermission) { $Identity = Convert-Identity -Identity $Perm.Principal -Verbose:$false $AdvancedIdentity = Get-WinADObject -Identity $Perm.Principal -Cache -Verbose:$false $Status = 'Not assesed' if ($Perm.FileSystemRights -eq [System.Security.AccessControl.FileSystemRights]::FullControl) { if ($Identity.Type -eq 'WellKnownAdministrative') { $Status = 'OK' } else { if ($AdvancedIdentity.ObjectClass -in 'user', 'computer') { $Status = 'Removal permission required' } else { $Status = 'Review permission required' } } } elseif ($Perm.FileSystemRights -like "*Modify*") { if ($AdvancedIdentity.ObjectClass -in 'user', 'computer') { $Status = 'Removal permission required' } else { $Status = 'Review permission required' } } elseif ($Perm.FileSystemRights -like "*Write*") { if ($AdvancedIdentity.ObjectClass -in 'user', 'computer') { $Status = 'Removal permission required' } else { $Status = 'Review permission required' } } if ($Identity.Type -eq 'Unknown') { $Status = 'Removal permission required' } [PSCustomObject] @{ FullName = $File.FullName Status = $Status DomainName = $Domain Extension = $File.Extension CreationTime = $File.CreationTime LastAccessTime = $File.LastAccessTime LastWriteTime = $File.LastWriteTime Attributes = $File.Attributes SizeMB = [math]::Round(($File.Length / 1MB), 2) AccessControlType = $Perm.AccessControlType Principal = $Identity.Name PrincipalSid = $Identity.SID PrincipalType = $Identity.Type PrincipalObjectClass = $AdvancedIdentity.ObjectClass FileSystemRights = $Perm.FileSystemRights IsInherited = $Perm.IsInherited FullNameOnSysVol = $File.FullName.Replace($Path, $PathOnSysvol) Size = $File.Length } } } else { if ($IdentityOwner.SID -eq "S-1-5-32-544") { $Status = 'OK' } else { $Status = 'Replace owner required' } [PSCustomObject] @{ FullName = $File.FullName Status = $Status DomainName = $Domain Extension = $File.Extension CreationTime = $File.CreationTime LastAccessTime = $File.LastAccessTime LastWriteTime = $File.LastWriteTime Attributes = $File.Attributes Owner = $IdentityOwner.Name OwnerSid = $IdentityOwner.SID OwnerType = $IdentityOwner.Type FullNameOnSysVol = $File.FullName.Replace($Path, $PathOnSysvol) } } } } $FilesAll } function Get-GPOZaurrOrganizationalUnit { <# .SYNOPSIS Retrieves information about Group Policy Objects (GPOs) linked to Organizational Units (OUs) within a specified forest. .DESCRIPTION This function retrieves detailed information about the GPOs linked to OUs within a specified forest. It provides information on linked GPOs, objects within OUs, and counts of objects at different levels. .PARAMETER Forest Specifies the name of the forest to retrieve information from. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from processing. .PARAMETER IncludeDomains Specifies an array of domains to include for processing. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER Option Specifies the action to perform on the retrieved data. Valid values are 'OK', 'Unlink', or 'Delete'. .PARAMETER ExcludeOrganizationalUnit Specifies an array of OUs to exclude from processing. .EXAMPLE Get-GPOZaurrOrganizationalUnit -Forest "contoso.com" -IncludeDomains "child.contoso.com" -ExcludeDomains "test.contoso.com" -ExtendedForestInformation $ExtendedInfo -Option "OK" -ExcludeOrganizationalUnit "OU=Test,DC=contoso,DC=com" Retrieves information about GPOs linked to OUs in the "contoso.com" forest, including the "child.contoso.com" domain, excluding the "test.contoso.com" domain, with additional forest information, performing the 'OK' action, and excluding the "OU=Test,DC=contoso,DC=com" OU. #> [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [ValidateSet('OK', 'Unlink', 'Delete')][string[]] $Option, [alias('ExcludeOU', 'Exclusions')][string[]] $ExcludeOrganizationalUnit ) $CachedOu = [ordered] @{} $CachedGPO = [ordered] @{} $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $DefaultFolders = Get-WellKnownFolders -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $GroupPolicies = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($GPO in $GroupPolicies) { $CachedGPO[$GPO.GPODistinguishedName] = $GPO } foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain)" $CountTop = 0 [Array] $TopOrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties LinkedGroupPolicyObjects, DistinguishedName, ntSecurityDescriptor -Server $ForestInformation['QueryServers'][$Domain]['hostname'][0] -SearchScope OneLevel foreach ($TopOU in $TopOrganizationalUnits) { $CountTop++ Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain) / $($TOPOU.DistinguishedName) [$CountTop/$($TopOrganizationalUnits.Count)]" if ($TopOU.LinkedGroupPolicyObjects) { $LinkedGPOs = $CachedGPO[$TopOU.LinkedGroupPolicyObjects] } else { $LinkedGPOs = $null } $CachedOu[$TopOU.DistinguishedName] = [ordered]@{ 'LinkedGroupPolicyObjects' = $TopOU.LinkedGroupPolicyObjects 'LinkedGroupPolicy' = $LinkedGPOs 'Objects' = [ordered] @{} 'ObjectsClasses' = [ordered] @{} 'ObjectsCountDirect' = 0 'ObjectsCountIndirect' = 0 'ObjectsCountTotal' = 0 'Level' = 'Top' 'RootLevel' = $TopOU.Name 'Domain' = $Domain } [Array] $OUs = Get-ADOrganizationalUnit -SearchScope Subtree -SearchBase $TopOU.DistinguishedName -Server $ForestInformation['QueryServers'][$Domain]['hostname'][0] -Properties LinkedGroupPolicyObjects, DistinguishedName -Filter * Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain) / $($TOPOU.DistinguishedName) [$CountTop/$($TopOrganizationalUnits.Count)], found $($OUs.Count) OU's to process." foreach ($OU in $OUs) { if (-not $CachedOu[$OU.DistinguishedName]) { if ($OU.LinkedGroupPolicyObjects) { $LinkedGPOs = $CachedGPO[$OU.LinkedGroupPolicyObjects] } else { $LinkedGPOs = $null } $CachedOu[$OU.DistinguishedName] = [ordered]@{ 'LinkedGroupPolicyObjects' = $OU.LinkedGroupPolicyObjects 'LinkedGroupPolicy' = $LinkedGPOs 'Objects' = [ordered] @{} 'ObjectsClasses' = [ordered] @{} 'ObjectsCountDirect' = 0 'ObjectsCountIndirect' = 0 'ObjectsCountTotal' = 0 'Level' = 'Child' 'RootLevel' = $TopOU.Name 'Domain' = $Domain } } } $ObjectsInOu = Get-ADObject -LDAPFilter "(|(ObjectClass=user)(ObjectClass=contact)(ObjectClass=computer)(ObjectClass=group)(objectClass=inetOrgPerson)(ObjectClass=PrintQueue))" -SearchBase $TopOU.distinguishedName -Server $ForestInformation['QueryServers'][$Domain]['hostname'][0] Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain) / $($TOPOU.DistinguishedName) [$CountTop/$($TopOrganizationalUnits.Count)], found $($ObjectsInOu.Count) objects to process." foreach ($Object in $ObjectsInOu) { $Place = ConvertFrom-DistinguishedName -ToOrganizationalUnit -DistinguishedName $Object.DistinguishedName [Array] $AllOUs = ConvertFrom-DistinguishedName -ToMultipleOrganizationalUnit -IncludeParent -DistinguishedName $Place foreach ($OU in $AllOUs) { if (-not $CachedOu[$OU]) { Write-Warning "Get-GPOZaurrOrganizationalUnit - Object $($Object.DistinguishedName) is in OU $($OU) but it's not in cache. This should not happen. Please report this issue." Write-Warning "Get-GPOZaurrOrganizationalUnit - Debug information: Place: $($Place), AllOUs: $($AllOUs.Count)" continue } if ($OU -eq $Place) { $CachedOu[$OU]['Objects'][$Object.DistinguishedName] = $Object $CachedOu[$OU]['ObjectsClasses'][$Object.ObjectClass] = '' $CachedOu[$OU]['ObjectsCountDirect']++ } else { $CachedOu[$OU]['ObjectsClasses'][$Object.ObjectClass] = '' $CachedOu[$OU]['ObjectsCountIndirect']++ } $CachedOu[$OU]['ObjectsCountTotal']++ } } } } foreach ($OU in $CachedOu.Keys) { $ObjectClasses = [string[]] $CachedOu[$OU]['ObjectsClasses'].Keys if ($CachedOu[$OU]['ObjectsCountTotal'] -eq 0 -and $CachedOu[$OU]['LinkedGroupPolicyObjects'].Count -gt 0) { $Status = "Unlink GPO", 'Delete OU' } elseif ($CachedOu[$OU]['ObjectsCountTotal'] -eq 0 -and $CachedOu[$OU]['LinkedGroupPolicyObjects'].Count -eq 0) { $Status = 'Delete OU' } elseif ($CachedOU[$Ou]['ObjectsCountTotal'] -gt 0 -and $CachedOu[$OU]['LinkedGroupPolicyObjects'].Count -gt 0 -and $ObjectClasses -notcontains 'User' -and $ObjectClasses -notcontains 'Computer' ) { $Status = "Unlink GPO" } else { $Status = 'OK' } if ($Option) { $Found = $false if ($Option -contains 'Ok' -and $Status -contains 'OK') { $Found = $true } elseif ($Option -contains 'Unlink' -and $Status -contains 'Unlink GPO') { $Found = $true } elseif ($Option -contains 'Delete' -and $Status -contains 'Delete OU') { $Found = $true } if ($ExcludeOrganizationalUnit) { foreach ($ExcludedOU in $ExcludeOrganizationalUnit) { if ($OU -like $ExcludedOU) { $Found = $false break } } } foreach ($Exclude in $DefaultFolders) { if ($OU -eq "$Exclude") { $Found = $false break } } if (-not $Found) { continue } } else { if ($ExcludeOrganizationalUnit) { foreach ($ExcludedOU in $ExcludeOrganizationalUnit) { if ($OU -like $ExcludedOU) { $Status = 'Excluded' break } } } foreach ($Exclude in $DefaultFolders) { if ($OU -eq "$Exclude") { $Status = 'Excluded, Default OU' break } } } [PSCustomObject] @{ Organizationalunit = $OU Level = $CachedOu[$OU]['Level'] RootLevel = $CachedOu[$OU]['RootLevel'] DomainName = $CachedOu[$OU]['Domain'] Status = $Status GPOCount = $CachedOu[$OU]['LinkedGroupPolicyObjects'].Count ObjectCountDirect = $CachedOu[$OU]['ObjectsCountDirect'] ObjectCountIndirect = $CachedOu[$OU]['ObjectsCountIndirect'] ObjectCountTotal = $CachedOu[$OU]['ObjectsCountTotal'] ObjectClasses = $ObjectClasses GPONames = $CachedOu[$OU]['LinkedGroupPolicy'].DisplayName Objects = $CachedOu[$OU]['Objects'].Values.Name GPO = $CachedOu[$OU]['LinkedGroupPolicy'] } } } function Get-GPOZaurrOwner { <# .SYNOPSIS Gets owners of GPOs from Active Directory and SYSVOL .DESCRIPTION Gets owners of GPOs from Active Directory and SYSVOL .PARAMETER GPOName Name of GPO. By default all GPOs are returned .PARAMETER GPOGuid GUID of GPO. By default all GPOs are returned .PARAMETER IncludeSysvol Includes Owner from SYSVOL as well .PARAMETER SkipBroken Doesn't display GPOs that have no SYSVOL content (orphaned GPOs) .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER ADAdministrativeGroups Ability to provide AD Administrative Groups from another command to speed up processing .PARAMETER ApprovedOwner Ability to provide different owner (non administrative that still is approved for use) .EXAMPLE Get-GPOZaurrOwner -Verbose -IncludeSysvol .EXAMPLE Get-GPOZaurrOwner -Verbose -IncludeSysvol -SkipBroken .NOTES General notes #> [cmdletbinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'GPOName')][string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid, [switch] $IncludeSysvol, [switch] $SkipBroken, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [System.Collections.IDictionary] $ADAdministrativeGroups, [alias('Exclusion', 'Exclusions')][string[]] $ApprovedOwner ) Begin { $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } $Count = 0 } Process { $getGPOZaurrADSplat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ForestInformation } if ($GPOName) { $getGPOZaurrADSplat['GPOName'] = $GPOName } elseif ($GPOGuid) { $getGPOZaurrADSplat['GPOGUID'] = $GPOGuid } $Objects = Get-GPOZaurrAD @getGPOZaurrADSplat foreach ($_ in $Objects) { $Count++ Write-Verbose "Get-GPOZaurrOwner - Processing GPO [$Count/$($Objects.Count)]: $($_.DisplayName) from domain: $($_.DomainName)" $ACL = Get-ADACLOwner -ADObject $_.GPODistinguishedName -Resolve -Verbose:$false $Object = [ordered] @{ DisplayName = $_.DisplayName DomainName = $_.DomainName GUID = $_.GUID Status = [System.Collections.Generic.List[string]]::new() Owner = $ACL.OwnerName OwnerSid = $ACL.OwnerSid OwnerType = $ACL.OwnerType } if ($IncludeSysvol) { $FileOwner = Get-FileOwner -JustPath -Path $_.Path -Resolve -Verbose:$false $Object['SysvolOwner'] = $FileOwner.OwnerName $Object['SysvolSid'] = $FileOwner.OwnerSid $Object['SysvolType'] = $FileOwner.OwnerType $Object['SysvolPath'] = $_.Path $Object['IsOwnerConsistent'] = if ($ACL.OwnerName -eq $FileOwner.OwnerName) { $true } else { $false } $Object['IsOwnerAdministrative'] = if ($Object['SysvolType'] -eq 'Administrative' -and $Object['OwnerType'] -eq 'Administrative') { $true } else { $false } if (Test-Path -LiteralPath $Object['SysvolPath']) { $Object['SysvolExists'] = $true } else { $Object['SysvolExists'] = $false } } else { $Object['IsOwnerAdministrative'] = if ($Object['OwnerType'] -eq 'Administrative') { $true } else { $false } } if ($Object['IsOwnerAdministrative'] -eq $true) { $Object['Status'].Add('Administrative') } else { $Object['Status'].Add('NotAdministrative') } if ($Object['IsOwnerConsistent']) { $Object['Status'].Add('Consistent') } else { $Object['Status'].Add('Inconsistent') } if ($Object['IsOwnerConsistent'] -eq $true -and $Object['IsOwnerAdministrative'] -eq $false) { foreach ($Owner in $ApprovedOwner) { if ($Owner -eq $Object['Owner']) { $Object['Status'].Add('Approved') break } elseif ($Owner -eq $Object['OwnerSid']) { $Object['Status'].Add('Approved') break } } } if ($SkipBroken -and $Object['SysvolExists'] -eq $false) { continue } $Object['DistinguishedName'] = $_.GPODistinguishedName [PSCUstomObject] $Object } } End { } } function Get-GPOZaurrPassword { <# .SYNOPSIS Tries to find CPassword in Group Policies or given path and translate it to readable value .DESCRIPTION Tries to find CPassword in Group Policies or given path and translate it to readable value .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER GPOPath Path where Group Policy content is located or where backup is located .EXAMPLE Get-GPOZaurrPassword -GPOPath 'C:\Users\przemyslaw.klys\Desktop\GPOExport_2020.10.12' .EXAMPLE Get-GPOZaurrPassword .NOTES General notes #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string[]] $GPOPath ) if ($GPOPath) { foreach ($Path in $GPOPath) { $Items = Get-ChildItem -LiteralPath $Path -Recurse -Filter *.xml -ErrorAction SilentlyContinue -ErrorVariable err $Output = foreach ($XMLFileName in $Items) { $Password = Find-GPOPassword -Path $XMLFileName.FullName if ($Password) { if ($XMLFileName.FullName -match '{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}}') { $GPOGUID = $matches[0] } [PSCustomObject] @{ RootPath = $Path PasswordFile = $XMLFileName.FullName GUID = $GPOGUID Password = $Password } } } } } else { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $Path = -join ('\\', $Domain, '\SYSVOL\', $Domain, '\Policies') $Items = Get-ChildItem -LiteralPath $Path -Recurse -Filter *.xml -ErrorAction SilentlyContinue -ErrorVariable err $Output = foreach ($XMLFileName in $Items) { $Password = Find-GPOPassword -Path $XMLFileName.FullName if ($Password) { if ($XMLFileName.FullName -match '{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}}') { $GPOGUID = $matches[0] $GPO = Get-GPOZaurrAD -GPOGuid $GPOGUID -IncludeDomains $Domain [PSCustomObject] @{ DisplayName = $GPO.DisplayName DomainName = $GPO.DomainName GUID = $GPO.GUID PasswordFile = $XMLFileName.FullName Password = $Password Created = $GPO.Created Modified = $GPO.Modified Description = $GPO.Description } } else { [PSCustomObject] @{ DisplayName = '' DomainName = '' GUID = '' PasswordFile = $XMLFileName.FullName Password = $Password Created = '' Modified = '' Description = '' } } } } foreach ($e in $err) { Write-Warning "Get-GPOZaurrPassword - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } $Output } } } function Get-GPOZaurrPermission { <# .SYNOPSIS Retrieves permissions for a Group Policy Object (GPO) based on specified criteria. .DESCRIPTION This function retrieves permissions for a specified GPO based on various criteria such as GPO name, GUID, principal, permission type, etc. .PARAMETER GPOName Specifies the name of the GPO to retrieve permissions for. .PARAMETER GPOGuid Specifies the GUID of the GPO to retrieve permissions for. .PARAMETER Principal Specifies the principal for which permissions are to be retrieved. .PARAMETER PrincipalType Specifies the type of principal to be used for permission retrieval. Valid values are 'DistinguishedName', 'Name', 'NetbiosName', 'Sid'. .PARAMETER Type Specifies the type of permissions to include. Valid values are 'AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', 'All'. .PARAMETER SkipWellKnown Skips well-known permissions when retrieving permissions. .PARAMETER SkipAdministrative Skips administrative permissions when retrieving permissions. .PARAMETER IncludeOwner Includes the owner of the GPO in the permission retrieval. .PARAMETER IncludePermissionType Specifies the permission types to include in the retrieval. .PARAMETER ExcludePermissionType Specifies the permission types to exclude from the retrieval. .PARAMETER PermitType Specifies the type of permissions to permit. Valid values are 'Allow', 'Deny', 'All'. .PARAMETER ExcludePrincipal Specifies principals to exclude from the permission retrieval. .PARAMETER ExcludePrincipalType Specifies the type of principal to exclude. Valid values are 'DistinguishedName', 'Name', 'Sid'. .PARAMETER IncludeGPOObject Includes the GPO object in the permission retrieval. .PARAMETER Forest Specifies the forest to retrieve permissions from. .PARAMETER ExcludeDomains Specifies domains to exclude from permission retrieval. .PARAMETER IncludeDomains Specifies domains to include in permission retrieval. .PARAMETER ExtendedForestInformation Specifies additional forest information to include in the retrieval. .PARAMETER ADAdministrativeGroups Specifies the administrative groups to include in the retrieval. .PARAMETER ReturnSecurityWhenNoData If no data is found, returns all data. .PARAMETER ReturnSingleObject Forces the return of a single object per GPO for processing. .EXAMPLE Get-GPOZaurrPermission -GPOName "TestGPO" -Principal "Domain Admins" -PermitType "Allow" Retrieves permissions for the GPO named "TestGPO" for the principal "Domain Admins" with permission type "Allow". .EXAMPLE Get-GPOZaurrPermission -GPOGuid "12345678-1234-1234-1234-1234567890AB" -Type "Administrative" -PermitType "Deny" Retrieves administrative permissions for the GPO with GUID "12345678-1234-1234-1234-1234567890AB" with permission type "Deny". #> [cmdletBinding(DefaultParameterSetName = 'GPO' )] param( [Parameter(ParameterSetName = 'GPOName')] [string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID')] [alias('GUID', 'GPOID')][string] $GPOGuid, [string[]] $Principal, [validateset('DistinguishedName', 'Name', 'NetbiosName', 'Sid')][string] $PrincipalType = 'Sid', [validateSet('AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', 'All')][string[]] $Type = 'All', [switch] $SkipWellKnown, [switch] $SkipAdministrative, #[switch] $ResolveAccounts, [switch] $IncludeOwner, [Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType, [Microsoft.GroupPolicy.GPPermissionType[]] $ExcludePermissionType, [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'All', [string[]] $ExcludePrincipal, [validateset('DistinguishedName', 'Name', 'Sid')][string] $ExcludePrincipalType = 'Sid', [switch] $IncludeGPOObject, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [System.Collections.IDictionary] $ADAdministrativeGroups, [switch] $ReturnSecurityWhenNoData, # if no data return all data [switch] $ReturnSingleObject # forces return of single object per GPO as one for ForEach-Object processing ) Begin { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } if ($Type -eq 'Unknown') { if ($SkipAdministrative -or $SkipWellKnown) { Write-Warning "Get-GPOZaurrPermission - Using SkipAdministrative or SkipWellKnown while looking for Unknown doesn't make sense as only Unknown will be displayed." } } } Process { foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] if ($GPOName) { $getGPOSplat = @{ Name = $GPOName Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } $TextForError = "Error running Get-GPO (QueryServer: $QueryServer / Domain: $Domain / Name: $GPOName) with:" } elseif ($GPOGuid) { $getGPOSplat = @{ Guid = $GPOGuid Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } $TextForError = "Error running Get-GPO (QueryServer: $QueryServer / Domain: $Domain / GUID: $GPOGuid) with:" } else { $getGPOSplat = @{ All = $true Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } $TextForError = "Error running Get-GPO (QueryServer: $QueryServer / Domain: $Domain / All: $True) with:" } Try { Get-GPO @getGPOSplat | ForEach-Object -Process { $GPOSecurity = $_.GetSecurityInfo() $getPrivPermissionSplat = @{ Principal = $Principal PrincipalType = $PrincipalType PermitType = $PermitType Type = $Type GPO = $_ SkipWellKnown = $SkipWellKnown.IsPresent SkipAdministrative = $SkipAdministrative.IsPresent IncludeOwner = $IncludeOwner.IsPresent IncludeGPOObject = $IncludeGPOObject.IsPresent IncludePermissionType = $IncludePermissionType ExcludePermissionType = $ExcludePermissionType ExcludePrincipal = $ExcludePrincipal ExcludePrincipalType = $ExcludePrincipalType ADAdministrativeGroups = $ADAdministrativeGroups ExtendedForestInformation = $ForestInformation SecurityRights = $GPOSecurity } try { $Output = Get-PrivPermission @getPrivPermissionSplat } catch { $Output = $null Write-Warning "Get-GPOZaurrPermission - Error running Get-PrivPermission: $($_.Exception.Message)" } if (-not $Output) { if ($ReturnSecurityWhenNoData) { $ReturnObject = [PSCustomObject] @{ DisplayName = $_.DisplayName GUID = $_.ID DomainName = $_.DomainName Enabled = $_.GpoStatus Description = $_.Description CreationDate = $_.CreationTime ModificationTime = $_.ModificationTime GPOObject = $_ GPOSecurity = $GPOSecurity } $ReturnObject } } else { if ($ReturnSingleObject) { , $Output } else { $Output } } } } catch { Write-Warning "Get-GPOZaurrPermission - $TextForError $($_.Exception.Message)" } } } End { } } function Get-GPOZaurrPermissionAnalysis { <# .SYNOPSIS Analyzes permissions for Group Policy Objects (GPOs) and administrative groups. .DESCRIPTION This function analyzes permissions for Group Policy Objects (GPOs) and identifies administrative groups with specific permissions. .PARAMETER Forest Specifies the name of the forest to analyze. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the analysis. .PARAMETER IncludeDomains Specifies an array of domains to include in the analysis. .PARAMETER Permissions Specifies an array of permissions to analyze. .EXAMPLE Get-GPOZaurrPermissionAnalysis -Forest "ContosoForest" -IncludeDomains @("Domain1", "Domain2") -ExcludeDomains @("Domain3") -Permissions $PermissionsArray Analyzes permissions for GPOs in the "ContosoForest" forest, including "Domain1" and "Domain2" while excluding "Domain3", using the specified permissions array. #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [Array] $Permissions ) if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } if (-not $Permissions) { $Permissions = Get-GPOZaurrPermission -IncludePermissionType GpoEditDeleteModifySecurity, GpoApply, GpoCustom, GpoRead -ReturnSecurityWhenNoData -IncludeGPOObject -ReturnSingleObject -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } foreach ($GPO in $Permissions) { $AdministrativeExists = [ordered] @{ DisplayName = $GPO[0].DisplayName DomainName = $GPO[0].DomainName GUID = $GPO[0].GUID Status = $false Administrative = $false AuthenticatedUsers = $false System = $false Unknown = $false DomainAdmins = $false EnterpriseAdmins = $false AuthenticatedUsersPermission = $null DomainAdminsPermission = $null EnterpriseAdminsPermission = $null SystemPermission = $null UnknownPermission = $null } foreach ($Permission in $GPO) { if ($Permission.GPOSecurityPermissionItem) { $AdministrativeGroup = $ADAdministrativeGroups['BySID'][$Permission.PrincipalSid] if ($AdministrativeGroup) { $PermissionType = [Microsoft.GroupPolicy.GPPermissionType]::GpoEditDeleteModifySecurity, [Microsoft.GroupPolicy.GPPermissionType]::GpoCustom if ($Permission.Permission -in $PermissionType) { if ($AdministrativeGroup.SID -like '*-519') { $AdministrativeExists['EnterpriseAdmins'] = $true $AdministrativeExists['EnterpriseAdminsPermission'] = $Permission.Permission } elseif ($AdministrativeGroup.SID -like '*-512') { $AdministrativeExists['DomainAdmins'] = $true $AdministrativeExists['DomainAdminsPermission'] = $Permission.Permission } } else { if ($AdministrativeGroup.SID -like '*-519') { $AdministrativeExists['EnterpriseAdminsPermission'] = $Permission.Permission } elseif ($AdministrativeGroup.SID -like '*-512') { $AdministrativeExists['DomainAdminsPermission'] = $Permission.Permission } } } if ($AdministrativeExists['DomainAdmins'] -and $AdministrativeExists['EnterpriseAdmins']) { $AdministrativeExists['Administrative'] = $true } if ($Permission.PrincipalSid -eq 'S-1-5-11') { $PermissionType = [Microsoft.GroupPolicy.GPPermissionType]::GpoApply, [Microsoft.GroupPolicy.GPPermissionType]::GpoRead if ($Permission.Permission -in $PermissionType) { $AdministrativeExists['AuthenticatedUsers'] = $true } $AdministrativeExists['AuthenticatedUsersPermission'] = $Permission.Permission } if ($Permission.PrincipalSid -eq 'S-1-5-18') { $PermissionType = [Microsoft.GroupPolicy.GPPermissionType]::GpoEditDeleteModifySecurity if ($Permission.Permission -in $PermissionType) { $AdministrativeExists['System'] = $true } $AdministrativeExists['SystemPermission'] = $Permission.Permission } if ($Permission.PrincipalSidType -eq 'Unknown') { $AdministrativeExists['Unknown'] = $true $AdministrativeExists['UnknownPermission'] = $Permission.Permission } if ( $AdministrativeExists['System'] -eq $true -and $AdministrativeExists['AuthenticatedUsers'] -eq $true -and $AdministrativeExists['Administrative'] -eq $true -and $AdministrativeExists['Unknown'] -eq $false) { $AdministrativeExists['Status'] = $true } } } [PSCustomObject] $AdministrativeExists } } function Get-GPOZaurrPermissionConsistency { <# .SYNOPSIS Retrieves information about Group Policy Objects (GPOs) and checks permission consistency across domains. .DESCRIPTION The Get-GPOZaurrPermissionConsistency function retrieves information about GPOs and checks permission consistency across domains. It can filter by GPO name, GPO GUID, or type of consistency. It also provides options to include/exclude specific domains and verify inheritance. .PARAMETER GPOName Specifies the name of the GPO to retrieve. .PARAMETER GPOGuid Specifies the GUID of the GPO to retrieve. .PARAMETER Type Specifies the type of consistency to check. Valid values are 'Consistent', 'Inconsistent', or 'All'. .PARAMETER Forest Specifies the forest name to retrieve GPO information from. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the search. .PARAMETER IncludeDomains Specifies an array of domains to include in the search. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER IncludeGPOObject Indicates whether to include the GPO object in the output. .PARAMETER VerifyInheritance Indicates whether to verify inheritance of permissions. .EXAMPLE Get-GPOZaurrPermissionConsistency -GPOName "TestGPO" -Forest "Contoso" -IncludeDomains @("DomainA", "DomainB") -Type "Consistent" Retrieves permission consistency information for the GPO named "TestGPO" in the forest "Contoso" for domains "DomainA" and "DomainB" with consistent permissions. .EXAMPLE Get-GPOZaurrPermissionConsistency -GPOGuid "12345678-1234-1234-1234-1234567890AB" -Forest "Fabrikam" -Type "Inconsistent" -VerifyInheritance Retrieves permission consistency information for the GPO with GUID "12345678-1234-1234-1234-1234567890AB" in the forest "Fabrikam" for all domains with inconsistent permissions and verifies inheritance. #> [cmdletBinding(DefaultParameterSetName = 'Type')] param( [Parameter(ParameterSetName = 'GPOName')][string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid, [Parameter(ParameterSetName = 'Type')][validateSet('Consistent', 'Inconsistent', 'All')][string[]] $Type = 'All', [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $IncludeGPOObject, [switch] $VerifyInheritance ) Begin { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } Process { foreach ($Domain in $ForestInformation.Domains) { $TimeLog = Start-TimeLog Write-Verbose "Get-GPOZaurrPermissionConsistency - Starting process for $Domain" $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] if ($GPOName) { $getGPOSplat = @{ Name = $GPOName Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } } elseif ($GPOGuid) { $getGPOSplat = @{ Guid = $GPOGuid Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } } else { $getGPOSplat = @{ All = $true Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } } $GroupPolicies = Get-GPO @getGPOSplat $Count = 0 $GroupPolicies | ForEach-Object -Process { $GPO = $_ $Count++ Write-Verbose "Get-GPOZaurrPermissionConsistency - Processing [$($_.DomainName)]($Count/$($GroupPolicies.Count)) $($_.DisplayName)" $SysVolpath = -join ('\\', $Domain, '\sysvol\', $Domain, '\Policies\{', $GPO.ID.GUID, '}') if (Test-Path -LiteralPath $SysVolpath) { try { $IsConsistent = $GPO.IsAclConsistent() $ErrorMessage = '' } catch { $ErrorMessage = $_.Exception.Message Write-Warning "Get-GPOZaurrPermissionConsistency - Processing $($GPO.DisplayName) / $($GPO.DomainName) failed to get consistency with error: $($_.Exception.Message)." $IsConsistent = 'Not available' } } else { Write-Warning "Get-GPOZaurrPermissionConsistency - Processing $($GPO.DisplayName) / $($GPO.DomainName) failed as path $SysvolPath doesn't exists!" $IsConsistent = $false } if ($VerifyInheritance) { if ($IsConsistent -eq $true) { $FolderPermissions = Get-WinADSharePermission -Path $SysVolpath -Verbose:$false if ($FolderPermissions) { [Array] $NotInheritedPermissions = foreach ($File in $FolderPermissions) { if ($File.Path -ne $SysVolpath -and $File.IsInherited -eq $false) { $File } } if ($NotInheritedPermissions.Count -eq 0) { $ACLConsistentInside = $true } else { $ACLConsistentInside = $false } } else { $ACLConsistentInside = 'Not available' $NotInheritedPermissions = $null } } else { $ACLConsistentInside = $IsConsistent $NotInheritedPermissions = $null } } $Object = [ordered] @{ DisplayName = $_.DisplayName DomainName = $_.DomainName ACLConsistent = $IsConsistent } if ($VerifyInheritance) { $Object['ACLConsistentInside'] = $ACLConsistentInside } $Object['Owner'] = $_.Owner $Object['Path'] = $_.Path $Object['SysVolPath'] = $SysvolPath $Object['Id'] = $_.Id $Object['GpoStatus'] = $_.GpoStatus $Object['Description'] = $_.Description $Object['CreationTime'] = $_.CreationTime $Object['ModificationTime'] = $_.ModificationTime $Object['UserVersion'] = $_.UserVersion $Object['ComputerVersion'] = $_.ComputerVersion $Object['WmiFilter'] = $_.WmiFilter $Object['Error'] = $ErrorMessage if ($IncludeGPOObject) { $Object['IncludeGPOObject'] = $_ } if ($VerifyInheritance) { $Object['ACLConsistentInsideDetails'] = $NotInheritedPermissions } if ($Type -eq 'All') { [PSCustomObject] $Object } elseif ($Type -eq 'Inconsistent') { if ($VerifyInheritance) { if (-not ($IsConsistent -eq $true) -or (-not $ACLConsistentInside -eq $true)) { [PSCustomObject] $Object } } else { if (-not ($IsConsistent -eq $true)) { [PSCustomObject] $Object } } } elseif ($Type -eq 'Consistent') { if ($VerifyInheritance) { if ($IsConsistent -eq $true -and $ACLConsistentInside -eq $true) { [PSCustomObject] $Object } } else { if ($IsConsistent -eq $true) { [PSCustomObject] $Object } } } } $TimeEnd = Stop-TimeLog -Time $TimeLog -Option OneLiner Write-Verbose "Get-GPOZaurrPermissionConsistency - Finishing process for $Domain (Time to process: $TimeEnd)" } } End { } } function Get-GPOZaurrPermissionIssue { <# .SYNOPSIS Detects Group Policy missing Authenticated Users permission while not having higher permissions. .DESCRIPTION Detects Group Policy missing Authenticated Users permission while not having higher permissions. .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE $Issues = Get-GPOZaurrPermissionIssue $Issues | Format-Table .NOTES General notes #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended foreach ($Domain in $ForestInformation.Domains) { $TimeLog = Start-TimeLog Write-Verbose "Get-GPOZaurrPermissionIssue - Starting process for $Domain" $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer if ($SystemsContainer) { $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer) $Properties = 'DisplayName', 'Name', 'DistinguishedName', 'ObjectClass', 'WhenCreated', 'WhenChanged' $PoliciesInAD = Get-ADObject -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties $Properties foreach ($Policy in $PoliciesInAD) { $GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName $GUIDFromDN = $GUIDFromDN -replace '{' -replace '}' $GUID = $Policy.Name -replace '{' -replace '}' [PSCustomObject] @{ DisplayName = $Policy.DisplayName DomainName = $Domain PermissionIssue = -not ($GUID -and $GUIDFromDN) ObjectClass = $Policy.ObjectClass Name = $Policy.Name DistinguishedName = $Policy.DistinguishedName GUID = $GUIDFromDN WhenCreated = $Policy.WhenCreated WhenChanged = $Policy.WhenChanged } } } $TimeEnd = Stop-TimeLog -Time $TimeLog -Option OneLiner Write-Verbose "Get-GPOZaurrPermissionIssue - Finishing process for $Domain (Time to process: $TimeEnd)" } } function Get-GPOZaurrPermissionRoot { <# .SYNOPSIS Retrieves the root permissions of Group Policy Objects (GPOs) based on specified criteria. .DESCRIPTION Retrieves the root permissions of GPOs based on the specified criteria, including filtering by permission types, forest, domains, and more. .PARAMETER IncludePermissionType Specifies the root permission types to include in the search. .PARAMETER ExcludePermissionType Specifies the root permission types to exclude from the search. .PARAMETER Forest Specifies the target forest. By default, the current forest is used. .PARAMETER ExcludeDomains Specifies domains to exclude from the search. .PARAMETER IncludeDomains Specifies domains to include in the search. .PARAMETER ExtendedForestInformation Provides additional forest information to speed up processing. .PARAMETER SkipNames Skips processing names during the operation. .EXAMPLE Get-GPOZaurrPermissionRoot -IncludePermissionType 'GpoRootCreate' -ExcludePermissionType 'GpoRootOwner' -Forest 'ExampleForest' -IncludeDomains 'Domain1', 'Domain2' -ExtendedForestInformation $ForestInfo -SkipNames .EXAMPLE Get-GPOZaurrPermissionRoot -IncludePermissionType 'GpoRootOwner' -ExcludePermissionType 'GpoRootCreate' -Forest 'AnotherForest' -ExcludeDomains 'Domain3' -SkipNames .NOTES General notes #> [cmdletBinding()] param( [ValidateSet('GpoRootCreate', 'GpoRootOwner')][string[]] $IncludePermissionType, [ValidateSet('GpoRootCreate', 'GpoRootOwner')][string[]] $ExcludePermissionType, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $SkipNames ) Begin { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended } Process { foreach ($Domain in $ForestInformation.Domains) { $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain].DistinguishedName $QueryServer = $ForestInformation['QueryServers'][$Domain].HostName[0] $getADACLSplat = @{ ADObject = "CN=Policies,CN=System,$DomainDistinguishedName" IncludeActiveDirectoryRights = 'GenericAll', 'CreateChild', 'WriteOwner', 'WriteDACL' IncludeObjectTypeName = 'All', 'Group-Policy-Container' IncludeInheritedObjectTypeName = 'All', 'Group-Policy-Container' ADRightsAsArray = $true ResolveTypes = $true } $GPOPermissionsGlobal = Get-ADACL @getADACLSplat -Verbose:$false $GPOs = Get-ADObject -SearchBase "CN=Policies,CN=System,$DomainDistinguishedName" -SearchScope OneLevel -Filter * -Properties DisplayName -Server $QueryServer -Verbose:$false foreach ($Permission in $GPOPermissionsGlobal) { $CustomPermission = foreach ($_ in $Permission.ActiveDirectoryRights) { if ($_ -in 'WriteDACL', 'WriteOwner', 'GenericAll' ) { 'GpoRootOwner' } if ($_ -in 'CreateChild', 'GenericAll') { 'GpoRootCreate' } } $CustomPermission = $CustomPermission | Sort-Object -Unique foreach ($SinglePermission in $CustomPermission) { if ($SinglePermission -in $ExcludePermissionType) { continue } if ($IncludePermissionType.Count -gt 0 -and $SinglePermission -notin $IncludePermissionType) { continue } $OutputEntry = [ordered] @{ PrincipalName = $Permission.Principal Permission = $SinglePermission PermissionType = $Permission.AccessControlType PrincipalSidType = $Permission.PrincipalType PrincipalObjectClass = $Permission.PrincipalObjectType PrincipalDomainName = $Permission.PrincipalObjectDomain PrincipalSid = $Permission.PrincipalObjectSid DomainName = $Domain GPOCount = $GPOs.Count } if (-not $SkipNames) { $OutputEntry['GPONames'] = $GPOs.DisplayName } [PSCustomObject] $OutputEntry } } } } End { } } function Get-GPOZaurrPermissionSummary { <# .SYNOPSIS Retrieves a summary of Group Policy Object (GPO) permissions based on specified criteria. .DESCRIPTION Retrieves a summary of GPO permissions based on the specified criteria, including filtering by permission types, permit types, and more. .PARAMETER Type Specifies the type of permissions to include. Options include 'AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', and 'All'. .PARAMETER PermitType Specifies the type of permission to permit. Options include 'Allow', 'Deny', and 'All'. .PARAMETER IncludePermissionType Specifies the permission types to include in the summary. .PARAMETER ExcludePermissionType Specifies the permission types to exclude from the summary. .PARAMETER Forest Specifies the target forest. By default, the current forest is used. .PARAMETER ExcludeDomains Specifies domains to exclude from the search. .PARAMETER IncludeDomains Specifies domains to include in the search. .PARAMETER ExtendedForestInformation Provides additional forest information to speed up processing. .PARAMETER Separator Specifies the separator to use in the output. .EXAMPLE Get-GPOZaurrPermissionSummary -Type 'All' -PermitType 'Allow' -IncludePermissionType 'GpoApply', 'GpoEdit' -ExcludePermissionType 'GpoOwner' -Forest 'ExampleForest' -IncludeDomains 'Domain1', 'Domain2' -ExtendedForestInformation $ForestInfo -Separator '|' .EXAMPLE Get-GPOZaurrPermissionSummary -Type 'Administrative' -PermitType 'All' -IncludePermissionType 'GpoRead' -ExcludePermissionType 'GpoRootOwner' -Forest 'AnotherForest' -ExcludeDomains 'Domain3' -Separator ',' .NOTES General notes #> [cmdletBinding()] param( [validateSet('AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', 'All')][string[]] $Type = 'All', [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'All', [ValidateSet('GpoApply', 'GpoEdit', 'GpoCustom', 'GpoEditDeleteModifySecurity', 'GpoRead', 'GpoOwner', 'GpoRootCreate', 'GpoRootOwner')][string[]] $IncludePermissionType, [ValidateSet('GpoApply', 'GpoEdit', 'GpoCustom', 'GpoEditDeleteModifySecurity', 'GpoRead', 'GpoOwner', 'GpoRootCreate', 'GpoRootOwner')][string[]] $ExcludePermissionType, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string] $Separator ) $IncludePermTypes = [System.Collections.Generic.List[Microsoft.GroupPolicy.GPPermissionType]]::new() $ExcludePermTypes = [System.Collections.Generic.List[Microsoft.GroupPolicy.GPPermissionType]]::new() $CustomPermissions = [System.Collections.Generic.List[string]]::new() foreach ($PermType in $IncludePermissionType) { if ($PermType -in 'GpoApply', 'GpoEdit', 'GPOCustom', 'GpoEditDeleteModifySecurity', 'GPORead') { $IncludePermTypes.Add([Microsoft.GroupPolicy.GPPermissionType]::$PermType) } elseif ($PermType -in 'GpoOwner') { $IncludeOwner = $true } elseif ($PermType -in 'GpoRootCreate', 'GpoRootOwner') { $CustomPermissions.Add($PermType) } } foreach ($PermType in $ExcludePermissionType) { if ($PermType -in 'GpoApply', 'GpoEdit', 'GPOCustom', 'GpoEditDeleteModifySecurity', 'GPORead') { $ExcludePermTypes.Add([Microsoft.GroupPolicy.GPPermissionType]::$PermType) } elseif ($PermType -in 'GpoOwner') { $IncludeOwner = $false } elseif ($PermType -in 'GpoRootCreate', 'GpoRootOwner') { $CustomPermissions.Add($PermType) } } $RootPermissions = Get-GPOZaurrPermissionRoot -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $Permissions = Get-GPOZaurrPermission -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -IncludePermissionType $IncludePermTypes -ExcludePermissionType $ExcludePermissionType -Type $Type -PermitType $PermitType -IncludeOwner:$IncludeOwner $Entries = @( foreach ($Permission in $Permissions) { [PSCustomObject] @{ PrincipalName = $Permission.PrincipalName PrincipalDomainName = $Permission.PrincipalDomainName Permission = $Permission.Permission PermissionType = $Permission.PermissionType PrincipalSid = $Permission.PrincipalSid PrincipalSidType = $Permission.PrincipalSidType PrincipalObjectClass = $Permission.PrincipalObjectClass DisplayName = $Permission.DisplayName DomainName = $Permission.DomainName } } foreach ($RootPermission in $RootPermissions) { [PSCustomObject] @{ PrincipalName = $RootPermission.PrincipalName PrincipalDomainName = $RootPermission.PrincipalDomainName Permission = $RootPermission.Permission PermissionType = $RootPermission.PermissionType PrincipalSid = $RootPermission.PrincipalSid PrincipalSidType = $RootPermission.PrincipalSidType PrincipalObjectClass = $RootPermission.PrincipalObjectClass DisplayName = $RootPermission.GPONames DomainName = $RootPermission.DomainName } } ) $PermissionsData = [ordered] @{} foreach ($Entry in $Entries) { $Key = -join ($Entry.Permission, $Entry.PrincipalName, $Entry.PrincipalDomainName) if (-not $PermissionsData[$Key]) { $PermissionsData[$Key] = [PSCustomObject] @{ Permission = $Entry.Permission PrincipalName = $Entry.PrincipalName PrincipalDomainName = $Entry.PrincipalDomainName PrincipalSidType = $Entry.PrincipalSidType DomainName = $Entry.DomainName PermissionType = $Entry.PermissionType GPOCOunt = 0 GPONames = [System.Collections.Generic.List[string]]::new() } } $PermissionsData[$Key].GPONames.Add($Entry.DisplayName) $PermissionsData[$Key].GPOCOunt = $PermissionsData[$Key].GPOCOunt + ($Entry.DisplayName).Count } $PermissionsData.Values } function Get-GPOZaurrRedirect { <# .SYNOPSIS Command to detect if GPOs have correct path in SYSVOL, or someone changed it manually. .DESCRIPTION Command to detect if GPOs have correct path in SYSVOL, or someone changed it manually. .PARAMETER GPOName Provide GPO name to search for. By default command returns all GPOs .PARAMETER GPOGuid Provide GPO GUID to search for. By default command returns all GPOs .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER DateFrom Provide a date from which to start the search, by default the last X days are used .PARAMETER DateTo Provide a date to which to end the search, by default the last X days are used .PARAMETER DateRange Provide a date range to search for, by default the last X days are used .PARAMETER DateProperty Choose a date property. It can be WhenCreated or WhenChanged or both. By default whenCreated is used for comparison purposes .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE Get-GPOZaurrRedirect | Format-Table .NOTES General notes #> [cmdletbinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'GPOName')] [string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID')] [alias('GUID', 'GPOID')][string] $GPOGuid, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [DateTime] $DateFrom, [DateTime] $DateTo, [ValidateSet('PastHour', 'CurrentHour', 'PastDay', 'CurrentDay', 'PastMonth', 'CurrentMonth', 'PastQuarter', 'CurrentQuarter', 'Last14Days', 'Last21Days', 'Last30Days', 'Last7Days', 'Last3Days', 'Last1Days')][string] $DateRange, [ValidateSet('WhenCreated', 'WhenChanged')][string[]] $DateProperty = 'WhenCreated', [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } Process { foreach ($Domain in $ForestInformation.Domains) { if ($PSCmdlet.ParameterSetName -eq 'GPOGUID') { if ($GPOGuid) { if ($GPOGUID -notlike '*{*') { $GUID = -join ("{", $GPOGUID, '}') } else { $GUID = $GPOGUID } $Splat = @{ Filter = "(objectClass -eq 'groupPolicyContainer') -and (Name -eq '$GUID')" Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } } else { Write-Warning "Get-GPOZaurrRedirect - GPOGUID parameter is empty. Provide name and try again." continue } } elseif ($PSCmdlet.ParameterSetName -eq 'GPOName') { if ($GPOName) { $Splat = @{ Filter = "(objectClass -eq 'groupPolicyContainer') -and (DisplayName -eq '$GPOName')" Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } } else { Write-Warning "Get-GPOZaurrRedirect - GPOName parameter is empty. Provide name and try again." continue } } else { $Splat = @{ Filter = "(objectClass -eq 'groupPolicyContainer')" Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] } } if ($PSBoundParameters.ContainsKey('DateRange')) { $Dates = Get-ChoosenDates -DateRange $DateRange $DateFrom = $($Dates.DateFrom) $DateTo = $($Dates.DateTo) if ($DateProperty -contains 'WhenChanged' -and $DateProperty -contains 'WhenCreated') { $Splat['Filter'] = -join ($Splat['Filter'], ' -and ((WhenChanged -ge $DateFrom -and WhenChanged -le $DateTo) -or (WhenCreated -ge $DateFrom -and WhenCreated -le $DateTo))') } elseif ($DateProperty -eq 'WhenChanged' -or $DateProperty -eq 'WhenCreated') { $Property = $DateProperty[0] $Splat['Filter'] = -join ($Splat['Filter'], ' -and ($Property -ge $DateFrom -and $Property -le $DateTo)') } else { Write-Warning -Message "Get-GPOZaurrRedirect - DateProperty parameter is empty. Provide name and try again." continue } } elseif ($PSBoundParameters.ContainsKey('DateFrom') -and $PSBoundParameters.ContainsKey('DateTo')) { if ($DateProperty -contains 'WhenChanged' -and $DateProperty -contains 'WhenCreated') { $Splat['Filter'] = -join ($Splat['Filter'], ' -and ((WhenChanged -ge $DateFrom -and WhenChanged -le $DateTo) -or (WhenCreated -ge $DateFrom -and WhenCreated -le $DateTo))') } elseif ($DateProperty -eq 'WhenChanged' -or $DateProperty -eq 'WhenCreated') { $Property = $DateProperty[0] $Splat['Filter'] = -join ($Splat['Filter'], ' -and ($Property -ge $DateFrom -and $Property -le $DateTo)') } else { Write-Warning -Message "Get-GPOZaurrRedirect - DateProperty parameter is empty. Provide name and try again." continue } } else { } Write-Verbose -Message "Get-GPOZaurrRedirect - Searching domain $Domain with filter $($Splat['Filter'])" $Objects = Get-ADObject @Splat -Properties DisplayName, Name, Created, Modified, ntSecurityDescriptor, gPCFileSysPath, gPCFunctionalityVersion, gPCWQLFilter, gPCMachineExtensionNames, Description, CanonicalName, DistinguishedName foreach ($Object in $Objects) { $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Object.DistinguishedName -ToDomainCN $GUID = $Object.Name -replace '{' -replace '}' if (($GUID).Length -ne 36) { Write-Warning "Get-GPOZaurrRedirect - GPO GUID ($($($GUID.Replace("`n",' ')))) is incorrect. Skipping $($Object.DisplayName) / Domain: $($DomainCN)" } else { $Path = $Object.gPCFileSysPath $ExpectedPath = "\\$($DomainCN)\SYSVOL\$($DomainCN)\Policies\{$($GUID)}" $Compare = if ($Path -eq $ExpectedPath) { $true } else { $false } [PSCustomObject]@{ 'DisplayName' = $Object.DisplayName 'DomainName' = $DomainCN 'Description' = $Object.Description 'IsCorrect' = $Compare 'GUID' = $GUID 'Path' = $Path 'ExpectedPath' = $ExpectedPath 'Created' = $Object.Created 'Modified' = $Object.Modified 'Owner' = $Object.ntSecurityDescriptor.Owner 'GPOCanonicalName' = $Object.CanonicalName 'GPODomainDistinguishedName' = ConvertFrom-DistinguishedName -DistinguishedName $Object.DistinguishedName -ToDC 'GPODistinguishedName' = $Object.DistinguishedName } } } } } End { } } function Get-GPOZaurrSysvolDFSR { <# .SYNOPSIS Gets DFSR information from the SYSVOL DFSR .DESCRIPTION Gets DFSR information from the SYSVOL DFSR .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExcludeDomainControllers Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER IncludeDomainControllers Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER SkipRODC Skip Read-Only Domain Controllers. By default all domain controllers are included. .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER SearchDFSR Define DFSR Share. By default it uses SYSVOL Share .EXAMPLE $DFSR = Get-GPOZaurrSysvolDFSR $DFSR | Format-Table * .NOTES General notes #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation, [string] $SearchDFSR = 'SYSVOL Share' ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-GPOZaurrSysvolDFSR - Processing $Domain" foreach ($DC in $ForestInformation.DomainDomainControllers[$Domain]) { Write-Verbose "Get-GPOZaurrSysvolDFSR - Processing $Domain \ $($DC.HostName)" $DFSRConfig = Get-CimInstance -Namespace 'root\microsoftdfs' -Class 'dfsrreplicatedfolderconfig' -ComputerName $($DC.HostName) | Where-Object { $_.ReplicatedFolderName -eq $SearchDFSR } $DFSR = Get-CimInstance -Namespace 'root\microsoftdfs' -Class 'dfsrreplicatedfolderinfo' -ComputerName $($DC.HostName) | Where-Object { $_.ReplicatedFolderName -eq $SearchDFSR } if ($DFSR -and $DFSRConfig -and ($DFSR.ReplicatedFolderGuid -eq $DFSRConfig.ReplicatedFolderGuid)) { [PSCustomObject] @{ ComputerName = $DFSR.PSComputerName DomainName = $Domain ConflictPath = $DFSRConfig.ConflictPath LastConflictCleanupTime = $DFSR.LastConflictCleanupTime CurrentConflictSizeInMb = $DFSR.CurrentConflictSizeInMb MaximumConflictSizeInMb = $DFSRConfig.ConflictSizeInMb LastErrorCode = $DFSR.LastErrorCode LastErrorMessageId = $DFSR.LastErrorMessageId LastTombstoneCleanupTime = $DFSR.LastTombstoneCleanupTime ReplicatedFolderGuid = $DFSR.ReplicatedFolderGuid DFSRConfig = $DFSRConfig DFSR = $DFSR } } else { Write-Warning "Get-GPOZaurrSysvolDFSR - Couldn't process $($DC.HostName). Conditions not met." } } } } function Get-GPOZaurrUpdates { <# .SYNOPSIS Gets the list of GPOs created or updated in the last X number of days. .DESCRIPTION Gets the list of GPOs created or updated in the last X number of days. .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER DateFrom Provide a date from which to start the search, by default the last X days are used .PARAMETER DateTo Provide a date to which to end the search, by default the last X days are used .PARAMETER DateRange Provide a date range to search for, by default the last X days are used .PARAMETER DateProperty Choose a date property. It can be WhenCreated or WhenChanged or both. By default whenCreated is used for comparison purposes .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE Get-GPOZaurrUpdates -DateRange Last14Days -DateProperty WhenCreated, WhenChanged -Verbose -IncludeDomains 'ad.evotec.pl' | Format-List .EXAMPLE Get-GPOZaurrUpdates -DateRange Last14Days -DateProperty WhenCreated -Verbose | Format-Table .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'DateRange')] param( [parameter(ParameterSetName = 'Dates')] [parameter(ParameterSetName = 'DateRange')] [alias('ForestName')][string] $Forest, [parameter(ParameterSetName = 'Dates')] [parameter(ParameterSetName = 'DateRange')] [string[]] $ExcludeDomains, [parameter(ParameterSetName = 'Dates')] [parameter(ParameterSetName = 'DateRange')] [alias('Domain', 'Domains')][string[]] $IncludeDomains, [parameter(Mandatory, ParameterSetName = 'Dates')][DateTime] $DateFrom, [parameter(Mandatory, ParameterSetName = 'Dates')][DateTime] $DateTo, [parameter(Mandatory, ParameterSetName = 'DateRange')][ValidateSet('PastHour', 'CurrentHour', 'PastDay', 'CurrentDay', 'PastMonth', 'CurrentMonth', 'PastQuarter', 'CurrentQuarter', 'Last14Days', 'Last21Days', 'Last30Days', 'Last7Days', 'Last3Days', 'Last1Days')][string] $DateRange, [parameter(ParameterSetName = 'Dates')] [parameter(ParameterSetName = 'DateRange')] [ValidateSet('WhenCreated', 'WhenChanged')][string[]] $DateProperty = 'WhenCreated', [parameter(ParameterSetName = 'Dates')] [parameter(ParameterSetName = 'DateRange')] [System.Collections.IDictionary] $ExtendedForestInformation ) $getGPOZaurrADSplat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation DateFrom = $DateFrom DateTo = $DateTo DateRange = $DateRange DateProperty = $DateProperty } Remove-EmptyValue -Hashtable $getGPOZaurrADSplat if ($DateRange) { Write-Verbose -Message "Get-GPOZaurrUpdates - Get group policies for defined range $DateRange" } elseif ($DateFrom -and $DateTo) { Write-Verbose -Message "Get-GPOZaurrUpdates - Get group policies for defined range $DateFrom to $DateTo" } else { Write-Warning -Message "Get-GPOZaurrUpdates - No range is selected. Try again." return } $LinksSummaryCache = Get-GPOZaurrLink -AsHashTable -Summary -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $OUCache = [ordered] @{} foreach ($Domain in $ForestInformation.Domains) { Write-Verbose -Message "Get-GPOZaurrUpdates - Getting OU's for $Domain" $OrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties gpOptions, canonicalName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0] $OUCache[$OrganizationalUnits.DistinguishedName] = if ($OrganizationalUnits.gpOptions -eq 1) { $true } else { $false } } $CurrentCount = 0 Write-Verbose -Message "Get-GPOZaurrUpdates - Getting GPO information" [Array] $GPOs = Get-GPOZaurrAD @getGPOZaurrADSplat foreach ($GPO in $GPOs) { $CurrentCount++ Write-Verbose -Message "Get-GPOZaurrUpdates - Processing $($GPO.DisplayName) / $($GPO.DomainName) [$CurrentCount/$($GPOs.Count)]" $GPOLinkData = $LinksSummaryCache["$($GPO.DomainName)$($GPO.GUID)"] [Array] $LinksDN = if ($GPOLinkData.Links.Count -gt 0) { foreach ($Link in $GPOLinkData.LinksObjects) { If ($Link.Enabled -eq $true) { $Link.DistinguishedName } } } if ($LinksDN.Count -gt 0) { $OrganizationalUnitsObjects = Get-ADOrganizationalUnitObject -OrganizationalUnit $LinksDN -Summary -IncludeAffectedOnly } else { $OrganizationalUnitsObjects = [PSCUstomObject] @{ ObjectsTotalCount = 0 ObjectsBlockedInheritanceCount = 0 ObjectsClasses = @() } } if ($GPO.Owner) { $Owner = Get-WinADObject -Identity $GPO.Owner -AddType } else { $Owner = [PSCustomObject] @{ Name = 'Unknown' Type = 'Unknown' ObjectClass = 'Unknown' } } [PSCustomObject] @{ DisplayName = $GPO.DisplayName GUID = ConvertFrom-DistinguishedName -DistinguishedName $GPO.GPODistinguishedName DomainName = $GPO.DomainName Owner = $GPO.Owner OwnerName = $Owner.Name OwnerType = $Owner.Type OwnerClass = $Owner.ObjectClass LinksCount = if ($GPOLinkData) { $GPOLinkData.LinksCount } else { 0 } LinksEnabledCount = if ($GPOLinkData) { $GPOLinkData.LinksEnabledCount } else { 0 } AffectedCount = $OrganizationalUnitsObjects.ObjectsTotalCount BlockedInheritanceCount = $OrganizationalUnitsObjects.ObjectsBlockedInheritanceCount AffectedClasses = $OrganizationalUnitsObjects.ObjectsClasses.GetEnumerator().Name Created = $GPO.Created Changed = $GPO.Modified LinksEnabled = $LinksDN } } } function Get-GPOZaurrWMI { <# .SYNOPSIS Get Group Policy WMI filter .DESCRIPTION Get Group Policy WMI filter .PARAMETER Guid Search for specific filter using GUID .PARAMETER Name Search for specific filter using Name .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER AsHashtable Return output as hashtable .EXAMPLE Get-GPOZaurrWMI -AsHashtable .EXAMPLE Get-GPOZaurrWMI | Format-Table .NOTES General notes #> [cmdletBinding()] Param( [Guid[]] $Guid, [string[]] $Name, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $AsHashtable ) $Dictionary = [ordered] @{} $wmiFilterAttr = 'msWMI-Name', 'msWMI-Parm1', 'msWMI-Parm2', 'msWMI-Author', 'msWMI-ID', 'CanonicalName', 'Created', 'Modified' $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $Objects = @( if ($Name) { foreach ($N in $Name) { try { $ldapFilter = "(&(objectClass=msWMI-Som)(msWMI-Name=$N))" Get-ADObject -LDAPFilter $ldapFilter -Properties $wmiFilterAttr -Server $QueryServer } catch { Write-Warning "Get-GPOZaurrWMI - Error processing WMI for $Domain`: $($_.Error.Exception)" } } } elseif ($GUID) { foreach ($G in $GUID) { try { $ldapFilter = "(&(objectClass=msWMI-Som)(Name={$G}))" Get-ADObject -LDAPFilter $ldapFilter -Properties $wmiFilterAttr -Server $QueryServer } catch { Write-Warning "Get-GPOZaurrWMI - Error processing WMI for $Domain`: $($_.Error.Exception)" } } } else { try { $ldapFilter = '(objectClass=msWMI-Som)' Get-ADObject -LDAPFilter $ldapFilter -Properties $wmiFilterAttr -Server $QueryServer } catch { Write-Warning "Get-GPOZaurrWMI - Error processing WMI for $Domain`: $($_.Error.Exception)" } } ) foreach ($_ in $Objects) { $WMI = $_.'msWMI-Parm2' -split ';' [Array] $Data = for ($i = 0; $i -lt $WMI.length; $i += 6) { if ($WMI[$i + 5]) { -join ($WMI[$i + 5], ';' , $WMI[$i + 6]) } } $WMIObject = [PSCustomObject] @{ DisplayName = $_.'msWMI-Name' Description = $_.'msWMI-Parm1' DomainName = $Domain QueryCount = $Data.Count Query = $Data -join ',' Author = $_.'msWMI-Author' ID = $_.'msWMI-ID' Created = $_.Created Modified = $_.Modified ObjectGUID = $_.'ObjectGUID' CanonicalName = $_.CanonicalName DistinguishedName = $_.'DistinguishedName' } if (-not $AsHashtable) { $WMIObject } else { $Dictionary[$WMIObject.ID] = $WMIObject } } } if ($AsHashtable) { $Dictionary } } function Invoke-GPOZaurr { <# .SYNOPSIS Single cmdlet that provides 360 degree overview of Group Policies in Active Directory Forest. .DESCRIPTION Single cmdlet that provides 360 degree overview of Group Policies in Active Directory Forest with ability to pick reports and export to HTML. .PARAMETER Exclusions Allows to mark as excluded some Group Policies or Organizational Units depending on type. Can be a scriptblock or array depending on supported way by underlying report. Not every report support exclusions. Not every report support exclusions the same way. Exclusions should be used only if there is single report being asked for. .PARAMETER FilePath Path to the file where the report will be saved. .PARAMETER Type Type of report to be generated from a list of available reports. .PARAMETER PassThru Returns created objects after the report is done .PARAMETER HideHTML Do not auto open HTML report in default browser .PARAMETER HideSteps Do not show steps in report .PARAMETER ShowError Show errors in HTML report. Useful in case the report is being run as Scheduled Task .PARAMETER ShowWarning Show warnings in HTML report. Useful in case the report is being run as Scheduled Task .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER Online Forces report to use online resources in HTML (using CDN most of the time), by default it is run offline, and inlines all CSS/JS code. .PARAMETER SplitReports Split report into multiple files, one for each report. This can be useful for large domains with huge reports. .EXAMPLE Invoke-GPOZaurr .EXAMPLE Invoke-GPOZaurr -Type GPOOrganizationalUnit -Online -FilePath $PSScriptRoot\Reports\GPOZaurrOU.html -Exclusions @( '*OU=Production,DC=ad,DC=evotec,DC=pl' ) .NOTES General notes #> [alias('Show-GPOZaurr', 'Show-GPO')] [cmdletBinding()] param( [alias('ExcludeGroupPolicies', 'ExclusionsCode')][Parameter(Position = 1)][object] $Exclusions, [string] $FilePath, [Parameter(Position = 0)][string[]] $Type, [switch] $PassThru, [switch] $HideHTML, [switch] $HideSteps, [switch] $ShowError, [switch] $ShowWarning, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $Online, [switch] $SplitReports, [Alias('Name')][string[]] $GPOName, [Alias('GUID')][string[]] $GPOGUID ) Reset-GPOZaurrStatus $Script:Reporting = [ordered] @{} $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Invoke-GPOZaurr' -RepositoryOwner 'evotecit' -RepositoryName 'GPOZaurr' $Script:Reporting['Settings'] = @{ ShowError = $ShowError.IsPresent ShowWarning = $ShowWarning.IsPresent HideSteps = $HideSteps.IsPresent } Write-Color '[i]', "[GPOZaurr] ", 'Version', ' [Informative] ', $Script:Reporting['Version'] -Color Yellow, DarkGray, Yellow, DarkGray, Magenta $Supported = [System.Collections.Generic.List[string]]::new() [Array] $NotSupported = foreach ($T in $Type) { if ($T -notin $Script:GPOConfiguration.Keys ) { $T } else { $Supported.Add($T) } } if ($Supported) { Write-Color '[i]', "[GPOZaurr] ", 'Supported types', ' [Informative] ', "Chosen by user: ", ($Supported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta } if ($NotSupported) { Write-Color '[i]', "[GPOZaurr] ", 'Not supported types', ' [Informative] ', "Following types are not supported: ", ($NotSupported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta Write-Color '[i]', "[GPOZaurr] ", 'Not supported types', ' [Informative] ', "Please use one/multiple from the list: ", ($Script:GPOConfiguration.Keys -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta return } $DisplayForest = if ($Forest) { $Forest } else { 'Not defined. Using current one' } $DisplayIncludedDomains = if ($IncludeDomains) { $IncludeDomains -join "," } else { 'Not defined. Using all domains of forest' } $DisplayExcludedDomains = if ($ExcludeDomains) { $ExcludeDomains -join ',' } else { 'No exclusions provided' } Write-Color '[i]', "[GPOZaurr] ", 'Domain Information', ' [Informative] ', "Forest: ", $DisplayForest -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta Write-Color '[i]', "[GPOZaurr] ", 'Domain Information', ' [Informative] ', "Included Domains: ", $DisplayIncludedDomains -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta Write-Color '[i]', "[GPOZaurr] ", 'Domain Information', ' [Informative] ', "Excluded Domains: ", $DisplayExcludedDomains -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta if ($Type) { foreach ($T in $Script:GPOConfiguration.Keys) { $Script:GPOConfiguration[$T].Enabled = $false } foreach ($T in $Type) { $Script:GPOConfiguration[$T].Enabled = $true } } foreach ($T in $Script:GPOConfiguration.Keys) { if ($Script:GPOConfiguration[$T].Enabled -eq $true) { $Script:Reporting[$T] = [ordered] @{ Name = $Script:GPOConfiguration[$T].Name ActionRequired = $null Data = $null Exclusions = $null WarningsAndErrors = $null Time = $null Summary = $null Variables = Copy-Dictionary -Dictionary $Script:GPOConfiguration[$T]['Variables'] } if ($Exclusions) { if ($Exclusions -is [scriptblock]) { $Script:Reporting[$T]['Exclusions'] = $Exclusions } if ($Exclusions -is [Array]) { $Script:Reporting[$T]['Exclusions'] = $Exclusions } } $TimeLogGPOList = Start-TimeLog Write-Color -Text '[i]', '[Start] ', $($Script:GPOConfiguration[$T]['Name']) -Color Yellow, DarkGray, Yellow $OutputCommand = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Execute'] -WarningVariable CommandWarnings -ErrorVariable CommandErrors -ArgumentList $Forest, $ExcludeDomains, $IncludeDomains if ($OutputCommand -is [System.Collections.IDictionary]) { $Script:Reporting[$T]['Data'] = $OutputCommand } else { $Script:Reporting[$T]['Data'] = [Array] $OutputCommand } Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Processing'] $Script:Reporting[$T]['WarningsAndErrors'] = @( if ($ShowWarning) { foreach ($War in $CommandWarnings) { [PSCustomObject] @{ Type = 'Warning' Comment = $War Reason = '' TargetName = '' } } } if ($ShowError) { foreach ($Err in $CommandErrors) { [PSCustomObject] @{ Type = 'Error' Comment = $Err Reason = $Err.CategoryInfo.Reason TargetName = $Err.CategoryInfo.TargetName } } } ) $TimeEndGPOList = Stop-TimeLog -Time $TimeLogGPOList -Option OneLiner $Script:Reporting[$T]['Time'] = $TimeEndGPOList Write-Color -Text '[i]', '[End ] ', $($Script:GPOConfiguration[$T]['Name']), " [Time to execute: $TimeEndGPOList]" -Color Yellow, DarkGray, Yellow, DarkGray if ($SplitReports) { $TimeLogHTML = Start-TimeLog New-HTMLReportWithSplit -FilePath $FilePath -Online:$Online -HideHTML:$HideHTML -CurrentReport $T $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report', " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray } } } if (-not $SplitReports) { $TimeLogHTML = Start-TimeLog if (-not $FilePath) { $FilePath = Get-FileName -Extension 'html' -Temporary } New-HTMLReportAll -FilePath $FilePath -Online:$Online -HideHTML:$HideHTML -Type $Type $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report', " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray } if ($PassThru) { $Script:Reporting } Reset-GPOZaurrStatus } [scriptblock] $SourcesAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Script:GPOConfiguration.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } } Register-ArgumentCompleter -CommandName Invoke-GPOZaurr -ParameterName Type -ScriptBlock $SourcesAutoCompleter function Invoke-GPOZaurrContent { <# .SYNOPSIS Invokes GPOZaurrContent function to retrieve Group Policy Objects information. .DESCRIPTION This function retrieves Group Policy Objects information based on the specified parameters. It can search for GPOs in a forest, exclude specific domains, include specific domains, and provide extended forest information. .PARAMETER Forest Specifies the forest name to search for Group Policy Objects. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the search. .PARAMETER IncludeDomains Specifies an array of domains to include in the search. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER GPOPath Specifies the path to a specific Group Policy Object. .PARAMETER Type Specifies the type of information to retrieve. .PARAMETER Splitter Specifies the delimiter to use for splitting information. .PARAMETER FullObjects Indicates whether to retrieve full objects. .PARAMETER OutputType Specifies the type of output (HTML or Object). .PARAMETER OutputPath Specifies the path to save the output. .PARAMETER Open Indicates whether to open the output after retrieval. .PARAMETER Online Indicates whether to retrieve information online. .PARAMETER CategoriesOnly Indicates whether to retrieve only categories. .PARAMETER SingleObject Indicates whether to retrieve a single object. .PARAMETER SkipNormalize Indicates whether to skip normalization. .EXAMPLE Invoke-GPOZaurrContent -Forest "Contoso" -IncludeDomains "Domain1", "Domain2" -Type "Security" -OutputType "HTML" -OutputPath "C:\Reports\GPOReport.html" Retrieves security-related Group Policy Objects information for the specified domains and saves the output as an HTML file. .EXAMPLE Invoke-GPOZaurrContent -GPOPath "CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=Contoso,DC=com" -Type "All" -OutputType "Object" Retrieves all information for a specific Group Policy Object and outputs the result as an object. #> [alias('Find-GPO')] [cmdletBinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'Default')][alias('ForestName')][string] $Forest, [Parameter(ParameterSetName = 'Default')][string[]] $ExcludeDomains, [Parameter(ParameterSetName = 'Default')][alias('Domain', 'Domains')][string[]] $IncludeDomains, [Parameter(ParameterSetName = 'Default')][System.Collections.IDictionary] $ExtendedForestInformation, [Parameter(ParameterSetName = 'Local')][string] $GPOPath, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [string[]] $Type, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [string] $Splitter = [System.Environment]::NewLine, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [switch] $FullObjects, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [ValidateSet('HTML', 'Object')][string[]] $OutputType = 'Object', [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [string] $OutputPath, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [switch] $Open, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [switch] $Online, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [switch] $CategoriesOnly, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [switch] $SingleObject, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [switch] $SkipNormalize, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Local')] [switch] $SkipCleanup, [switch] $Extended, [Parameter(ParameterSetName = 'Default')] [Alias('Name')][string[]] $GPOName, [Parameter(ParameterSetName = 'Default')] [string[]] $GPOGUID ) if ($Type.Count -eq 0) { $Type = $Script:GPODitionary.Keys } if ($GPOPath) { Write-Verbose "Invoke-GPOZaurrContent - Reading GPOs from $GPOPath" if (Test-Path -LiteralPath $GPOPath) { $GPOFiles = Get-ChildItem -LiteralPath $GPOPath -Recurse -File -Filter *.xml [Array] $GPOs = foreach ($File in $GPOFiles) { if ($File.Name -ne 'GPOList.xml') { try { [xml] $GPORead = Get-Content -LiteralPath $File.FullName } catch { Write-Warning "Invoke-GPOZaurrContent - Couldn't process $($File.FullName) error: $($_.Exception.message)" continue } [PSCustomObject] @{ DisplayName = $GPORead.GPO.Name DomainName = $GPORead.GPO.Identifier.Domain.'#text' GUID = $GPORead.GPO.Identifier.Identifier.'#text' -replace '{' -replace '}' GPOOutput = $GPORead } } } } else { Write-Warning "Invoke-GPOZaurrContent - $GPOPath doesn't exists." return } } elseif ($GPOName -or $GPOGUID) { Write-Verbose "Invoke-GPOZaurrContent - Query AD for GPOs" [Array] $GPOs = @( foreach ($Name in $GPOName) { Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -GPOName $Name } foreach ($GUID in $GPOGUID) { Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -GPOGuid $GUID } ) } else { Write-Verbose "Invoke-GPOZaurrContent - Query AD for GPOs" [Array] $GPOs = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } $TemporaryCachedSingleReports = [ordered] @{} $TemporaryCachedSingleReports['ReportsSingle'] = [ordered] @{} $Output = [ordered] @{} $Output['Reports'] = [ordered] @{} $Output['CategoriesFull'] = [ordered] @{} $ForestInformation = Get-WinADForestDetails -PreferWritable -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation Write-Verbose "Invoke-GPOZaurrContent - Loading GPO Report to Categories" $CountGPO = 0 [Array] $GPOCategories = foreach ($GPO in $GPOs) { $CountGPO++ Write-Verbose "Invoke-GPOZaurrContent - Processing [$CountGPO/$($GPOs.Count)] $($GPO.DisplayName)" if ($GPOPath) { $GPOOutput = $GPO.GPOOutput } else { $QueryServer = $ForestInformation['QueryServers'][$GPO.DomainName].HostName[0] [xml] $GPOOutput = Get-GPOReport -Guid $GPO.GUID -Domain $GPO.DomainName -ReportType Xml -Server $QueryServer } Get-GPOCategories -GPO $GPO -GPOOutput $GPOOutput.GPO -Splitter $Splitter -FullObjects:$FullObjects -CachedCategories $Output['CategoriesFull'] } $Output['Categories'] = $GPOCategories | Select-Object -Property * -ExcludeProperty DataSet if ($CategoriesOnly) { return $Output['Categories'] } [Array] $FindRequiredSingle = foreach ($Key in $Script:GPODitionary.Keys) { $Script:GPODitionary[$Key].ByReports.Report } if ($Output['CategoriesFull'].Count -gt 0) { foreach ($Report in $Type) { Write-Verbose "Invoke-GPOZaurrContent - Processing report type $Report" foreach ($CategoryType in $Script:GPODitionary[$Report].Types) { $Category = $CategoryType.Category $Settings = $CategoryType.Settings if (-not $Output['CategoriesFull'][$Category]) { continue } if (-not $Output['CategoriesFull'][$Category][$Settings]) { continue } $CategorizedGPO = $Output['CategoriesFull'][$Category][$Settings] foreach ($GPO in $CategorizedGPO) { if (-not $Output['Reports'][$Report]) { $Output['Reports'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new() } if (-not $TemporaryCachedSingleReports['ReportsSingle'][$Report]) { $TemporaryCachedSingleReports['ReportsSingle'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new() } $TranslatedGpo = $null if ($SingleObject -or ($Report -in $FindRequiredSingle)) { if (-not $Script:GPODitionary[$Report]['CodeSingle']) { If ($Script:GPODitionary[$Report]['Code']) { $Script:GPODitionary[$Report]['CodeSingle'] = $Script:GPODitionary[$Report]['Code'] } } if ($Script:GPODitionary[$Report]['CodeSingle']) { $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['CodeSingle'] if ($Report -in $FindRequiredSingle) { foreach ($T in $TranslatedGpo) { $TemporaryCachedSingleReports['ReportsSingle'][$Report].Add($T) } } if ($SingleObject) { foreach ($T in $TranslatedGpo) { $Output['Reports'][$Report].Add($T) } } } } if (-not $SingleObject) { if ($Script:GPODitionary[$Report]['Code']) { $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['Code'] foreach ($T in $TranslatedGpo) { $Output['Reports'][$Report].Add($T) } } } } } } } foreach ($Report in $Type) { foreach ($ReportType in $Script:GPODitionary[$Report].ByReports) { if (-not $Output['Reports'][$Report]) { $Output['Reports'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new() } $FindReport = $ReportType.Report Write-Verbose "Invoke-GPOZaurrContent - Processing reports based on other report $Report ($FindReport)" foreach ($GPO in $TemporaryCachedSingleReports['ReportsSingle'][$FindReport]) { $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['CodeReport'] foreach ($T in $TranslatedGpo) { $Output['Reports'][$Report].Add($T) } } } } if (-not $SkipNormalize) { foreach ($Report in [string[]] $Output['Reports'].Keys) { $FirstProperties = 'DisplayName', 'DomainName', 'GUID', 'GpoType' $EndProperties = 'Filters', 'Linked', 'LinksCount', 'Links' $Properties = $Output['Reports'][$Report] | Select-Properties -ExcludeProperty ($FirstProperties + $EndProperties) -AllProperties -WarningAction SilentlyContinue $DisplayProperties = @( $FirstProperties foreach ($Property in $Properties) { if ($Property -notin $FirstProperties -and $Property -notin $EndProperties) { $Property } } $EndProperties ) $Output['Reports'][$Report] = $Output['Reports'][$Report] | Select-Object -Property $DisplayProperties } } $Output['PoliciesTotal'] = $Output.Reports.Policies.PolicyCategory | Group-Object | Select-Object Name, Count | Sort-Object -Property Name if (-not $SkipCleanup) { Write-Verbose "Invoke-GPOZaurrContent - Cleaning up output" Remove-EmptyValue -Hashtable $Output -Recursive } if ($Extended) { $Output } else { if ($Output.Reports) { if ($OutputType -eq 'Object') { $Output.Reports } if ($OutputType -eq 'HTML') { if (-not $OutputPath) { $OutputPath = Get-FileName -Extension 'html' -Temporary Write-Warning "Invoke-GPOZaurrContent - OutputPath not given. Using $OutputPath" } Write-Verbose "Invoke-GPOZaurrContent - Generating HTML output" New-HTML { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLTableOption -DataStore JavaScript foreach ($Key in $Output.Reports.Keys) { New-HTMLTab -Name $Key { Write-Verbose "Invoke-GPOZaurrContent - Generating HTML Table for $Key" New-HTMLTable -DataTable $Output.Reports[$Key] -Filtering -Title $Key -PagingOptions 7, 15, 30, 45, 60 -ScrollX } } } -FilePath $OutputPath -ShowHTML:$Open -Online:$Online } } else { Write-Warning "Invoke-GPOZaurrContent - There was no data output for requested types." } } } [scriptblock] $SourcesAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Script:GPODitionary.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } } Register-ArgumentCompleter -CommandName Invoke-GPOZaurrContent -ParameterName Type -ScriptBlock $SourcesAutoCompleter function Invoke-GPOZaurrPermission { <# .SYNOPSIS Sets permissions on Group Policy Objects (GPOs) based on specified criteria. .DESCRIPTION The Invoke-GPOZaurrPermission function sets permissions on GPOs based on various criteria such as GPO name, GPO GUID, AD objects, linked objects, permission levels, and more. .PARAMETER PermissionRules Specifies the permission rules to apply to the GPOs. This can be a script block containing the permission rules. .PARAMETER GPOName Specifies the name of the GPO to set permissions for. .PARAMETER GPOGuid Specifies the GUID of the GPO to set permissions for. .PARAMETER Level Specifies the permission level to set. This is a mandatory parameter. .PARAMETER Limit Specifies the limit for the permission level. This is a mandatory parameter. .PARAMETER Linked Specifies the type of linked object to set permissions for. Valid values are 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit'. .PARAMETER ADObject Specifies the Active Directory objects to set permissions for. This parameter accepts input from the pipeline and by property name. .PARAMETER Filter Specifies the filter to apply when selecting objects. Default filter is "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')". .PARAMETER SearchBase Specifies the search base for filtering objects. .PARAMETER SearchScope Specifies the search scope for filtering objects. .PARAMETER Type Specifies the type of permissions to set. Valid values are 'Unknown', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'All'. .PARAMETER ApprovedGroups Specifies the approved groups for setting permissions. .EXAMPLE Invoke-GPOZaurrPermission -GPOName "TestGPO" -PermissionRules { New-GPOPermission -Group "Domain Admins" -AccessLevel FullControl } Description: Sets FullControl permission for the "Domain Admins" group on the GPO named "TestGPO". .EXAMPLE Get-GPO -All | Invoke-GPOZaurrPermission -PermissionRules { New-GPOPermission -Group "Help Desk" -AccessLevel Read } -Type "NotAdministrative" Description: Sets Read permission for the "Help Desk" group on all GPOs except administrative ones. #> [cmdletBinding(SupportsShouldProcess)] param( [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [parameter(Position = 0)] [scriptblock] $PermissionRules, # ParameterSet1 [Parameter(ParameterSetName = 'GPOName')][string] $GPOName, # ParameterSet2 [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid, # ParameterSet3 [parameter(ParameterSetName = 'Level', Mandatory)][int] $Level, [parameter(ParameterSetName = 'Level', Mandatory)][int] $Limit, # ParameterSet4 [parameter(ParameterSetName = 'Linked', Mandatory)][validateset('Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string] $Linked, # ParameterSet5 [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, # ParameterSet6 [parameter(ParameterSetName = 'Filter')][string] $Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')", [parameter(ParameterSetName = 'Filter')][string] $SearchBase, [parameter(ParameterSetName = 'Filter')][Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope, # All other paramerrs are for for all parametersets [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [validateSet('Unknown', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'All')][string[]] $Type, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [Array] $ApprovedGroups, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [alias('Principal')][Array] $Trustee, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [Microsoft.GroupPolicy.GPPermissionType] $TrusteePermissionType, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [alias('PrincipalType')][validateset('DistinguishedName', 'Name', 'Sid')][string] $TrusteeType = 'DistinguishedName', [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [System.Collections.IDictionary] $GPOCache, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [alias('ForestName')][string] $Forest, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [string[]] $ExcludeDomains, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [alias('Domain', 'Domains')][string[]] $IncludeDomains, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [System.Collections.IDictionary] $ExtendedForestInformation, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [parameter(ParameterSetName = 'Level')] [switch] $LimitAdministrativeGroupsToDomain, [Parameter(ParameterSetName = 'GPOGUID')] [Parameter(ParameterSetName = 'GPOName')] [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] [switch] $SkipDuplicates ) if ($PermissionRules) { $Rules = & $PermissionRules } else { Write-Warning "Invoke-GPOZaurrPermission - No rules defined. Stopping processing." return } $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation if ($LimitAdministrativeGroupsToDomain) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation } else { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest } if ($PSCmdlet.ParameterSetName -ne 'Level') { $Splat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ForestInformation SkipDuplicates = $SkipDuplicates.IsPresent } if ($ADObject) { $Splat['ADObject'] = $ADObject } elseif ($Linked) { $Splat['Linked'] = $Linked } elseif ($GPOName) { } elseif ($GPOGuid) { } else { if ($Filter) { $Splat['Filter'] = $Filter } if ($SearchBase) { $Splat['SearchBase'] = $SearchBase } if ($SearchScope) { $Splat['SearchScope'] = $SearchScope } } Get-GPOZaurrLink @Splat | ForEach-Object -Process { $GPO = $_ foreach ($Rule in $Rules) { if ($Rule.Action -eq 'Owner') { if ($Rule.Type -eq 'Administrative') { if ($GPO.Owner) { $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($GPO.Owner)"] } else { $AdministrativeGroup = $null } if (-not $AdministrativeGroup) { $DefaultPrincipal = $ADAdministrativeGroups["$($GPO.DomainName)"]['DomainAdmins'] Write-Verbose "Invoke-GPOZaurrPermission - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) to $DefaultPrincipal" Set-GPOZaurrOwner -GPOGuid $GPO.Guid -IncludeDomains $GPO.Domain -Principal $DefaultPrincipal -WhatIf:$WhatIfPreference } } elseif ($Rule.Type -eq 'Default') { Write-Verbose "Invoke-GPOZaurrPermission - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) to $($Rule.Principal)" Set-GPOZaurrOwner -GPOGuid $GPO.Guid -IncludeDomains $GPO.Domain -Principal $Rule.Principal -WhatIf:$WhatIfPreference } } elseif ($Rule.Action -eq 'Remove') { $GPOPermissions = Get-GPOZaurrPermission -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName -IncludePermissionType $Rule.IncludePermissionType -ExcludePermissionType $Rule.ExcludePermissionType -Type $Rule.Type -IncludeGPOObject -PermitType $Rule.PermitType -Principal $Rule.Principal -PrincipalType $Rule.PrincipalType -ExcludePrincipal $Rule.ExcludePrincipal -ExcludePrincipalType $Rule.ExcludePrincipalType -ADAdministrativeGroups $ADAdministrativeGroups foreach ($Permission in $GPOPermissions) { Remove-PrivPermission -Principal $Permission.PrincipalSid -PrincipalType Sid -GPOPermission $Permission -IncludePermissionType $Permission.Permission } } elseif ($Rule.Action -eq 'Add') { $SplatPermissions = @{ IncludeDomains = $GPO.DomainName GPOGuid = $GPO.GUID IncludePermissionType = $Rule.IncludePermissionType Type = $Rule.Type PermitType = $Rule.PermitType Principal = $Rule.Principal ADAdministrativeGroups = $ADAdministrativeGroups } if ($Rule.PrincipalType) { $SplatPermissions.PrincipalType = $Rule.PrincipalType } Add-GPOZaurrPermission @SplatPermissions } } } } else { $Report = Get-GPOZaurrLinkSummary -Report OneLink $AffectedGPOs = foreach ($GPO in $Report) { $Property = "Level$($Level)" if ($GPO."$Property" -gt $Limit) { foreach ($Rule in $Rules) { if ($Rule.Action -eq 'Owner') { if ($Rule.Type -eq 'Administrative') { if ($GPO.Owner) { $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($GPO.Owner)"] } else { $AdministrativeGroup = $null } if (-not $AdministrativeGroup) { $DefaultPrincipal = $ADAdministrativeGroups["$($GPO.DomainName)"]['DomainAdmins'] Write-Verbose "Invoke-GPOZaurrPermission - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) to $DefaultPrincipal" Set-GPOZaurrOwner -GPOGuid $GPO.Guid -IncludeDomains $GPO.Domain -Principal $DefaultPrincipal -WhatIf:$WhatIfPreference } } elseif ($Rule.Type -eq 'Default') { Write-Verbose "Invoke-GPOZaurrPermission - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) to $($Rule.Principal)" Set-GPOZaurrOwner -GPOGuid $GPO.Guid -IncludeDomains $GPO.Domain -Principal $Rule.Principal -WhatIf:$WhatIfPreference } } elseif ($Rule.Action -eq 'Remove') { $GPOPermissions = Get-GPOZaurrPermission -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName -IncludePermissionType $Rule.IncludePermissionType -ExcludePermissionType $Rule.ExcludePermissionType -Type $Rule.Type -IncludeGPOObject -PermitType $Rule.PermitType -Principal $Rule.Principal -PrincipalType $Rule.PrincipalType -ExcludePrincipal $Rule.ExcludePrincipal -ExcludePrincipalType $Rule.ExcludePrincipalType -ADAdministrativeGroups $ADAdministrativeGroups foreach ($Permission in $GPOPermissions) { Remove-PrivPermission -Principal $Permission.Sid -PrincipalType Sid -GPOPermission $Permission -IncludePermissionType $Permission.Permission } } elseif ($Rule.Action -eq 'Add') { $SplatPermissions = @{ IncludeDomains = $GPO.DomainName GPOGuid = $GPO.GUID IncludePermissionType = $Rule.IncludePermissionType Type = $Rule.Type PermitType = $Rule.PermitType Principal = $Rule.Principal ADAdministrativeGroups = $ADAdministrativeGroups } if ($Rule.PrincipalType) { $SplatPermissions.PrincipalType = $Rule.PrincipalType } Add-GPOZaurrPermission @SplatPermissions } } } } $AffectedGPOs } } function Invoke-GPOZaurrSupport { <# .SYNOPSIS Invokes GPOZaurrSupport function to retrieve Group Policy information. .DESCRIPTION This function retrieves Group Policy information using either HTML, XML, or Object format. It can be run locally or on a remote computer. .PARAMETER Type Specifies the type of output format. Valid values are 'NativeHTML', 'HTML', 'XML', or 'Object'. Default is 'HTML'. .PARAMETER ComputerName Specifies the name of the remote computer to retrieve Group Policy information from. .PARAMETER UserName Specifies the username to run the function as on the remote computer. .PARAMETER Path Specifies the path to save the output file. If not provided, a temporary file will be created. .PARAMETER Splitter Specifies the delimiter for splitting output data. Default is a new line. .PARAMETER PreventShow Prevents displaying the output in the console. .PARAMETER Online Runs the function online to retrieve the latest Group Policy information. .EXAMPLE Invoke-GPOZaurrSupport -Type HTML -ComputerName "RemoteComputer" -UserName "Admin" -Path "C:\Temp\GPOReport.html" Retrieves Group Policy information in HTML format from a remote computer and saves it to a specified path. .EXAMPLE Invoke-GPOZaurrSupport -Type XML -Path "C:\Temp\GPOReport.xml" -Online Retrieves the latest Group Policy information in XML format and saves it to a specified path. #> [cmdletBinding()] param( [ValidateSet('NativeHTML', 'HTML', 'XML', 'Object')][string] $Type = 'HTML', [alias('Server')][string] $ComputerName, [alias('User')][string] $UserName, [string] $Path, [string] $Splitter = [System.Environment]::NewLine, [switch] $PreventShow, [switch] $Online ) if (-not $UserName -and -not $ComputerName) { $UserName = $Env:USERNAME if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { $ComputerName = $Env:COMPUTERNAME } } If ($Type -eq 'HTML') { $Exists = Get-Command -Name 'New-HTML' -ErrorAction SilentlyContinue if (-not $Exists) { Write-Warning "Invoke-GPOZaurrSupport - PSWriteHTML module is required for HTML functionality. Use XML, Object or NativeHTML option instead." return } } $Command = Get-Command -Name 'Get-GPResultantSetOfPolicy' -ErrorAction SilentlyContinue $NativeCommand = Get-Command -Name 'gpresult' -ErrorAction SilentlyContinue if (-not $Command -and -not $NativeCommand) { Write-Warning "Invoke-GPOZaurrSupport - Neither gpresult or Get-GPResultantSetOfPolicy are available. Terminating." return } $SplatPolicy = @{} if ($Type -in 'Object', 'XML', 'HTML') { if ($Path) { $SplatPolicy['Path'] = $Path } else { $SplatPolicy['Path'] = [io.path]::GetTempFileName().Replace('.tmp', ".xml") } $SplatPolicy['ReportType'] = 'xml' } elseif ($Type -eq 'NativeHTML') { if ($Path) { $SplatPolicy['Path'] = $Path } else { $SplatPolicy['Path'] = [io.path]::GetTempFileName().Replace('.tmp', ".html") } $SplatPolicy['ReportType'] = 'html' } if ($ComputerName) { $SplatPolicy['Computer'] = $ComputerName } if ($UserName) { $SplatPolicy['User'] = $UserName } $SplatPolicy['TempXmlPath'] = [io.path]::GetTempFileName().Replace('.tmp', ".xml") $Arguments = @( if ($SplatPolicy['Computer']) { "/S $ComputerName" } if ($SplatPolicy['User']) { "/USER $($SplatPolicy['User'])" } if ($SplatPolicy['ReportType'] -eq 'HTML') { '/H' $SplatPolicy['Path'] } elseif ($SplatPolicy['ReportType'] -eq 'XML') { '/X' $SplatPolicy['TempXmlPath'] } "/F" ) Write-Verbose "Invoke-GPOZaurrSupport - GPResult Arguments: $Arguments" Start-Process -NoNewWindow -FilePath 'gpresult' -ArgumentList $Arguments -Wait if ($Type -eq 'NativeHTML') { if (-not $PreventShow) { Write-Verbose "Invoke-GPOZaurrSupport - Opening up file $($SplatPolicy['Path'])" Start-Process -FilePath $SplatPolicy['Path'] } return } if ($SplatPolicy.TempXmlPath -and (Test-Path -LiteralPath $SplatPolicy.TempXmlPath)) { [xml] $PolicyContent = Get-Content -LiteralPath $SplatPolicy.TempXmlPath if ($PolicyContent) { Remove-Item -LiteralPath $SplatPolicy.TempXmlPath } else { Write-Warning "Invoke-GPOZaurrSupport - Couldn't load XML file from drive $($SplatPolicy.TempXmlPath). Terminating." return } } else { Write-Warning "Invoke-GPOZaurrSupport - Couldn't find XML file on drive $($SplatPolicy.TempXmlPath). Terminating." return } if ($ComputerName) { if (-not $PolicyContent.Rsop.'ComputerResults'.EventsDetails) { Write-Warning "Invoke-GPOZaurrSupport - Windows Events for Group Policy are missing. Amount of data will be limited. Firewall issue?" } } if (-not $ComputerName) { $ComputerName = $Env:COMPUTERNAME } if ($Type -eq 'XML') { $PolicyContent.Rsop } else { if ($VerbosePreference -ne 'SilentlyContinue') { $Verbose = $true } else { $Verbose = $false } $Output = [ordered] @{ ComputerInformation = Get-Computer -ComputerName $ComputerName -Verbose:$Verbose } if ($PolicyContent.Rsop.ComputerResults) { $Output.ComputerResults = ConvertFrom-XMLRSOP -Content $PolicyContent.Rsop -ResultsType 'ComputerResults' -Splitter $Splitter } if ($PolicyContent.Rsop.UserResults) { $Output.UserResults = ConvertFrom-XMLRSOP -Content $PolicyContent.Rsop -ResultsType 'UserResults' -Splitter $Splitter } New-GPOZaurrReportConsole -Results $Output -ComputerName $ComputerName if ($Type -contains 'Object') { $Output } elseif ($Type -contains 'HTML') { New-GPOZaurrReportHTML -Path $Path -Online:$Online -Open:(-not $PreventShow) -Support $Output } } } function New-GPOZaurrWMI { <# .SYNOPSIS Creates a new WMI filter based on a WMI filter query. .DESCRIPTION This function creates a new WMI filter in Active Directory based on a specified WMI filter query. .PARAMETER Name The name of the new WMI filter to be created. .PARAMETER Description The description for the new WMI filter. Default is an empty string. .PARAMETER Namespace The WMI namespace to target. Default is 'root\CIMv2'. .PARAMETER Query The WMI filter query to be applied to the WMI entry. .PARAMETER SkipQueryCheck Switch to skip the query check before creating the WMI entry. .PARAMETER Force Switch to force the creation of the WMI entry without confirmation. .PARAMETER Forest The forest to target for WMI creation. .PARAMETER ExcludeDomains An array of domains to exclude from WMI application. .PARAMETER IncludeDomains An array of domains to include for WMI application. .PARAMETER ExtendedForestInformation Additional information about the forest for WMI customization. .EXAMPLE New-GPOZaurrWMI -Name "TestWMIFilter1" -Query "SELECT * FROM Win32_OperatingSystem" -Force Creates a new WMI filter named "TestWMIFilter1" targeting all Windows operating systems. .EXAMPLE New-GPOZaurrWMI -Name "TestWMIFilter2" -Query "SELECT * FROM Win32_Processor" -Forest "Contoso" -IncludeDomains "FinanceDomain" Creates a new WMI filter named "TestWMIFilter2" targeting all processors in the "FinanceDomain" within the "Contoso" forest. #> [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][string] $Name, [string] $Description = ' ', [string] $Namespace = 'root\CIMv2', [parameter(Mandatory)][string] $Query, [switch] $SkipQueryCheck, [switch] $Force, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) if (-not $Forest -and -not $ExcludeDomains -and -not $IncludeDomains -and -not $ExtendedForestInformation) { $IncludeDomains = $Env:USERDNSDOMAIN } if (-not $SkipQueryCheck) { try { $null = Get-CimInstance -Query $Query -ErrorAction Stop -Verbose:$false } catch { Write-Warning "New-GPOZaurrWMI - Query error $($_.Exception.Message). Terminating." return } } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $DomainInformation = Get-ADDomain -Server $QueryServer $defaultNamingContext = $DomainInformation.DistinguishedName [string] $Author = (([ADSI]"LDAP://<SID=$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)>").UserPrincipalName).ToString() [string] $GUID = "{" + ([System.Guid]::NewGuid()) + "}" [string] $DistinguishedName = -join ("CN=", $GUID, ",CN=SOM,CN=WMIPolicy,CN=System,", $defaultNamingContext) $CurrentTime = (Get-Date).ToUniversalTime() [string] $CurrentDate = -join ( ($CurrentTime.Year).ToString("0000"), ($CurrentTime.Month).ToString("00"), ($CurrentTime.Day).ToString("00"), ($CurrentTime.Hour).ToString("00"), ($CurrentTime.Minute).ToString("00"), ($CurrentTime.Second).ToString("00"), ".", ($CurrentTime.Millisecond * 1000).ToString("000000"), "-000" ) [Array] $ExistingWmiFilter = Get-GPOZaurrWMI -ExtendedForestInformation $ForestInformation -IncludeDomains $Domain -Name $Name if ($ExistingWmiFilter.Count -eq 0) { [string] $WMIParm2 = -join ("1;3;10;", $Query.Length.ToString(), ";WQL;$Namespace;", $Query , ";") $OtherAttributes = @{ "msWMI-Name" = $Name "msWMI-Parm1" = $Description "msWMI-Parm2" = $WMIParm2 "msWMI-Author" = $Author "msWMI-ID" = $GUID "instanceType" = 4 "showInAdvancedViewOnly" = "TRUE" "distinguishedname" = $DistinguishedName "msWMI-ChangeDate" = $CurrentDate "msWMI-CreationDate" = $CurrentDate } $WMIPath = -join ("CN=SOM,CN=WMIPolicy,CN=System,", $defaultNamingContext) try { Write-Verbose "New-GPOZaurrWMI - Creating WMI filter $Name in $Domain" New-ADObject -Name $GUID -Type "msWMI-Som" -Path $WMIPath -OtherAttributes $OtherAttributes -Server $QueryServer } catch { Write-Warning "New-GPOZaurrWMI - Creating GPO filter error $($_.Exception.Message). Terminating." return } } else { foreach ($_ in $ExistingWmiFilter) { Write-Warning "New-GPOZaurrWMI - Skipping creation of GPO because name: $($_.DisplayName) guid: $($_.ID) for $($_.DomainName) already exists." } } } } function Optimize-GPOZaurr { <# .SYNOPSIS Enables or disables user/computer section of group policy based on it's content. .DESCRIPTION Enables or disables user/computer section of group policy based on it's content. .PARAMETER ExcludeGroupPolicies Provide a list of group policies to skip using Skip-GroupPolicy cmdlet .PARAMETER LimitProcessing Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE Optimize-GPOZaurr -All -WhatIf -Verbose -LimitProcessing 2 .EXAMPLE Optimize-GPOZaurr -All -WhatIf -Verbose -LimitProcessing 2 { Skip-GroupPolicy -Name 'TEST | Drive Mapping 1' Skip-GroupPolicy -Name 'TEST | Drive Mapping 2' } .NOTES General notes #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'GPOName')] param( [Parameter(Position = 1)][scriptblock] $ExcludeGroupPolicies, [alias('Name', 'DisplayName')][Parameter(ParameterSetName = 'GPOName', Mandatory)][string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID', Mandatory)][alias('GUID', 'GPOID')][string] $GPOGuid, [Parameter(ParameterSetName = 'All', Mandatory)][switch] $All, [int] $LimitProcessing, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { $Count = 0 } Process { $getGPOZaurrSplat = @{ GpoName = $GPOName GPOGUID = $GPOGuid Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation ExcludeGroupPolicies = $ExcludeGroupPolicies } Remove-EmptyValue -Hashtable $getGPOZaurrSplat Get-GPOZaurr @getGPOZaurrSplat | ForEach-Object { $GPO = $_ if (-not $GPO.Exclude) { if ($GPO.Optimized -eq $false -and $GPO.Problem -eq $false) { if ($GPO.Empty) { Write-Verbose "Optimize-GPOZaurr - GPO ($($GPO.DisplayName)) / $($GPO.DomainName)) is not optimized, but GPO is empty, so leaving as is." } else { if ($GPO.UserSettingsAvailable -and $GPO.ComputerSettingsAvailable) { Write-Verbose "Optimize-GPOZaurr - " if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Enabling computer and user settings in domain $($GPO.DomainName)")) { try { $GPO.GPOObject.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::AllSettingsEnabled } catch { Write-Warning -Message "Optimize-GPOZaurr - Couldn't set $($GPO.DisplayName) / $($GPO.DomainName) to $Status. Error $($_.Exception.Message)" } } } elseif ($GPO.UserSettingsAvailable) { if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Disabling computer setings in domain $($GPO.DomainName)")) { try { $GPO.GPOObject.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::ComputerSettingsDisabled } catch { Write-Warning -Message "Optimize-GPOZaurr - Couldn't set $($GPO.DisplayName) / $($GPO.DomainName) to $Status. Error $($_.Exception.Message)" } } } elseif ($GPO.ComputerSettingsAvailable) { if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Disabling user setings in domain $($GPO.DomainName)")) { try { $GPO.GPOObject.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled } catch { Write-Warning -Message "Optimize-GPOZaurr - Couldn't set $($GPO.DisplayName) / $($GPO.DomainName) to $Status. Error $($_.Exception.Message)" } } } } $Count++ if ($LimitProcessing -eq $Count) { break } } elseif ($GPO.Optimized -eq $false) { Write-Warning "Optimize-GPOZaurr - GPO ($($GPO.DisplayName)) / $($GPO.DomainName)) is not optimized, but GPO is marked as one with problem. Skipping." } } } } } function Remove-GPOPermission { <# .SYNOPSIS Removes permissions from a Group Policy Object (GPO). .DESCRIPTION This function removes specified permissions from a GPO based on the provided criteria. .PARAMETER Type Specifies the type of permissions to remove. Valid values are 'Unknown', 'NotWellKnown', 'NotWellKnownAdministrative', 'Administrative', 'NotAdministrative', and 'All'. .PARAMETER IncludePermissionType Specifies the permission types to include in the removal process. .PARAMETER ExcludePermissionType Specifies the permission types to exclude from the removal process. .PARAMETER PermitType Specifies whether to allow or deny the specified permissions. Valid values are 'Allow', 'Deny', and 'All'. .PARAMETER Principal Specifies the principal(s) for which permissions should be removed. .PARAMETER PrincipalType Specifies the type of principal(s) provided. Valid values are 'DistinguishedName', 'Name', and 'Sid'. .PARAMETER ExcludePrincipal Specifies the principal(s) for which permissions should be excluded from removal. .PARAMETER ExcludePrincipalType Specifies the type of principal(s) to exclude. Valid values are 'DistinguishedName', 'Name', and 'Sid'. .EXAMPLE Remove-GPOPermission -Type 'Administrative' -PermitType 'Deny' -Principal 'S-1-5-21-3623811015-3361044348-30300820-1013' -PrincipalType 'Sid' Removes administrative permissions denied for a specific SID from the GPO. .EXAMPLE Remove-GPOPermission -Type 'All' -PermitType 'Allow' -Principal 'CN=John Doe,OU=Users,DC=contoso,DC=com' -PrincipalType 'DistinguishedName' -ExcludePrincipal 'S-1-5-21-3623811015-3361044348-30300820-1013' -ExcludePrincipalType 'Sid' Removes all permissions allowed for a specific distinguished name while excluding permissions for a specific SID from the GPO. #> [cmdletBinding()] param( [validateSet('Unknown', 'NotWellKnown', 'NotWellKnownAdministrative', 'Administrative', 'NotAdministrative', 'All')][string[]] $Type, [Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType, [Microsoft.GroupPolicy.GPPermissionType[]] $ExcludePermissionType, [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'Allow', [string[]] $Principal, [validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'Sid', [string[]] $ExcludePrincipal, [validateset('DistinguishedName', 'Name', 'Sid')][string] $ExcludePrincipalType = 'Sid' ) if ($Type) { @{ Action = 'Remove' Type = $Type IncludePermissionType = $IncludePermissionType ExcludePermissionType = $ExcludePermissionType PermitType = $PermitType Principal = $Principal PrincipalType = $PrincipalType ExcludePrincipal = $ExcludePrincipal ExcludePrincipalType = $ExcludePrincipalType } } } function Remove-GPOZaurr { <# .SYNOPSIS Removes Group Policy Objects based on specified criteria. .DESCRIPTION The Remove-GPOZaurr function removes Group Policy Objects (GPOs) based on the specified criteria. It allows for filtering by various parameters such as GPO type, forest, domains, and more. .PARAMETER ExcludeGroupPolicies Specifies the Group Policies to exclude from removal. .PARAMETER Type Specifies the type of GPOs to target for removal. Valid values are 'Empty', 'Unlinked', 'Disabled', 'NoApplyPermission'. .PARAMETER LimitProcessing Specifies the maximum number of GPOs to process before stopping. .PARAMETER Forest Specifies the forest to target for GPO removal. .PARAMETER ExcludeDomains Specifies the domains to exclude from GPO removal. .PARAMETER IncludeDomains Specifies the domains to include for GPO removal. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER GPOPath Specifies the path to the GPOs to be removed. .PARAMETER BackupPath Specifies the path for backing up GPOs before removal. .PARAMETER BackupDated Indicates whether the backup should be dated. .PARAMETER RequireDays Specifies the number of days before GPO removal is required. .EXAMPLE Remove-GPOZaurr -Type 'Empty' -Forest 'Contoso' -IncludeDomains 'Domain1', 'Domain2' -BackupPath 'C:\GPOBackups' -BackupDated -RequireDays 7 Removes all empty GPOs from the 'Contoso' forest for 'Domain1' and 'Domain2', backs them up to 'C:\GPOBackups' with dated folders, and requires removal after 7 days. .EXAMPLE Remove-GPOZaurr -Type 'Disabled' -Forest 'Fabrikam' -ExcludeDomains 'Domain3' -LimitProcessing 10 Removes all disabled GPOs from the 'Fabrikam' forest excluding 'Domain3' and processes only the first 10 GPOs. #> [cmdletBinding(SupportsShouldProcess)] param( [Parameter(Position = 1)][scriptblock] $ExcludeGroupPolicies, [parameter(Position = 0, Mandatory)][validateset('Empty', 'Unlinked', 'Disabled', 'NoApplyPermission')][string[]] $Type, [int] $LimitProcessing, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string[]] $GPOPath, [string] $BackupPath, [switch] $BackupDated, [int] $RequireDays ) Begin { if ($BackupPath) { $BackupRequired = $true if ($BackupDated) { $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))" } else { $BackupFinalPath = $BackupPath } Write-Verbose "Remove-GPOZaurr - Backing up to $BackupFinalPath" $null = New-Item -ItemType Directory -Path $BackupFinalPath -Force -WhatIf:$false } else { $BackupRequired = $false } $CountProcessedGPO = 0 } Process { $getGPOZaurrSplat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation GPOPath = $GPOPath ExcludeGroupPolicies = $ExcludeGroupPolicies } Get-GPOZaurr @getGPOZaurrSplat | ForEach-Object { if ($LimitProcessing -ne 0 -and $CountProcessedGPO -ge $LimitProcessing) { Write-Warning -Message "Remove-GPOZaurr - LimitProcessing ($CountProcessedGPO / $LimitProcessing) reached. Stopping processing" break } $DeleteRequired = $false if ($Type -contains 'Empty') { if ($_.Empty -eq $true) { $DeleteRequired = $true } } if ($Type -contains 'Unlinked') { if ($_.Linked -eq $false) { $DeleteRequired = $true } } if ($Type -contains 'Disabled') { if ($_.Enabled -eq $false) { $DeleteRequired = $true } } if ($Type -contains 'NoApplyPermission') { if ($_.ApplyPermission -eq $false) { $DeleteRequired = $true } } if ($RequireDays) { if ($RequireDays -ge $_.Days) { $DeleteRequired = $false } } if ($_.Exclude -eq $true) { Write-Verbose "Remove-GPOZaurr - Excluded GPO $($_.DisplayName) from $($_.DomainName). Skipping!" } elseif ($DeleteRequired) { if ($BackupRequired) { try { Write-Verbose "Remove-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName)" $BackupInfo = Backup-GPO -Guid $_.Guid -Domain $_.DomainName -Path $BackupFinalPath -ErrorAction Stop $BackupInfo $BackupOK = $true } catch { Write-Warning "Remove-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName) failed: $($_.Exception.Message)" $BackupOK = $false } } if (($BackupRequired -and $BackupOK) -or (-not $BackupRequired)) { try { Write-Verbose "Remove-GPOZaurr - Removing GPO $($_.DisplayName) from $($_.DomainName)" Remove-GPO -Domain $_.DomainName -Guid $_.Guid -ErrorAction Stop } catch { Write-Warning "Remove-GPOZaurr - Removing GPO $($_.DisplayName) from $($_.DomainName) failed: $($_.Exception.Message)" } } $CountProcessedGPO++ } } } End { } } function Remove-GPOZaurrBroken { <# .SYNOPSIS Finds and removes broken Group Policies from SYSVOL or AD or both. .DESCRIPTION Finds and removes broken Group Policies from SYSVOL or AD or both. Assesment is based on Get-GPOZaurrBroken and there are 3 supported types: - AD - meaning GPOs which have no SYSVOL content will be deleted from AD - SYSVOL - meaning GPOs which have no AD content will be deleted from SYSVOL - ObjectClass - meaning GPOs which have ObjectClass category of Container rather than groupPolicyContainer will be deleted from AD & SYSVOL .PARAMETER Type Choose one or more types to delete. Options are AD, ObjectClass, SYSVOL .PARAMETER BackupPath Path to optional backup of SYSVOL content before deletion .PARAMETER BackupDated Forces backup to be created within folder that has date in it .PARAMETER LimitProcessing Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE Remove-GPOZaurrBroken -Verbose -WhatIf -Type AD, SYSVOL .EXAMPLE Remove-GPOZaurrBroken -Verbose -WhatIf -Type AD, SYSVOL -IncludeDomains 'ad.evotec.pl' -LimitProcessing 2 .EXAMPLE Remove-GPOZaurrBroken -Verbose -IncludeDomains 'ad.evotec.xyz' -BackupPath $Env:UserProfile\Desktop\MyBackup1 -WhatIf -Type AD, SYSVOL .NOTES General notes #> [alias('Remove-GPOZaurrOrphaned')] [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory, Position = 0)][ValidateSet('SYSVOL', 'AD', 'ObjectClass')][string[]] $Type, [string] $BackupPath, [switch] $BackupDated, [int] $LimitProcessing = [int32]::MaxValue, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) if ($BackupPath) { if ($BackupDated) { $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))" } else { $BackupFinalPath = $BackupPath } } else { $BackupFinalPath = '' } Get-GPOZaurrBroken -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object { if ($Type -contains 'SYSVOL') { if ($_.Status -eq 'Not available in AD') { $_ } } if ($Type -contains 'AD') { if ($_.Status -eq 'Not available on SYSVOL') { $_ } } if ($Type -contains 'ObjectClass') { if ($_.Status -eq 'ObjectClass issue') { $_ } } } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object { $GPO = $_ if ($GPO.Status -in 'Not available in AD', 'ObjectClass issue') { Write-Verbose "Remove-GPOZaurrBroken - Removing from SYSVOL [$($GPO.Status)] $($GPO.Path)" if ($BackupFinalPath) { Try { Write-Verbose "Remove-GPOZaurrBroken - Backing up $($GPO.Path)" Copy-Item -LiteralPath $GPO.Path -Recurse -Destination $BackupFinalPath -ErrorAction Stop $BackupWorked = $true } catch { Write-Warning "Remove-GPOZaurrBroken - Error backing up $($GPO.Path) error: $($_.Exception.Message)" $BackupWorked = $false } } if ($BackupWorked -or $BackupFinalPath -eq '') { Write-Verbose "Remove-GPOZaurrBroken - Removing $($GPO.Path)" try { Remove-Item -Recurse -Force -LiteralPath $GPO.Path -ErrorAction Stop } catch { Write-Warning "Remove-GPOZaurrBroken - Failed to remove file $($GPO.Path): $($_.Exception.Message)." } } } if ($GPO.Status -in 'Not available on SYSVOL', 'ObjectClass issue') { Write-Verbose "Remove-GPOZaurrBroken - Removing from AD [$($GPO.Status)] $($GPO.DistinguishedName)" if ($GPO -and $GPO.ObjectClass -in 'groupPolicyContainer', 'Container') { Write-Verbose "Remove-GPOZaurrBroken - Removing DN: $($GPO.DistinguishedName) / ObjectClass: $($GPO.ObjectClass)" try { Remove-ADObject -Server $GPO.DomainName -Identity $GPO.DistinguishedName -Recursive -Confirm:$false -ErrorAction Stop } catch { Write-Warning "Remove-GPOZaurrBroken - Failed to remove $($GPO.DistinguishedName) from AD error: $($_.Exception.Message)" } } else { Write-Warning "Remove-GPOZaurrBroken - DistinguishedName $($GPO.DistinguishedName) not found or ObjectClass is not groupPolicyContainer/Container ($($GPO.ObjectClass))" } } } } function Remove-GPOZaurrDuplicateObject { <# .SYNOPSIS Removes duplicate Group Policy Objects (GPOs) identified by the Get-GPOZaurrDuplicateObject function. .DESCRIPTION This function removes duplicate GPOs based on the criteria provided. It retrieves duplicate GPO objects using Get-GPOZaurrDuplicateObject and then attempts to remove them from the Active Directory. .PARAMETER LimitProcessing Specifies the maximum number of duplicate GPOs to process. Default is set to [int32]::MaxValue. .PARAMETER Forest Specifies the forest where the duplicate GPOs are located. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the duplicate GPO removal process. .PARAMETER IncludeDomains Specifies an array of domains to include in the duplicate GPO removal process. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .EXAMPLE Remove-GPOZaurrDuplicateObject -Forest "contoso.com" -IncludeDomains "domain1.com", "domain2.com" -ExcludeDomains "domain3.com" -LimitProcessing 5 Description: Removes duplicate GPOs from the forest "contoso.com" for domains "domain1.com" and "domain2.com", excluding "domain3.com", processing only the first 5 duplicates. #> [cmdletBinding(SupportsShouldProcess)] param( [int] $LimitProcessing = [int32]::MaxValue, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $getGPOZaurrDuplicateObjectSplat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation } $DuplicateGpoObjects = Get-GPOZaurrDuplicateObject @getGPOZaurrDuplicateObjectSplat foreach ($Duplicate in $DuplicateGpoObjects | Select-Object -First $LimitProcessing) { try { Remove-ADObject -Identity $Duplicate.ObjectGUID -Recursive -ErrorAction Stop -Server $Duplicate.DomainName -Confirm:$false } catch { Write-Warning "Remove-GPOZaurrDuplicateObject - Deleting $($Duplicate.ConflictDN) / $($Duplicate.DomainName) via GUID: $($Duplicate.ObjectGUID) failed with error: $($_.Exception.Message)" } } } function Remove-GPOZaurrFolders { <# .SYNOPSIS Removes specified GPOZaurr folders and backs them up to a specified path. .DESCRIPTION This function removes specified GPOZaurr folders based on the provided criteria and backs them up to a specified path. It allows for filtering by folder type, domain, and other parameters. .PARAMETER BackupPath The path where the GPOZaurr folders will be backed up. .PARAMETER BackupDated Indicates whether the backup path should include a timestamp. .PARAMETER Type Specifies the type of folders to remove. Options are 'All', 'Netlogon', or 'Sysvol'. .PARAMETER FolderType Specifies the type of folders to remove. Options are 'NTFRS' or 'Empty'. .PARAMETER FolderName Specifies the name of the folder to remove. .PARAMETER LimitProcessing Limits the number of folders to process. .PARAMETER Forest Specifies the forest to target. .PARAMETER ExcludeDomains Specifies domains to exclude from processing. .PARAMETER IncludeDomains Specifies domains to include in processing. .PARAMETER ExtendedForestInformation Specifies additional forest information. .EXAMPLE Remove-GPOZaurrFolders -BackupPath "C:\Backups" -BackupDated -Type 'All' -FolderType 'NTFRS' -FolderName "Folder1" -LimitProcessing 10 -Forest "ExampleForest" -ExcludeDomains "Domain1" -IncludeDomains "Domain2" -ExtendedForestInformation $info Removes GPOZaurr folders of type 'NTFRS' named "Folder1" from all domains in the forest "ExampleForest", backs them up to "C:\Backups" with a timestamp, and limits processing to 10 folders. #> [cmdletBinding(SupportsShouldProcess)] param( [string] $BackupPath, [switch] $BackupDated, [ValidateSet('All', 'Netlogon', 'Sysvol')][string[]] $Type = 'All', [Parameter(Mandatory)][ValidateSet('NTFRS', 'Empty')][string] $FolderType, [string[]] $FolderName, [int] $LimitProcessing = [int32]::MaxValue, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) if ($BackupPath) { if ($BackupDated) { $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))" } else { $BackupFinalPath = $BackupPath } } else { $BackupFinalPath = '' } Get-GPOZaurrFolders -Type $Type -FolderType $FolderType -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object { if ($FolderName) { foreach ($Folder in $FolderName) { if ($_.Name -eq $Folder) { $_ } } } else { $_ } } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object { if ($BackupFinalPath) { $SYSVOLRoot = "\\$($_.DomainName)\SYSVOL\$($_.DomainName)\" $DestinationFile = ($_.FullName).Replace($SYSVOLRoot, '') $DestinationFilePath = [system.io.path]::Combine($BackupFinalPath, $DestinationFile) Write-Verbose "Remove-GPOZaurrFolders - Backing up $($_.FullName)" Try { Copy-Item -LiteralPath $_.FullName -Recurse -Destination $DestinationFilePath -ErrorAction Stop -Force $BackupWorked = $true } catch { Write-Warning "Remove-GPOZaurrFolders - Error backing up error: $($_.Exception.Message)" $BackupWorked = $false } } if ($BackupWorked -or $BackupFinalPath -eq '') { try { Write-Verbose "Remove-GPOZaurrFolders - Removing $($_.FullName)" Remove-Item -Path $_.FullName -Force -Recurse } catch { Write-Warning "Remove-GPOZaurrFolders - Failed to remove directory $($_.FullName): $($_.Exception.Message)." } } } } function Remove-GPOZaurrLegacyFiles { <# .SYNOPSIS Removes legacy Group Policy Objects (GPO) files from specified domains. .DESCRIPTION The Remove-GPOZaurrLegacyFiles function removes legacy GPO files from specified domains. It can back up the files before removal and optionally remove empty folders. .PARAMETER BackupPath Specifies the path where backup files will be stored. .PARAMETER BackupDated Indicates whether backup files should be timestamped with the current date and time. .PARAMETER RemoveEmptyFolders Indicates whether empty folders should be removed after GPO files are deleted. .PARAMETER Forest Specifies the forest where the GPO files are located. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from processing. .PARAMETER IncludeDomains Specifies an array of domains to include for processing. .PARAMETER LimitProcessing Specifies the maximum number of GPO files to process. .EXAMPLE Remove-GPOZaurrLegacyFiles -BackupPath "C:\GPOBackups" -BackupDated -RemoveEmptyFolders -Forest "Contoso" -IncludeDomains "Domain1", "Domain2" -ExcludeDomains "Domain3" -LimitProcessing 100 Removes legacy GPO files from the "Contoso" forest for "Domain1" and "Domain2", excluding "Domain3". Backs up files to "C:\GPOBackups" with timestamps and removes empty folders after deletion. #> [cmdletBinding(SupportsShouldProcess)] param( [string] $BackupPath, [switch] $BackupDated, [switch] $RemoveEmptyFolders, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [int] $LimitProcessing = [int32]::MaxValue ) if ($BackupPath) { if ($BackupDated) { $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))" } else { $BackupFinalPath = $BackupPath } } else { $BackupFinalPath = '' } $Splat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation Verbose = $VerbosePreference } [Array] $Deleted = Get-GPOZaurrLegacyFiles @Splat | Select-Object -First $LimitProcessing | ForEach-Object { Write-Verbose "Remove-GPOZaurrLegacyFiles - Processing $($_.FullName)" if ($BackupFinalPath) { $SYSVOLRoot = "\\$($_.DomainName)\SYSVOL\$($_.DomainName)\policies\" $DestinationFile = ($_.FullName).Replace($SYSVOLRoot, '') $DestinationFilePath = [system.io.path]::Combine($BackupFinalPath, $DestinationFile) Write-Verbose "Remove-GPOZaurrLegacyFiles - Backing up $($_.FullName)" $Created = New-Item -ItemType File -Path $DestinationFilePath -Force if ($Created) { Try { Copy-Item -LiteralPath $_.FullName -Recurse -Destination $DestinationFilePath -ErrorAction Stop -Force $BackupWorked = $true } catch { Write-Warning "Remove-GPOZaurrLegacyFiles - Error backing up error: $($_.Exception.Message)" $BackupWorked = $false } } else { $BackupWorked = $false } } if ($BackupWorked -or $BackupFinalPath -eq '') { try { Write-Verbose "Remove-GPOZaurrLegacyFiles - Deleting $($_.FullName)" Remove-Item -Path $_.FullName -ErrorAction Stop -Force $_ } catch { Write-Warning "Remove-GPOZaurrLegacyFiles - Failed to remove file $($_.FullName): $($_.Exception.Message)." } } } if ($Deleted.Count -gt 0) { if ($RemoveEmptyFolders) { $FoldersToCheck = $Deleted.DirectoryName | Sort-Object -Unique foreach ($Folder in $FoldersToCheck) { $FolderName = $Folder.Substring($Folder.Length - 4) if ($FolderName -eq '\Adm') { try { $MeasureCount = Get-ChildItem -LiteralPath $Folder -Force -ErrorAction Stop | Select-Object -First 1 | Measure-Object } catch { Write-Warning "Remove-GPOZaurrLegacyFiles - Couldn't verify if folder $Folder is empty. Skipping. Error: $($_.Exception.Message)." continue } if ($MeasureCount.Count -eq 0) { Write-Verbose "Remove-GPOZaurrLegacyFiles - Deleting empty folder $($Folder)" try { Remove-Item -LiteralPath $Folder -Force -Recurse:$false } catch { Write-Warning "Remove-GPOZaurrLegacyFiles - Failed to remove folder $($Folder): $($_.Exception.Message)." } } else { Write-Verbose "Remove-GPOZaurrLegacyFiles - Skipping not empty folder from deletion $($Folder)" } } } } } } function Remove-GPOZaurrLinkEmptyOU { <# .SYNOPSIS Removes Group Policy Object (GPO) links from empty Organizational Units (OUs) in a specified forest. .DESCRIPTION This function removes GPO links from OUs that are empty and meet specified criteria. It processes OUs within the specified forest based on inclusion and exclusion rules. .PARAMETER Forest Specifies the name of the forest to target for processing. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from processing. .PARAMETER IncludeDomains Specifies an array of domains to include for processing. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER ExcludeOrganizationalUnit Specifies an array of OUs to exclude from processing. .PARAMETER LimitProcessing Specifies the maximum number of OUs to process. .EXAMPLE Remove-GPOZaurrLinkEmptyOU -Forest "ContosoForest" -IncludeDomains @("domain1", "domain2") -ExcludeDomains @("domain3") -ExtendedForestInformation $info -ExcludeOrganizationalUnit @("OU=TestOU,DC=contoso,DC=com") -LimitProcessing 100 Removes GPO links from empty OUs in the "ContosoForest" forest, including domains "domain1" and "domain2" but excluding "domain3". Additional forest information is provided, and processing is limited to 100 OUs. #> [cmdletbinding(SupportsShouldProcess)] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string[]] $ExcludeOrganizationalUnit, [int] $LimitProcessing = [int32]::MaxValue ) $Processed = 0 $OrganizationalUnits = Get-GPOZaurrOrganizationalUnit -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Option Unlink -ExcludeOrganizationalUnit $ExcludeOrganizationalUnit foreach ($OU in $OrganizationalUnits) { if ($OU.Status -contains 'Unlink GPO') { if ($OU.OrganizationalUnit -in $ExcludeOrganizationalUnit) { Write-Verbose "Remove-GPOZaurrLinkEmptyOU - Processing $($OU.Organizationalunit) was skipped as it's excluded." continue } Write-Verbose "Remove-GPOZaurrLinkEmptyOU - Processing $($OU.Organizationalunit) found OU with GPOs to unlink" $Processed++ foreach ($GPO in $OU.GPO) { Write-Verbose "Remove-GPOZaurrLinkEmptyOU - Removing $($GPO.DisplayName) link from $($OU.Organizationalunit)" try { Remove-GPLink -ErrorAction Stop -Guid $GPO.GUID -Domain $GPO.DomainName -Target $OU.Organizationalunit } catch { Write-Warning "Remove-GPOZaurrLinkEmptyOU - Error removing link of $($GPO.DisplayName) from $($OU.OrganizationalUnit) error: $($_.Exception.Message)" } } if ($Processed -ge $LimitProcessing) { Write-Verbose "Remove-GPOZaurrLinkEmptyOU - Limit processing hit, stopping." break } } } } function Remove-GPOZaurrPermission { <# .SYNOPSIS Removes permissions from a Group Policy Object (GPO) for specified principals. .DESCRIPTION The Remove-GPOZaurrPermission function removes permissions from a specified GPO for the specified principals. It allows for fine-grained control over the removal of permissions based on various parameters. .PARAMETER GPOName Specifies the name of the GPO from which permissions will be removed. .PARAMETER GPOGuid Specifies the GUID of the GPO from which permissions will be removed. .PARAMETER Principal Specifies the principal(s) for which permissions will be removed. .PARAMETER PrincipalType Specifies the type of principal(s) provided. Valid values are 'DistinguishedName', 'Name', 'NetbiosName', or 'Sid'. .PARAMETER Type Specifies the type of permissions to remove. Valid values are 'Unknown', 'NotAdministrative', or 'Default'. .PARAMETER IncludePermissionType Specifies the permission types to include in the removal process. .PARAMETER ExcludePermissionType Specifies the permission types to exclude from the removal process. .PARAMETER SkipWellKnown Skips well-known permissions during the removal process. .PARAMETER SkipAdministrative Skips administrative permissions during the removal process. .PARAMETER Forest Specifies the forest in which the GPO resides. .PARAMETER ExcludeDomains Specifies the domains to exclude from the removal process. .PARAMETER IncludeDomains Specifies the domains to include in the removal process. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER LimitProcessing Specifies the maximum number of permissions to process. .EXAMPLE Remove-GPOZaurrPermission -GPOName "TestGPO" -Principal "User1" -PrincipalType "Name" -Type "Default" -Forest "Contoso" -IncludeDomains "Domain1", "Domain2" Removes default permissions for "User1" from the GPO named "TestGPO" in the "Contoso" forest for domains "Domain1" and "Domain2". .EXAMPLE Remove-GPOZaurrPermission -GPOGuid "12345678-1234-1234-1234-1234567890AB" -Principal "Group1" -PrincipalType "Sid" -Type "Unknown" -Forest "Fabrikam" -ExcludeDomains "Domain3" Removes unknown permissions for "Group1" from the GPO with GUID "12345678-1234-1234-1234-1234567890AB" in the "Fabrikam" forest excluding "Domain3". #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Global')] param( [Parameter(ParameterSetName = 'GPOName', Mandatory)] [string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID', Mandatory)] [alias('GUID', 'GPOID')][string] $GPOGuid, [string[]] $Principal, [validateset('DistinguishedName', 'Name', 'NetbiosName', 'Sid')][string] $PrincipalType = 'Sid', [validateset('Unknown', 'NotAdministrative', 'Default')][string[]] $Type = 'Default', [alias('PermissionType')][Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType, [Microsoft.GroupPolicy.GPPermissionType[]] $ExcludePermissionType, [switch] $SkipWellKnown, [switch] $SkipAdministrative, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [int] $LimitProcessing ) Begin { $Count = 0 $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation if ($Type -eq 'Unknown') { if ($SkipAdministrative -or $SkipWellKnown) { Write-Warning "Remove-GPOZaurrPermission - Using SkipAdministrative or SkipWellKnown while looking for Unknown doesn't make sense as only Unknown will be displayed." } } } Process { if ($Type -contains 'Named' -and $Principal.Count -eq 0) { Write-Warning "Remove-GPOZaurrPermission - When using type Named you need to provide names to remove. Terminating." return } foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] if ($GPOName) { $getGPOSplat = @{ Name = $GPOName Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } } elseif ($GPOGuid) { $getGPOSplat = @{ Guid = $GPOGuid Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } } else { $getGPOSplat = @{ All = $true Domain = $Domain Server = $QueryServer ErrorAction = 'SilentlyContinue' } } Get-GPO @getGPOSplat | ForEach-Object -Process { $GPOSecurity = $_.GetSecurityInfo() $getPrivPermissionSplat = @{ Principal = $Principal PrincipalType = $PrincipalType GPO = $_ SkipWellKnown = $SkipWellKnown.IsPresent SkipAdministrative = $SkipAdministrative.IsPresent IncludeOwner = $false IncludeGPOObject = $true IncludePermissionType = $IncludePermissionType ExcludePermissionType = $ExcludePermissionType ADAdministrativeGroups = $ADAdministrativeGroups SecurityRights = $GPOSecurity } if ($Type -ne 'Default') { $getPrivPermissionSplat['Type'] = $Type } [Array] $GPOPermissions = Get-PrivPermission @getPrivPermissionSplat if ($GPOPermissions.Count -gt 0) { foreach ($Permission in $GPOPermissions) { Remove-PrivPermission -Principal $Permission.PrincipalSid -PrincipalType Sid -GPOPermission $Permission -IncludePermissionType $Permission.Permission } $Count++ if ($Count -eq $LimitProcessing) { break } } } } } End { } } function Remove-GPOZaurrWMI { <# .SYNOPSIS Removes Group Policy WMI filters based on specified criteria. .DESCRIPTION This function removes WMI filters based on the provided GUIDs or names within the specified forest or domains. It retrieves WMI filters associated with the GPOs and removes them. .PARAMETER Guid Specifies an array of GUIDs of the WMI filters to be removed. .PARAMETER Name Specifies an array of names of the WMI filters to be removed. .PARAMETER Forest Specifies the forest name where the WMI filters are located. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the removal process. .PARAMETER IncludeDomains Specifies an array of domains to include in the removal process. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .EXAMPLE Remove-GPOZaurrWMI -Guid "12345678-1234-1234-1234-123456789012" Description ----------- Removes the WMI filter with the specified GUID. .EXAMPLE Remove-GPOZaurrWMI -Name "TestWMIFilter" Description ----------- Removes the WMI filter with the specified name. #> [CmdletBinding(SupportsShouldProcess)] Param ( [Guid[]] $Guid, [string[]] $Name, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) if (-not $Forest -and -not $ExcludeDomains -and -not $IncludeDomains -and -not $ExtendedForestInformation) { $IncludeDomains = $Env:USERDNSDOMAIN } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] [Array] $Objects = @( if ($Guid) { Get-GPOZaurrWMI -Guid $Guid -ExtendedForestInformation $ForestInformation -IncludeDomains $Domain } if ($Name) { Get-GPOZaurrWMI -Name $Name -ExtendedForestInformation $ForestInformation -IncludeDomains $Domain } ) $Objects | ForEach-Object -Process { if ($_.DistinguishedName) { Write-Verbose "Remove-GPOZaurrWMI - Removing WMI Filter $($_.DistinguishedName)" Remove-ADObject $_.DistinguishedName -Confirm:$false -Server $QueryServer } } } } function Repair-GPOZaurrBrokenLink { <# .SYNOPSIS Removes any link to GPO that no longer exists. .DESCRIPTION Removes any link to GPO that no longer exists. It scans all site, organizational unit or domain root making sure every single link that may be linking to GPO that doesn't exists anymore is gone. .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER LimitProcessing Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed .EXAMPLE Repair-GPOZaurrBrokenLink -Verbose -LimitProcessing 1 -WhatIf .EXAMPLE Repair-GPOZaurrBrokenLink -Verbose -IncludeDomains ad.evotec.pl -LimitProcessing 1 -WhatIf .NOTES General notes #> [cmdletBinding(SupportsShouldProcess)] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [int] $LimitProcessing ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Extended $Links = Get-GPOZaurrBrokenLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation $Cache = @{} foreach ($Link in $Links) { if (-not $Cache[$Link.DistinguishedName]) { $Cache[$Link.DistinguishedName] = [System.Collections.Generic.List[PSCustomObject]]::new() } $Cache[$Link.DistinguishedName].Add($Link) } $Count = 0 foreach ($Key in $Cache.Keys) { $Count++ Write-Verbose "Repair-GPOZaurrBrokenLink - processing [$Count/$($Cache.Keys.Count)] $Key " $Domain = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Key $Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $Object = Get-ADObject -Identity $Key -Server $Server -Properties gpLink $MatchLinks = [Regex]::Matches($Object.gpLink, '(?<=LDAP:\/\/)(.*?)(?=])').Value $Found = $false $FixedLinks = foreach ($Match in $MatchLinks) { $SplittedValue = $Match -split ';' $GPODN = $SplittedValue[0] if ($Cache[$Key].GPODistinguishedName -notcontains $GPODN) { "[LDAP://$Match]" Write-Verbose "Repair-GPOZaurrBrokenLink - legitimate link to GPO $GPODN ($Key)" } else { $Found = $true Write-Verbose "Repair-GPOZaurrBrokenLink - preparing for removal link to $GPODN ($Key)" } } if ($Found) { $NewGpLink = $($FixedLinks -join '') if ($NewGpLink) { try { Write-Verbose "Repair-GPOZaurrBrokenLink - setting gpLink to $Key - $NewGPLink" Set-ADObject -Identity $Key -Server $Server -Replace @{ gPLink = $NewGpLink } -ErrorAction Stop } catch { Write-Warning "Repair-GPOZaurrBrokenLink - setting gpLink to $Key - $NewGpLink failed! Error $($_.Exception.Message)" } } else { try { Write-Verbose "Repair-GPOZaurrBrokenLink - clearing gpLink for $Key (no other links)" Set-ADObject -Identity $Key -Server $Server -Clear gPLink -ErrorAction Stop } catch { Write-Warning "Repair-GPOZaurrBrokenLink - clearing gpLink for $Key failed! Error $($_.Exception.Message)" } } if ($LimitProcessing -eq $Count) { break } } } } function Repair-GPOZaurrNetLogonOwner { <# .SYNOPSIS Sets new owner to each file in NetLogon share. .DESCRIPTION Sets new owner to each file in NetLogon share. .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER Principal Provide named owner. If not provided default S-1-5-32-544 is used. .PARAMETER LimitProcessing Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed .EXAMPLE Repair-GPOZaurrNetLogonOwner -WhatIf -Verbose -IncludeDomains ad.evotec.pl .NOTES General notes #> [cmdletBinding(SupportsShouldProcess)] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string] $Principal = 'S-1-5-32-544', [int] $LimitProcessing = [int32]::MaxValue ) $Identity = Convert-Identity -Identity $Principal -Verbose:$false if ($Identity.Error) { Write-Warning "Repair-GPOZaurrNetLogonOwner - couldn't convert Identity $Principal to desired name. Error: $($Identity.Error)" return } $Principal = $Identity.Name $getGPOZaurrNetLogonSplat = @{ OwnerOnly = $true Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation } Get-GPOZaurrNetLogon @getGPOZaurrNetLogonSplat -Verbose | Where-Object { if ($_.OwnerSid -ne 'S-1-5-32-544') { $_ } } | Select-Object -First $LimitProcessing | ForEach-Object { if ($PSCmdlet.ShouldProcess($_.FullName, "Setting NetLogon Owner to $($Principal)")) { Set-FileOwner -JustPath -Path $_.FullName -Owner $Principal -Verbose:$true -WhatIf:$WhatIfPreference } } } function Repair-GPOZaurrPermission { <# .SYNOPSIS Repairs permissions for Group Policy Objects (GPOs) based on specified criteria. .DESCRIPTION The Repair-GPOZaurrPermission function repairs permissions for GPOs based on the specified criteria. It analyzes the permissions of GPOs and adds necessary permissions if they are missing. .PARAMETER Type Specifies the type of permissions to repair. Valid values are 'AuthenticatedUsers', 'Unknown', 'System', 'Administrative', and 'All'. .PARAMETER Forest Specifies the forest name to analyze GPO permissions. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the analysis. .PARAMETER IncludeDomains Specifies an array of domains to include in the analysis. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER LimitProcessing Specifies the maximum number of GPOs to process. .EXAMPLE Repair-GPOZaurrPermission -Type 'All' -Forest 'ContosoForest' -IncludeDomains @('Domain1', 'Domain2') -ExcludeDomains @('Domain3') -ExtendedForestInformation $info -LimitProcessing 100 Repairs permissions for all types of users in the 'ContosoForest' forest, including only 'Domain1' and 'Domain2' while excluding 'Domain3', with extended forest information and processing a maximum of 100 GPOs. #> [cmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][ValidateSet('AuthenticatedUsers', 'Unknown', 'System', 'Administrative', 'All')][string[]] $Type, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [int] $LimitProcessing = [int32]::MaxValue ) Get-GPOZaurrPermissionAnalysis -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains | Where-Object { $RequiresProcessing = $false if ($_.Status -eq $false) { if ($Type -contains 'System' -or $Type -contains 'All') { if ($_.System -eq $false) { $RequiresProcessing = $true } } if ($Type -contains 'Administrative' -or $Type -contains 'All') { if ($_.Administrative -eq $false) { $RequiresProcessing = $true } } if ($Type -contains 'AuthenticatedUsers' -or $Type -contains 'All') { if ($_.AuthenticatedUsers -eq $false) { $RequiresProcessing = $true } } if ($Type -contains 'Unknown' -or $Type -contains 'All') { if ($_.Unknown -eq $true) { $RequiresProcessing = $true } } if ($RequiresProcessing -eq $true) { $_ } } } | Select-Object -First $LimitProcessing | ForEach-Object { $GPO = $_ if ($GPO.Status -eq $false) { if ($GPO.System -eq $false) { Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName } if ($GPO.Administrative -eq $false) { Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName } if ($GPO.AuthenticatedUsers -eq $false) { Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName } if ($GPO.Unknown -eq $true) { Remove-GPOZaurrPermission -Type Unknown -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName } } } } function Repair-GPOZaurrPermissionConsistency { <# .SYNOPSIS Repairs permission consistency for Group Policy Objects (GPOs) in a specified domain or forest. .DESCRIPTION The Repair-GPOZaurrPermissionConsistency function repairs permission consistency for GPOs in a specified domain or forest. It checks for inconsistencies in GPO permissions and attempts to make them consistent. .PARAMETER GPOName Specifies the name of the GPO to repair. .PARAMETER GPOGuid Specifies the GUID of the GPO to repair. .PARAMETER Forest Specifies the forest where the GPOs are located. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the repair process. .PARAMETER IncludeDomains Specifies an array of domains to include in the repair process. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .PARAMETER LimitProcessing Specifies the maximum number of GPOs to process. .EXAMPLE Repair-GPOZaurrPermissionConsistency -GPOName "ExampleGPO" -Forest "example.com" Repairs permission consistency for the GPO named "ExampleGPO" in the "example.com" forest. .EXAMPLE Repair-GPOZaurrPermissionConsistency -GPOGuid "12345678-1234-1234-1234-1234567890AB" -ExcludeDomains @("domain1", "domain2") -LimitProcessing 5 Repairs permission consistency for the GPO with the specified GUID, excluding domains "domain1" and "domain2", and processing a maximum of 5 GPOs. #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'GPOName')][string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [int] $LimitProcessing = [int32]::MaxValue ) $ConsistencySplat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation Verbose = $VerbosePreference } if ($GPOName) { $ConsistencySplat['GPOName'] = $GPOName } elseif ($GPOGuid) { $ConsistencySplat['GPOGuid'] = $GPOGUiD } else { $ConsistencySplat['Type'] = 'Inconsistent' } Get-GPOZaurrPermissionConsistency @ConsistencySplat -IncludeGPOObject | Where-Object { if ($_.ACLConsistent -eq $false) { $_ } } | Select-Object -First $LimitProcessing | ForEach-Object { if ($PSCmdlet.ShouldProcess($_.DisplayName, "Reparing GPO permissions consistency in domain $($_.DomainName)")) { try { $_.IncludeGPOObject.MakeAclConsistent() } catch { $ErrorMessage = $_.Exception.Message Write-Warning "Repair-GPOZaurrPermissionConsistency - Failed to set consistency: $($ErrorMessage)." } } } } function Restore-GPOZaurr { <# .SYNOPSIS Restores Group Policy Objects (GPOs) from a specified backup folder. .DESCRIPTION Restores Group Policy Objects (GPOs) from a specified backup folder. This function allows restoring GPOs with the option to provide a new display name for the GPO. .PARAMETER BackupFolder The path to the folder containing the GPO backups. .PARAMETER DisplayName The display name of the GPO to be restored. .PARAMETER NewDisplayName (Optional) The new display name for the restored GPO. .PARAMETER Domain (Optional) The domain name where the GPO should be restored. .PARAMETER SkipBackupSummary (Switch) Skip displaying the backup summary information. .EXAMPLE Restore-GPOZaurr -BackupFolder 'C:\GPOBackups' -DisplayName 'TestGPO' .EXAMPLE Restore-GPOZaurr -BackupFolder 'C:\GPOBackups' -DisplayName 'TestGPO' -NewDisplayName 'NewTestGPO' -Domain 'example.com' .NOTES General notes #> [cmdletBinding()] param( [parameter(Mandatory)][string] $BackupFolder, [alias('Name')][string] $DisplayName, [string] $NewDisplayName, [string] $Domain, [switch] $SkipBackupSummary ) if ($BackupFolder) { if (Test-Path -LiteralPath $BackupFolder) { if ($DisplayName) { if (-not $SkipBackupSummary) { $BackupSummary = Get-GPOZaurrBackupInformation -BackupFolder $BackupFolder if ($Domain) { [Array] $FoundGPO = $BackupSummary | Where-Object { $_.DisplayName -eq $DisplayName -and $_.DomainName -eq $Domain } } else { [Array] $FoundGPO = $BackupSummary | Where-Object { $_.DisplayName -eq $DisplayName } } foreach ($GPO in $FoundGPO) { if ($NewDisplayName) { Import-GPO -Path $BackupFolder -BackupId $GPO.ID -Domain $GPO.Domain -TargetName $NewDisplayName -CreateIfNeeded } else { Write-Verbose "Restore-GPOZaurr - Restoring GPO $($GPO.DisplayName) from $($GPO.DomainName) / BackupId: $($GPO.ID)" try { Restore-GPO -Path $BackupFolder -BackupId $GPO.ID -Domain $GPO.DomainName } catch { Write-Warning "Restore-GPOZaurr - Restoring GPO $($GPO.DisplayName) from $($GPO.DomainName) failed: $($_.Exception.Message)" } } } } else { if ($Domain) { Write-Verbose "Restore-GPOZaurr - Restoring GPO $($Name) from $($Domain)" try { Restore-GPO -Path $BackupFolder -Name $Name -Domain $Domain } catch { Write-Warning "Restore-GPOZaurr - Restoring GPO $($Name) from $($Domain) failed: $($_.Exception.Message)" } } else { Write-Verbose "Restore-GPOZaurr - Restoring GPO $($Name)" try { Restore-GPO -Path $BackupFolder -Name $Name } catch { Write-Warning "Restore-GPOZaurr - Restoring GPO $($Name) failed: $($_.Exception.Message)" } } } } else { $BackupSummary = Get-GPOZaurrBackupInformation -BackupFolder $BackupFolder foreach ($GPO in $BackupSummary) { Write-Verbose "Restore-GPOZaurr - Restoring GPO $($GPO.DisplayName) from $($GPO.DomainName) / BackupId: $($GPO.ID)" try { Restore-GPO -Path $BackupFolder -Domain $GPO.DomainName -BackupId $GPO.ID } catch { Write-Warning "Restore-GPOZaurr - Restoring GPO $($GPO.DisplayName) from $($GPO.DomainName) failed: $($_.Exception.Message)" } } } } else { Write-Warning "Restore-GPOZaurr - BackupFolder incorrect ($BackupFolder)" } } } function Save-GPOZaurrFiles { <# .SYNOPSIS Exports GPO XML data to files and saves it to a given path .DESCRIPTION Exports GPO XML data to files and saves it to a given path .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER GPOPath Path where to save XML files from GPOReport .PARAMETER DeleteExisting Delete existing files before saving new ones .EXAMPLE Save-GPOZaurrFiles -GPOPath 'C:\Support\GitHub\GpoZaurr\Ignore\GPOExportEvotec' -DeleteExisting -Verbose .NOTES General notes #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string[]] $GPOPath, [switch] $DeleteExisting ) if ($GPOPath) { if ($DeleteExisting) { $Test = Test-Path -LiteralPath $GPOPath if ($Test) { Write-Verbose "Save-GPOZaurrFiles - Removing existing content in $GPOPath" Remove-Item -LiteralPath $GPOPath -Recurse } } $null = New-Item -ItemType Directory -Path $GPOPath -Force Write-Verbose "Save-GPOZaurrFiles - Gathering GPO data" $Count = 0 $GPOs = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($GPO in $GPOS) { $Count++ Write-Verbose "Save-GPOZaurrFiles - Processing GPO ($Count/$($GPOS.Count)) $($GPO.DomainName) | $($GPO.DisplayName)" $XMLContent = Get-GPOReport -ID $GPO.Guid -ReportType XML -Domain $GPO.DomainName $GPODOmainFolder = [io.path]::Combine($GPOPath, $GPO.DomainName) if (-not (Test-Path -Path $GPODOmainFolder)) { $null = New-Item -ItemType Directory -Path $GPODOmainFolder -Force } $Path = [io.path]::Combine($GPODOmainFolder, "$($GPO.Guid).xml") $XMLContent | Set-Content -LiteralPath $Path -Force -Encoding Unicode } $GPOListPath = [io.path]::Combine($GPOPath, "GPOList.xml") $GPOs | Export-Clixml -Depth 5 -Path $GPOListPath } } function Set-GPOOwner { <# .SYNOPSIS Used within Invoke-GPOZaurrPermission only. Set new group policy owner. .DESCRIPTION Used within Invoke-GPOZaurrPermission only. Set new group policy owner. .PARAMETER Type Choose Owner Type. When chosing Administrative Type, owner will be set to Domain Admins for current GPO domain. When Default is set Owner will be set to Principal given in another parameter. .PARAMETER Principal Choose Owner Name to set for Group Policy .EXAMPLE Invoke-GPOZaurrPermission -Verbose -SearchBase 'OU=Computers,OU=Production,DC=ad,DC=evotec,DC=xyz' { Set-GPOOwner -Type Administrative Remove-GPOPermission -Type NotAdministrative, NotWellKnownAdministrative -IncludePermissionType GpoEdit, GpoEditDeleteModifySecurity Add-GPOPermission -Type Administrative -IncludePermissionType GpoEditDeleteModifySecurity Add-GPOPermission -Type WellKnownAdministrative -IncludePermissionType GpoEditDeleteModifySecurity } -WhatIf .NOTES General notes #> [cmdletBinding()] param( [validateset('Administrative', 'Default')][string] $Type = 'Default', [string] $Principal ) if ($Type -eq 'Default') { if ($Principal) { @{ Action = 'Owner' Type = 'Default' Principal = $Principal } } } elseif ($Type -eq 'Administrative') { @{ Action = 'Owner' Type = 'Administrative' Principal = '' } } } function Set-GPOZaurrOwner { <# .SYNOPSIS Sets GPO Owner to Domain Admins or other choosen account .DESCRIPTION Sets GPO Owner to Domain Admins or other choosen account. GPO Owner is set in AD and SYSVOL unless specified otherwise. If account doesn't require change, no change is done. .PARAMETER Type Unknown - finds unknown Owners and sets them to Administrative (Domain Admins) or chosen principal NotMatching - find administrative groups only and if sysvol and gpo doesn't match - replace with chosen principal or Domain Admins if not specified Inconsistent - same as not NotMatching NotAdministrative - combination of Unknown/NotMatching and NotAdministrative - replace with chosen principal or Domain Admins if not specified All - if Owner is known it checks if it's Administrative, if it sn't it fixes that. If owner is unknown it fixes it .PARAMETER GPOName Name of GPO. By default all GPOs are targetted .PARAMETER GPOGuid GUID of GPO. By default all GPOs are targetted .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER Principal Parameter description .PARAMETER SkipSysvol Set GPO Owner only in Active Directory. By default GPO Owner is being set in both places .PARAMETER LimitProcessing Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed .PARAMETER Force Pushes new owner regardless if it's already set or not .EXAMPLE Set-GPOZaurrOwner -Type All -Verbose -WhatIf -LimitProcessing 2 .NOTES General notes #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Type')] param( [Parameter(ParameterSetName = 'Type', Mandatory)] [validateset('Unknown', 'NotAdministrative', 'NotMatching', 'Inconsistent', 'All')][string] $Type, [Parameter(ParameterSetName = 'Named')][string] $GPOName, [Parameter(ParameterSetName = 'Named')][alias('GUID', 'GPOID')][string] $GPOGuid, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [alias('ForestName')][string] $Forest, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [string[]] $ExcludeDomains, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [alias('Domain', 'Domains')][string[]] $IncludeDomains, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [System.Collections.IDictionary] $ExtendedForestInformation, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [string] $Principal, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [switch] $SkipSysvol, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [int] $LimitProcessing = [int32]::MaxValue, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [alias('Exclusion', 'Exclusions')][string[]] $ApprovedOwner, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [validateset('OnlyAD', 'OnlyFileSystem')][string] $Action, [Parameter(ParameterSetName = 'Type')] [Parameter(ParameterSetName = 'Named')] [switch] $Force ) Begin { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } Process { $getGPOZaurrOwnerSplat = @{ IncludeSysvol = -not $SkipSysvol.IsPresent Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation ADAdministrativeGroups = $ADAdministrativeGroups Verbose = $VerbosePreference SkipBroken = $true ApprovedOwner = $ApprovedOwner } if ($GPOName) { $getGPOZaurrOwnerSplat['GPOName'] = $GPOName } elseif ($GPOGuid) { $getGPOZaurrOwnerSplat['GPOGuid'] = $GPOGUiD } $Count = 0 Get-GPOZaurrOwner @getGPOZaurrOwnerSplat | Where-Object { $Count++ if ($Force) { Write-Verbose "Set-GPOZaurrOwner - Force was used to push new owner to $($_.DisplayName) from domain: $($_.DomainName) - owner $($_.Owner) / sysvol owner $($_.SysvolOwner)." $_ } else { if ($Type -eq 'NotAdministrative') { if ($_.Status -contains 'NotAdministrative' -and $_.Status -notcontains 'Approved') { $_ } elseif ($_.Status -contains 'Inconsistent') { $_ } } elseif ($Type -eq 'Unknown') { if (-not $_.Owner -or (-not $_.SysvolOwner -and -not $SkipSysvol)) { $_ } } elseif ($Type -in 'NotMatching', 'Inconsistent') { if ($SkipSysvol) { Write-Verbose "Set-GPOZaurrOwner - Detected mismatch GPO: $($_.DisplayName) from domain: $($_.DomainName) - owner $($_.Owner) / sysvol owner $($_.SysvolOwner). SysVol scanning is disabled. Skipping." } else { if ($_.Status -contains 'Inconsistent') { $_ } } } else { if ($_.Owner) { if ($_.Status -contains 'NotAdministrative' -and $_.Status -notcontains 'Approved') { $_ } elseif ($_.Status -contains 'Inconsistent') { $_ } } else { $_ } } } } | Select-Object -First $LimitProcessing | ForEach-Object -Process { $GPO = $_ if (-not $Principal) { $DefaultPrincipal = $ADAdministrativeGroups["$($_.DomainName)"]['DomainAdmins'] } else { $DefaultPrincipal = $Principal } if ($Action -eq 'OnlyAD') { Write-Verbose "Set-GPOZaurrOwner - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) (SID: $($GPO.OwnerSID)) to $DefaultPrincipal" Set-ADACLOwner -ADObject $GPO.DistinguishedName -Principal $DefaultPrincipal -Verbose:$false -WhatIf:$WhatIfPreference } elseif ($Action -eq 'OnlyFileSystem') { if (-not $SkipSysvol) { Write-Verbose "Set-GPOZaurrOwner - Changing Sysvol Owner GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.SysvolOwner) (SID: $($GPO.SysvolSid)) to $DefaultPrincipal" Set-FileOwner -JustPath -Path $GPO.SysvolPath -Owner $DefaultPrincipal -Verbose:$true -WhatIf:$WhatIfPreference } } else { Write-Verbose "Set-GPOZaurrOwner - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) (SID: $($GPO.OwnerSID)) to $DefaultPrincipal" Set-ADACLOwner -ADObject $GPO.DistinguishedName -Principal $DefaultPrincipal -Verbose:$false -WhatIf:$WhatIfPreference if (-not $SkipSysvol) { Write-Verbose "Set-GPOZaurrOwner - Changing Sysvol Owner GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.SysvolOwner) (SID: $($GPO.SysvolSid)) to $DefaultPrincipal" Set-FileOwner -JustPath -Path $GPO.SysvolPath -Owner $DefaultPrincipal -Verbose:$true -WhatIf:$WhatIfPreference } } } } End { } } function Set-GPOZaurrStatus { <# .SYNOPSIS Enables or disables user/computer section of Group Policy. .DESCRIPTION Enables or disables user/computer section of Group Policy. .PARAMETER GPOName Provide Group Policy Name .PARAMETER GPOGuid Provide Group Policy GUID .PARAMETER Status Choose a status for provided Group Policy .PARAMETER Forest Choose forest to target. .PARAMETER ExcludeDomains Exclude domains from trying to find Group Policy Name or GUID .PARAMETER IncludeDomains Include domain (one or more) to find Group Policy Name or GUID .PARAMETER ExtendedForestInformation Provide Extended Forest Information .EXAMPLE Set-GPOZaurrStatus -Name 'TEST | Empty GPO - AD.EVOTEC.PL CrossDomain GPO' -Status AllSettingsEnabled -Verbose .EXAMPLE Set-GPOZaurrStatus -Name 'TEST | Empty GPO - AD.EVOTEC.PL CrossDomain GPO' -DomainName ad.evotec.pl -Status AllSettingsEnabled -Verbose .NOTES General notes #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'GPOName')] param( [alias('Name', 'DisplayName')][Parameter(ParameterSetName = 'GPOName', Mandatory)][string] $GPOName, [Parameter(ParameterSetName = 'GPOGUID', Mandatory)][alias('GUID', 'GPOID')][string] $GPOGuid, [Parameter(Mandatory)][Microsoft.GroupPolicy.GpoStatus] $Status, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains', 'DomainName')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { } Process { $getGPOZaurrSplat = @{ Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation GpoName = $GPOName GPOGUID = $GPOGuid } Remove-EmptyValue -Hashtable $getGPOZaurrSplat Get-GPOZaurr @getGPOZaurrSplat | ForEach-Object { $GPO = $_ if ($Status -eq [Microsoft.GroupPolicy.GpoStatus]::AllSettingsEnabled) { $Text = "Enabling computer and user settings in domain $($GPO.DomainName)" } elseif ($Status -eq [Microsoft.GroupPolicy.GpoStatus]::ComputerSettingsDisabled) { $Text = "Disabling computer setings in domain $($GPO.DomainName)" } elseif ($Status -eq [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled) { $Text = "Disabling user setings in domain $($GPO.DomainName)" } else { $Text = "Disabling computer user settings in domain $($GPO.DomainName)" } if ($PSCmdlet.ShouldProcess($GPO.DisplayName, $Text)) { try { $GPO.GPOObject.GpoStatus = $Status } catch { Write-Warning -Message "Set-GPOZaurrStatus - Couldn't set $($GPO.DisplayName) / $($GPO.DomainName) to $Status. Error $($_.Exception.Message)" } } } } } function Skip-GroupPolicy { <# .SYNOPSIS Used within ScriptBlocks only. Allows to exclude Group Policy from being affected by fixes .DESCRIPTION Used within ScriptBlocks only. Allows to exclude Group Policy from being affected by fixes. Only some commands support it. The goal is to support all cmdlets. .PARAMETER Name Define Group Policy Name to skip .PARAMETER DomaiName Define DomainName where Group Policy is located. Otherwise each domain will be checked and skipped if found with same name. .EXAMPLE Optimize-GPOZaurr -All -WhatIf -Verbose -LimitProcessing 2 { Skip-GroupPolicy -Name 'TEST | Drive Mapping 1' Skip-GroupPolicy -Name 'TEST | Drive Mapping 2' } .EXAMPLE Remove-GPOZaurr -Type Empty, Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -BackupDated -LimitProcessing 2 -Verbose -WhatIf { Skip-GroupPolicy -Name 'TEST | Drive Mapping 1' Skip-GroupPolicy -Name 'TEST | Drive Mapping 2' -DomaiName 'ad.evotec.pl' } .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'Name')] param( #[ValidateSet('GPOList')][string] $Type, [parameter(ParameterSetName = 'Name')][alias('GpoName', 'DisplayName')][string] $Name, [parameter(ParameterSetName = 'Guid')] [alias('ID')][string] $GUID, [parameter(ParameterSetName = 'Name')] [parameter(ParameterSetName = 'Guid')] [string] $DomaiName ) $Output = @{ Name = $Name ID = $GUID DomainName = $DomaiName } Remove-EmptyValue -Hashtable $Output [PSCustomObject] $Output } $ModuleFunctions = @{ GroupPolicy = @{ 'Add-GPOPermission' = '' 'Add-GPOZaurrPermission' = '' 'Backup-GPOZaurr' = '' 'Clear-GPOZaurrSysvolDFSR' = '' 'ConvertFrom-CSExtension' = '' 'Find-CSExtension' = '' 'Get-GPOZaurr' = '' 'Get-GPOZaurrAD' = '' 'Get-GPOZaurrBackupInformation' = '' 'Get-GPOZaurrBroken' = 'Get-GPOZaurrSysvol' 'Get-GPOZaurrDictionary' = '' 'Get-GPOZaurrDuplicateObject' = '' 'Get-GPOZaurrFiles' = '' 'Get-GPOZaurrFilesPolicyDefinition' = 'Get-GPOZaurrFilesPolicyDefinitions' 'Get-GPOZaurrFolders' = '' 'Get-GPOZaurrInheritance' = '' 'Get-GPOZaurrLegacyFiles' = '' 'Get-GPOZaurrLink' = '' 'Get-GPOZaurrLinkSummary' = '' 'Get-GPOZaurrNetLogon' = '' 'Get-GPOZaurrOwner' = '' 'Get-GPOZaurrPassword' = '' 'Get-GPOZaurrPermission' = '' 'Get-GPOZaurrPermissionConsistency' = '' 'Get-GPOZaurrPermissionRoot' = '' 'Get-GPOZaurrPermissionSummary' = '' 'Get-GPOZaurrSysvolDFSR' = '' 'Get-GPOZaurrWMI' = '' 'Invoke-GPOZaurr' = 'Show-GPOZaurr', 'Show-GPO' 'Invoke-GPOZaurrPermission' = '' 'Invoke-GPOZaurrSupport' = '' 'New-GPOZaurrWMI' = '' 'Optimize-GPOZaurr' = '' 'Remove-GPOPermission' = '' 'Remove-GPOZaurr' = '' 'Remove-GPOZaurrBroken' = 'Remove-GPOZaurrOrphaned' 'Remove-GPOZaurrDuplicateObject' = '' 'Remove-GPOZaurrFolders' = '' 'Remove-GPOZaurrLegacyFiles' = '' 'Remove-GPOZaurrPermission' = '' 'Remove-GPOZaurrWMI' = '' 'Repair-GPOZaurrNetLogonOwner' = '' 'Repair-GPOZaurrPermissionConsistency' = '' 'Restore-GPOZaurr' = '' 'Save-GPOZaurrFiles' = '' 'Set-GPOOwner' = '' 'Set-GPOZaurrOwner' = '' 'Find-GPO' = '' 'Get-GPOZaurrFilesPolicyDefinitions' = '' 'Get-GPOZaurrSysvol' = '' 'Remove-GPOZaurrOrphaned' = '' 'Show-GPO' = '' 'Show-GPOZaurr' = '' } ActiveDirectory = @{ 'Add-GPOPermission' = '' 'Add-GPOZaurrPermission' = '' 'Backup-GPOZaurr' = '' 'Clear-GPOZaurrSysvolDFSR' = '' 'ConvertFrom-CSExtension' = '' 'Find-CSExtension' = '' 'Get-GPOZaurr' = '' 'Get-GPOZaurrAD' = '' 'Get-GPOZaurrBackupInformation' = '' 'Get-GPOZaurrBroken' = 'Get-GPOZaurrSysvol' 'Get-GPOZaurrDictionary' = '' 'Get-GPOZaurrDuplicateObject' = '' 'Get-GPOZaurrFiles' = '' 'Get-GPOZaurrFilesPolicyDefinition' = 'Get-GPOZaurrFilesPolicyDefinitions' 'Get-GPOZaurrFolders' = '' 'Get-GPOZaurrInheritance' = '' 'Get-GPOZaurrLegacyFiles' = '' 'Get-GPOZaurrLink' = '' 'Get-GPOZaurrLinkSummary' = '' 'Get-GPOZaurrNetLogon' = '' 'Get-GPOZaurrOwner' = '' 'Get-GPOZaurrPassword' = '' 'Get-GPOZaurrPermission' = '' 'Get-GPOZaurrPermissionConsistency' = '' 'Get-GPOZaurrPermissionRoot' = '' 'Get-GPOZaurrPermissionSummary' = '' 'Get-GPOZaurrSysvolDFSR' = '' 'Get-GPOZaurrWMI' = '' 'Invoke-GPOZaurr' = 'Show-GPOZaurr', 'Show-GPO' 'Invoke-GPOZaurrPermission' = '' 'Invoke-GPOZaurrSupport' = '' 'New-GPOZaurrWMI' = '' 'Optimize-GPOZaurr' = '' 'Remove-GPOPermission' = '' 'Remove-GPOZaurr' = '' 'Remove-GPOZaurrBroken' = 'Remove-GPOZaurrOrphaned' 'Remove-GPOZaurrDuplicateObject' = '' 'Remove-GPOZaurrFolders' = '' 'Remove-GPOZaurrLegacyFiles' = '' 'Remove-GPOZaurrPermission' = '' 'Remove-GPOZaurrWMI' = '' 'Repair-GPOZaurrNetLogonOwner' = '' 'Repair-GPOZaurrPermissionConsistency' = '' 'Restore-GPOZaurr' = '' 'Save-GPOZaurrFiles' = '' 'Set-GPOOwner' = '' 'Set-GPOZaurrOwner' = '' 'Find-GPO' = '' 'Get-GPOZaurrFilesPolicyDefinitions' = '' 'Get-GPOZaurrSysvol' = '' 'Remove-GPOZaurrOrphaned' = '' 'Show-GPO' = '' 'Show-GPOZaurr' = '' } } [Array] $FunctionsAll = 'Add-GPOPermission', 'Add-GPOZaurrPermission', 'Backup-GPOZaurr', 'Clear-GPOZaurrSysvolDFSR', 'ConvertFrom-CSExtension', 'Export-GPOZaurrContent', 'Find-CSExtension', 'Get-GPOZaurr', 'Get-GPOZaurrAD', 'Get-GPOZaurrBackupInformation', 'Get-GPOZaurrBroken', 'Get-GPOZaurrBrokenLink', 'Get-GPOZaurrDictionary', 'Get-GPOZaurrDuplicateObject', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinition', 'Get-GPOZaurrFolders', 'Get-GPOZaurrInheritance', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrMissingFiles', 'Get-GPOZaurrNetLogon', 'Get-GPOZaurrOrganizationalUnit', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionAnalysis', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrPermissionIssue', 'Get-GPOZaurrPermissionRoot', 'Get-GPOZaurrPermissionSummary', 'Get-GPOZaurrRedirect', 'Get-GPOZaurrSysvolDFSR', 'Get-GPOZaurrUpdates', 'Get-GPOZaurrWMI', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrContent', 'Invoke-GPOZaurrPermission', 'Invoke-GPOZaurrSupport', 'New-GPOZaurrWMI', 'Optimize-GPOZaurr', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrBroken', 'Remove-GPOZaurrDuplicateObject', 'Remove-GPOZaurrFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrLinkEmptyOU', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrBrokenLink', 'Repair-GPOZaurrNetLogonOwner', 'Repair-GPOZaurrPermission', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Set-GPOOwner', 'Set-GPOZaurrOwner', 'Set-GPOZaurrStatus', 'Skip-GroupPolicy' [Array] $AliasesAll = 'Find-GPO', 'Get-GPOZaurrFilesPolicyDefinitions', 'Get-GPOZaurrSysvol', 'Remove-GPOZaurrOrphaned', 'Show-GPO', 'Show-GPOZaurr' $AliasesToRemove = [System.Collections.Generic.List[string]]::new() $FunctionsToRemove = [System.Collections.Generic.List[string]]::new() foreach ($Module in $ModuleFunctions.Keys) { try { Import-Module -Name $Module -ErrorAction Stop } catch { foreach ($Function in $ModuleFunctions[$Module].Keys) { $FunctionsToRemove.Add($Function) $ModuleFunctions[$Module][$Function] | ForEach-Object { if ($_) { $AliasesToRemove.Add($_) } } } } } $FunctionsToLoad = foreach ($Function in $FunctionsAll) { if ($Function -notin $FunctionsToRemove) { $Function } } $AliasesToLoad = foreach ($Alias in $AliasesAll) { if ($Alias -notin $AliasesToRemove) { $Alias } } Export-ModuleMember -Function @($FunctionsToLoad) -Alias @($AliasesToLoad) # SIG # Begin signature block # MIItqwYJKoZIhvcNAQcCoIItnDCCLZgCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAymWEYQgXQT2J+ # 5u3cRm3XOJCdamSGWaHEbmNqypQmcqCCJq4wggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ # CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw # MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k # jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9 # NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9 # URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY # E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS # 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa # wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w # c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR # Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2 # 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK # ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC # AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2 # O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH # BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6 # mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/ # SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY # gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9 # kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ # 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew # Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm # Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA # SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr # y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR # ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu # v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnp # BOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5 # NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEy # NTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYD # VQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQAD # ggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBD # Er4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo # 76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rO # H3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9R # eNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgX # j3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTV # DSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16J # idj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/C # acBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93N # Rxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1X # CB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMB # AAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUB # Af8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s # BwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9X # LAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwz # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1l # U3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhho # dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNl # cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZU # aW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQS # R9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWB # b0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDC # zFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1 # UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3Wp # ByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGE # sshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8 # a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNF # YagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7Q # EY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgE # deoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/J # ceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHXzCCBUegAwIBAgIQB8JSdCgUotar/iTq # F+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAwMDAwMFoX # DTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1pa2/FgsOz # dzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYDVQQDDBhQ # cnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmVOrRBVRQA # 8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVEh0C/Daeh # vxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNdGVXRYOLn # 47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0235CN4Rr # W+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuAo3+jVB8w # iUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw8/FNzGNP # lAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP0ib98XLf # QpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxiW4oHYO28 # eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFKRqwvSSr4 # fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKABGoIqSW0 # 5nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQIDAQABo4IC # AzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE # FHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK # BggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz # ODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j # cmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3 # dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu # Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB # CwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50ZHzoWs6E # BlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa1W47YSrc # 5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2CbE3JroJ # f2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0djvQSx51 # 0MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N9E8hUVev # xALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgizpwBasrx # h6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38wwtaJ3KY # D0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Yn8kQMB6/ # Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Zn3exUAKq # G+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe6nB6bSYH # v8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGCBlMwggZP # AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw # PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2 # IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgBZQMEAgEF # AKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgor # BgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3 # DQEJBDEiBCBry5y4Ec+F7/mp8CfZ7c6BQMSa4VJ7lzXOZBsieFJugzANBgkqhkiG # 9w0BAQEFAASCAgBOnEST9TjWgxmZ3oReQjjRlTy5oXKT38CclALLbaDJfGh0jFxj # mUEvsMqG/+rxw4wkzgYC9aXKKWc+eflatFnbpTzIhf3f2JuEmWoojzAo8yEdhd/R # YEOP8tk2h2+L+InpqO0ioMIKkGiQuLh/noLMavc4Rqik1t+XYEHv6WqeL1dlTL9K # zZ+zzpYyfDzs3E+mhh0nJDtnNHWSz3RVOL8bkas5aFPul8bUn8iI+KkaD8JmIJOh # YtajYW6AxjZx7PqIw53MrqHLfjIwNUVV6Hq5A7qdWyooKcXf3q+/ik6Cd9MPb3N0 # az0CcsuwgEaLlSQ+N5/JpRklZTq9XKt5k2RS3o1dGfWyowRBG5qmKf3GPpBDCIxq # jFVCTEA0hgOeqLjL3k7sNJig8rqcZhc8uY815aaIYWXZQlLompaBUGYbxakr1Esb # r/uQq3EJuUEOgubWhZFoBgG5n7DJMYkOS+2blo5FEIH9lQjxx4qZEy88I+3v1QWN # Q9Kx+G68seSU3BK7JmkSYIuJvOeuH2iZAqsl2ZGadY9HXhFjK3Z2cvxRDuycONVG # 8RSC/D7vFwBds+MD86gX/0Ac/o/xll9R0IH5L8Jj4755iq0eqi+j2V/o5Gm42x0b # izDrqXSx66dqCan7GXdkonFaw+DEHVyCZVs0BsLnD3rQidaYjVynQA8Y4KGCAyAw # ggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYD # VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH # NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTj # MwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG # CSqGSIb3DQEJBTEPFw0yNDExMjAxOTQ3NTRaMC8GCSqGSIb3DQEJBDEiBCB/q4KQ # qnSZTKgIOVMoitouUz7WyVkiFqDxXjqjHxEMyTANBgkqhkiG9w0BAQEFAASCAgAX # pOn6feUulrEesF8zI6k9UDM/NhWeX3Af8NELyPtWYSkIHpaBzYHXBcpM/QxLKwL3 # Jf6FkGcR358aRKvOK28LXM50R2975+Mvfop28bVwIM3GfQZSxilqFexLwQX+qnO6 # BYhfeBvLf+ZcqK9TG2wIXmJflDAn775wZ3UXsMkkn603/cEInbWaa29sBPfvGMdr # GC8Rr/BGUW2gotOndOmBbauOneI7Fsz3UAIIjrMJ1SoF24KxktCLN5gxEA3xtdSP # GEuG4+YfRLBUXkWy0Ufe5Deuplbvy72mDPJFWzAHB27Iai4IrA2ODdjKfTX++7rm # 954StgP6ifIUJvk0zv/O9aGljgmjBUoPdIfDgxjSaYETsVJuipWsiBiynRN5O6r8 # QtjyxegoCosWruZO0hoveuStfpD5wFPCC8bmsUSAbAj1h0DvJMSTRxNIiVw6u4lm # 1yNuG8qJ/Lbw3a1uCSsbzw81iDdk/vt0QYsI/FRyuVO/koM9HLCmLLVUqUFD2jjS # oOW25prA9PwCUDZ8FQtJYHNSqXbcLfuMuaEIiIg5e5gZR2ZqGFufhy2z7AdgZ+WA # NRyvqKuZNyLP11MCQXbgagqzdGGbfBkXPiRSe2aVUhw9K8fit496TGz3g+l9NBcj # idt32NNLSgfg+CIsc3VFgr+Lihxw+5lWjeotcwvm2g== # SIG # End signature block |