CertNoob.psm1
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 already has proper security descriptor we don't need to do additional querying 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 # Since we already request an object we might as well use the data and overwrite it if people use the string $ObjectClass = $ADObjectData.ObjectClass $CanonicalName = $ADObjectData.CanonicalName # Real ACL $ACLs = $ADObjectData.ntSecurityDescriptor } catch { Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)" continue } } else { # Real ACL $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 ) Begin { if (-not $Script:ForestDetails) { Write-Verbose "Get-ADACLOwner - 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 already has proper security descriptor we don't need to do additional querying 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 { 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-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 # Since we already request an object we might as well use the data and overwrite it if people use the string $ObjectClass = $ADObjectData.ObjectClass $CanonicalName = $ADObjectData.CanonicalName # Real ACL $ACLs = $ADObjectData.ntSecurityDescriptor } catch { Write-Warning "Get-ADACLOwner - Path $PathACL - Error: $($_.Exception.Message)" continue } } else { # Real ACL $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-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 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 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 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 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-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 ) # Let's make sure we have all the required data 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) { # if it's not inherited and we require inherited lets continue continue } } if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } } if ($IncludeActiveDirectoryRightsExactMatch) { # We expect all rights to be found in the ACL (could be more rights than specified, but all of them have to be there) [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) { #Write-Verbose "Get-ADACL - Reverting to Convert-Identity for $IdentityReference" $ConvertIdentity = Convert-Identity -Identity $IdentityReference -Verbose:$false $ReturnObject['PrincipalType'] = $ConvertIdentity.Type # it's not really foreignSecurityPrincipal but can't tell what it is... # https://superuser.com/questions/1067246/is-nt-authority-system-a-user-or-a-group $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 } # We compare principal to real principal based on Resolve, we compare both PrincipalName and SID to cover our ground if ($PrincipalRequested -and $PrincipalRequested.SID -ne $ReturnObject['PrincipalObjectSid']) { continue } } else { # We compare principal to principal as returned without resolve 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 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 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 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 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-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 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 = @{} } # This is purely for calling group workaround Add-Type -AssemblyName System.DirectoryServices.AccountManagement $GroupTypes = @{ '2' = @{ Name = 'Distribution Group - Global' # distribution Type = 'Distribution' Scope = 'Global' } '4' = @{ Name = 'Distribution Group - Domain Local' # distribution 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' # Builtin local Security Group Type = 'Security' Scope = 'Builtin local' } '-2147483644' = @{ Name = 'Security Group - Domain Local' Type = 'Security' Scope = 'Domain local' } '-2147483646' = @{ Name = 'Security Group - Global' # security 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 it's an object we need to make sure we pass only DN if ($Ident.DistinguishedName) { $Ident = $Ident.DistinguishedName } # we reset domain name to it's given value if at all $TemporaryName = $Ident $TemporaryDomainName = $DomainName # Since we change $Ident below to different names we need to be sure we use original query for cache if ($Cache -and $Script:CacheObjectsWinADObject[$TemporaryName]) { Write-Verbose "Get-WinADObject - Requesting $TemporaryName from Cache" $Script:CacheObjectsWinADObject[$TemporaryName] continue } # if Domain Name is provided we don't check for anything as it's most likely already good Ident value 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 } } } } # Building up ADSI call $Search = [System.DirectoryServices.DirectorySearcher]::new() #$Search.SizeLimit = $SizeLimit 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)" } } #Convert Identity Input String to HEX, if possible Try { $IdentityGUID = "" ([System.Guid]$Ident).ToByteArray() | ForEach-Object { $IdentityGUID += $("\{0:x2}" -f $_) } } Catch { $IdentityGUID = "null" } # Building search filter $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') { # we only do this additional step when requested. It's not nessecary for day to day use but can hurt performance real bad for normal use cases # This was especially visible for group with 50k members and Get-WinADObjectMember which doesn't even require this data if ($IncludeGroupMembership) { # This is weird case but for some reason $Object.properties.member doesn't always return all values # the workaround is to do additional query for group and assing it $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 '') { # If object is foreignSecurityPrincipal (which shouldn't happen at this point) we need to set it to temporary name we # used before. Usually this is to fix 'NT AUTHORITY\INTERACTIVE' # I have no clue if there's better way to do it $DisplayName = $ResolvedIdentity.Name if ($DisplayName -like '*\*') { $NetbiosWithName = $DisplayName -split '\\' if ($NetbiosWithName.Count -eq 2) { #$NetbiosName = $NetbiosWithName[0] $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] #Adspath = $Object.properties.adspath -as [string] WhenCreated = $Object.properties.whencreated -as [string] WhenChanged = $Object.properties.whenchanged -as [string] #Deleted = $Object.properties.isDeleted -as [string] #Recycled = $Object.properties.isRecycled -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 #Administrative = if ($Object.properties.admincount -eq '1') { $true } else { $false } #Type = $ResolvedIdentity.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) { # This is purely to get special types $ResolvedIdentity = ConvertFrom-SID -SID $ReturnObject['ObjectSID'] } $ReturnObject['Type'] = $ResolvedIdentity.Type } if ($ReturnObject['Type'] -eq 'WellKnownAdministrative') { if (-not $TemporaryDomainName) { # This is so BUILTIN\Administrators would not report domain name that's always related to current one, while it could be someone expects it to be from different forest # this is to mainly address issues with Get-ADACL IdentityReference returning data that's hard to manage otherwise $ReturnObject['DomainName'] = '' } } <# $LastLogon = $Object.properties.lastlogon -as [string] if ($LastLogon) { $LastLogonDate = [datetime]::FromFileTime($LastLogon) } else { $LastLogonDate = $null } $AccountExpires = $Object.Properties.accountexpires -as [string] $AccountExpiresDate = ConvertTo-Date -accountExpires $AccountExpires $PasswordLastSet = $Object.Properties.pwdlastset -as [string] if ($PasswordLastSet) { $PasswordLastSetDate = [datetime]::FromFileTime($PasswordLastSet) } else { $PasswordLastSetDate = $null } $BadPasswordTime = $Object.Properties.badpasswordtime -as [string] if ($BadPasswordTime) { $BadPasswordDate = [datetime]::FromFileTime($BadPasswordTime) } else { $BadPasswordDate = $null } $ReturnObject['LastLogonDate'] = $LastLogonDate $ReturnObject['PasswordLastSet'] = $PasswordLastSetDate $ReturnObject['BadPasswordTime'] = $BadPasswordDate $ReturnObject['AccountExpiresDate'] = $AccountExpiresDate #> if ($Cache) { $Script:CacheObjectsWinADObject[$TemporaryName] = [PSCustomObject] $ReturnObject $Script:CacheObjectsWinADObject[$TemporaryName] } else { [PSCustomObject] $ReturnObject } } } } } 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 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 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-WinADPKI { [CmdletBinding()] param( ) $Output = [ordered]@{ 'CertificateTemplates' = $null 'AIA' = $null 'CDP' = $null 'CA' = $null 'ES' = $null 'KRA' = $null 'OID' = $null } $Properties = @( 'Name' 'CanonicalName' 'Description' 'DisplayName' 'DistinguishedName' 'nTSecurityDescriptor' 'ObjectCategory' 'ObjectClass' 'ObjectGUID' 'pKICriticalExtensions' 'pKIDefaultCSPs' 'pKIDefaultKeySpec' 'pKIExpirationPeriod' 'pKIExtendedKeyUsage' 'pKIKeyUsage' 'pKIMaxIssuingDepth' 'pKIOverlapPeriod' 'msPKI-Certificate-Application-Policy' 'msPKI-Certificate-Name-Flag' 'msPKI-Cert-Template-OID' 'msPKI-Enrollment-Flag' 'msPKI-Minimal-Key-Size' 'msPKI-Private-Key-Flag' 'msPKI-RA-Application-Policies' 'msPKI-RA-Signature' 'msPKI-Supersede-Templates' 'msPKI-Template-Minor-Revision' 'msPKI-Template-Schema-Version' 'ProtectedFromAccidentalDeletion' 'revision' 'whenChanged' 'whenCreated' ) $PropertiesAIA = @( 'CanonicalName' 'Description' 'Name' 'nTSecurityDescriptor' 'ObjectCategory' 'ObjectClass' 'ObjectGUID' 'whenChanged' 'whenCreated' 'authorityRevocationList' 'certificateRevocationList' 'cACertificate' ) $PropertiesCDP = @( 'CanonicalName' 'Description' 'Name' 'nTSecurityDescriptor' 'ObjectCategory' 'ObjectClass' 'ObjectGUID' 'whenChanged' 'whenCreated' 'deltaRevocationList' 'certificateRevocationList' ) $PropertiesCA = @( 'CanonicalName' 'Description' 'Name' 'nTSecurityDescriptor' 'ObjectCategory' 'ObjectClass' 'ObjectGUID' 'whenChanged' 'whenCreated' 'Flags' 'caCertificate' 'certificateRevocationList' ) $PropertiesES = @( 'CanonicalName' 'Description' 'Name' 'nTSecurityDescriptor' 'ObjectCategory' 'ObjectClass' 'ObjectGUID' 'whenChanged' 'whenCreated' 'caCertificate' 'certificateTemplates' 'Flags' ) $PropertiesKRA = @( 'CanonicalName' 'Description' 'Name' 'nTSecurityDescriptor' 'ObjectCategory' 'ObjectClass' 'ObjectGUID' 'whenChanged' 'whenCreated' 'userCertificate' ) $PropertiesOID = @( 'CanonicalName' 'Description' 'Name' 'nTSecurityDescriptor' 'ObjectCategory' 'ObjectClass' 'ObjectGUID' 'whenChanged' 'whenCreated' 'msPKI-Cert-Template-OID' 'flags' 'showInAdvancedViewOnly' ) $GlobalExcludeProperties = @( 'PropertyNames' 'AddedProperties' 'RemovedProperties' 'ModifiedProperties' 'PropertyCount' ) $Filter = "objectClass -ne 'container'" $ForestInformation = Get-WinADForestDetails -Extended $ForestDN = $ForestInformation['DomainsExtended'][$ForestInformation['Forest'].RootDomain].DistinguishedName $FindDN = "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,$ForestDN" $CertificateTemplates = Get-ADObject -Filter $Filter -SearchBase $FindDN -Properties $Properties -ErrorAction SilentlyContinue -SearchScope Subtree | Select-Object -ExcludeProperty $GlobalExcludeProperties $FindDN = "CN=AIA,CN=Public Key Services,CN=Services,CN=Configuration,$ForestDN" $AIA = Get-ADObject -Filter $Filter -SearchBase $FindDN -Properties $PropertiesAIA -ErrorAction SilentlyContinue -SearchScope Subtree | Select-Object -ExcludeProperty $GlobalExcludeProperties $FindDN = "CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,$ForestDN" $CDP = Get-ADObject -Filter $Filter -SearchBase $FindDN -Properties $PropertiesCDP -ErrorAction SilentlyContinue -SearchScope Subtree | Select-Object -ExcludeProperty $GlobalExcludeProperties $FindDN = "CN=Certification Authorities,CN=Public Key Services,CN=Services,CN=Configuration,$ForestDN" $CA = Get-ADObject -Filter $Filter -SearchBase $FindDN -Properties $PropertiesCA -ErrorAction SilentlyContinue -SearchScope Subtree | Select-Object -ExcludeProperty $GlobalExcludeProperties $FindDN = "CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,$ForestDN" $ES = Get-ADObject -Filter $Filter -SearchBase $FindDN -Properties $PropertiesES -ErrorAction SilentlyContinue -SearchScope Subtree | Select-Object -ExcludeProperty $GlobalExcludeProperties $FindDN = "CN=KRA,CN=Public Key Services,CN=Services,CN=Configuration,$ForestDN" $KRA = Get-ADObject -Filter $Filter -SearchBase $FindDN -Properties $PropertiesKRA -ErrorAction SilentlyContinue -SearchScope Subtree | Select-Object -ExcludeProperty $GlobalExcludeProperties $FindDN = "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration,$ForestDN" $OID = Get-ADObject -Filter $Filter -SearchBase $FindDN -Properties $PropertiesOID -ErrorAction SilentlyContinue -SearchScope Subtree | Select-Object -ExcludeProperty $GlobalExcludeProperties $Output['CertificateTemplates'] = $CertificateTemplates $Output['AIA'] = $AIA $Output['CDP'] = $CDP $Output['CA'] = $CA $Output['ES'] = $ES $Output['KRA'] = $KRA $Output['OID'] = $OID $ACL = Get-ADACL -ADObject $CertificateTemplates -Resolve $ACLOwner = Get-ADACLOwner -ADObject $CertificateTemplates -Resolve $Output['CertificateTemplatesOwner'] = $ACLOwner $Output['CertificateTemplatesACL'] = $ACL $ACL = Get-ADACL -ADObject $AIA -Resolve $ACLOwner = Get-ADACLOwner -ADObject $AIA -Resolve $Output['AIAOwner'] = $ACLOwner $Output['AIAACL'] = $ACL $ACL = Get-ADACL -ADObject $CDP -Resolve $ACLOwner = Get-ADACLOwner -ADObject $CDP -Resolve $Output['CDPOwner'] = $ACLOwner $Output['CDPACL'] = $ACL $ACL = Get-ADACL -ADObject $CA -Resolve $ACLOwner = Get-ADACLOwner -ADObject $CA -Resolve $Output['CAOwner'] = $ACLOwner $Output['CAACL'] = $ACL $ACL = Get-ADACL -ADObject $ES -Resolve $ACLOwner = Get-ADACLOwner -ADObject $ES -Resolve $Output['ESOwner'] = $ACLOwner $Output['ESACL'] = $ACL $ACL = Get-ADACL -ADObject $KRA -Resolve $ACLOwner = Get-ADACLOwner -ADObject $KRA -Resolve $Output['KRAOwner'] = $ACLOwner $Output['KRAACL'] = $ACL $ACL = Get-ADACL -ADObject $OID -Resolve $ACLOwner = Get-ADACLOwner -ADObject $OID -Resolve $Output['OIDOwner'] = $ACLOwner $Output['OIDACL'] = $ACL $Output } function Invoke-CANoob { [CmdletBinding()] param( ) } function Show-WinADPKI { [CmdletBinding()] param( [string] $FilePath, [switch] $HideHTML, [switch] $Online, [switch] $PassThru ) $PKI = Get-WinADPKI -Verbose #$CAAnalysis = Start-CATemplateAnalysis -PKI $PKI New-HTML { 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 $ExcludeProperty = @( 'AddedProperties' 'RemovedProperties' 'ModifiedProperties' 'PropertyCount' 'PropertyNames' ) 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 "CertNoob - 0.1.0" -Color Blue } -JustifyContent flex-end -Invisible } } New-HTMLTab -Name 'Certificate Templates' { New-HTMLSection -HeaderText "Templates" { New-HTMLTable -DataTable $PKI.CertificateTemplates -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 -ExcludeProperty $ExcludeProperty } New-HTMLSection -HeaderText "Ownership" { New-HTMLTable -DataTable $PKI.CertificateTemplatesOwner -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } New-HTMLSection -HeaderText "Access" { New-HTMLTable -DataTable $PKI.CertificateTemplatesACL -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } } New-HTMLTab -Name 'AIA' { New-HTMLSection -HeaderText "AIA" { New-HTMLTable -DataTable $PKI.AIA -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 -ExcludeProperty $ExcludeProperty } New-HTMLSection -HeaderText "Ownership" { New-HTMLTable -DataTable $PKI.AIAOwner -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } New-HTMLSection -HeaderText "Access" { New-HTMLTable -DataTable $PKI.AIAACL -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } } New-HTMLTab -Name 'CDP' { New-HTMLSection -HeaderText "CDP" { New-HTMLTable -DataTable $PKI.CDP -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 -ExcludeProperty $ExcludeProperty } New-HTMLSection -HeaderText "Ownership" { New-HTMLTable -DataTable $PKI.CDPOwner -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } New-HTMLSection -HeaderText "Access" { New-HTMLTable -DataTable $PKI.CDPACL -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } } New-HTMLTab -Name 'CA' { New-HTMLSection -HeaderText "CA" { New-HTMLTable -DataTable $PKI.CA -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 -ExcludeProperty $ExcludeProperty } New-HTMLSection -HeaderText "Ownership" { New-HTMLTable -DataTable $PKI.CAOwner -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } New-HTMLSection -HeaderText "Access" { New-HTMLTable -DataTable $PKI.CAACL -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } } New-HTMLTab -Name 'ES' { New-HTMLSection -HeaderText "ES" { New-HTMLTable -DataTable $PKI.ES -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 -ExcludeProperty $ExcludeProperty } New-HTMLSection -HeaderText "Ownership" { New-HTMLTable -DataTable $PKI.ESOwner -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } New-HTMLSection -HeaderText "Access" { New-HTMLTable -DataTable $PKI.ESACL -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } } New-HTMLTab -Name 'KRA' { New-HTMLSection -HeaderText "KRA" { New-HTMLTable -DataTable $PKI.KRA -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 -ExcludeProperty $ExcludeProperty } New-HTMLSection -HeaderText "Ownership" { New-HTMLTable -DataTable $PKI.KRAOwner -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } New-HTMLSection -HeaderText "Access" { New-HTMLTable -DataTable $PKI.KRAACL -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } } New-HTMLTab -Name 'OID' { New-HTMLSection -HeaderText "OID" { New-HTMLTable -DataTable $PKI.OID -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 -ExcludeProperty $ExcludeProperty } New-HTMLSection -HeaderText "Ownership" { New-HTMLTable -DataTable $PKI.OIDOwner -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } New-HTMLSection -HeaderText "Access" { New-HTMLTable -DataTable $PKI.OIDACL -ScrollX -Filtering { } -PagingLength 5 -PagingOptions 5, 10, 15, 25, 50, 100 } } } -Online:$Online.Present -FilePath $FilePath -ShowHTML:(-not $HideHTML) if ($PassThru) { $PKI } } # Export functions and aliases as required Export-ModuleMember -Function @('Get-WinADPKI', 'Invoke-CANoob', 'Show-WinADPKI') -Alias @() |