From f029bcb154f62e06ccdd87fee9edd5c4375e99cb Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sat, 8 Jun 2024 23:22:59 -0400 Subject: [PATCH 1/9] The IP address is not displayed in the DC Network Settings section. #176 --- Src/Private/Get-AbrADDomainController.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/Private/Get-AbrADDomainController.ps1 b/Src/Private/Get-AbrADDomainController.ps1 index 8484d0c..79da351 100644 --- a/Src/Private/Get-AbrADDomainController.ps1 +++ b/Src/Private/Get-AbrADDomainController.ps1 @@ -263,14 +263,14 @@ function Get-AbrADDomainController { try { Section -ExcludeFromTOC -Style NOTOCHeading5 "Networking Settings" { $inObj = [ordered] @{ - 'IPv4 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { $_.AddressFamily -eq 'IPv4' -and $_.IPAddress -ne '127.0.0.1' }).IPv4Address))) { + 'IPv4 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv4' -or $_.AddressFamily -eq 2) -and $_.IPAddress -ne '127.0.0.1' }).IPv4Address))) { $true { "--" } - $false { ($DCNetSettings | Where-Object { $_.AddressFamily -eq 'IPv4' -and $_.IPAddress -ne '127.0.0.1' }).IPv4Address -join ", " } + $false { ($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv4' -or $_.AddressFamily -eq 2) -and $_.IPAddress -ne '127.0.0.1' }).IPv4Address -join ", " } default { "Unknown" } } - 'IPv6 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { $_.AddressFamily -eq 'IPv6' -and $_.IPAddress -ne '::1' }).IPv6Address))) { + 'IPv6 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv6' -or $_.AddressFamily -eq 23) -and $_.IPAddress -ne '::1' }).IPv6Address))) { $true { "--" } - $false { ($DCNetSettings | Where-Object { $_.AddressFamily -eq 'IPv6' -and $_.IPAddress -ne '::1' }).IPv6Address -join "," } + $false { ($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv6' -or $_.AddressFamily -eq 23) -and $_.IPAddress -ne '::1' }).IPv6Address -join "," } default { "Unknown" } } "LDAP Port" = $DCInfo.LdapPort From ed0de74d13b370e73fe9d0320711e5455665df77 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sun, 9 Jun 2024 20:10:22 -0400 Subject: [PATCH 2/9] HealthCheck - Find SMBv1 status in Active Directory DC #168 --- Src/Private/Get-AbrADDomainController.ps1 | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Src/Private/Get-AbrADDomainController.ps1 b/Src/Private/Get-AbrADDomainController.ps1 index 79da351..d0de5c5 100644 --- a/Src/Private/Get-AbrADDomainController.ps1 +++ b/Src/Private/Get-AbrADDomainController.ps1 @@ -148,6 +148,7 @@ function Get-AbrADDomainController { $DCComputerObject = try { Invoke-Command -Session $TempPssSession { Get-ADComputer ($using:DCInfo).ComputerObjectDN -Properties * -Server $using:DC } } catch { Out-Null } $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DCNetSettings' $DCNetSettings = try { Invoke-Command -Session $DCPssSession { Get-NetIPAddress } } catch { Out-Null } + $DCNetSMBv1Setting = try { Invoke-Command -Session $DCPssSession { Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol } } catch { Out-Null } Remove-PSSession -Session $DCPssSession if ($InfoLevel.Domain -eq 1) { try { @@ -217,14 +218,19 @@ function Get-AbrADDomainController { } 'Global Catalog' = ConvertTo-TextYN $DCInfo.IsGlobalCatalog 'Read Only' = ConvertTo-TextYN $DCInfo.IsReadOnly - 'Operation Master Roles' = $DCInfo.OperationMasterRoles -join ', ' - 'Location' = $DCComputerObject.Location + 'Operation Master Roles' = ConvertTo-EmptyToFiller ($DCInfo.OperationMasterRoles -join ', ') + 'Location' = ConvertTo-EmptyToFiller $DCComputerObject.Location 'Computer Object SID' = $DCComputerObject.SID - "Operating System" = $DCInfo.OperatingSystem - 'Description' = $DCComputerObject.Description + 'Operating System' = $DCInfo.OperatingSystem + 'SMB1 Status' = $DCNetSMBv1Setting.State + 'Description' = ConvertTo-EmptyToFiller $DCComputerObject.Description } $OutObj = [pscustomobject]$inobj + if ($HealthCheck.DomainController.BestPractice) { + $OutObj | Where-Object { $_.'SMB1 Status' -eq 'Enabled' } | Set-Style -Style Critical -Property 'SMB1 Status' + } + $TableParams = @{ Name = "General Information - $($DCInfo.Name)" List = $true @@ -234,6 +240,14 @@ function Get-AbrADDomainController { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Table @TableParams + if ($HealthCheck.DomainController.BestPractice -and ($OutObj | Where-Object { $_.'SMB1 Status' -eq 'Enabled' })) { + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Best Practice:" -Bold + Text "Disable SMB v1: SMB v1 is an outdated protocol that is vulnerable to several security issues. It is recommended to disable SMBv1 on all systems." + } + } } } catch { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (General Information Section)" From 9faae4b2f0cf4b63879b3b79ddf47a7191fe4fdf Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Mon, 10 Jun 2024 13:49:10 -0400 Subject: [PATCH 3/9] Fix HealthCheck - Empty Groups #172 --- Src/Private/Get-AbrADDomainObject.ps1 | 62 +++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/Src/Private/Get-AbrADDomainObject.ps1 b/Src/Private/Get-AbrADDomainObject.ps1 index bc61ba8..709cf36 100644 --- a/Src/Private/Get-AbrADDomainObject.ps1 +++ b/Src/Private/Get-AbrADDomainObject.ps1 @@ -32,6 +32,7 @@ function Get-AbrADDomainObject { Paragraph "The following section details information about computers, groups and users objects found in $($Domain) " try { try { + $script:DomainSID = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity $using:Domain).domainsid.Value } $ADLimitedProperties = @("Name", "Enabled", "SAMAccountname", "DisplayName", "Enabled", "LastLogonDate", "PasswordLastSet", "PasswordNeverExpires", "PasswordNotRequired", "PasswordExpired", "SmartcardLogonRequired", "AccountExpirationDate", "AdminCount", "Created", "Modified", "LastBadPasswordAttempt", "badpwdcount", "mail", "CanonicalName", "DistinguishedName", "ServicePrincipalName", "SIDHistory", "PrimaryGroupID", "UserAccountControl", "CannotChangePassword", "PwdLastSet", "LockedOut", "TrustedForDelegation", "TrustedtoAuthForDelegation", "msds-keyversionnumber", "SID", "AccountNotDelegated", "EmailAddress") $script:DC = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity $using:Domain).ReplicaDirectoryServers | Select-Object -First 1 } $script:Computers = Invoke-Command -Session $TempPssSession { (Get-ADComputer -ResultPageSize 1000 -Server $using:DC -Filter * -Properties Enabled, OperatingSystem, lastlogontimestamp, PasswordLastSet, SIDHistory -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName) } @@ -39,6 +40,8 @@ function Get-AbrADDomainObject { $script:Users = Invoke-Command -Session $TempPssSession { Get-ADUser -ResultPageSize 1000 -Server $using:DC -Filter * -Property $using:ADLimitedProperties -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName } $script:PrivilegedUsers = $Users | Where-Object { $_.AdminCount -eq 1 } $script:GroupOBj = Invoke-Command -Session $TempPssSession { (Get-ADGroup -Server $using:DC -Filter * -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName) } + $excludedDomainGroupsBySID = @("$DomainSID-525", "$DomainSID-522", "$DomainSID-572", "$DomainSID-571", "$DomainSID-514", "$DomainSID-553", "$DomainSID-513", "$DomainSID-1106", "$DomainSID-515", "$DomainSID-512", "$DomainSID-498", "$DomainSID-527", "$DomainSID-520", "$DomainSID-521", "$DomainSID-519", "$DomainSID-526", "$DomainSID-516", "$DomainSID-517", "$DomainSID-518") + $excludedForestGroupsBySID = ($GroupOBj | Where-Object { $_.SID -like 'S-1-5-32-*' }).SID $script:DomainController = Invoke-Command -Session $TempPssSession { (Get-ADDomainController -Server $using:DC -Filter *) | Select-Object name | Measure-Object } $script:GC = Invoke-Command -Session $TempPssSession { (Get-ADDomainController -Server $using:DC -Filter { IsGlobalCatalog -eq "True" }) | Select-Object name | Measure-Object } @@ -67,7 +70,7 @@ function Get-AbrADDomainObject { $TableParams['Caption'] = "- $($TableParams.Name)" } try { - + # Chart Section $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Category' $chartFileItem = Get-PieChart -SampleData $sampleData -ChartName 'UsersObject' -XField 'Name' -YField 'Value' -ChartLegendName 'Category' -ChartTitleName 'UsersObject' -ChartTitleText 'User Objects' -ReversePalette $True @@ -168,7 +171,7 @@ function Get-AbrADDomainObject { $TableParams['Caption'] = "- $($TableParams.Name)" } try { - + # Chart Section $sampleData = $OutObj $chartFileItem = Get-PieChart -SampleData $sampleData -ChartName 'StatusofUsersAccounts' -XField 'Category' -YField 'Total' -ChartLegendName 'Category' -ChartTitleName 'StatusofUsersAccounts' -ChartTitleText 'Status of Users Accounts' -ReversePalette $True @@ -247,7 +250,7 @@ function Get-AbrADDomainObject { $TableParams['Caption'] = "- $($TableParams.Name)" } try { - + # Chart Section $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Name' $chartFileItem = Get-PieChart -SampleData $sampleData -ChartName 'GroupCategoryObject' -XField 'Name' -YField 'Value' -ChartLegendName 'Category' -ChartTitleName 'GroupCategoryObject' -ChartTitleText 'Group Categories' -ReversePalette $True @@ -284,7 +287,7 @@ function Get-AbrADDomainObject { $TableParams['Caption'] = "- $($TableParams.Name)" } try { - + # Chart Section $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Name' $chartFileItem = Get-PieChart -SampleData $sampleData -ChartName 'GroupCategoryObject' -XField 'Name' -YField 'Value' -ChartLegendName 'Category' -ChartTitleName 'GroupScopesObject' -ChartTitleText 'Group Scopes' -ReversePalette $True @@ -342,15 +345,14 @@ function Get-AbrADDomainObject { $OutObj = @() if ($Domain) { try { - $DomainSID = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity $using:Domain).domainsid.Value } if ($Domain -eq $ADSystem.Name) { - $GroupsSID = "$DomainSID-512", "$DomainSID-519", 'S-1-5-32-544', 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', "$DomainSID-518" + $GroupsSID = "$DomainSID-1107", "$DomainSID-512", "$DomainSID-519", 'S-1-5-32-544', 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', "$DomainSID-518", 'S-1-5-32-578' } else { - $GroupsSID = "$DomainSID-512", 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548' + $GroupsSID = "$DomainSID-1107", "$DomainSID-512", 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', 'S-1-5-32-578' } if ($GroupsSID) { if ($InfoLevel.Domain -eq 1) { - Paragraph "The following session summarizes the counts of users within the privileged groups." + Paragraph "The following session summarizes the counts of users within the privileged groups. (Empty group are excluded)" BlankLine foreach ($GroupSID in $GroupsSID) { try { @@ -416,7 +418,7 @@ function Get-AbrADDomainObject { } } } else { - Paragraph "The following session details the members users within the privilege groups." + Paragraph "The following session details the members users within the privilege groups. (Empty group are excluded)" BlankLine foreach ($GroupSID in $GroupsSID) { try { @@ -516,6 +518,46 @@ function Get-AbrADDomainObject { } } } + if ($HealthCheck.Domain.BestPractice) { + try { + Section -Style Heading5 'Empty Groups (Non-Default)' { + $OutObj = @() + foreach ($Group in ($GroupOBj | Where-Object { -Not $_.Members }) ) { + if ($Group.SID -notin $excludedForestGroupsBySID -and $Group.SID -notin $excludedDomainGroupsBySID ) { + try { + $inObj = [ordered] @{ + 'Group Name' = $Group.Name + 'Group SID' = $Group.SID + } + $OutObj += [pscustomobject]$inobj + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Empty Groups Objects Table)" + } + } + } + + $TableParams = @{ + Name = "Empty Groups - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 50, 50 + } + + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OutObj | Sort-Object -Property 'Group Name' | Table @TableParams + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Best Practice:" -Bold + Text "Remove empty or unused Active Directory Groups. An empty Active Directory security group causes two major problems. First, they add unnecessary clutter and make active directory administration difficult, even when paired with user friendly Active Directory tools. The second and most important point to note is that empty groups are a security risk to your network." + } + } + + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Empty Groups Objects Section)" + } + } } } catch { Write-PScriboMessage -IsWarning $($_.Exception.Message) @@ -538,6 +580,7 @@ function Get-AbrADDomainObject { $TableParams['Caption'] = "- $($TableParams.Name)" } try { + # Chart Section $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Category' $chartFileItem = Get-PieChart -SampleData $sampleData -ChartName 'ComputersObject' -XField 'Name' -YField 'Value' -ChartLegendName 'Category' -ChartTitleName 'ComputersObject' -ChartTitleText 'Computers Count' -ReversePalette $True @@ -614,6 +657,7 @@ function Get-AbrADDomainObject { } try { + # Chart Section $sampleData = $OutObj $chartFileItem = Get-PieChart -SampleData $sampleData -ChartName 'StatusofComputerAccounts' -XField 'Category' -YField 'Total' -ChartLegendName 'Category' -ChartTitleName 'StatusofComputerAccounts' -ChartTitleText 'Status of Computers Accounts' -ReversePalette $True From 581866160701ac58176c0427fee99034f27428d4 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Mon, 10 Jun 2024 14:23:20 -0400 Subject: [PATCH 4/9] Fix HealthCheck - Groups with AdminCount set to 1 #171 --- Src/Private/Get-AbrADDomainObject.ps1 | 48 +++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/Src/Private/Get-AbrADDomainObject.ps1 b/Src/Private/Get-AbrADDomainObject.ps1 index 709cf36..2928558 100644 --- a/Src/Private/Get-AbrADDomainObject.ps1 +++ b/Src/Private/Get-AbrADDomainObject.ps1 @@ -40,8 +40,9 @@ function Get-AbrADDomainObject { $script:Users = Invoke-Command -Session $TempPssSession { Get-ADUser -ResultPageSize 1000 -Server $using:DC -Filter * -Property $using:ADLimitedProperties -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName } $script:PrivilegedUsers = $Users | Where-Object { $_.AdminCount -eq 1 } $script:GroupOBj = Invoke-Command -Session $TempPssSession { (Get-ADGroup -Server $using:DC -Filter * -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName) } - $excludedDomainGroupsBySID = @("$DomainSID-525", "$DomainSID-522", "$DomainSID-572", "$DomainSID-571", "$DomainSID-514", "$DomainSID-553", "$DomainSID-513", "$DomainSID-1106", "$DomainSID-515", "$DomainSID-512", "$DomainSID-498", "$DomainSID-527", "$DomainSID-520", "$DomainSID-521", "$DomainSID-519", "$DomainSID-526", "$DomainSID-516", "$DomainSID-517", "$DomainSID-518") + $excludedDomainGroupsBySID = @("$DomainSID-525", "$DomainSID-522", "$DomainSID-572", "$DomainSID-571", "$DomainSID-514", "$DomainSID-553", "$DomainSID-513", "$DomainSID-515", "$DomainSID-512", "$DomainSID-498", "$DomainSID-527", "$DomainSID-520", "$DomainSID-521", "$DomainSID-519", "$DomainSID-526", "$DomainSID-516", "$DomainSID-517", "$DomainSID-518") $excludedForestGroupsBySID = ($GroupOBj | Where-Object { $_.SID -like 'S-1-5-32-*' }).SID + $AdminGroupsBySID = "S-1-5-32-552", "$DomainSID-521", "$DomainSID-516", "$DomainSID-1107", "$DomainSID-512", "$DomainSID-519", 'S-1-5-32-544', 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', "$DomainSID-518", 'S-1-5-32-578' $script:DomainController = Invoke-Command -Session $TempPssSession { (Get-ADDomainController -Server $using:DC -Filter *) | Select-Object name | Measure-Object } $script:GC = Invoke-Command -Session $TempPssSession { (Get-ADDomainController -Server $using:DC -Filter { IsGlobalCatalog -eq "True" }) | Select-Object name | Measure-Object } @@ -341,7 +342,7 @@ function Get-AbrADDomainObject { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Groups Objects Section)" } } - Section -Style Heading5 'Privileged Groups' { + Section -Style Heading5 'Privileged Groups (Built-in)' { $OutObj = @() if ($Domain) { try { @@ -519,6 +520,49 @@ function Get-AbrADDomainObject { } } if ($HealthCheck.Domain.BestPractice) { + try { + $AdminGroupOBj = Invoke-Command -Session $TempPssSession { (Get-ADGroup -Server $using:DC -Filter "admincount -eq '1'" -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName) } + if ($AdminGroupOBj) { + Section -Style Heading5 'Privileged Group (Non-Default)' { + Paragraph "The following session summarizes the privileged groups with AdminCount set to 1 (non-defaults)." + BlankLine + $OutObj = @() + foreach ($Group in ($AdminGroupOBj | Where-Object { $_.SID -notin $AdminGroupsBySID }) ) { + try { + $inObj = [ordered] @{ + 'Group Name' = $Group.Name + 'Group SID' = $Group.SID + } + $OutObj += [pscustomobject]$inobj + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Privileged Group (Non-Default) Table)" + } + } + + $TableParams = @{ + Name = "Privileged Group (Non-Default) - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 50, 50 + } + + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OutObj | Sort-Object -Property 'Group Name' | Table @TableParams + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Best Practice:" -Bold + Text "Remove empty or unused Active Directory Groups. An empty Active Directory security group causes two major problems. First, they add unnecessary clutter and make active directory administration difficult, even when paired with user friendly Active Directory tools. The second and most important point to note is that empty groups are a security risk to your network." + } + } + } + + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Privileged Group (Non-Default) Section)" + } + } + if ($HealthCheck.Domain.BestPractice -and ($GroupOBj | Where-Object { -Not $_.Members })) { try { Section -Style Heading5 'Empty Groups (Non-Default)' { $OutObj = @() From 8d22b0d844dadf94897e3d30dd9dca7ebcae8a95 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Mon, 10 Jun 2024 15:59:48 -0400 Subject: [PATCH 5/9] Fix HealthCheck - Groups with AdminCount set to 1 #171 --- Src/Private/Get-AbrADDomainObject.ps1 | 57 ++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/Src/Private/Get-AbrADDomainObject.ps1 b/Src/Private/Get-AbrADDomainObject.ps1 index 2928558..ded4087 100644 --- a/Src/Private/Get-AbrADDomainObject.ps1 +++ b/Src/Private/Get-AbrADDomainObject.ps1 @@ -42,7 +42,7 @@ function Get-AbrADDomainObject { $script:GroupOBj = Invoke-Command -Session $TempPssSession { (Get-ADGroup -Server $using:DC -Filter * -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName) } $excludedDomainGroupsBySID = @("$DomainSID-525", "$DomainSID-522", "$DomainSID-572", "$DomainSID-571", "$DomainSID-514", "$DomainSID-553", "$DomainSID-513", "$DomainSID-515", "$DomainSID-512", "$DomainSID-498", "$DomainSID-527", "$DomainSID-520", "$DomainSID-521", "$DomainSID-519", "$DomainSID-526", "$DomainSID-516", "$DomainSID-517", "$DomainSID-518") $excludedForestGroupsBySID = ($GroupOBj | Where-Object { $_.SID -like 'S-1-5-32-*' }).SID - $AdminGroupsBySID = "S-1-5-32-552", "$DomainSID-521", "$DomainSID-516", "$DomainSID-1107", "$DomainSID-512", "$DomainSID-519", 'S-1-5-32-544', 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', "$DomainSID-518", 'S-1-5-32-578' + $AdminGroupsBySID = "S-1-5-32-552", "$DomainSID-527", "$DomainSID-521", "$DomainSID-516", "$DomainSID-1107", "$DomainSID-512", "$DomainSID-519", 'S-1-5-32-544', 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', "$DomainSID-518", 'S-1-5-32-578' $script:DomainController = Invoke-Command -Session $TempPssSession { (Get-ADDomainController -Server $using:DC -Filter *) | Select-Object name | Measure-Object } $script:GC = Invoke-Command -Session $TempPssSession { (Get-ADDomainController -Server $using:DC -Filter { IsGlobalCatalog -eq "True" }) | Select-Object name | Measure-Object } @@ -353,7 +353,7 @@ function Get-AbrADDomainObject { } if ($GroupsSID) { if ($InfoLevel.Domain -eq 1) { - Paragraph "The following session summarizes the counts of users within the privileged groups. (Empty group are excluded)" + Paragraph "The following section summarizes the counts of users within the privileged groups. (Empty group are excluded)" BlankLine foreach ($GroupSID in $GroupsSID) { try { @@ -419,7 +419,7 @@ function Get-AbrADDomainObject { } } } else { - Paragraph "The following session details the members users within the privilege groups. (Empty group are excluded)" + Paragraph "The following section details the members users within the privilege groups. (Empty group are excluded)" BlankLine foreach ($GroupSID in $GroupsSID) { try { @@ -524,7 +524,7 @@ function Get-AbrADDomainObject { $AdminGroupOBj = Invoke-Command -Session $TempPssSession { (Get-ADGroup -Server $using:DC -Filter "admincount -eq '1'" -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName) } if ($AdminGroupOBj) { Section -Style Heading5 'Privileged Group (Non-Default)' { - Paragraph "The following session summarizes the privileged groups with AdminCount set to 1 (non-defaults)." + Paragraph "The following section summarizes the privileged groups with AdminCount set to 1 (non-defaults)." BlankLine $OutObj = @() foreach ($Group in ($AdminGroupOBj | Where-Object { $_.SID -notin $AdminGroupsBySID }) ) { @@ -553,7 +553,7 @@ function Get-AbrADDomainObject { BlankLine Paragraph { Text "Best Practice:" -Bold - Text "Remove empty or unused Active Directory Groups. An empty Active Directory security group causes two major problems. First, they add unnecessary clutter and make active directory administration difficult, even when paired with user friendly Active Directory tools. The second and most important point to note is that empty groups are a security risk to your network." + Text "Regularly validate and remove unneeded privileged group members in Active Directory." } } } @@ -769,6 +769,53 @@ function Get-AbrADDomainObject { } catch { Write-PScriboMessage -IsWarning $($_.Exception.Message) } + try { + Section -ExcludeFromTOC -Style NOTOCHeading5 'Computers with Password-Not-Required Attribute Set' { + $OutObj = @() + if ($Domain) { + try { + $OSObjects = $Computers | Where-Object { $_.name -like '*' } | Group-Object -Property operatingSystem | Select-Object Name, Count + if ($OSObjects) { + foreach ($OSObject in $OSObjects) { + $inObj = [ordered] @{ + 'Operating System' = Switch ([string]::IsNullOrEmpty($OSObject.Name)) { + $True { 'No OS Specified' } + default { $OSObject.Name } + } + 'Count' = $OSObject.Count + } + $OutObj += [pscustomobject]$inobj + } + if ($HealthCheck.Domain.Security) { + $OutObj | Where-Object { $_.'Operating System' -like '* NT*' -or $_.'Operating System' -like '*2000*' -or $_.'Operating System' -like '*2003*' -or $_.'Operating System' -like '*2008*' -or $_.'Operating System' -like '* NT*' -or $_.'Operating System' -like '*2000*' -or $_.'Operating System' -like '* 95*' -or $_.'Operating System' -like '* 7*' -or $_.'Operating System' -like '* 8 *' -or $_.'Operating System' -like '* 98*' -or $_.'Operating System' -like '*XP*' -or $_.'Operating System' -like '* Vista*' } | Set-Style -Style Critical -Property 'Operating System' + } + + $TableParams = @{ + Name = "Operating System Count - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 60, 40 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OutObj | Sort-Object -Property 'Operating System' | Table @TableParams + if ($HealthCheck.Domain.Security -and ($OutObj | Where-Object { $_.'Operating System' -like '* NT*' -or $_.'Operating System' -like '*2000*' -or $_.'Operating System' -like '*2003*' -or $_.'Operating System' -like '*2008*' -or $_.'Operating System' -like '* NT*' -or $_.'Operating System' -like '*2000*' -or $_.'Operating System' -like '* 95*' -or $_.'Operating System' -like '* 7*' -or $_.'Operating System' -like '* 8 *' -or $_.'Operating System' -like '* 98*' -or $_.'Operating System' -like '*XP*' -or $_.'Operating System' -like '* Vista*' })) { + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Security Best Practice:" -Bold + Text "Operating systems that are no longer supported for security updates are not maintained or updated for vulnerabilities leaving them open to potential attack. Organizations must transition to a supported operating system to ensure continued support and to increase the organization security posture." + } + } + } + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Operating Systems in Active Directory)" + } + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } if ($InfoLevel.Domain -ge 4) { try { Section -Style Heading4 'Computers Inventory' { From 30701063e1832b07a9e3dea8145e77bf16af9482 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 13 Jun 2024 10:15:48 -0400 Subject: [PATCH 6/9] Fix HealthCheck - Computers with Password-not-required attribute set #174 --- Src/Private/Get-AbrADDomainObject.ps1 | 81 ++++++++++++--------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/Src/Private/Get-AbrADDomainObject.ps1 b/Src/Private/Get-AbrADDomainObject.ps1 index ded4087..368e65d 100644 --- a/Src/Private/Get-AbrADDomainObject.ps1 +++ b/Src/Private/Get-AbrADDomainObject.ps1 @@ -527,15 +527,17 @@ function Get-AbrADDomainObject { Paragraph "The following section summarizes the privileged groups with AdminCount set to 1 (non-defaults)." BlankLine $OutObj = @() - foreach ($Group in ($AdminGroupOBj | Where-Object { $_.SID -notin $AdminGroupsBySID }) ) { - try { - $inObj = [ordered] @{ - 'Group Name' = $Group.Name - 'Group SID' = $Group.SID + foreach ($Group in $AdminGroupOBj) { + if ($Group.SID -notin $AdminGroupsBySID) { + try { + $inObj = [ordered] @{ + 'Group Name' = $Group.Name + 'Group SID' = $Group.SID + } + $OutObj += [pscustomobject]$inobj + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Privileged Group (Non-Default) Table)" } - $OutObj += [pscustomobject]$inobj - } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Privileged Group (Non-Default) Table)" } } @@ -770,51 +772,42 @@ function Get-AbrADDomainObject { Write-PScriboMessage -IsWarning $($_.Exception.Message) } try { - Section -ExcludeFromTOC -Style NOTOCHeading5 'Computers with Password-Not-Required Attribute Set' { - $OutObj = @() - if ($Domain) { + $ComputerObjects = Invoke-Command -Session $TempPssSession { Get-ADComputer -Filter { PasswordNotRequired -eq $true } -Properties Name, DistinguishedName, Enabled } + if ($ComputerObjects) { + Section -ExcludeFromTOC -Style NOTOCHeading5 'Computers with Password-Not-Required Attribute Set' { + $OutObj = @() try { - $OSObjects = $Computers | Where-Object { $_.name -like '*' } | Group-Object -Property operatingSystem | Select-Object Name, Count - if ($OSObjects) { - foreach ($OSObject in $OSObjects) { - $inObj = [ordered] @{ - 'Operating System' = Switch ([string]::IsNullOrEmpty($OSObject.Name)) { - $True { 'No OS Specified' } - default { $OSObject.Name } - } - 'Count' = $OSObject.Count - } - $OutObj += [pscustomobject]$inobj - } - if ($HealthCheck.Domain.Security) { - $OutObj | Where-Object { $_.'Operating System' -like '* NT*' -or $_.'Operating System' -like '*2000*' -or $_.'Operating System' -like '*2003*' -or $_.'Operating System' -like '*2008*' -or $_.'Operating System' -like '* NT*' -or $_.'Operating System' -like '*2000*' -or $_.'Operating System' -like '* 95*' -or $_.'Operating System' -like '* 7*' -or $_.'Operating System' -like '* 8 *' -or $_.'Operating System' -like '* 98*' -or $_.'Operating System' -like '*XP*' -or $_.'Operating System' -like '* Vista*' } | Set-Style -Style Critical -Property 'Operating System' + foreach ($ComputerObject in $ComputerObjects) { + $inObj = [ordered] @{ + 'Computer Name' = $ComputerObject.Name + 'Distinguished Name' = $ComputerObject.DistinguishedName + 'Enabled' = ConvertTo-TextYN $ComputerObject.Enabled } + $OutObj += [pscustomobject]$inobj + } - $TableParams = @{ - Name = "Operating System Count - $($Domain.ToString().ToUpper())" - List = $false - ColumnWidths = 60, 40 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $OutObj | Sort-Object -Property 'Operating System' | Table @TableParams - if ($HealthCheck.Domain.Security -and ($OutObj | Where-Object { $_.'Operating System' -like '* NT*' -or $_.'Operating System' -like '*2000*' -or $_.'Operating System' -like '*2003*' -or $_.'Operating System' -like '*2008*' -or $_.'Operating System' -like '* NT*' -or $_.'Operating System' -like '*2000*' -or $_.'Operating System' -like '* 95*' -or $_.'Operating System' -like '* 7*' -or $_.'Operating System' -like '* 8 *' -or $_.'Operating System' -like '* 98*' -or $_.'Operating System' -like '*XP*' -or $_.'Operating System' -like '* Vista*' })) { - Paragraph "Health Check:" -Bold -Underline - BlankLine - Paragraph { - Text "Security Best Practice:" -Bold - Text "Operating systems that are no longer supported for security updates are not maintained or updated for vulnerabilities leaving them open to potential attack. Organizations must transition to a supported operating system to ensure continued support and to increase the organization security posture." - } - } + $TableParams = @{ + Name = "Computers with Password-Not-Required - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 30, 58, 12 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OutObj | Sort-Object -Property 'Computer Name' | Table @TableParams + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Security Best Practice:" -Bold + Text "Ensure there aren't any computer account with weak security posture." } } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Operating Systems in Active Directory)" + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Computers with Password-Not-Required table)" } } } } catch { - Write-PScriboMessage -IsWarning $($_.Exception.Message) + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Computers with Password-Not-Required section)" } if ($InfoLevel.Domain -ge 4) { try { From 1b84e9ff51a2c0c8ad1e1e08fc6369878a68f9b7 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 13 Jun 2024 14:30:22 -0400 Subject: [PATCH 7/9] Zone Transfer section displaying an empty table. #178 --- Src/Private/Get-AbrADDNSZone.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Src/Private/Get-AbrADDNSZone.ps1 b/Src/Private/Get-AbrADDNSZone.ps1 index cb448af..e7d9cad 100644 --- a/Src/Private/Get-AbrADDNSZone.ps1 +++ b/Src/Private/Get-AbrADDNSZone.ps1 @@ -5,7 +5,7 @@ function Get-AbrADDNSZone { .DESCRIPTION .NOTES - Version: 0.8.1 + Version: 0.8.2 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -31,6 +31,7 @@ function Get-AbrADDNSZone { process { try { + $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DDNSInfrastructure' $DNSSetting = Get-DnsServerZone -CimSession $TempCIMSession -ComputerName $DC | Where-Object { $_.IsReverseLookupZone -like "False" -and $_.ZoneType -notlike "Forwarder" } if ($DNSSetting) { Section -Style Heading3 "$($DC.ToString().ToUpper().Split(".")[0]) DNS Zones" { @@ -115,7 +116,11 @@ function Get-AbrADDNSZone { if ($InfoLevel.DNS -ge 2) { try { - $DNSSetting = Invoke-Command -Session $DCPssSession { Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\*" | Get-ItemProperty | Where-Object { $_ -match 'SecondaryServers' } } + $DNSSetting = $Null + if ($DCPssSession) { + $DNSSetting = Invoke-Command -Session $DCPssSession { Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\*" | Get-ItemProperty | Where-Object { $_ -match 'SecondaryServers' } } + Remove-PSSession -Session $DCPssSession + } if ($DNSSetting) { Section -Style Heading4 "Zone Transfers" { $OutObj = @() @@ -298,7 +303,6 @@ function Get-AbrADDNSZone { } } } - Remove-PSSession -Session $DCPssSession } catch { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Global DNS Zone Information)" } From 6c5a89dda4a9019c382ddd3085b038f6dd352328 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 13 Jun 2024 14:31:07 -0400 Subject: [PATCH 8/9] HealthCheck - Computers with Password-not-required attribute set #174 --- Src/Private/Get-AbrADDomainObject.ps1 | 76 ++++++++++++++------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/Src/Private/Get-AbrADDomainObject.ps1 b/Src/Private/Get-AbrADDomainObject.ps1 index 368e65d..818bc6f 100644 --- a/Src/Private/Get-AbrADDomainObject.ps1 +++ b/Src/Private/Get-AbrADDomainObject.ps1 @@ -347,9 +347,9 @@ function Get-AbrADDomainObject { if ($Domain) { try { if ($Domain -eq $ADSystem.Name) { - $GroupsSID = "$DomainSID-1107", "$DomainSID-512", "$DomainSID-519", 'S-1-5-32-544', 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', "$DomainSID-518", 'S-1-5-32-578' + $GroupsSID = "", "$DomainSID-512", "$DomainSID-519", 'S-1-5-32-544', 'S-1-5-32-549', 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', "$DomainSID-518", 'S-1-5-32-578' } else { - $GroupsSID = "$DomainSID-1107", "$DomainSID-512", 'S-1-5-32-549', "$DomainSID-1101", 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', 'S-1-5-32-578' + $GroupsSID = "$DomainSID-512", 'S-1-5-32-549', 'S-1-5-32-555', 'S-1-5-32-557', "$DomainSID-526", 'S-1-5-32-551', "$DomainSID-517", 'S-1-5-32-550', 'S-1-5-32-548', 'S-1-5-32-578' } if ($GroupsSID) { if ($InfoLevel.Domain -eq 1) { @@ -523,39 +523,41 @@ function Get-AbrADDomainObject { try { $AdminGroupOBj = Invoke-Command -Session $TempPssSession { (Get-ADGroup -Server $using:DC -Filter "admincount -eq '1'" -SearchBase (Get-ADDomain -Identity $using:Domain).distinguishedName) } if ($AdminGroupOBj) { - Section -Style Heading5 'Privileged Group (Non-Default)' { - Paragraph "The following section summarizes the privileged groups with AdminCount set to 1 (non-defaults)." - BlankLine - $OutObj = @() - foreach ($Group in $AdminGroupOBj) { - if ($Group.SID -notin $AdminGroupsBySID) { - try { - $inObj = [ordered] @{ - 'Group Name' = $Group.Name - 'Group SID' = $Group.SID - } - $OutObj += [pscustomobject]$inobj - } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Privileged Group (Non-Default) Table)" + $OutObj = @() + foreach ($Group in $AdminGroupOBj) { + if ($Group.SID -notin $AdminGroupsBySID) { + try { + $inObj = [ordered] @{ + 'Group Name' = $Group.Name + 'Group SID' = $Group.SID } + $OutObj += [pscustomobject]$inobj + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Privileged Group (Non-Default) Table)" } } + } - $TableParams = @{ - Name = "Privileged Group (Non-Default) - $($Domain.ToString().ToUpper())" - List = $false - ColumnWidths = 50, 50 - } + $TableParams = @{ + Name = "Privileged Group (Non-Default) - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 50, 50 + } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $OutObj | Sort-Object -Property 'Group Name' | Table @TableParams - Paragraph "Health Check:" -Bold -Underline - BlankLine - Paragraph { - Text "Best Practice:" -Bold - Text "Regularly validate and remove unneeded privileged group members in Active Directory." + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + if ($OutObj) { + Section -Style Heading5 'Privileged Group (Non-Default)' { + Paragraph "The following section summarizes the privileged groups with AdminCount set to 1 (non-defaults)." + BlankLine + $OutObj | Sort-Object -Property 'Group Name' | Table @TableParams + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Best Practice:" -Bold + Text "Regularly validate and remove unneeded privileged group members in Active Directory." + } } } } @@ -975,24 +977,24 @@ function Get-AbrADDomainObject { foreach ($Item in $Domain) { $DomainInfo = Invoke-Command -Session $TempPssSession { Get-ADDomain $using:Domain -ErrorAction Stop } $DCPDC = Invoke-Command -Session $TempPssSession { Get-ADDomain -Identity $using:Item | Select-Object -ExpandProperty PDCEmulator } - $LAPS = Invoke-Command -Session $TempPssSession { Get-ADObject -Server $using:DCPDC "CN=ms-Mcs-AdmPwd,CN=Schema,CN=Configuration,$(($using:DomainInfo).DistinguishedName)" } | Sort-Object -Property Name - Section -Style Heading3 'Windows LAPS ' { + $LAPS = try { Invoke-Command -Session $TempPssSession -ErrorAction Stop { Get-ADObject -Server $using:DCPDC "CN=ms-Mcs-AdmPwd,CN=Schema,CN=Configuration,$(($using:DomainInfo).DistinguishedName)" -ErrorAction SilentlyContinue } | Sort-Object -Property Name } catch { Out-Null } + Section -Style Heading3 'Microsoft LAPS ' { $LAPSInfo = @() try { $inObj = [ordered] @{ - 'Name' = $LAPS.Name + 'Name' = 'Local Administrator Password Solution' 'Domain Name' = $Item 'Enabled' = Switch ($LAPS.Count) { 0 { 'No' } default { 'Yes' } } - 'Distinguished Name' = $LAPS.DistinguishedName + 'Distinguished Name' = ConvertTo-EmptyToFiller $LAPS.DistinguishedName } $LAPSInfo += [pscustomobject]$inobj if ($HealthCheck.Domain.Security) { - $LAPSInfo | Where-Object { $_.'Enabled' -eq 'No' } | Set-Style -Style Warning + $LAPSInfo | Where-Object { $_.'Enabled' -eq 'No' } | Set-Style -Style Warning -Property 'Enabled' } } catch { @@ -1002,7 +1004,7 @@ function Get-AbrADDomainObject { if ($InfoLevel.Domain -ge 2) { foreach ($LAP in $LAPSInfo) { $TableParams = @{ - Name = "Windows LAPS - $($Domain.ToString().ToUpper())" + Name = "Microsoft LAPS - $($Domain.ToString().ToUpper())" List = $true ColumnWidths = 40, 60 } @@ -1013,7 +1015,7 @@ function Get-AbrADDomainObject { } } else { $TableParams = @{ - Name = "Windows LAPS - $($Domain.ToString().ToUpper())" + Name = "Microsoft LAPS - $($Domain.ToString().ToUpper())" List = $false Columns = 'Name', 'Domain Name', 'Enabled' ColumnWidths = 34, 33, 33 From 76ac38a84a2e3c126b44bb5f327eeee6b134b9fb Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 13 Jun 2024 14:31:31 -0400 Subject: [PATCH 9/9] Improved code to better handle errors --- CHANGELOG.md | 15 + Src/Private/Get-AbrADDCRoleFeature.ps1 | 3 +- Src/Private/Get-AbrADDFSHealth.ps1 | 32 +- Src/Private/Get-AbrADDomainController.ps1 | 652 ++++++++++-------- Src/Private/Get-AbrADExchange.ps1 | 4 +- Src/Private/Get-AbrADFSMO.ps1 | 6 +- Src/Private/Get-AbrADGPO.ps1 | 16 +- .../Get-AbrADInfrastructureService.ps1 | 5 +- Src/Private/Get-AbrADOU.ps1 | 4 +- Src/Private/Get-AbrADSecurityAssessment.ps1 | 6 +- Src/Private/Get-AbrADSite.ps1 | 9 +- Src/Private/Get-AbrADSiteReplication.ps1 | 6 +- Src/Private/Get-AbrDNSSection.ps1 | 4 - Src/Private/Get-AbrDomainSection.ps1 | 40 +- Src/Private/SharedUtilsFunctions.ps1 | 60 +- 15 files changed, 497 insertions(+), 365 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f1e88..3fee177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.8.1] - 2024-05-16 +### Changed + +- Improved code to better handle errors + +### Fixed + +- [#168](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/168) +- [#171](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/171) +- [#172](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/172) +- [#174](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/174) +- [#176](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/176) +- [#178](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/178) + +## [0.8.1] - 2024-05-16 + ### Added - Site Topology diagram diff --git a/Src/Private/Get-AbrADDCRoleFeature.ps1 b/Src/Private/Get-AbrADDCRoleFeature.ps1 index ed13611..7032fe6 100644 --- a/Src/Private/Get-AbrADDCRoleFeature.ps1 +++ b/Src/Private/Get-AbrADDCRoleFeature.ps1 @@ -29,7 +29,8 @@ function Get-AbrADDCRoleFeature { process { try { - if ($DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'ADDCRoleFeature') { + $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'ADDCRoleFeature' + if ($DCPssSession) { $Features = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-WindowsFeature | Where-Object { $_.installed -eq "True" -and $_.FeatureType -eq 'Role' } } Remove-PSSession -Session $DCPssSession } diff --git a/Src/Private/Get-AbrADDFSHealth.ps1 b/Src/Private/Get-AbrADDFSHealth.ps1 index 82f6d53..7da3679 100644 --- a/Src/Private/Get-AbrADDFSHealth.ps1 +++ b/Src/Private/Get-AbrADDFSHealth.ps1 @@ -5,7 +5,7 @@ function Get-AbrADDFSHealth { .DESCRIPTION .NOTES - Version: 0.8.1 + Version: 0.8.2 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -106,12 +106,14 @@ function Get-AbrADDFSHealth { $DC = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity $using:Domain).ReplicaDirectoryServers | Select-Object -First 1 } $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainSysvolHealth' # Code taken from ClaudioMerola (https://github.com/ClaudioMerola/ADxRay) - $SYSVOLFolder = Invoke-Command -Session $DCPssSession { Get-ChildItem -Path $('\\' + $using:Domain + '\SYSVOL\' + $using:Domain) -Recurse | Where-Object -FilterScript { $_.PSIsContainer -eq $false } | Group-Object -Property Extension | ForEach-Object -Process { - New-Object -TypeName PSObject -Property @{ - 'Extension' = $_.name - 'Count' = $_.count - 'TotalSize' = '{0:N2}' -f ((($_.group | Measure-Object length -Sum).Sum) / 1MB) - } } | Sort-Object -Descending -Property 'Totalsize' } + if ($DCPssSession) { + $SYSVOLFolder = Invoke-Command -Session $DCPssSession { Get-ChildItem -Path $('\\' + $using:Domain + '\SYSVOL\' + $using:Domain) -Recurse | Where-Object -FilterScript { $_.PSIsContainer -eq $false } | Group-Object -Property Extension | ForEach-Object -Process { + New-Object -TypeName PSObject -Property @{ + 'Extension' = $_.name + 'Count' = $_.count + 'TotalSize' = '{0:N2}' -f ((($_.group | Measure-Object length -Sum).Sum) / 1MB) + } } | Sort-Object -Descending -Property 'Totalsize' } + } if ($SYSVOLFolder) { Section -ExcludeFromTOC -Style NOTOCHeading4 'Sysvol Content Status' { Paragraph "The following section details domain $($Domain.ToString().ToUpper()) sysvol health status." @@ -166,12 +168,14 @@ function Get-AbrADDFSHealth { $DC = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity $using:Domain).ReplicaDirectoryServers | Select-Object -First 1 } $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'NetlogonHealth' # Code taken from ClaudioMerola (https://github.com/ClaudioMerola/ADxRay) - $NetlogonFolder = Invoke-Command -Session $DCPssSession { Get-ChildItem -Path $('\\' + $using:Domain + '\NETLOGON\') -Recurse | Where-Object -FilterScript { $_.PSIsContainer -eq $false } | Group-Object -Property Extension | ForEach-Object -Process { - New-Object -TypeName PSObject -Property @{ - 'Extension' = $_.name - 'Count' = $_.count - 'TotalSize' = '{0:N2}' -f ((($_.group | Measure-Object length -Sum).Sum) / 1MB) - } } | Sort-Object -Descending -Property 'Totalsize' } + if ($DCPssSession) { + $NetlogonFolder = Invoke-Command -Session $DCPssSession { Get-ChildItem -Path $('\\' + $using:Domain + '\NETLOGON\') -Recurse | Where-Object -FilterScript { $_.PSIsContainer -eq $false } | Group-Object -Property Extension | ForEach-Object -Process { + New-Object -TypeName PSObject -Property @{ + 'Extension' = $_.name + 'Count' = $_.count + 'TotalSize' = '{0:N2}' -f ((($_.group | Measure-Object length -Sum).Sum) / 1MB) + } } | Sort-Object -Descending -Property 'Totalsize' } + } if ($NetlogonFolder) { Section -ExcludeFromTOC -Style NOTOCHeading4 'Netlogon Content Status' { Paragraph "The following section details domain $($Domain.ToString().ToUpper()) netlogon health status." @@ -220,7 +224,7 @@ function Get-AbrADDFSHealth { Remove-PSSession -Session $DCPssSession } } catch { - Write-PScriboMessage -IsWarning "Sysvol Health Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning "Netlogon Content Status Section: $($_.Exception.Message)" } } } diff --git a/Src/Private/Get-AbrADDomainController.ps1 b/Src/Private/Get-AbrADDomainController.ps1 index d0de5c5..7ab62fd 100644 --- a/Src/Private/Get-AbrADDomainController.ps1 +++ b/Src/Private/Get-AbrADDomainController.ps1 @@ -46,6 +46,7 @@ function Get-AbrADDomainController { $TableParams['Caption'] = "- $($TableParams.Name)" } try { + # Chart Section $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Category' $chartFileItem = Get-PieChart -SampleData $sampleData -ChartName 'DomainControllerObject' -XField 'Name' -YField 'value' -ChartLegendName 'Category' -ChartTitleName 'DomainControllerObject' -ChartTitleText 'DC vs GC Distribution' -ReversePalette $True @@ -69,8 +70,10 @@ function Get-AbrADDomainController { if (Test-Connection -ComputerName $DC -Quiet -Count 2) { $DCInfo = Invoke-Command -Session $TempPssSession { Get-ADDomainController -Identity $using:DC -Server $using:DC } $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DCNetSettings' - $DCNetSettings = try { Invoke-Command -Session $DCPssSession { Get-NetIPAddress } } catch { Write-PScriboMessage -IsWarning "Unable to get $DC network interfaces information" } - Remove-PSSession -Session $DCPssSession + if ($DCPssSession ) { + $DCNetSettings = try { Invoke-Command -Session $DCPssSession { Get-NetIPAddress } } catch { Write-PScriboMessage -IsWarning "Unable to get $DC network interfaces information" } + Remove-PSSession -Session $DCPssSession + } try { $inObj = [ordered] @{ 'DC Name' = $DC.ToString().ToUpper().Split(".")[0] @@ -145,11 +148,13 @@ function Get-AbrADDomainController { foreach ($DC in $DCs) { if (Test-Connection -ComputerName $DC -Quiet -Count 2) { $DCInfo = Invoke-Command -Session $TempPssSession { Get-ADDomainController -Identity $using:DC -Server $using:DC } - $DCComputerObject = try { Invoke-Command -Session $TempPssSession { Get-ADComputer ($using:DCInfo).ComputerObjectDN -Properties * -Server $using:DC } } catch { Out-Null } + $DCComputerObject = try { Invoke-Command -Session $TempPssSession -ErrorAction Stop { Get-ADComputer ($using:DCInfo).ComputerObjectDN -Properties * -Server $using:DC } } catch { Out-Null } $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DCNetSettings' - $DCNetSettings = try { Invoke-Command -Session $DCPssSession { Get-NetIPAddress } } catch { Out-Null } - $DCNetSMBv1Setting = try { Invoke-Command -Session $DCPssSession { Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol } } catch { Out-Null } - Remove-PSSession -Session $DCPssSession + if ($DCPssSession) { + $DCNetSettings = try { Invoke-Command -Session $DCPssSession -ErrorAction Stop { Get-NetIPAddress } } catch { Out-Null } + $DCNetSMBv1Setting = try { Invoke-Command -Session $DCPssSession -ErrorAction Stop { Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol } } catch { Out-Null } + Remove-PSSession -Session $DCPssSession + } if ($InfoLevel.Domain -eq 1) { try { $inObj = [ordered] @{ @@ -275,42 +280,45 @@ function Get-AbrADDomainController { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Partitions Section)" } try { - Section -ExcludeFromTOC -Style NOTOCHeading5 "Networking Settings" { - $inObj = [ordered] @{ - 'IPv4 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv4' -or $_.AddressFamily -eq 2) -and $_.IPAddress -ne '127.0.0.1' }).IPv4Address))) { - $true { "--" } - $false { ($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv4' -or $_.AddressFamily -eq 2) -and $_.IPAddress -ne '127.0.0.1' }).IPv4Address -join ", " } - default { "Unknown" } - } - 'IPv6 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv6' -or $_.AddressFamily -eq 23) -and $_.IPAddress -ne '::1' }).IPv6Address))) { - $true { "--" } - $false { ($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv6' -or $_.AddressFamily -eq 23) -and $_.IPAddress -ne '::1' }).IPv6Address -join "," } - default { "Unknown" } + if ($DCNetSettings) { + Section -ExcludeFromTOC -Style NOTOCHeading5 "Networking Settings" { + $inObj = [ordered] @{ + 'IPv4 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv4' -or $_.AddressFamily -eq 2) -and $_.IPAddress -ne '127.0.0.1' }).IPv4Address))) { + $true { "--" } + $false { ($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv4' -or $_.AddressFamily -eq 2) -and $_.IPAddress -ne '127.0.0.1' }).IPv4Address -join ", " } + default { "Unknown" } + } + 'IPv6 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv6' -or $_.AddressFamily -eq 23) -and $_.IPAddress -ne '::1' }).IPv6Address))) { + $true { "--" } + $false { ($DCNetSettings | Where-Object { ($_.AddressFamily -eq 'IPv6' -or $_.AddressFamily -eq 23) -and $_.IPAddress -ne '::1' }).IPv6Address -join "," } + default { "Unknown" } + } + "LDAP Port" = $DCInfo.LdapPort + "SSL Port" = $DCInfo.SslPort } - "LDAP Port" = $DCInfo.LdapPort - "SSL Port" = $DCInfo.SslPort - } - $OutObj = [pscustomobject]$inobj - - if ($HealthCheck.DomainController.BestPractice) { - $OutObj | Where-Object { $_.'IPv4 Addresses'.Split(",").Count -gt 1 } | Set-Style -Style Warning -Property 'IPv4 Addresses' - } + $OutObj = [pscustomobject]$inobj - $TableParams = @{ - Name = "Networking Settings - $($DCInfo.Name)" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $OutObj | Table @TableParams - if ($HealthCheck.DomainController.BestPractice -and ($OutObj | Where-Object { $_.'IPv4 Addresses'.Split(",").Count -gt 1 })) { - Paragraph "Health Check:" -Bold -Underline - BlankLine - Paragraph { - Text "Best Practice:" -Bold - Text "On Domain Controllers with more than one NIC where each NIC is connected to separate Network, there's a possibility that the Host A DNS registration can occur for unwanted NICs. Avoid registering unwanted NICs in DNS on a multihomed domain controller." + if ($HealthCheck.DomainController.BestPractice) { + $OutObj | Where-Object { $_.'IPv4 Addresses'.Split(",").Count -gt 1 } | Set-Style -Style Warning -Property 'IPv4 Addresses' + } + if ($OutObj) { + $TableParams = @{ + Name = "Networking Settings - $($DCInfo.Name)" + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OutObj | Table @TableParams + if ($HealthCheck.DomainController.BestPractice -and ($OutObj | Where-Object { $_.'IPv4 Addresses'.Split(",").Count -gt 1 })) { + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Best Practice:" -Bold + Text "On Domain Controllers with more than one NIC where each NIC is connected to separate Network, there's a possibility that the Host A DNS registration can occur for unwanted NICs. Avoid registering unwanted NICs in DNS on a multihomed domain controller." + } + } } } } @@ -318,68 +326,75 @@ function Get-AbrADDomainController { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Networking Settings Section)" } try { - Section -ExcludeFromTOC -Style NOTOCHeading5 'Hardware Inventory' { - $DCHWInfo = @() - try { - $CimSession = New-CimSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainControllerHardware' - $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainControllerHardware' + $DCHWInfo = @() + try { + $CimSession = New-CimSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainControllerHardware' + $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainControllerHardware' + if ($DCPssSession) { $HW = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ComputerInfo } - $License = Get-CimInstance -Query 'Select * from SoftwareLicensingProduct' -CimSession $CimSession | Where-Object { $_.LicenseStatus -eq 1 } $HWCPU = Get-CimInstance -Class Win32_Processor -CimSession $CimSession Remove-PSSession -Session $DCPssSession + } + + if ($CimSession) { + $License = Get-CimInstance -Query 'Select * from SoftwareLicensingProduct' -CimSession $CimSession | Where-Object { $_.LicenseStatus -eq 1 } Remove-CimSession $CimSession - if ($HW) { - $inObj = [ordered] @{ - 'Name' = $HW.CsName - 'Windows Product Name' = $HW.WindowsProductName - 'Windows Build Number' = $HW.OsVersion - 'AD Domain' = $HW.CsDomain - 'Windows Installation Date' = $HW.OsInstallDate - 'Time Zone' = $HW.TimeZone - 'License Type' = $License.ProductKeyChannel - 'Partial Product Key' = $License.PartialProductKey - 'Manufacturer' = $HW.CsManufacturer - 'Model' = $HW.CsModel - 'Processor Model' = $HWCPU[0].Name - 'Number of Processors' = ($HWCPU | Measure-Object).Count - 'Number of CPU Cores' = $HWCPU[0].NumberOfCores - 'Number of Logical Cores' = $HWCPU[0].NumberOfLogicalProcessors - 'Physical Memory' = & { - try { - ConvertTo-FileSizeString $HW.CsTotalPhysicalMemory - } catch { '0.00 GB' } - } + } + if ($HW) { + $inObj = [ordered] @{ + 'Name' = $HW.CsName + 'Windows Product Name' = $HW.WindowsProductName + 'Windows Build Number' = $HW.OsVersion + 'AD Domain' = $HW.CsDomain + 'Windows Installation Date' = $HW.OsInstallDate + 'Time Zone' = $HW.TimeZone + 'License Type' = $License.ProductKeyChannel + 'Partial Product Key' = $License.PartialProductKey + 'Manufacturer' = $HW.CsManufacturer + 'Model' = $HW.CsModel + 'Processor Model' = $HWCPU[0].Name + 'Number of Processors' = ($HWCPU | Measure-Object).Count + 'Number of CPU Cores' = $HWCPU[0].NumberOfCores + 'Number of Logical Cores' = $HWCPU[0].NumberOfLogicalProcessors + 'Physical Memory' = & { + try { + ConvertTo-FileSizeString $HW.CsTotalPhysicalMemory + } catch { '0.00 GB' } } - $DCHWInfo += [pscustomobject]$inobj } + $DCHWInfo += [pscustomobject]$inobj + } - if ($HealthCheck.DomainController.Diagnostic) { - if ([int]([regex]::Matches($DCHWInfo.'Physical Memory', "\d+(\.*\d+)").value) -lt 8) { - $DCHWInfo | Set-Style -Style Warning -Property 'Physical Memory' - } - } - $TableParams = @{ - Name = "Hardware Inventory - $($DCHWInfo.Name.ToString().ToUpper())" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" + if ($HealthCheck.DomainController.Diagnostic) { + if ([int]([regex]::Matches($DCHWInfo.'Physical Memory', "\d+(\.*\d+)").value) -lt 8) { + $DCHWInfo | Set-Style -Style Warning -Property 'Physical Memory' } - $DCHWInfo | Table @TableParams - if ($HealthCheck.DomainController.Diagnostic) { - if ([int]([regex]::Matches($DCHWInfo.'Physical Memory', "\d+(\.*\d+)").value) -lt 8) { - Paragraph "Health Check:" -Bold -Underline - BlankLine - Paragraph { - Text "Best Practice:" -Bold - Text "Microsoft recommend putting enough RAM 8GB+ to load the entire DIT into memory, plus accommodate the operating system and other installed applications, such as anti-virus, backup software, monitoring, and so on." + } + if ($DCHWInfo) { + Section -ExcludeFromTOC -Style NOTOCHeading5 'Hardware Inventory' { + $TableParams = @{ + Name = "Hardware Inventory - $($DCHWInfo.Name.ToString().ToUpper())" + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DCHWInfo | Table @TableParams + if ($HealthCheck.DomainController.Diagnostic) { + if ([int]([regex]::Matches($DCHWInfo.'Physical Memory', "\d+(\.*\d+)").value) -lt 8) { + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Best Practice:" -Bold + Text "Microsoft recommend putting enough RAM 8GB+ to load the entire DIT into memory, plus accommodate the operating system and other installed applications, such as anti-virus, backup software, monitoring, and so on." + } } } } - } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Hardware Inventory Table)" } + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Hardware Inventory Table)" } } catch { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Domain Controller Hardware Section)" @@ -392,100 +407,107 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Domain Controller Table)" + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Domain Controller Section)" } } #---------------------------------------------------------------------------------------------# # DNS IP Section # #---------------------------------------------------------------------------------------------# try { - Section -Style Heading4 "DNS IP Configuration" { - $OutObj = @() - foreach ($DC in $DCs) { - if (Test-Connection -ComputerName $DC -Quiet -Count 2) { - $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DNSIPConfiguration' - try { + $OutObj = @() + foreach ($DC in $DCs) { + if (Test-Connection -ComputerName $DC -Quiet -Count 2) { + $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DNSIPConfiguration' + try { + if ($DCPssSession) { $DCIPAddress = Invoke-Command -Session $DCPssSession { [System.Net.Dns]::GetHostAddresses($using:DC).IPAddressToString } $DNSSettings = Invoke-Command -Session $DCPssSession { Get-NetAdapter | Get-DnsClientServerAddress -AddressFamily IPv4 } $PrimaryDNSSoA = Invoke-Command -Session $DCPssSession { (Get-DnsServerResourceRecord -RRType Soa -ZoneName $using:Domain).RecordData.PrimaryServer } - $UnresolverDNS = @() - foreach ($DNSServer in $DNSSettings.ServerAddresses) { + } + $UnresolverDNS = @() + foreach ($DNSServer in $DNSSettings.ServerAddresses) { + if ($DCPssSession) { $Unresolver = Invoke-Command -Session $DCPssSession { Resolve-DnsName -Server $using:DNSServer -Name $using:PrimaryDNSSoA -DnsOnly -ErrorAction SilentlyContinue } - if ([string]::IsNullOrEmpty($Unresolver)) { - $UnresolverDNS += $DNSServer - } } - foreach ($DNSSetting in $DNSSettings) { - try { - $inObj = [ordered] @{ - 'DC Name' = $DC.ToString().ToUpper().Split(".")[0] - 'Interface' = $DNSSetting.InterfaceAlias - 'Prefered DNS' = ConvertTo-EmptyToFiller $DNSSetting.ServerAddresses[0] - 'Alternate DNS' = ConvertTo-EmptyToFiller $DNSSetting.ServerAddresses[1] - 'DNS 3' = ConvertTo-EmptyToFiller $DNSSetting.ServerAddresses[2] - 'DNS 4' = ConvertTo-EmptyToFiller $DNSSetting.ServerAddresses[3] - } - $OutObj += [pscustomobject]$inobj - } catch { - Write-PScriboMessage -IsWarning "$($DC.ToString().ToUpper().Split(".")[0]) DNS IP Configuration Section: $($_.Exception.Message)" + if ([string]::IsNullOrEmpty($Unresolver)) { + $UnresolverDNS += $DNSServer + } + } + foreach ($DNSSetting in $DNSSettings) { + try { + $inObj = [ordered] @{ + 'DC Name' = $DC.ToString().ToUpper().Split(".")[0] + 'Interface' = $DNSSetting.InterfaceAlias + 'Prefered DNS' = ConvertTo-EmptyToFiller $DNSSetting.ServerAddresses[0] + 'Alternate DNS' = ConvertTo-EmptyToFiller $DNSSetting.ServerAddresses[1] + 'DNS 3' = ConvertTo-EmptyToFiller $DNSSetting.ServerAddresses[2] + 'DNS 4' = ConvertTo-EmptyToFiller $DNSSetting.ServerAddresses[3] } + $OutObj += [pscustomobject]$inobj + } catch { + Write-PScriboMessage -IsWarning "$($DC.ToString().ToUpper().Split(".")[0]) DNS IP Configuration Section: $($_.Exception.Message)" } - } catch { - Write-PScriboMessage -IsWarning "Domain Controller DNS IP Configuration Table Section: $($_.Exception.Message)" } + } catch { + Write-PScriboMessage -IsWarning "Domain Controller DNS IP Configuration Table Section: $($_.Exception.Message)" + } - if ($DCPssSession) { - Remove-PSSession -Session $DCPssSession - } + if ($DCPssSession) { + Remove-PSSession -Session $DCPssSession } } + } - if ($HealthCheck.DomainController.BestPractice) { - $OutObj | Where-Object { $_.'Prefered DNS' -eq "127.0.0.1" -or $_.'Prefered DNS' -in $DCIPAddress } | Set-Style -Style Warning -Property 'Prefered DNS' - $OutObj | Where-Object { $_.'Alternate DNS' -eq "--" } | Set-Style -Style Warning -Property 'Alternate DNS' - $OutObj | Where-Object { $_.'Prefered DNS' -in $UnresolverDNS } | Set-Style -Style Critical -Property 'Prefered DNS' - $OutObj | Where-Object { $_.'Alternate DNS' -in $UnresolverDNS } | Set-Style -Style Critical -Property 'Alternate DNS' - $OutObj | Where-Object { $_.'DNS 3' -in $UnresolverDNS } | Set-Style -Style Critical -Property 'DNS 3' - $OutObj | Where-Object { $_.'DNS 4' -in $UnresolverDNS } | Set-Style -Style Critical -Property 'DNS 4' - } + if ($HealthCheck.DomainController.BestPractice) { + $OutObj | Where-Object { $_.'Prefered DNS' -eq "127.0.0.1" -or $_.'Prefered DNS' -in $DCIPAddress } | Set-Style -Style Warning -Property 'Prefered DNS' + $OutObj | Where-Object { $_.'Alternate DNS' -eq "--" } | Set-Style -Style Warning -Property 'Alternate DNS' + $OutObj | Where-Object { $_.'Prefered DNS' -in $UnresolverDNS } | Set-Style -Style Critical -Property 'Prefered DNS' + $OutObj | Where-Object { $_.'Alternate DNS' -in $UnresolverDNS } | Set-Style -Style Critical -Property 'Alternate DNS' + $OutObj | Where-Object { $_.'DNS 3' -in $UnresolverDNS } | Set-Style -Style Critical -Property 'DNS 3' + $OutObj | Where-Object { $_.'DNS 4' -in $UnresolverDNS } | Set-Style -Style Critical -Property 'DNS 4' + } - $TableParams = @{ - Name = "DNS IP Configuration - $($Domain.ToString().ToUpper())" - List = $false - ColumnWidths = 20, 20, 15, 15, 15, 15 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $OutObj | Sort-Object -Property 'DC Name' | Table @TableParams - if ($HealthCheck.DomainController.BestPractice -and (($OutObj | Where-Object { $_.'Prefered DNS' -eq "127.0.0.1" }) -or ($OutObj | Where-Object { $_.'Prefered DNS' -in $DCIPAddress }) -or ($OutObj | Where-Object { $_.'Alternate DNS' -eq "--" }) -or ($OutObj | Where-Object { $_.'Prefered DNS' -in $UnresolverDNS -or $_.'Alternate DNS' -in $UnresolverDNS -or $_.'DNS 3' -in $UnresolverDNS -or $_.'DNS 4' -in $UnresolverDNS }))) { - Paragraph "Health Check:" -Bold -Underline - BlankLine - if ($OutObj | Where-Object { $_.'Prefered DNS' -eq "127.0.0.1" }) { - Paragraph { - Text "Best Practices:" -Bold - Text "DNS configuration on network adapter should include the loopback address, but not as the first entry." - } + if ($OutObj) { + Section -Style Heading4 "DNS IP Configuration" { + $TableParams = @{ + Name = "DNS IP Configuration - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 20, 20, 15, 15, 15, 15 } - if ($OutObj | Where-Object { $_.'Prefered DNS' -in $DCIPAddress }) { - BlankLine - Paragraph { - Text "Best Practices:" -Bold - Text "DNS configuration on network adapter shouldn't include the Domain Controller own IP address as the first entry." - } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" } - if ($OutObj | Where-Object { $_.'Alternate DNS' -eq "--" }) { + + $OutObj | Sort-Object -Property 'DC Name' | Table @TableParams + if ($HealthCheck.DomainController.BestPractice -and (($OutObj | Where-Object { $_.'Prefered DNS' -eq "127.0.0.1" }) -or ($OutObj | Where-Object { $_.'Prefered DNS' -in $DCIPAddress }) -or ($OutObj | Where-Object { $_.'Alternate DNS' -eq "--" }) -or ($OutObj | Where-Object { $_.'Prefered DNS' -in $UnresolverDNS -or $_.'Alternate DNS' -in $UnresolverDNS -or $_.'DNS 3' -in $UnresolverDNS -or $_.'DNS 4' -in $UnresolverDNS }))) { + Paragraph "Health Check:" -Bold -Underline BlankLine - Paragraph { - Text "Best Practices:" -Bold - Text "For redundancy reasons, the DNS configuration on the network adapter should include an Alternate DNS address." + if ($OutObj | Where-Object { $_.'Prefered DNS' -eq "127.0.0.1" }) { + Paragraph { + Text "Best Practices:" -Bold + Text "DNS configuration on network adapter should include the loopback address, but not as the first entry." + } } - } - if ($OutObj | Where-Object { $_.'Prefered DNS' -in $UnresolverDNS -or $_.'Alternate DNS' -in $UnresolverDNS -or $_.'DNS 3' -in $UnresolverDNS -or $_.'DNS 4' -in $UnresolverDNS }) { - BlankLine - Paragraph { - Text "Corrective Actions:" -Bold - Text "Network interfaces must be configured with DNS servers that can resolve names in the forest root domain. The following DNS server did not respond to the query for the forest root domain $($Domain.ToString().toUpper()): $(($UnresolverDNS -join ", "))" + if ($OutObj | Where-Object { $_.'Prefered DNS' -in $DCIPAddress }) { + BlankLine + Paragraph { + Text "Best Practices:" -Bold + Text "DNS configuration on network adapter shouldn't include the Domain Controller own IP address as the first entry." + } + } + if ($OutObj | Where-Object { $_.'Alternate DNS' -eq "--" }) { + BlankLine + Paragraph { + Text "Best Practices:" -Bold + Text "For redundancy reasons, the DNS configuration on the network adapter should include an Alternate DNS address." + } + } + if ($OutObj | Where-Object { $_.'Prefered DNS' -in $UnresolverDNS -or $_.'Alternate DNS' -in $UnresolverDNS -or $_.'DNS 3' -in $UnresolverDNS -or $_.'DNS 4' -in $UnresolverDNS }) { + BlankLine + Paragraph { + Text "Corrective Actions:" -Bold + Text "Network interfaces must be configured with DNS servers that can resolve names in the forest root domain. The following DNS server did not respond to the query for the forest root domain $($Domain.ToString().toUpper()): $(($UnresolverDNS -join ", "))" + } } } } @@ -495,117 +517,127 @@ function Get-AbrADDomainController { } try { - Section -Style Heading4 'NTDS Information' { - $OutObj = @() - foreach ($DC in $DCs) { - if (Test-Connection -ComputerName $DC -Quiet -Count 2) { - try { - $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'NTDS' + $OutObj = @() + foreach ($DC in $DCs) { + if (Test-Connection -ComputerName $DC -Quiet -Count 2) { + try { + $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'NTDS' + if ($DCPssSession) { $NTDS = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Services\NTDS\Parameters | Select-Object -ExpandProperty 'DSA Database File' } $size = Invoke-Command -Session $DCPssSession -ScriptBlock { (Get-ItemProperty -Path $using:NTDS).Length } $LogFiles = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Services\NTDS\Parameters | Select-Object -ExpandProperty 'Database log files path' } $SYSVOL = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters | Select-Object -ExpandProperty 'SysVol' } Remove-PSSession -Session $DCPssSession - if ( $NTDS -and $size ) { - $inObj = [ordered] @{ - 'DC Name' = $DC.ToString().ToUpper().Split(".")[0] - 'Database File' = $NTDS - 'Database Size' = ConvertTo-FileSizeString $size - 'Log Path' = $LogFiles - 'SysVol Path' = $SYSVOL - } - $OutObj += [pscustomobject]$inobj + } + if ( $NTDS -and $size ) { + $inObj = [ordered] @{ + 'DC Name' = $DC.ToString().ToUpper().Split(".")[0] + 'Database File' = $NTDS + 'Database Size' = ConvertTo-FileSizeString $size + 'Log Path' = $LogFiles + 'SysVol Path' = $SYSVOL } - } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (NTDS Item)" + $OutObj += [pscustomobject]$inobj } + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (NTDS Item)" } } + } - $TableParams = @{ - Name = "NTDS Database File Usage - $($Domain.ToString().ToUpper())" - List = $false - ColumnWidths = 20, 22, 14, 22, 22 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" + if ($OutObj) { + Section -Style Heading4 'NTDS Information' { + $TableParams = @{ + Name = "NTDS Database File Usage - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 20, 22, 14, 22, 22 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OutObj | Sort-Object -Property 'DC Name' | Table @TableParams } - $OutObj | Sort-Object -Property 'DC Name' | Table @TableParams } } catch { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (NTDS Table)" } try { - Section -Style Heading4 'Time Source Information' { - $OutObj = @() - foreach ($DC in $DCs) { - if (Test-Connection -ComputerName $DC -Quiet -Count 2) { - try { - $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'TimeSource' + $OutObj = @() + foreach ($DC in $DCs) { + if (Test-Connection -ComputerName $DC -Quiet -Count 2) { + try { + $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'TimeSource' + if ($DCPssSession) { $NtpServer = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Services\W32Time\Parameters | Select-Object -ExpandProperty 'NtpServer' } $SourceType = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Services\W32Time\Parameters | Select-Object -ExpandProperty 'Type' } Remove-PSSession -Session $DCPssSession - if ( $NtpServer -and $SourceType ) { - try { - $inObj = [ordered] @{ - 'Name' = $DC.ToString().ToUpper().Split(".")[0] - 'Time Server' = Switch ($NtpServer) { - 'time.windows.com,0x8' { "Domain Hierarchy" } - 'time.windows.com' { "Domain Hierarchy" } - '0x8' { "Domain Hierarchy" } - default { $NtpServer } - } - 'Type' = Switch ($SourceType) { - 'NTP' { "MANUAL (NTP)" } - 'NT5DS' { "DOMHIER" } - default { $SourceType } - } + } + if ( $NtpServer -and $SourceType ) { + try { + $inObj = [ordered] @{ + 'Name' = $DC.ToString().ToUpper().Split(".")[0] + 'Time Server' = Switch ($NtpServer) { + 'time.windows.com,0x8' { "Domain Hierarchy" } + 'time.windows.com' { "Domain Hierarchy" } + '0x8' { "Domain Hierarchy" } + default { $NtpServer } + } + 'Type' = Switch ($SourceType) { + 'NTP' { "MANUAL (NTP)" } + 'NT5DS' { "DOMHIER" } + default { $SourceType } } - $OutObj += [pscustomobject]$inobj - } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Time Source Item)" } + $OutObj += [pscustomobject]$inobj + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Time Source Item)" } - } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Time Source Table)" } + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Time Source Table)" } } + } - $TableParams = @{ - Name = "Time Source Configuration - $($Domain.ToString().ToUpper())" - List = $false - ColumnWidths = 30, 50, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" + if ($OutObj) { + Section -Style Heading4 'Time Source Information' { + $TableParams = @{ + Name = "Time Source Configuration - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 30, 50, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + + $OutObj | Sort-Object -Property 'DC Name' | Table @TableParams } - $OutObj | Sort-Object -Property 'DC Name' | Table @TableParams } } catch { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Time Source)" } if ($HealthCheck.DomainController.Diagnostic) { try { - Section -Style Heading4 'SRV Records Status' { - $OutObj = @() - foreach ($DC in $DCs) { - if (Test-Connection -ComputerName $DC -Quiet -Count 2) { - try { - $CimSession = New-CimSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'SRVRecordsStatus' - $PDCEmulator = Invoke-Command -Session $TempPssSession { (Get-ADDomain $using:Domain -ErrorAction Stop).PDCEmulator } - if ($Domain -eq $ADSystem.RootDomain) { - $SRVRR = Get-DnsServerResourceRecord -CimSession $CimSession -ZoneName _msdcs.$Domain -RRType Srv - $DCARR = Get-DnsServerResourceRecord -CimSession $CimSession -ZoneName $Domain -RRType A | Where-Object { $_.Hostname -eq $DC.ToString().ToUpper().Split(".")[0] } - if ($DC -in $PDCEmulator) { - $PDC = $SRVRR | Where-Object { $_.Hostname -eq "_ldap._tcp.pdc" -and $_.RecordData.DomainName -eq "$($DC)." } - } else { $PDC = 'NonPDC' } - if ($DC -in $ADSystem.GlobalCatalogs) { - $GC = $SRVRR | Where-Object { $_.Hostname -eq "_ldap._tcp.gc" -and $_.RecordData.DomainName -eq "$($DC)." } - } else { $GC = 'NonGC' } - $KDC = $SRVRR | Where-Object { $_.Hostname -eq "_kerberos._tcp.dc" -and $_.RecordData.DomainName -eq "$($DC)." } - $DCRR = $SRVRR | Where-Object { $_.Hostname -eq "_ldap._tcp.dc" -and $_.RecordData.DomainName -eq "$($DC)." } - } else { + $OutObj = @() + foreach ($DC in $DCs) { + if (Test-Connection -ComputerName $DC -Quiet -Count 2) { + try { + $CimSession = New-CimSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'SRVRecordsStatus' + $PDCEmulator = Invoke-Command -Session $TempPssSession { (Get-ADDomain $using:Domain -ErrorAction Stop).PDCEmulator } + if ($CimSession -and ($Domain -eq $ADSystem.RootDomain)) { + $SRVRR = Get-DnsServerResourceRecord -CimSession $CimSession -ZoneName _msdcs.$Domain -RRType Srv + $DCARR = Get-DnsServerResourceRecord -CimSession $CimSession -ZoneName $Domain -RRType A | Where-Object { $_.Hostname -eq $DC.ToString().ToUpper().Split(".")[0] } + if ($DC -in $PDCEmulator) { + $PDC = $SRVRR | Where-Object { $_.Hostname -eq "_ldap._tcp.pdc" -and $_.RecordData.DomainName -eq "$($DC)." } + } else { $PDC = 'NonPDC' } + if ($DC -in $ADSystem.GlobalCatalogs) { + $GC = $SRVRR | Where-Object { $_.Hostname -eq "_ldap._tcp.gc" -and $_.RecordData.DomainName -eq "$($DC)." } + } else { $GC = 'NonGC' } + $KDC = $SRVRR | Where-Object { $_.Hostname -eq "_kerberos._tcp.dc" -and $_.RecordData.DomainName -eq "$($DC)." } + $DCRR = $SRVRR | Where-Object { $_.Hostname -eq "_ldap._tcp.dc" -and $_.RecordData.DomainName -eq "$($DC)." } + Remove-CimSession $CimSession + } else { + if ($CimSession) { $SRVRR = Get-DnsServerResourceRecord -CimSession $CimSession -ZoneName $Domain -RRType Srv $DCARR = Get-DnsServerResourceRecord -CimSession $CimSession -ZoneName $Domain -RRType A | Where-Object { $_.Hostname -eq $DC.ToString().ToUpper().Split(".")[0] } if ($DC -in $PDCEmulator) { @@ -616,77 +648,83 @@ function Get-AbrADDomainController { } else { $GC = 'NonGC' } $KDC = $SRVRR | Where-Object { $_.Hostname -eq "_kerberos._tcp.dc._msdcs" -and $_.RecordData.DomainName -eq "$($DC)." } $DCRR = $SRVRR | Where-Object { $_.Hostname -eq "_ldap._tcp.dc._msdcs" -and $_.RecordData.DomainName -eq "$($DC)." } + Remove-CimSession $CimSession } - Remove-CimSession $CimSession - if ( $SRVRR ) { - try { - $inObj = [ordered] @{ - 'Name' = $DC.ToString().ToUpper().Split(".")[0] - 'A Record' = Switch ([string]::IsNullOrEmpty($DCARR)) { - $True { 'Fail' } - default { 'OK' } - } - 'KDC SRV' = Switch ([string]::IsNullOrEmpty($KDC)) { - $True { 'Fail' } - default { 'OK' } - } - 'PDC SRV' = Switch ([string]::IsNullOrEmpty($PDC)) { - $True { 'Fail' } - $False { - Switch ($PDC) { - 'NonPDC' { 'Non PDC' } - default { 'OK' } - } + } + + if ( $SRVRR ) { + try { + $inObj = [ordered] @{ + 'Name' = $DC.ToString().ToUpper().Split(".")[0] + 'A Record' = Switch ([string]::IsNullOrEmpty($DCARR)) { + $True { 'Fail' } + default { 'OK' } + } + 'KDC SRV' = Switch ([string]::IsNullOrEmpty($KDC)) { + $True { 'Fail' } + default { 'OK' } + } + 'PDC SRV' = Switch ([string]::IsNullOrEmpty($PDC)) { + $True { 'Fail' } + $False { + Switch ($PDC) { + 'NonPDC' { 'Non PDC' } + default { 'OK' } } } - 'GC SRV' = Switch ([string]::IsNullOrEmpty($GC)) { - $True { 'Fail' } - $False { - Switch ($GC) { - 'NonGC' { 'Non GC' } - default { 'OK' } - } + } + 'GC SRV' = Switch ([string]::IsNullOrEmpty($GC)) { + $True { 'Fail' } + $False { + Switch ($GC) { + 'NonGC' { 'Non GC' } + default { 'OK' } } } - 'DC SRV' = Switch ([string]::IsNullOrEmpty($DCRR)) { - $True { 'Fail' } - default { 'OK' } - } } - $OutObj += [pscustomobject]$inobj - } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (SRV Records Status Item)" - } - if ($HealthCheck.DomainController.Diagnostic) { - $OutObj | Where-Object { $_.'A Record' -eq 'Fail' } | Set-Style -Style Critical -Property 'A Record' - $OutObj | Where-Object { $_.'KDC SRV' -eq 'Fail' } | Set-Style -Style Critical -Property 'KDC SRV' - $OutObj | Where-Object { $_.'PDC SRV' -eq 'Fail' } | Set-Style -Style Critical -Property 'PDC SRV' - $OutObj | Where-Object { $_.'GC SRV' -eq 'Fail' } | Set-Style -Style Critical -Property 'GC SRV' - $OutObj | Where-Object { $_.'GC SRV' -eq 'Non GC' } | Set-Style -Style Warning -Property 'GC SRV' - $OutObj | Where-Object { $_.'DC SRV' -eq 'Fail' } | Set-Style -Style Critical -Property 'DC SRV' + 'DC SRV' = Switch ([string]::IsNullOrEmpty($DCRR)) { + $True { 'Fail' } + default { 'OK' } + } } + $OutObj += [pscustomobject]$inobj + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (SRV Records Status Item)" + } + if ($HealthCheck.DomainController.Diagnostic) { + $OutObj | Where-Object { $_.'A Record' -eq 'Fail' } | Set-Style -Style Critical -Property 'A Record' + $OutObj | Where-Object { $_.'KDC SRV' -eq 'Fail' } | Set-Style -Style Critical -Property 'KDC SRV' + $OutObj | Where-Object { $_.'PDC SRV' -eq 'Fail' } | Set-Style -Style Critical -Property 'PDC SRV' + $OutObj | Where-Object { $_.'GC SRV' -eq 'Fail' } | Set-Style -Style Critical -Property 'GC SRV' + $OutObj | Where-Object { $_.'GC SRV' -eq 'Non GC' } | Set-Style -Style Warning -Property 'GC SRV' + $OutObj | Where-Object { $_.'DC SRV' -eq 'Fail' } | Set-Style -Style Critical -Property 'DC SRV' } - } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (SRV Records Status Table)" } + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (SRV Records Status Table)" } } + } - $TableParams = @{ - Name = "SRV Records Status - $($Domain.ToString().ToUpper())" - List = $false - ColumnWidths = 20, 16, 16, 16, 16, 16 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $OutObj | Sort-Object -Property 'Name' | Table @TableParams - if ( $OutObj | Where-Object { $_.'KDC SRV' -eq 'Fail' -or $_.'PDC SRV' -eq 'Fail' -or $_.'GC SRV' -eq 'Fail' -or $_.'DC SRV' -eq 'Fail' }) { - Paragraph "Health Check:" -Bold -Underline - BlankLine - Paragraph { - Text "Best Practice:" -Bold - Text "The SRV record is a Domain Name System (DNS) resource record. It's used to identify computers hosting specific services. SRV resource records are used to locate domain controllers for Active Directory." + if ($OutObj) { + Section -Style Heading4 'SRV Records Status' { + $TableParams = @{ + Name = "SRV Records Status - $($Domain.ToString().ToUpper())" + List = $false + ColumnWidths = 20, 16, 16, 16, 16, 16 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + + $OutObj | Sort-Object -Property 'Name' | Table @TableParams + if ( $OutObj | Where-Object { $_.'KDC SRV' -eq 'Fail' -or $_.'PDC SRV' -eq 'Fail' -or $_.'GC SRV' -eq 'Fail' -or $_.'DC SRV' -eq 'Fail' }) { + Paragraph "Health Check:" -Bold -Underline + BlankLine + Paragraph { + Text "Best Practice:" -Bold + Text "The SRV record is a Domain Name System (DNS) resource record. It's used to identify computers hosting specific services. SRV resource records are used to locate domain controllers for Active Directory." + } } } } @@ -700,7 +738,9 @@ function Get-AbrADDomainController { if (Test-Connection -ComputerName $DC -Quiet -Count 2) { try { $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainControllersFileShares' - $Shares = Invoke-Command -Session $DCPssSession { Get-SmbShare | Where-Object { $_.Description -ne 'Default share' -and $_.Description -notmatch 'Remote' -and $_.Name -ne 'NETLOGON' -and $_.Name -ne 'SYSVOL' } } + if ($DCPssSession) { + $Shares = Invoke-Command -Session $DCPssSession -ErrorAction Stop { Get-SmbShare | Where-Object { $_.Description -ne 'Default share' -and $_.Description -notmatch 'Remote' -and $_.Name -ne 'NETLOGON' -and $_.Name -ne 'SYSVOL' } } + } if ($Shares) { Section -ExcludeFromTOC -Style NOTOCHeading5 $($DC.ToString().ToUpper().Split(".")[0]) { $FSObj = @() @@ -762,9 +802,11 @@ function Get-AbrADDomainController { try { $Software = @() $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainControllerInstalledSoftware' - $SoftwareX64 = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object { ($_.Publisher -notlike "Microsoft*" -and $_.DisplayName -notlike "VMware*" -and $_.DisplayName -notlike "Microsoft*") -and ($Null -ne $_.Publisher -or $Null -ne $_.DisplayName) } | Select-Object -Property DisplayName, Publisher, InstallDate | Sort-Object -Property DisplayName } - $SoftwareX86 = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object { ($_.Publisher -notlike "Microsoft*" -and $_.DisplayName -notlike "VMware*" -and $_.DisplayName -notlike "Microsoft*") -and ($Null -ne $_.Publisher -or $Null -ne $_.DisplayName) } | Select-Object -Property DisplayName, Publisher, InstallDate | Sort-Object -Property DisplayName } - Remove-PSSession -Session $DCPssSession + if ($DCPssSession) { + $SoftwareX64 = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object { ($_.Publisher -notlike "Microsoft*" -and $_.DisplayName -notlike "VMware*" -and $_.DisplayName -notlike "Microsoft*") -and ($Null -ne $_.Publisher -or $Null -ne $_.DisplayName) } | Select-Object -Property DisplayName, Publisher, InstallDate | Sort-Object -Property DisplayName } + $SoftwareX86 = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object { ($_.Publisher -notlike "Microsoft*" -and $_.DisplayName -notlike "VMware*" -and $_.DisplayName -notlike "Microsoft*") -and ($Null -ne $_.Publisher -or $Null -ne $_.DisplayName) } | Select-Object -Property DisplayName, Publisher, InstallDate | Sort-Object -Property DisplayName } + Remove-PSSession -Session $DCPssSession + } If ($SoftwareX64) { $Software += $SoftwareX64 @@ -833,8 +875,10 @@ function Get-AbrADDomainController { try { $Software = @() $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainControllerPendingMissingPatch' - $Updates = Invoke-Command -Session $DCPssSession -ScriptBlock { (New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsHidden=0 and IsInstalled=0").Updates | Select-Object Title, KBArticleIDs } - Remove-PSSession -Session $DCPssSession + if ($DCPssSession ) { + $Updates = Invoke-Command -Session $DCPssSession -ScriptBlock { (New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsHidden=0 and IsInstalled=0").Updates | Select-Object Title, KBArticleIDs } + Remove-PSSession -Session $DCPssSession + } if ( $Updates ) { Section -ExcludeFromTOC -Style NOTOCHeading5 $($DC.ToString().ToUpper().Split(".")[0]) { diff --git a/Src/Private/Get-AbrADExchange.ps1 b/Src/Private/Get-AbrADExchange.ps1 index c6efb6c..aaf9557 100644 --- a/Src/Private/Get-AbrADExchange.ps1 +++ b/Src/Private/Get-AbrADExchange.ps1 @@ -5,7 +5,7 @@ function Get-AbrADExchange { .DESCRIPTION .NOTES - Version: 0.8.1 + Version: 0.8.2 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -23,7 +23,7 @@ function Get-AbrADExchange { } process { - $EXServers = Get-ADExchangeServer + $EXServers = try {Get-ADExchangeServer} catch {Out-Null} try { if ($EXServers ) { Section -Style Heading3 'Exchange Infrastructure' { diff --git a/Src/Private/Get-AbrADFSMO.ps1 b/Src/Private/Get-AbrADFSMO.ps1 index 2b2dbbe..21eb3b2 100644 --- a/Src/Private/Get-AbrADFSMO.ps1 +++ b/Src/Private/Get-AbrADFSMO.ps1 @@ -5,7 +5,7 @@ function Get-AbrADFSMO { .DESCRIPTION .NOTES - Version: 0.8.1 + Version: 0.8.2 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -33,9 +33,9 @@ function Get-AbrADFSMO { $ForestData = Invoke-Command -Session $TempPssSession { Get-ADForest $using:Domain | Select-Object DomainNamingMaster, SchemaMaster } if ($DomainData -and $ForestData) { $DC = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity $using:Domain).ReplicaDirectoryServers | Select-Object -First 1 } - $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'FSMORoles' + $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'FSMORoles' -ErrorAction SilentlyContinue Section -Style Heading3 'FSMO Roles' { - $IsInfraMasterGC = (Invoke-Command -Session $DCPssSession { Get-ADDomainController -Identity ($using:DomainData).InfrastructureMaster }).IsGlobalCatalog + if ($DCPssSession) {$IsInfraMasterGC = (Invoke-Command -Session $DCPssSession -ErrorAction Stop { Get-ADDomainController -Identity ($using:DomainData).InfrastructureMaster }).IsGlobalCatalog} $OutObj = @() try { $inObj = [ordered] @{ diff --git a/Src/Private/Get-AbrADGPO.ps1 b/Src/Private/Get-AbrADGPO.ps1 index 57a3eea..4439760 100644 --- a/Src/Private/Get-AbrADGPO.ps1 +++ b/Src/Private/Get-AbrADGPO.ps1 @@ -5,7 +5,7 @@ function Get-AbrADGPO { .DESCRIPTION .NOTES - Version: 0.8.1 + Version: 0.8.2 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -198,8 +198,8 @@ function Get-AbrADGPO { $DC = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity $using:Domain).ReplicaDirectoryServers | Select-Object -First 1 } $DCPssSession = New-PSSession -ComputerName $DC -Name "WmiFilters" -Credential $Credential -Authentication $Options.PSDefaultAuthentication $DomainInfo = Invoke-Command -Session $TempPssSession { Get-ADDomain $using:Domain -ErrorAction Stop } - $WmiFilters = Get-ADObjectSearch -DN "CN=SOM,CN=WMIPolicy,CN=System,$($DomainInfo.DistinguishedName)" -Filter { objectClass -eq "msWMI-Som" } -SelectPrty '*' -Session $DCPssSession | Sort-Object if ($DCPssSession) { + $WmiFilters = Get-ADObjectSearch -DN "CN=SOM,CN=WMIPolicy,CN=System,$($DomainInfo.DistinguishedName)" -Filter { objectClass -eq "msWMI-Som" } -SelectPrty '*' -Session $DCPssSession | Sort-Object Remove-PSSession -Session $DCPssSession } if ($WmiFilters) { @@ -492,7 +492,7 @@ function Get-AbrADGPO { if ($OUs) { foreach ($OU in $OUs) { try { - $GpoEnforces = Invoke-Command -Session $TempPssSession -ScriptBlock { Get-GPInheritance -Domain $using:Domain -Server $using:DC -Target $using:OU | Select-Object -ExpandProperty GpoLinks } + $GpoEnforces = Invoke-Command -Session $TempPssSession -ErrorAction Stop -ScriptBlock { Get-GPInheritance -Domain $using:Domain -Server $using:DC -Target $using:OU | Select-Object -ExpandProperty GpoLinks } foreach ($GpoEnforced in $GpoEnforces) { if ($GpoEnforced.Enforced -eq "True") { $TargetCanonical = Invoke-Command -Session $TempPssSession -ScriptBlock { Get-ADObject -Server $using:DC -Identity ($using:GpoEnforced).Target -Properties * | Select-Object -ExpandProperty CanonicalName } @@ -547,8 +547,9 @@ function Get-AbrADGPO { $DomainInfo = Invoke-Command -Session $TempPssSession { Get-ADDomain $using:Domain -ErrorAction Stop } $GPOPoliciesSYSVOLUNC = "\\$Domain\SYSVOL\$Domain\Policies" $OrphanGPOs = @() - $GPOPoliciesADSI = (Get-ADObjectSearch -DN "CN=Policies,CN=System,$($DomainInfo.DistinguishedName)" -Filter { objectClass -eq "groupPolicyContainer" } -Properties "Name" -SelectPrty 'Name' -Session $DCPssSession).Name.Trim("{}") | Sort-Object if ($DCPssSession) { + $GPOPoliciesADSI = (Get-ADObjectSearch -DN "CN=Policies,CN=System,$($DomainInfo.DistinguishedName)" -Filter { objectClass -eq "groupPolicyContainer" } -Properties "Name" -SelectPrty 'Name' -Session $DCPssSession).Name.Trim("{}") | Sort-Object + Remove-PSSession -Session $DCPssSession } $GPOPoliciesSYSVOL = (Invoke-Command -Session $TempPssSession -ScriptBlock { Get-ChildItem $using:GPOPoliciesSYSVOLUNC | Sort-Object }).Name.Trim("{}") @@ -558,8 +559,11 @@ function Get-AbrADGPO { $SYSVOLGPOList += $GPOinSYSVOL } } - $MissingADGPOs = Compare-Object $SYSVOLGPOList $GPOPoliciesADSI -PassThru | Where-Object { $_.SideIndicator -eq '<=' } - $MissingSYSVOLGPOs = Compare-Object $GPOPoliciesADSI $SYSVOLGPOList -PassThru | Where-Object { $_.SideIndicator -eq '<=' } + if ($GPOPoliciesADSI -and $SYSVOLGPOList) { + $MissingADGPOs = Compare-Object $SYSVOLGPOList $GPOPoliciesADSI -PassThru | Where-Object { $_.SideIndicator -eq '<=' } + $MissingSYSVOLGPOs = Compare-Object $GPOPoliciesADSI $SYSVOLGPOList -PassThru | Where-Object { $_.SideIndicator -eq '<=' } + } + $OrphanGPOs += $MissingADGPOs $OrphanGPOs += $MissingSYSVOLGPOs if ($OrphanGPOs) { diff --git a/Src/Private/Get-AbrADInfrastructureService.ps1 b/Src/Private/Get-AbrADInfrastructureService.ps1 index ca00598..f42aac8 100644 --- a/Src/Private/Get-AbrADInfrastructureService.ps1 +++ b/Src/Private/Get-AbrADInfrastructureService.ps1 @@ -30,7 +30,10 @@ function Get-AbrADInfrastructureService { process { try { $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DomainControllerInfrastructureServices' - if ($DCPssSession -and ($Available = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-Service "W32Time" | Select-Object DisplayName, Name, Status })) { + if ($DCPssSession) { + $Available = Invoke-Command -Session $DCPssSession -ScriptBlock { Get-Service "W32Time" | Select-Object DisplayName, Name, Status } + } + if ($Available) { $OutObj = @() $Services = @('CertSvc', 'DHCPServer', 'DNS', 'DFS Replication', 'Intersite Messaging', 'Kerberos Key Distribution Center', 'NetLogon', 'Active Directory Domain Services', 'W32Time', 'ADWS', 'RPCSS', 'EVENTSYSTEM', 'DNSCACHE', 'SAMSS', 'WORKSTATION', 'Spooler') foreach ($Service in $Services) { diff --git a/Src/Private/Get-AbrADOU.ps1 b/Src/Private/Get-AbrADOU.ps1 index cc98e67..7994618 100644 --- a/Src/Private/Get-AbrADOU.ps1 +++ b/Src/Private/Get-AbrADOU.ps1 @@ -5,7 +5,7 @@ function Get-AbrADOU { .DESCRIPTION .NOTES - Version: 0.8.1 + Version: 0.8.2 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -88,7 +88,7 @@ function Get-AbrADOU { if ($OUs) { foreach ($OU in $OUs) { try { - $GpoInheritance = Invoke-Command -Session $TempPssSession -ScriptBlock { Get-GPInheritance -Domain $using:Domain -Server $using:DC -Target ($using:OU).DistinguishedName } + $GpoInheritance = Invoke-Command -Session $TempPssSession -ErrorAction Stop -ScriptBlock { Get-GPInheritance -Domain $using:Domain -Server $using:DC -Target ($using:OU).DistinguishedName } if ( $GpoInheritance.GPOInheritanceBlocked -eq "True") { $inObj = [ordered] @{ 'OU Name' = $GpoInheritance.Name diff --git a/Src/Private/Get-AbrADSecurityAssessment.ps1 b/Src/Private/Get-AbrADSecurityAssessment.ps1 index ff7fac7..0ce6057 100644 --- a/Src/Private/Get-AbrADSecurityAssessment.ps1 +++ b/Src/Private/Get-AbrADSecurityAssessment.ps1 @@ -5,7 +5,7 @@ function Get-AbrADSecurityAssessment { .DESCRIPTION .NOTES - Version: 0.8.1 + Version: 0.8.2 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -82,7 +82,7 @@ function Get-AbrADSecurityAssessment { $TableParams['Caption'] = "- $($TableParams.Name)" } try { - + # Chart Section $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Category'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } $chartFileItem = Get-ColumnChart -SampleData $sampleData -ChartName 'AccountSecurityAssessment' -XField 'Category' -YField 'Value' -ChartAreaName 'Account Security Assessment' -AxisXTitle 'Categories' -AxisYTitle 'Number of Users' -ChartTitleName 'AccountSecurityAssessment' -ChartTitleText 'Assessment' -ReversePalette $True } catch { @@ -254,7 +254,7 @@ function Get-AbrADSecurityAssessment { try { $UserSPNs = Invoke-Command -Session $TempPssSession { Get-ADUser -ResultPageSize 1000 -Server $using:Domain -Filter { ServicePrincipalName -like '*' } -Properties AdminCount, PasswordLastSet, LastLogonDate, ServicePrincipalName, TrustedForDelegation, TrustedtoAuthForDelegation } if ($UserSPNs) { - Section -ExcludeFromTOC -Style NOTOCHeading4 'Service Accounts Assessment' { + Section -ExcludeFromTOC -Style NOTOCHeading4 'Service Accounts Assessment (Kerberoastable)' { Paragraph "The following section details probable AD Service Accounts (user accounts with SPNs) on Domain $($Domain.ToString().ToUpper())" BlankLine $OutObj = @() diff --git a/Src/Private/Get-AbrADSite.ps1 b/Src/Private/Get-AbrADSite.ps1 index c41efca..eeab57d 100644 --- a/Src/Private/Get-AbrADSite.ps1 +++ b/Src/Private/Get-AbrADSite.ps1 @@ -238,7 +238,7 @@ function Get-AbrADSite { Remove-PSSession -Session $DCPssSession } } catch { - Write-PScriboMessage -IsWarning "Missing Subnet in AD Item Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning "Missing Subnet in AD Item table: $($_.Exception.Message)" } } } @@ -272,7 +272,7 @@ function Get-AbrADSite { Write-PScriboMessage -IsWarning "No Missing Subnets in AD information found in $ForestInfo, disabling the section." } } catch { - Write-PScriboMessage -IsWarning "Sysvol Replication Table Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning "Missing Subnet in AD Item Section: $($_.Exception.Message)" } } } @@ -302,7 +302,7 @@ function Get-AbrADSite { } try { $DomainDN = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity (Get-ADForest | Select-Object -ExpandProperty RootDomain )).DistinguishedName } - $InterSiteTransports = Invoke-Command -Session $TempPssSession { Get-ADObject -Filter { (objectClass -eq "interSiteTransport") } -SearchBase "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$using:DomainDN" -Properties * } + $InterSiteTransports = try {Invoke-Command -Session $TempPssSession -ErrorAction Stop { Get-ADObject -Filter { (objectClass -eq "interSiteTransport") } -SearchBase "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$using:DomainDN" -Properties * }} catch {Out-Null} if ($InterSiteTransports) { Section -Style Heading4 'Inter-Site Transports' { Paragraph "Site links in Active Directory represent the inter-site connectivity and method used to transfer replication traffic.There are two transport protocols that can be used for replication via site links. The default protocol used in site link is IP, and it performs synchronous replication between available domain controllers. The SMTP method can be used when the link between sites is not reliable." @@ -690,8 +690,7 @@ function Get-AbrADSite { foreach ($Domain in $ADSystem.Domains | Where-Object { $_ -notin $Options.Exclude.Domains }) { $DomainInfo = Invoke-Command -Session $TempPssSession { Get-ADDomain $using:Domain -ErrorAction Stop } foreach ($DC in ($DomainInfo.ReplicaDirectoryServers | Where-Object { $_ -notin $Options.Exclude.DCs })) { - if (Test-Connection -ComputerName $DC -Quiet -Count 2) { - $DCCIMSession = New-CimSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name "SysvolReplication" + if ((Test-Connection -ComputerName $DC -Quiet -Count 2) -and ($DCCIMSession = New-CimSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name "SysvolReplication")) { $Replication = Get-CimInstance -CimSession $DCCIMSession -Namespace "root/microsoftdfs" -Class "dfsrreplicatedfolderinfo" -Filter "ReplicatedFolderName = 'SYSVOL Share'" -EA 0 -Verbose:$False | Select-Object State if ($DCCIMSession) { Remove-CimSession -CimSession $DCCIMSession diff --git a/Src/Private/Get-AbrADSiteReplication.ps1 b/Src/Private/Get-AbrADSiteReplication.ps1 index 05d6ec5..e93ee27 100644 --- a/Src/Private/Get-AbrADSiteReplication.ps1 +++ b/Src/Private/Get-AbrADSiteReplication.ps1 @@ -5,7 +5,7 @@ function Get-AbrADSiteReplication { .DESCRIPTION .NOTES - Version: 0.8.1 + Version: 0.8.2 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -120,7 +120,9 @@ function Get-AbrADSiteReplication { if ($HealthCheck.Site.Replication) { $DC = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity $using:Domain).ReplicaDirectoryServers | Select-Object -First 1 } $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'ActiveDirectoryReplicationStatus' - $RepStatus = Invoke-Command -Session $DCPssSession -ScriptBlock { repadmin /showrepl /repsto /csv | ConvertFrom-Csv } + if ($DCPssSession) { + $RepStatus = Invoke-Command -Session $DCPssSession -ScriptBlock { repadmin /showrepl /repsto /csv | ConvertFrom-Csv } + } if ($RepStatus) { Section -Style Heading4 'Replication Status' { $OutObj = @() diff --git a/Src/Private/Get-AbrDNSSection.ps1 b/Src/Private/Get-AbrDNSSection.ps1 index a8c3732..1c1274f 100644 --- a/Src/Private/Get-AbrDNSSection.ps1 +++ b/Src/Private/Get-AbrDNSSection.ps1 @@ -51,12 +51,8 @@ function Get-AbrDNSSection { $DCs = Invoke-Command -Session $TempPssSession { Get-ADDomain $using:Domain | Select-Object -ExpandProperty ReplicaDirectoryServers | Where-Object { $_ -notin ($using:Options).Exclude.DCs } } foreach ($DC in $DCs) { if (Test-Connection -ComputerName $DC -Quiet -Count 2) { - $DCPssSession = New-PSSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'DDNSInfrastructure' Get-AbrADDNSZone -Domain $Domain -DC $DC } - if ($DCPssSession) { - Remove-PSSession -Session $DCPssSession - } } } } else { diff --git a/Src/Private/Get-AbrDomainSection.ps1 b/Src/Private/Get-AbrDomainSection.ps1 index e562cfd..b9a0232 100644 --- a/Src/Private/Get-AbrDomainSection.ps1 +++ b/Src/Private/Get-AbrDomainSection.ps1 @@ -85,28 +85,34 @@ function Get-AbrDomainSection { Get-AbrADDomainController -Domain $Domain -Dcs $DCs if ($InfoLevel.Domain -ge 2) { - Section -Style Heading4 "Roles" { - Paragraph "The following section provides a summary of installed role & features on $Domain DCs." - foreach ($DC in $DCs) { - $DCStatus = Test-Connection -ComputerName $DC -Quiet -Count 2 - if (-Not $DCStatus) { - Write-PScriboMessage -IsWarning "Unable to connect to $DC. Removing it from the $Domain report" - } - if ($DCStatus) { - Get-AbrADDCRoleFeature -DC $DC - } + $RolesObj = foreach ($DC in $DCs) { + $DCStatus = Test-Connection -ComputerName $DC -Quiet -Count 2 + if (-Not $DCStatus) { + Write-PScriboMessage -IsWarning "Unable to connect to $DC. Removing it from the $Domain report" + } + if ($DCStatus) { + Get-AbrADDCRoleFeature -DC $DC + } + } + if ($RolesObj) { + Section -Style Heading4 "Roles" { + Paragraph "The following section provides a summary of installed role & features on $Domain DCs." + $RolesObj } } } if ($HealthCheck.DomainController.Diagnostic) { try { - Section -Style Heading4 'DC Diagnostic' { - Paragraph "The following section provides a summary of the Active Directory DC Diagnostic." - BlankLine - foreach ($DC in $DCs) { - if (Test-Connection -ComputerName $DC -Quiet -Count 2) { - Get-AbrADDCDiag -Domain $Domain -DC $DC - } + $DCDiagObj = foreach ($DC in $DCs) { + if (Test-Connection -ComputerName $DC -Quiet -Count 2) { + Get-AbrADDCDiag -Domain $Domain -DC $DC + } + } + if ($DCDiagObj) { + Section -Style Heading4 'DC Diagnostic' { + Paragraph "The following section provides a summary of the Active Directory DC Diagnostic." + BlankLine + $DCDiagObj } } } catch { diff --git a/Src/Private/SharedUtilsFunctions.ps1 b/Src/Private/SharedUtilsFunctions.ps1 index 5a7aa46..4069c5c 100644 --- a/Src/Private/SharedUtilsFunctions.ps1 +++ b/Src/Private/SharedUtilsFunctions.ps1 @@ -2121,4 +2121,62 @@ function Get-ColumnChart { return $Base64Image -} # end \ No newline at end of file +} # end + +function Get-ADObjectList { + param ( + [Parameter(Mandatory = $true)] + [string]$Domain, + + [Parameter(Mandatory = $false)] + [string]$Server, + + [Parameter(Mandatory = $false)] + [ValidateSet("Users", "Computers", "Groups", "DomainControllers", "GPOs", "OUs")] + [string[]]$Object + ) + + [System.Collections.Generic.List[PSObject]]$adObjects = New-Object System.Collections.Generic.List[PSObject] + $searcher = New-Object System.DirectoryServices.DirectorySearcher + $ConstructedDomainName = "DC=" + $Domain.Split(".") + $ConstructedDomainName = $ConstructedDomainName -replace " ", ",DC=" + + if ($Server) { + $searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Server/$ConstructedDomainName",$Credential.UserName,$Credential.GetNetworkCredential().Password) + } else { + $searcher.SearchRoot = "LDAP://$ConstructedDomainName" + } + + $searcher.PageSize = 1000 + $searcher.PropertiesToLoad.Add("*") | Out-Null + $searcher.SearchScope = "Subtree" + + # Construct the LDAP filter based on the -Collect parameter + $filters = @() + foreach ($item in $Object) { + switch ($item) { + "Users" { $filters += "(objectCategory=person)" } + "Computers" { $filters += "(objectCategory=computer)" } + "Groups" { $filters += "(objectCategory=group)" } + "DomainControllers" { $filters += "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" } + "OUs" { $filters += "(objectCategory=organizationalUnit)" } + "GPOs" { $filters += "(objectClass=groupPolicyContainer)" } + } + } + # Combine the filters with an OR if multiple categories are specified + $searcher.Filter = if ($filters.Count -gt 1) { "(|" + ($filters -join "") + ")" } else { $filters[0] } + + $results = $searcher.FindAll() + foreach ($result in $results) { + $properties = $result.Properties + $obj = New-Object PSObject + foreach ($propertyName in $properties.PropertyNames) { + $value = if ($properties[$propertyName].Count -eq 1) { $properties[$propertyName][0] } else { $properties[$propertyName] } + $obj | Add-Member -NotePropertyName $propertyName -NotePropertyValue $value + } + $obj | Add-Member -NotePropertyName "domain" -NotePropertyValue $Domain + $adObjects.Add($obj) + } + $searcher.Dispose() + return $adObjects +} \ No newline at end of file