From cc542d8559c3a251f9cec47c6fb52e812499d179 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 14 May 2024 23:42:05 -0400 Subject: [PATCH] Misc Fixes --- CHANGELOG.md | 9 ++-- Src/Private/Get-AbrADDNSInfrastructure.ps1 | 2 +- Src/Private/Get-AbrADDomain.ps1 | 2 +- Src/Private/Get-AbrADDomainController.ps1 | 42 ++++++++++++++-- Src/Private/Get-AbrADForest.ps1 | 6 +-- Src/Private/Get-AbrADOU.ps1 | 4 +- Src/Private/Get-AbrADSecurityAssessment.ps1 | 6 +-- Src/Private/Get-AbrADSite.ps1 | 8 ++-- Src/Private/SharedUtilsFunctions.ps1 | 48 +++++++++++++++---- .../Invoke-AsBuiltReport.Microsoft.AD.ps1 | 7 +++ 10 files changed, 106 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 203981f..179677e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.8.1] - 2024-02-18 +## [0.8.1] - Unreleased ### Added -- Site Inventory Diagram +- Site Topology diagram +- Domain and Trust diagram - Foreign Security Principals section ### Changed @@ -18,10 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Code cleanup - Improve script logging - Increase Diagrammer.Microsoft.AD module version requirements +- Change charts default font to Segoe Ui +- Improved Forest diagram ### Fixed -- Improve error handling on Forest Diagram section +- Improve error handling on Forest diagram section - Fix issues with Diagrammer.Microsoft.AD module - Fix DNS section not getting data when there are unavailable DC servers - Fix [#154](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/154) diff --git a/Src/Private/Get-AbrADDNSInfrastructure.ps1 b/Src/Private/Get-AbrADDNSInfrastructure.ps1 index c8e1f39..96a09b5 100644 --- a/Src/Private/Get-AbrADDNSInfrastructure.ps1 +++ b/Src/Private/Get-AbrADDNSInfrastructure.ps1 @@ -280,7 +280,7 @@ function Get-AbrADDNSInfrastructure { BlankLine Paragraph { Text "Reference:" -Bold - Text "https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/forwarders-resolution-timeouts" + Text "https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/forwarders-resolution-timeouts" -Color blue } BlankLine } diff --git a/Src/Private/Get-AbrADDomain.ps1 b/Src/Private/Get-AbrADDomain.ps1 index b335cce..fe9ed32 100644 --- a/Src/Private/Get-AbrADDomain.ps1 +++ b/Src/Private/Get-AbrADDomain.ps1 @@ -90,7 +90,7 @@ function Get-AbrADDomain { BlankLine Paragraph { Text "Reference:" -Bold - Text "https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/managing-rid-pool-depletion/ba-p/399736" + Text "https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/managing-rid-pool-depletion/ba-p/399736" -Color blue } } } diff --git a/Src/Private/Get-AbrADDomainController.ps1 b/Src/Private/Get-AbrADDomainController.ps1 index 7630dd8..a157a64 100644 --- a/Src/Private/Get-AbrADDomainController.ps1 +++ b/Src/Private/Get-AbrADDomainController.ps1 @@ -29,6 +29,41 @@ function Get-AbrADDomainController { } process { + try { + $OutObj = @() + $inObj = [ordered] @{ + 'Domain Controller' = $DomainController.Count + 'Global Catalog' = $GC.Count + } + $OutObj += [pscustomobject]$inobj + + $TableParams = @{ + Name = "Domain Controller Counts - $($Domain.ToString().ToUpper())" + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + if ($Options.EnableCharts) { + try { + $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 + + } catch { + Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Domain Controller Count Chart)" + } + } + if ($OutObj) { + if ($chartFileItem) { + Image -Text 'Domain Controller Object - Diagram' -Align 'Center' -Percent 100 -Base64 $chartFileItem + } + $OutObj | Table @TableParams + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } if ($InfoLevel.Domain -eq 1) { try { $OutObj = @() @@ -36,7 +71,7 @@ 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 { Out-Null } + $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] @{ @@ -184,6 +219,7 @@ function Get-AbrADDomainController { } 'Global Catalog' = ConvertTo-TextYN $DCInfo.IsGlobalCatalog 'Read Only' = ConvertTo-TextYN $DCInfo.IsReadOnly + 'Operation Master Roles' = $DCInfo.OperationMasterRoles -join ', ' 'Location' = $DCComputerObject.Location 'Computer Object SID' = $DCComputerObject.SID "Operating System" = $DCInfo.OperatingSystem @@ -231,7 +267,7 @@ function Get-AbrADDomainController { $inObj = [ordered] @{ 'IPv4 Addresses' = Switch ([string]::IsNullOrEmpty((($DCNetSettings | Where-Object { $_.AddressFamily -eq 'IPv4' -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' -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))) { @@ -262,7 +298,7 @@ function Get-AbrADDomainController { 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" + 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." } } } diff --git a/Src/Private/Get-AbrADForest.ps1 b/Src/Private/Get-AbrADForest.ps1 index 4a1ed50..fd6389a 100644 --- a/Src/Private/Get-AbrADForest.ps1 +++ b/Src/Private/Get-AbrADForest.ps1 @@ -72,7 +72,7 @@ function Get-AbrADForest { } if ($HealthCheck.Domain.Security) { - $OutObj | Where-Object { $_.'Anonymous Access (dsHeuristics)' -eq 'Enabled' } | Set-Style -Style Warning -Property 'Anonymous Access (dsHeuristics)' + $OutObj | Where-Object { $_.'Anonymous Access (dsHeuristics)' -eq 'Enabled' } | Set-Style -Style Critical -Property 'Anonymous Access (dsHeuristics)' $OutObj | Where-Object { $_.'Tombstone Lifetime (days)' -lt 180 } | Set-Style -Style Warning -Property 'Tombstone Lifetime (days)' } @@ -93,10 +93,10 @@ function Get-AbrADForest { Text "Best Practice:" -Bold Text "Anonymous Access to Active Directory forest data above the rootDSE level must be disabled." } + BlankLine Paragraph "Reference:" -Bold BlankLine Paragraph "https://www.stigviewer.com/stig/active_directory_forest/2016-02-19/finding/V-8555" -Color blue - BlankLine } if ($OutObj | Where-Object { $_.'Tombstone Lifetime (days)' -lt 180 }) { Paragraph { @@ -260,7 +260,7 @@ function Get-AbrADForest { Paragraph { Text "Reference:" -Bold BlankLine - Text "https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-ad-recycle-bin-understanding-implementing-best-practices-and/ba-p/396944" + Text "https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-ad-recycle-bin-understanding-implementing-best-practices-and/ba-p/396944" -Color blue } } diff --git a/Src/Private/Get-AbrADOU.ps1 b/Src/Private/Get-AbrADOU.ps1 index 36c2261..cc98e67 100644 --- a/Src/Private/Get-AbrADOU.ps1 +++ b/Src/Private/Get-AbrADOU.ps1 @@ -77,7 +77,7 @@ function Get-AbrADOU { BlankLine Paragraph { Text "Best Practice:" -Bold - Text "If the Organizational Units in your Active Directory are not protected from accidental deletion, your environment can experience disruptions that might be caused by accidental bulk deletion of objects. All OUs in this domain should be protected from accidental deletion" + Text "If the Organizational Units in your Active Directory are not protected from accidental deletion, your environment can experience disruptions that might be caused by accidental bulk deletion of objects. All OUs in this domain should be protected from accidental deletion." } } if ($HealthCheck.Domain.GPO) { @@ -104,7 +104,7 @@ function Get-AbrADOU { } } if ($OutObj) { - Section -ExcludeFromTOC -Style NOTOCHeading3 "GPO Blocked Inheritance" { + Section -ExcludeFromTOC -Style NOTOCHeading4 "GPO Blocked Inheritance" { if ($HealthCheck.Domain.GPO) { $OutObj | Set-Style -Style Warning } diff --git a/Src/Private/Get-AbrADSecurityAssessment.ps1 b/Src/Private/Get-AbrADSecurityAssessment.ps1 index 3196b2a..4594b82 100644 --- a/Src/Private/Get-AbrADSecurityAssessment.ps1 +++ b/Src/Private/Get-AbrADSecurityAssessment.ps1 @@ -85,7 +85,7 @@ function Get-AbrADSecurityAssessment { try { $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' + $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 { Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Account Security Assessment Chart)" } @@ -185,7 +185,7 @@ function Get-AbrADSecurityAssessment { Paragraph { Text "** Privileged accounts such as those belonging to any of the administrator groups must not be trusted for delegation. Allowing privileged accounts to be trusted for delegation provides a means for privilege escalation from a compromised system. Delegation of privileged accounts must be prohibited." Text "Reference: " - Text "https://www.stigviewer.com/stig/active_directory_domain/2017-12-15/finding/V-36435" + Text "https://www.stigviewer.com/stig/active_directory_domain/2017-12-15/finding/V-36435" -Color blue } } } @@ -304,7 +304,7 @@ function Get-AbrADSecurityAssessment { Paragraph { Text "Security Best Practice:" -Bold - Text "**Attackers are most interested in Service Accounts that are members of highly privileged groups like Domain Admins. A quick way to check for this is to enumerate all user accounts with the attribute AdminCount equal to 1. This means an attacker may just ask AD for all user accounts with a SPN and with AdminCount=1. Ensure that there are no privileged accounts that have SPNs assigned to them. " + Text "**Attackers are most interested in Service Accounts that are members of highly privileged groups like Domain Admins. A quick way to check for this is to enumerate all user accounts with the attribute AdminCount equal to 1. This means an attacker may just ask AD for all user accounts with a SPN and with AdminCount=1. Ensure that there are no privileged accounts that have SPNs assigned to them." } } } diff --git a/Src/Private/Get-AbrADSite.ps1 b/Src/Private/Get-AbrADSite.ps1 index de9ed8d..70f2b5d 100644 --- a/Src/Private/Get-AbrADSite.ps1 +++ b/Src/Private/Get-AbrADSite.ps1 @@ -287,19 +287,19 @@ function Get-AbrADSite { try { $Graph = New-ADDiagram -Target $System -Credential $Credential -Format base64 -Direction top-to-bottom -DiagramType Sites } catch { - Write-PScriboMessage -IsWarning "Site Inventory Diagram Graph: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning "Site Topology Diagram Graph: $($_.Exception.Message)" } if ($Graph) { If ((Get-DiaImagePercent -GraphObj $Graph).Width -gt 1500) { $ImagePrty = 10 } else { $ImagePrty = 50 } - Section -Style Heading4 "Site Inventory Diagram." { - Image -Base64 $Graph -Text "Site Inventory Diagram" -Percent $ImagePrty -Align Center + Section -Style Heading4 "Site Topology Diagram." { + Image -Base64 $Graph -Text "Site Topology Diagram" -Percent $ImagePrty -Align Center Paragraph "Image preview: Opens the image in a new tab to view it at full resolution." -Tabs 2 } BlankLine -Count 2 } } catch { - Write-PScriboMessage -IsWarning "Site Inventory Diagram Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning "Site Topology Diagram Section: $($_.Exception.Message)" } } try { diff --git a/Src/Private/SharedUtilsFunctions.ps1 b/Src/Private/SharedUtilsFunctions.ps1 index 60fac85..27a7625 100644 --- a/Src/Private/SharedUtilsFunctions.ps1 +++ b/Src/Private/SharedUtilsFunctions.ps1 @@ -1968,14 +1968,29 @@ function Get-PieChart { [int] $Width = 600, [int] - $Height = 400 + $Height = 400, + [bool] + $ReversePalette = $false ) - $exampleChart = New-Chart -Name $ChartName -Width $Width -Height $Height + $AbrCustomPalette = @( + [System.Drawing.ColorTranslator]::FromHtml('#355780') + [System.Drawing.ColorTranslator]::FromHtml('#48678f') + [System.Drawing.ColorTranslator]::FromHtml('#5b789e') + [System.Drawing.ColorTranslator]::FromHtml('#6e89ae') + [System.Drawing.ColorTranslator]::FromHtml('#809bbe') + [System.Drawing.ColorTranslator]::FromHtml('#94acce') + [System.Drawing.ColorTranslator]::FromHtml('#a7bfde') + [System.Drawing.ColorTranslator]::FromHtml('#bbd1ee') + [System.Drawing.ColorTranslator]::FromHtml('#cfe4ff') + ) + + $exampleChart = New-Chart -Name $ChartName -Width $Width -Height $Height -BorderColor 'DarkBlue' -BorderStyle Dash -BorderWidth 1 $addChartAreaParams = @{ Chart = $exampleChart Name = 'exampleChartArea' + AxisXInterval = 1 } $exampleChartArea = Add-ChartArea @addChartAreaParams -PassThru @@ -1985,8 +2000,9 @@ function Get-PieChart { Name = 'exampleChartSeries' XField = $XField YField = $YField - Palette = 'Blue' + CustomPalette = $AbrCustomPalette ColorPerDataPoint = $true + ReversePalette = $ReversePalette } $sampleData | Add-PieChartSeries @addChartSeriesParams @@ -2002,7 +2018,7 @@ function Get-PieChart { ChartArea = $exampleChartArea Name = $ChartTitleName Text = $ChartTitleText - Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList @('Arial', '12', [System.Drawing.FontStyle]::Bold) + Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList @('Segoe Ui', '12', [System.Drawing.FontStyle]::Bold) } Add-ChartTitle @addChartTitleParams @@ -2057,10 +2073,24 @@ function Get-ColumnChart { [int] $Width = 600, [int] - $Height = 400 + $Height = 400, + [bool] + $ReversePalette = $false + ) + + $AbrCustomPalette = @( + [System.Drawing.ColorTranslator]::FromHtml('#355780') + [System.Drawing.ColorTranslator]::FromHtml('#48678f') + [System.Drawing.ColorTranslator]::FromHtml('#5b789e') + [System.Drawing.ColorTranslator]::FromHtml('#6e89ae') + [System.Drawing.ColorTranslator]::FromHtml('#809bbe') + [System.Drawing.ColorTranslator]::FromHtml('#94acce') + [System.Drawing.ColorTranslator]::FromHtml('#a7bfde') + [System.Drawing.ColorTranslator]::FromHtml('#bbd1ee') + [System.Drawing.ColorTranslator]::FromHtml('#cfe4ff') ) - $exampleChart = New-Chart -Name $ChartName -Width $Width -Height $Height + $exampleChart = New-Chart -Name $ChartName -Width $Width -Height $Height -BorderColor 'DarkBlue' -BorderStyle Dash -BorderWidth 1 $addChartAreaParams = @{ Chart = $exampleChart @@ -2069,6 +2099,7 @@ function Get-ColumnChart { AxisYTitle = $AxisYTitle NoAxisXMajorGridLines = $true NoAxisYMajorGridLines = $true + AxisXInterval = 1 } $exampleChartArea = Add-ChartArea @addChartAreaParams -PassThru @@ -2078,8 +2109,9 @@ function Get-ColumnChart { Name = 'exampleChartSeries' XField = $XField YField = $YField - Palette = 'Blue' + CustomPalette = $AbrCustomPalette ColorPerDataPoint = $true + ReversePalette = $ReversePalette } $sampleData | Add-ColumnChartSeries @addChartSeriesParams @@ -2088,7 +2120,7 @@ function Get-ColumnChart { ChartArea = $exampleChartArea Name = $ChartTitleName Text = $ChartTitleText - Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList @('Arial', '12', [System.Drawing.FontStyle]::Bold) + Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList @('Segoe Ui', '12', [System.Drawing.FontStyle]::Bold) } Add-ChartTitle @addChartTitleParams diff --git a/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 b/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 index d007930..ae5b494 100644 --- a/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 +++ b/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 @@ -75,6 +75,13 @@ function Invoke-AsBuiltReport.Microsoft.AD { # Used to set values to TitleCase where required $script:TextInfo = (Get-Culture).TextInfo + if ($Global:BuitReportParams.EnableHealthCheck) { + Section -Style TOC -ExcludeFromTOC 'DISCLAIMER' { + Paragraph "The information contained in this report has been obtained through automation and observations. Opinions, recommendations and conclusions are disseminated using insight, knowledge, training and experience. This assessment was not intended to be exhaustive. However, we have done our best to capture the most relevant opportunities for improvement. It is expected that responsibility for the implementation of these recommendations will be reviewed and implemented by a person with the necessary knowledge, experience or expertise. In no event shall the author(s) be liable for damages of any kind (including, but not limited to, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use or inability to use these recommendations or the statements made in this documentation." + } + PageBreak + } + #---------------------------------------------------------------------------------------------# # Connection Section # #---------------------------------------------------------------------------------------------#