From 39a9ed871d25b103dadf2eeaf074e9394c6d4afe Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 12 May 2026 20:39:04 -0400 Subject: [PATCH 01/21] feat: Update module version to 1.0.1 and add Get-AbrAdLog cmdlet for diagnostic logging --- .../AsBuiltReport.Microsoft.AD.psd1 | 4 +- .../Src/Public/Get-AbrAdLog.ps1 | 318 ++++++++++++++++++ CHANGELOG.md | 6 + 3 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 diff --git a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 index 4c25f69..6f1bbff 100644 --- a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 +++ b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 @@ -12,7 +12,7 @@ RootModule = 'AsBuiltReport.Microsoft.AD.psm1' # Version number of this module. - ModuleVersion = '1.0.0' + ModuleVersion = '1.0.1' # Supported PSEditions CompatiblePSEditions = @('Core') @@ -82,7 +82,7 @@ # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = @('Invoke-AsBuiltReport.Microsoft.AD', 'Start-AsBuiltReportMSAD') + FunctionsToExport = @('Invoke-AsBuiltReport.Microsoft.AD', 'Start-AsBuiltReportMSAD', 'Get-AbrAdLog') # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. # CmdletsToExport = '*' diff --git a/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 b/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 new file mode 100644 index 0000000..d7c4c1d --- /dev/null +++ b/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 @@ -0,0 +1,318 @@ + +function Get-AbrAdLog { + <# + .SYNOPSIS + Collects diagnostic information for AsBuiltReport.ActiveDirectory troubleshooting. + .DESCRIPTION + Gathers environment, module, PowerShell session, and error information from + the current session and the machine running the report. Output is written to + a structured JSON file, and a status message is written to the host when the + collection completes successfully. + .PARAMETER OutputFolderPath + Directory where the diagnostic bundle (JSON file) is saved. + Defaults to the system temporary folder. + .PARAMETER IncludeErrorDetails + When specified, captures the full $Error collection including stack traces. + By default only the most recent 25 errors are included (without stack traces). + .PARAMETER PassThru + Returns the diagnostic object to the pipeline in addition to writing the file. + .EXAMPLE + Get-AbrAdLog + + Saves a diagnostic JSON to the system temp folder. + .EXAMPLE + Get-AbrAdLog -OutputFolderPath 'C:\Logs' -IncludeErrorDetails -PassThru + + Saves a full diagnostic JSON (with stack traces) to C:\Logs and returns the + object to the pipeline. + .NOTES + Version: 0.1.0 + Author: Jonathan Colon + Github: rebelinux + .LINK + https://github.com/AsBuiltReport/AsBuiltReport.ActiveDirectory + #> + + [CmdletBinding()] + [OutputType([PSCustomObject])] + param ( + [Parameter(Mandatory = $false, HelpMessage = 'Directory where the diagnostic bundle is saved.')] + [ValidateScript({ Test-Path $_ -PathType Container })] + [String] $OutputFolderPath = ([System.IO.Path]::GetTempPath()), + + [Parameter(Mandatory = $false, HelpMessage = 'Include full stack traces for every error in $Error.')] + [Switch] $IncludeErrorDetails, + + [Parameter(Mandatory = $false, HelpMessage = 'Return the diagnostic object to the pipeline.')] + [Switch] $PassThru + ) + + begin { + Write-Verbose 'Get-AbrAdLog: Starting diagnostic collection.' + $TimeStamp = Get-Date -Format 'yyyyMMdd_HHmmss' + $FileName = "AbrAdDiagnostics_$TimeStamp.json" + $OutputFile = Join-Path -Path $OutputFolderPath -ChildPath $FileName + + # Compute platform once; used throughout process block. + # PS 5.1 (Desktop) lacks $PSVersionTable.Platform, so fall back to env/API. + $IsWindowsPlatform = if ($PSVersionTable.ContainsKey('Platform') -and $PSVersionTable.Platform) { + $PSVersionTable.Platform -eq 'Win32NT' + } else { + ($env:OS -eq 'Windows_NT') -or ([System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT) + } + + $Platform = if ($PSVersionTable.ContainsKey('Platform') -and $PSVersionTable.Platform) { + $PSVersionTable.Platform + } elseif ($IsWindowsPlatform) { + 'Win32NT' + } else { + [System.Environment]::OSVersion.Platform.ToString() + } + } + + process { + $Diag = [ordered] @{} + + # --- Collection timestamp ----------------------------------------------- + $Diag['CollectedAt'] = (Get-Date -Format 'o') + + # --- PowerShell session info -------------------------------------------- + try { + $Diag['PowerShellSession'] = [ordered] @{ + PSVersion = $PSVersionTable.PSVersion.ToString() + PSEdition = $PSVersionTable.PSEdition + CLRVersion = if ($PSVersionTable.CLRVersion) { $PSVersionTable.CLRVersion.ToString() } else { 'N/A' } + WSManStackVersion = if ($PSVersionTable.WSManStackVersion) { $PSVersionTable.WSManStackVersion.ToString() } else { 'N/A' } + OS = $PSVersionTable.OS + Platform = $Platform + ExecutionPolicy = (Get-ExecutionPolicy -Scope Process).ToString() + CurrentPrincipal = if ($IsWindowsPlatform) { + [Security.Principal.WindowsIdentity]::GetCurrent().Name + } else { + $EnvUser = [System.Environment]::GetEnvironmentVariable('USER') + if ($EnvUser) { $EnvUser } else { [System.Environment]::GetEnvironmentVariable('LOGNAME') } + } + IsAdministrator = if ($IsWindowsPlatform) { + ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + } else { + try { (& id -u).Trim() -eq '0' } catch { 'N/A' } + } + HostName = $Host.Name + HostVersion = $Host.Version.ToString() + PID = $PID + } + } catch { + $Diag['PowerShellSession'] = "Error collecting PowerShell session info: $($_.Exception.Message)" + } + + # --- Machine / OS info -------------------------------------------------- + if ($IsWindowsPlatform) { + try { + $OS = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop + $CS = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop + $CPU = Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop | Select-Object -First 1 + $Diag['Machine'] = [ordered] @{ + ComputerName = $env:COMPUTERNAME + Domain = $CS.Domain + Manufacturer = $CS.Manufacturer + Model = $CS.Model + TotalMemoryGB = [math]::Round($CS.TotalPhysicalMemory / 1GB, 2) + OSCaption = $OS.Caption + OSVersion = $OS.Version + OSBuildNumber = $OS.BuildNumber + OSArchitecture = $OS.OSArchitecture + OSLastBootUpTime = $OS.LastBootUpTime.ToString('o') + CPUName = $CPU.Name + CPUCores = $CPU.NumberOfCores + CPULogicalProc = $CPU.NumberOfLogicalProcessors + TimeZone = (Get-TimeZone).DisplayName + } + } catch { + $Diag['Machine'] = "Error collecting machine info: $($_.Exception.Message)" + } + } else { + # Unix (Linux / macOS) + try { + $KernelName = try { (& uname -s).Trim() } catch { 'N/A' } + $KernelRelease = try { (& uname -r).Trim() } catch { 'N/A' } + $Architecture = try { (& uname -m).Trim() } catch { 'N/A' } + $HostName = [System.Net.Dns]::GetHostName() + $EnvUser = [System.Environment]::GetEnvironmentVariable('USER') + $CurrentUser = if ($EnvUser) { $EnvUser } else { [System.Environment]::GetEnvironmentVariable('LOGNAME') } + $IsRoot = try { (& id -u).Trim() -eq '0' } catch { 'N/A' } + + if ($KernelName -eq 'Linux') { + $OSDescription = try { + $Release = Get-Content '/etc/os-release' -ErrorAction Stop + ($Release | Where-Object { $_ -match '^PRETTY_NAME=' } | Select-Object -First 1) -replace '^PRETTY_NAME=|"', '' + } catch { 'N/A' } + + $MemInfo = Get-Content '/proc/meminfo' -ErrorAction SilentlyContinue + $MemKB = ($MemInfo | Where-Object { $_ -match '^MemTotal:' } | Select-Object -First 1) -replace '\D', '' + $MemGB = if ($MemKB) { [math]::Round([long]$MemKB / 1MB, 2) } else { 'N/A' } + + $CpuInfo = Get-Content '/proc/cpuinfo' -ErrorAction SilentlyContinue + $CpuName = ($CpuInfo | Where-Object { $_ -match '^model name' } | Select-Object -First 1) -replace '^model name\s*:\s*', '' + $CpuCores = ($CpuInfo | Where-Object { $_ -match '^cpu cores' } | Select-Object -First 1) -replace '\D', '' + $CpuLogical = ($CpuInfo | Where-Object { $_ -match '^processor' }).Count + } elseif ($KernelName -eq 'Darwin') { + $OSDescription = try { "$(& sw_vers -productName) $(& sw_vers -productVersion)".Trim() } catch { 'N/A' } + $MemBytes = try { [long](& sysctl -n hw.memsize) } catch { $null } + $MemGB = if ($null -ne $MemBytes) { [math]::Round($MemBytes / 1GB, 2) } else { 'N/A' } + $CpuName = try { (& sysctl -n machdep.cpu.brand_string).Trim() } catch { 'N/A' } + $CpuCores = try { (& sysctl -n hw.physicalcpu).Trim() } catch { 'N/A' } + $CpuLogical = try { (& sysctl -n hw.logicalcpu).Trim() } catch { 'N/A' } + } else { + $OSDescription = "Unknown Unix ($KernelName)" + $MemGB = $CpuName = $CpuCores = $CpuLogical = 'N/A' + } + + $Diag['Machine'] = [ordered] @{ + ComputerName = $HostName + CurrentUser = $CurrentUser + IsRoot = $IsRoot + KernelName = $KernelName + KernelRelease = $KernelRelease + OSDescription = $OSDescription + Architecture = $Architecture + TotalMemoryGB = $MemGB + CPUName = if ($CpuName) { $CpuName } else { 'N/A' } + CPUCores = if ($CpuCores) { $CpuCores } else { 'N/A' } + CPULogicalProc = if ($CpuLogical) { $CpuLogical } else { 'N/A' } + TimeZone = (Get-TimeZone).DisplayName + } + } catch { + $Diag['Machine'] = "Error collecting machine info: $($_.Exception.Message)" + } + } + + # --- Relevant installed modules ----------------------------------------- + try { + $RelevantModuleNames = @( + 'AsBuiltReport.Microsoft.AD', + 'AsBuiltReport.Core', + 'AsBuiltReport.Chart', + 'AsBuiltReport.Diagram', + 'PScribo' + ) + $ModuleInfo = foreach ($ModName in $RelevantModuleNames) { + $Mods = Get-Module -ListAvailable -Name $ModName -ErrorAction SilentlyContinue | + Sort-Object -Property Version -Descending + if ($Mods) { + foreach ($Mod in $Mods) { + [ordered] @{ + Name = $Mod.Name + Version = $Mod.Version.ToString() + Path = $Mod.ModuleBase + Description = $Mod.Description + } + } + } else { + [ordered] @{ + Name = $ModName + Version = 'Not installed' + Path = $null + Description = $null + } + } + } + $Diag['InstalledModules'] = @($ModuleInfo) + } catch { + $Diag['InstalledModules'] = "Error collecting module info: $($_.Exception.Message)" + } + + # --- Currently loaded modules in session -------------------------------- + try { + $Diag['LoadedModules'] = @( + Get-Module | Sort-Object -Property Name | ForEach-Object { + [ordered] @{ + Name = $_.Name + Version = $_.Version.ToString() + Path = $_.ModuleBase + } + } + ) + } catch { + $Diag['LoadedModules'] = "Error collecting loaded modules: $($_.Exception.Message)" + } + + # --- $Error variable collection ----------------------------------------- + try { + $MaxErrors = if ($IncludeErrorDetails) { $global:Error.Count } else { [math]::Min(25, $global:Error.Count) } + $ErrorCollection = for ($i = 0; $i -lt $MaxErrors; $i++) { + $Err = $global:Error[$i] + if ($null -eq $Err) { continue } + $ErrObj = [ordered] @{ + Index = $i + Message = $Err.Exception.Message + FullyQualifiedErrorId = $Err.FullyQualifiedErrorId + Type = $Err.Exception.GetType().FullName + Category = $Err.CategoryInfo.Category.ToString() + CategoryReason = $Err.CategoryInfo.Reason + TargetName = $Err.CategoryInfo.TargetName + ErrorDetails = if ($Err.ErrorDetails) { $Err.ErrorDetails.Message } else { $null } + ScriptName = $Err.InvocationInfo.ScriptName + LineNumber = $Err.InvocationInfo.ScriptLineNumber + Line = $Err.InvocationInfo.Line -replace '\s+', ' ' + CommandName = $Err.InvocationInfo.MyCommand.Name + } + if ($IncludeErrorDetails) { + $ErrObj['StackTrace'] = $Err.Exception.StackTrace + # Build full inner exception chain + $InnerChain = [System.Collections.Generic.List[string]]::new() + $Inner = $Err.Exception.InnerException + while ($null -ne $Inner) { + $InnerChain.Add("[$($Inner.GetType().FullName)] $($Inner.Message)") + $Inner = $Inner.InnerException + } + $ErrObj['InnerExceptions'] = if ($InnerChain.Count -gt 0) { $InnerChain.ToArray() } else { $null } + } + $ErrObj + } + $Diag['ErrorLog'] = [ordered] @{ + TotalErrors = $global:Error.Count + CapturedErrors = $MaxErrors + FullDetails = $IncludeErrorDetails.IsPresent + Errors = @($ErrorCollection) + } + } catch { + $Diag['ErrorLog'] = "Error collecting `$Error log: $($_.Exception.Message)" + } + + # --- Environment variables (safe subset) -------------------------------- + try { + $SafeEnvVars = if ($IsWindowsPlatform) { + @('COMPUTERNAME', 'USERNAME', 'USERDOMAIN', 'USERDNSDOMAIN', + 'OS', 'PROCESSOR_ARCHITECTURE', 'NUMBER_OF_PROCESSORS', + 'TEMP', 'TMP', 'APPDATA', 'LOCALAPPDATA', 'PSModulePath') + } else { + @('USER', 'LOGNAME', 'HOME', 'SHELL', 'HOSTNAME', 'TMPDIR', 'TEMP', 'TMP', + 'XDG_DATA_HOME', 'XDG_CONFIG_HOME', 'PSModulePath') + } + $EnvInfo = [ordered] @{} + foreach ($VarName in $SafeEnvVars) { + $EnvInfo[$VarName] = [System.Environment]::GetEnvironmentVariable($VarName) + } + $Diag['EnvironmentVariables'] = $EnvInfo + } catch { + $Diag['EnvironmentVariables'] = "Error collecting environment variables: $($_.Exception.Message)" + } + + # --- Write output file -------------------------------------------------- + $DiagObject = [pscustomobject] $Diag + try { + $DiagObject | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputFile -Encoding UTF8 -Force + Write-Host " [Get-AbrAdLog] Diagnostic bundle saved to: $OutputFile" -ForegroundColor Green + } catch { + Write-Warning "Get-AbrAdLog: Failed to write diagnostic file '$OutputFile': $($_.Exception.Message)" + } + + if ($PassThru) { + $DiagObject + } + } + + end { + Write-Verbose 'Get-AbrAdLog: Diagnostic collection complete.' + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 60af01e..6e6736a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ##### This project is community maintained and has no sponsorship from Microsoft, its employees or any of its affiliates. +## [1.0.1] - unreleased + +### Added + +- Add cmdlet Get-AbrAdLog to collect report diagnostic log + ## [1.0.0] - 2026-05-02 ### Added From 5af960b7c373e11ebf8e5877728ea4c26d21f0d0 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Fri, 15 May 2026 13:00:56 -0400 Subject: [PATCH 02/21] chore: Update module versions and adjust icon property in module manifest --- .github/workflows/Release.yml | 52 +++++++++---------- .../AsBuiltReport.Microsoft.AD.psd1 | 6 +-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 72e943b..39a2492 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -33,29 +33,29 @@ jobs: shell: pwsh run: | Publish-Module -Path ./AsBuiltReport.Microsoft.AD -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose - tweet: - needs: publish-to-gallery - runs-on: ubuntu-latest - steps: - - uses: Eomm/why-don-t-you-tweet@v2 - # We don't want to tweet if the repository is not a public one - if: ${{ !github.event.repository.private }} - with: - # GitHub event payload - # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release - tweet-message: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Microsoft #ActiveDirectory #AsBuiltReport #PowerShell #MicrosoftMVP #MVPBuzz #cybersecurity #infosec" - env: - TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }} - TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} - TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - bsky-post: - needs: publish-to-gallery - runs-on: ubuntu-latest - steps: - - uses: zentered/bluesky-post-action@v0.4.0 - with: - post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Microsoft #ActiveDirectory #AsBuiltReport #PowerShell #MicrosoftMVP #MVPBuzz #cybersecurity #infosec" - env: - BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }} - BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }} + # tweet: + # needs: publish-to-gallery + # runs-on: ubuntu-latest + # steps: + # - uses: Eomm/why-don-t-you-tweet@v2 + # # We don't want to tweet if the repository is not a public one + # if: ${{ !github.event.repository.private }} + # with: + # # GitHub event payload + # # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release + # tweet-message: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Microsoft #ActiveDirectory #AsBuiltReport #PowerShell #MicrosoftMVP #MVPBuzz #cybersecurity #infosec" + # env: + # TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }} + # TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} + # TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} + # TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + # bsky-post: + # needs: publish-to-gallery + # runs-on: ubuntu-latest + # steps: + # - uses: zentered/bluesky-post-action@v0.4.0 + # with: + # post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Microsoft #ActiveDirectory #AsBuiltReport #PowerShell #MicrosoftMVP #MVPBuzz #cybersecurity #infosec" + # env: + # BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }} + # BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }} diff --git a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 index 6f1bbff..faa3f73 100644 --- a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 +++ b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 @@ -54,11 +54,11 @@ RequiredModules = @( @{ ModuleName = 'AsBuiltReport.Core'; - ModuleVersion = '1.6.2' + ModuleVersion = '1.6.3' }, @{ ModuleName = 'AsBuiltReport.Chart'; - ModuleVersion = '0.3.1' + ModuleVersion = '0.3.2' }, @{ ModuleName = 'AsBuiltReport.Diagram'; @@ -117,7 +117,7 @@ ProjectUri = 'https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD' # A URL to an icon representing this module. - IconUri = 'https://github.com/AsBuiltReport.png' + PackageIcon = 'https://github.com/AsBuiltReport.png' # ReleaseNotes of this module ReleaseNotes = 'https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/master/CHANGELOG.md' From f89efbd1dd0f77df72593a20bb4d9e9eb7a7a01d Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Fri, 15 May 2026 14:35:15 -0400 Subject: [PATCH 03/21] fix: Add validation for ADSystem before accessing RootDomain to prevent errors --- .../Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 b/AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 index b943c1a..a7def1b 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 @@ -141,7 +141,11 @@ function Invoke-AsBuiltReport.Microsoft.AD { throw ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ForestError -f $System, $_.Exception.Message) } - $script:ForestInfo = $ADSystem.RootDomain.toUpper() + if (-not $ADSystem) { + throw ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ForestError -f $System, $reportTranslate.InvokeAsBuiltReportMicrosoftAD.NoData) + } else { + $script:ForestInfo = $ADSystem.RootDomain.toUpper() + } $RootDomains = $ADSystem.RootDomain $ChildDomains = [System.Collections.Generic.List[object]]::new() if ($Options.Include.Domains) { From ed824bcc6b55a6e2cecb41e3037409bc32b9cfc3 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Fri, 15 May 2026 14:35:35 -0400 Subject: [PATCH 04/21] fix: Add success message logging for completed commands in Invoke-CommandWithTimeout function --- .../Src/Private/Tools/Invoke-CommandWithTimeout.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Tools/Invoke-CommandWithTimeout.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Tools/Invoke-CommandWithTimeout.ps1 index 9fadaf2..7c80bf4 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Tools/Invoke-CommandWithTimeout.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Tools/Invoke-CommandWithTimeout.ps1 @@ -75,5 +75,6 @@ function Invoke-CommandWithTimeout { Remove-Job -Job $job -ErrorAction SilentlyContinue # Return the job results + Write-PScriboMessage -Message "Command '$ScriptBlock' completed successfully." return $results } From 7a626d0094202abf1931b7f30425c2213e79ba1b Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Fri, 15 May 2026 14:36:00 -0400 Subject: [PATCH 05/21] fix: Update localization files to include NoData messages and improve key matching in tests --- .../Language/en-US/MicrosoftAD.psd1 | 1 + .../Language/es-ES/MicrosoftAD.psd1 | 182 +----------------- Tests/AsBuiltReport.Microsoft.AD.Tests.ps1 | 28 +-- Tests/LocalizationData.Tests.ps1 | 8 +- 4 files changed, 11 insertions(+), 208 deletions(-) diff --git a/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 b/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 index 01b82cb..a792eec 100644 --- a/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 +++ b/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 @@ -41,6 +41,7 @@ SystemsUnreachable = The following systems could not be contacted: DomainControllers = Domain Controllers Domains = Domains + NoData = No data returned (Get-ADForest returned $null) '@ # InvokeAsBuiltReportMicrosoftAD diff --git a/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 b/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 index 8e9ceb4..6d82dfe 100644 --- a/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 +++ b/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 @@ -41,6 +41,7 @@ SystemsUnreachable = Los siguientes sistemas no pudieron ser contactados: DomainControllers = Controladores de Dominio Domains = Dominios + NoData = No hay datos disponibles (Get-ADForest devolvió $null) '@ # ConvertToTextYN @@ -610,187 +611,6 @@ DomainNotInForest = El dominio del PC actual {0} no está en la lista de dominio del bosque {1}. Deshabilitando sección de Autoridad de Certificación '@ - GetAbrADCASummary = ConvertFrom-StringData @' - Collecting = Recopilando información de Autoridad de Certificación. - CAName = Nombre de CA - ServerName = Nombre del Servidor - Type = Tipo - Status = Estado - TableName = Autoridad de Certificación -'@ - - GetAbrADCARoot = ConvertFrom-StringData @' - Collecting = Recopilando información de Autoridad de Certificación de AD Por Dominio. - Heading = Autoridad de Certificación Raíz Empresarial - Paragraph = La siguiente sección proporciona información detallada sobre la configuración y estado operacional de la Autoridad de Certificación (CA) Raíz Empresarial. - CAName = Nombre de CA - ServerName = Nombre del Servidor - Type = Tipo - ConfigString = Cadena de Configuración - OperatingSystem = Sistema Operativo - Certificate = Certificado - Auditing = Auditoría - Status = Estado - AuditingNotConfigured = No Configurado - Auditing1 = Iniciar y detener Servicios de Certificados de Active Directory (1) - Auditing2 = Respaldar y restaurar la base de datos de CA (2) - Auditing4 = Emitir y gestionar solicitudes de certificados (4) - Auditing8 = Revocar certificados y publicar CRL (8) - Auditing16 = Cambiar configuración de seguridad de CA (16) - Auditing32 = Cambiar configuración de seguridad de CA (32) - Auditing64 = Cambiar configuración de CA (64) - AuditingFull = Auditoría completamente habilitada (127) - AuditingUnknown = Desconocido - TableName = CA Raíz Empresarial - HealthCheck = Verificación de Salud: - SecurityBestPractice = Mejor Práctica de Seguridad: - AuditingBP = La auditoría debe estar completamente habilitada para la Autoridad de Certificación para asegurar que todos los eventos relevantes se registren para propósitos de monitoreo de seguridad y respuesta a incidentes. Esto incluye eventos relacionados con emisión de certificados, revocación y cambios en la configuración de CA. -'@ - - GetAbrADCASubordinate = ConvertFrom-StringData @' - Collecting = Recopilando información de Autoridad de Certificación de AD Por Dominio. - Heading = Autoridad de Certificación Subordinada Empresarial - Paragraph = La siguiente sección proporciona información detallada sobre Autoridades de Certificación Subordinadas Empresariales dentro del dominio. - CAName = Nombre de CA - ServerName = Nombre del Servidor - Type = Tipo - ConfigString = Cadena de Configuración - OperatingSystem = Sistema Operativo - Certificate = Certificado - Auditing = Auditoría - Status = Estado - AuditingNotConfigured = No Configurado - Auditing1 = Iniciar y detener Servicios de Certificados de Active Directory (1) - Auditing2 = Respaldar y restaurar la base de datos de CA (2) - Auditing4 = Emitir y gestionar solicitudes de certificados (4) - Auditing8 = Revocar certificados y publicar CRL (8) - Auditing16 = Cambiar configuración de seguridad de CA (16) - Auditing32 = Cambiar configuración de seguridad de CA (32) - Auditing64 = Cambiar configuración de CA (64) - AuditingFull = Auditoría completamente habilitada (127) - AuditingUnknown = Desconocido - TableName = CA Subordinada Empresarial - HealthCheck = Verificación de Salud: - SecurityBestPractice = Mejor Práctica de Seguridad: - AuditingBP = La auditoría debe estar completamente habilitada para la Autoridad de Certificación para asegurar que todos los eventos relevantes se registren para propósitos de monitoreo de seguridad y respuesta a incidentes. Esto incluye eventos relacionados con emisión de certificados, revocación y cambios en la configuración de CA. -'@ - - GetAbrADCASecurity = ConvertFrom-StringData @' - Collecting = Recopilando información de Seguridad de Autoridad de Certificación de AD. - CertValidityPeriod = Período de Validez del Certificado - CertValidityPeriodParagraph = La siguiente sección proporciona detalles sobre la configuración del período de validez del certificado para la Autoridad de Certificación. - CertValidityPeriodTable = Período de Validez del Certificado - CAName = Nombre de CA - ServerName = Nombre del Servidor - ValidityPeriod = Período de Validez - ACL = Lista de Control de Acceso (ACL) - ACLTable = Lista de Control de Acceso - DCName = Nombre del DC - Owner = Propietario - Group = Grupo - AccessRights = Derechos de Acceso - AccessRightsTable = Derechos de Acceso - Identity = Identidad - AccessControlType = Tipo de Control de Acceso - Rights = Derechos -'@ - - GetAbrADCACryptographyConfig = ConvertFrom-StringData @' - Collecting = Recopilando información de Configuración de Criptografía de CA de Autoridad de Certificación. - Heading = Configuración de Criptografía - Paragraph = La siguiente sección proporciona información detallada sobre la configuración de criptografía de la Autoridad de Certificación, incluyendo algoritmos, proveedores y especificaciones de clave. - CAName = Nombre de CA - ServerName = Nombre del Servidor - PublicKeyAlgorithm = Algoritmo de Clave Pública - HashingAlgorithm = Algoritmo de Hash - ProviderName = Nombre del Proveedor - AlternateSignatureAlgorithm = Algoritmo de Firma Alternativa - ProviderIsCNG = El Proveedor es CNG - TableName = Configuración de Criptografía -'@ - - GetAbrADCAAIA = ConvertFrom-StringData @' - Collecting = Recopilando información de Acceso a Información de Autoridad de CA de AD en {0}. - Heading = Acceso a Información de Autoridad (AIA) - Paragraph = La siguiente sección proporciona la configuración de Acceso a Información de Autoridad (AIA) para la Autoridad de Certificación, que especifica dónde se pueden recuperar certificados e información de revocación de certificados. - RegURI = URI de Registro - ConfigURI = URI de Configuración - Flags = Banderas - ServerPublish = Publicación del Servidor - IncludeToExtension = Incluir en Extensión - OCSP = OCSP - TableName = Acceso a Información de Autoridad -'@ - - GetAbrADCACRLSetting = ConvertFrom-StringData @' - CollectingVP = Recopilando información de Período de Validez de CRL de CA de AD en {0}. - CollectingCDP = Recopilando información de Punto de Distribución de CRL de CA de AD en {0}. - CollectingHealth = Recopilando Estado de Salud de AIA y CDP desde {0}. - CRLHeading = Lista de Revocación de Certificados (CRL) - CRLParagraph = La siguiente sección proporciona información detallada sobre la configuración de distribución de Lista de Revocación de Certificados (CRL) y estado de salud para la Autoridad de Certificación. - CRLValidityPeriod = Período de Validez de CRL - CRLValidityPeriodTable = Período de Validez de CRL - CAName = Nombre de CA - BaseCRL = CRL Base - BaseCRLOverlap = Superposición de CRL Base - DeltaCRL = CRL Delta - DeltaCRLOverlap = Superposición de CRL Delta - ServerName = Nombre del Servidor - CRLFlags = Banderas de CRL - CRLFlagsSettings = Configuración de Banderas de CRL - CRLFlagsTable = Banderas de CRL - CRLDistributionPoint = Punto de Distribución de CRL - CRLDistributionPointParagraph = La siguiente sección proporciona información detallada sobre los Puntos de Distribución de Lista de Revocación de Certificados (CRL) configurados en la Autoridad de Certificación, incluyendo ubicaciones URI y configuración de publicación. - RegURI = URI de Registro - ConfigURI = URI de Configuración - UrlScheme = Esquema de URL - ProjectedURI = URI Proyectado - Flags = Banderas - CRLPublish = Publicación de CRL - DeltaCRLPublish = Publicación de CRL Delta - AddToCertCDP = Agregar a CDP de Certificado - AddToFreshestCRL = Agregar a CRL Más Reciente - AddToCrlCDP = Agregar a CDP de CRL - CRLDistributionPointTable = Punto de Distribución de CRL - AIACDPHealth = Estado de Salud de AIA y CDP - AIACDPHealthParagraph = La siguiente sección proporciona una evaluación de estado de salud de la Autoridad de Certificación verificando el estado de la cadena de certificados de CA y validando la accesibilidad de todas las URLs de Lista de Revocación de Certificados (CDP) y Acceso a Información de Autoridad (AIA) para cada certificado en la cadena. - Childs = Secundarios - Health = Salud - OK = OK - CAHealthTable = Salud de Autoridad de Certificación -'@ - - GetAbrADCATemplate = ConvertFrom-StringData @' - Collecting = Recopilando información de Plantillas de Autoridad de Certificación de AD desde {0}. - Heading = Resumen de Plantilla de Certificado - Paragraph = La siguiente sección lista las plantillas de certificado asignadas a la Autoridad de Certificación. La CA solo puede emitir certificados basados en estas plantillas asignadas. - TemplateName = Nombre de Plantilla - SchemaVersion = Versión del Esquema - SupportedCA = CA Soportada - Autoenrollment = Inscripción Automática - IssuedTemplateTable = Plantilla de Certificado Emitida - IssuedTemplateACLs = ACL de Plantilla de Certificado Emitida - IssuedTemplateACLsParagraph = La siguiente sección proporciona la Lista de Control de Acceso (ACL) para plantillas de certificado asignadas a la Autoridad de Certificación. - Identity = Identidad - AccessControlType = Tipo de Control de Acceso - Rights = Derechos - Inherited = Heredado - TemplateACLTable = ACL de Plantilla de Certificado - ADTemplates = Plantilla de Certificado en Active Directory - ADTemplatesParagraph = La siguiente sección lista todas las plantillas de certificado registradas en Active Directory, independientemente de si están asignadas a alguna Autoridad de Certificación. - ADTemplatesTable = Plantilla de Certificado en AD -'@ - - GetAbrADCAKeyRecoveryAgent = ConvertFrom-StringData @' - Collecting = Recopilando información de Certificado de Agente de Recuperación de Clave de Autoridad de Certificación de AD. - Heading = Certificado de Agente de Recuperación de Clave - Paragraph = La siguiente sección proporciona detalles sobre el certificado del Agente de Recuperación de Clave, que encripta las claves privadas de certificados de usuarios para almacenamiento en la base de datos de CA. Si un usuario pierde acceso a su clave privada del certificado, el Agente de Recuperación de Clave puede recuperarla cuando se configuró el archivado de clave para el certificado. - CAName = Nombre de CA - ServerName = Nombre del Servidor - Certificate = Certificado - TableName = Certificado de Agente de Recuperación de Clave -'@ - # Get-AbrDomainSection GetAbrDomainSection = ConvertFrom-StringData @' Collecting = Recopilando información de Dominio desde {0}. diff --git a/Tests/AsBuiltReport.Microsoft.AD.Tests.ps1 b/Tests/AsBuiltReport.Microsoft.AD.Tests.ps1 index ff00a3c..d3cd45f 100644 --- a/Tests/AsBuiltReport.Microsoft.AD.Tests.ps1 +++ b/Tests/AsBuiltReport.Microsoft.AD.Tests.ps1 @@ -61,10 +61,6 @@ Describe 'AsBuiltReport.Microsoft.AD Module Tests' { $DiagramModule = $Manifest.RequiredModules | Where-Object { $_.Name -eq 'AsBuiltReport.Diagram' } $DiagramModule.Version | Should -BeGreaterOrEqual ([Version]'1.0.5') } - It 'Should require PSPKI version 4.3.0 or higher' { - $PSPKIModule = $Manifest.RequiredModules | Where-Object { $_.Name -eq 'PSPKI' } - $PSPKIModule.Version | Should -BeGreaterOrEqual ([Version]'4.3.0') - } It 'Should export the Invoke-AsBuiltReport.Microsoft.AD function' { $Manifest.ExportedFunctions.Keys | Should -Contain 'Invoke-AsBuiltReport.Microsoft.AD' @@ -91,8 +87,7 @@ Describe 'AsBuiltReport.Microsoft.AD Module Tests' { $Manifest.PowerShellVersion | Should -BeGreaterOrEqual ([Version]'5.1') } - It 'Should support Desktop and Core editions' { - $Manifest.CompatiblePSEditions | Should -Contain 'Desktop' + It 'Should support Core editions' { $Manifest.CompatiblePSEditions | Should -Contain 'Core' } @@ -111,11 +106,6 @@ Describe 'AsBuiltReport.Microsoft.AD Module Tests' { $Manifest.PrivateData.PSData.ReleaseNotes | Should -Match '^https?://' } - It 'Should have an IconUri' { - $Manifest.PrivateData.PSData.IconUri | Should -Not -BeNullOrEmpty - $Manifest.PrivateData.PSData.IconUri | Should -Match '^https?://' - } - It 'Should have module version matching expected format' { $Manifest.Version.ToString() | Should -Match '^\d+\.\d+\.\d+$' } @@ -183,7 +173,7 @@ Describe 'AsBuiltReport.Microsoft.AD Module Tests' { Context 'Public Functions' { It 'Should export Invoke-AsBuiltReport.Microsoft.AD function' { - Get-Command -name 'Invoke-AsBuiltReport.Microsoft.AD' -Module 'AsBuiltReport.Microsoft.AD' -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty + Get-Command -Name 'Invoke-AsBuiltReport.Microsoft.AD' -Module 'AsBuiltReport.Microsoft.AD' -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty } It 'Should have exactly 1 exported function' { @@ -222,18 +212,18 @@ Describe 'AsBuiltReport.Microsoft.AD Module Tests' { Context 'Help Content' { It 'Invoke-AsBuiltReport.Microsoft.AD should have help content' { - $Help = Get-Help -name 'Invoke-AsBuiltReport.Microsoft.AD' -ErrorAction SilentlyContinue + $Help = Get-Help -Name 'Invoke-AsBuiltReport.Microsoft.AD' -ErrorAction SilentlyContinue $Help | Should -Not -BeNullOrEmpty $Help.Synopsis | Should -Not -BeNullOrEmpty } It 'Invoke-AsBuiltReport.Microsoft.AD should have description' { - $Help = Get-Help -name 'Invoke-AsBuiltReport.Microsoft.AD' -ErrorAction SilentlyContinue + $Help = Get-Help -Name 'Invoke-AsBuiltReport.Microsoft.AD' -ErrorAction SilentlyContinue $Help.Description | Should -Not -BeNullOrEmpty } It 'Invoke-AsBuiltReport.Microsoft.AD should have a link' { - $Help = Get-Help -name 'Invoke-AsBuiltReport.Microsoft.AD' -ErrorAction SilentlyContinue + $Help = Get-Help -Name 'Invoke-AsBuiltReport.Microsoft.AD' -ErrorAction SilentlyContinue $Help.relatedLinks | Should -Not -BeNullOrEmpty } } @@ -276,10 +266,6 @@ Describe 'AsBuiltReport.Microsoft.AD Module Tests' { $JsonConfig.InfoLevel.PSObject.Properties.Name | Should -Contain 'DNS' } - It 'InfoLevel should include CA' { - $JsonConfig.InfoLevel.PSObject.Properties.Name | Should -Contain 'CA' - } - It 'HealthCheck should include Domain checks' { $JsonConfig.HealthCheck.PSObject.Properties.Name | Should -Contain 'Domain' } @@ -295,10 +281,6 @@ Describe 'AsBuiltReport.Microsoft.AD Module Tests' { It 'HealthCheck should include DNS checks' { $JsonConfig.HealthCheck.PSObject.Properties.Name | Should -Contain 'DNS' } - - It 'HealthCheck should include CA checks' { - $JsonConfig.HealthCheck.PSObject.Properties.Name | Should -Contain 'CA' - } } Context 'Configuration Schema Validation' { diff --git a/Tests/LocalizationData.Tests.ps1 b/Tests/LocalizationData.Tests.ps1 index 9a0fe33..161c330 100644 --- a/Tests/LocalizationData.Tests.ps1 +++ b/Tests/LocalizationData.Tests.ps1 @@ -12,15 +12,15 @@ BeforeAll { foreach ($line in $content) { # Match section headers like: GetAbrAzTenant = ConvertFrom-StringData @' - if ($line -match '^\s*(\w+)\s*=\s*ConvertFrom-StringData') { + if ($line -match '^\s*([\w-]+)\s*=\s*ConvertFrom-StringData') { $currentSection = $Matches[1] } - # Match key-value pairs within sections - elseif ($currentSection -and $line -match '^\s+(\w+)\s*=' -and $line -notmatch "^'@") { + # Match key-value pairs within sections (allow dashes and dots in keys) + elseif ($currentSection -and $line -match '^\s+([\w.-]+)\s*=' -and $line -notmatch "^\s*'@") { $keys += "$currentSection.$($Matches[1])" } # End of section - elseif ($line -match "^'@") { + elseif ($line -match "^\s*'@") { $currentSection = $null } } From f48701d0388397a495ffda80c64590f3c5e5fe29 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Fri, 15 May 2026 14:36:09 -0400 Subject: [PATCH 06/21] fix: Update changelog to include recent fixes and enhancements --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e6736a..0fa5dd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add cmdlet Get-AbrAdLog to collect report diagnostic log +### Fixed + +- Add validation for ADSystem before accessing RootDomain to prevent errors +- Add success message logging for completed commands in Invoke-CommandWithTimeout function +- Update localization files to include NoData messages and improve key matching in tests + ## [1.0.0] - 2026-05-02 ### Added From d4cd3a40f219a06ed1b0147a3ff0fd3f4a68d525 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 19 May 2026 22:51:09 -0400 Subject: [PATCH 07/21] docs: Add Log Collection section to README with usage example for Get-AbrAdLog cmdlet --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 80afbf6..3b0a6a2 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,15 @@ PS C:\> Start-AsBuiltReportMSAD ![alt text](Samples/Sample-Gui.png) +### :memo: Log Collection + +The `Get-AbrAdLog` cmdlet can be used to collect AsBuiltReport.Microsoft.AD logs for troubleshooting purposes. This cmdlet collects the logs and diagnostic information from the powershell host running the report and saves them to a specified output folder. + +```powershell +# Collect powershell host logs and diagnostic information. Save logs to 'C:\Users\Jon\Desktop\'. +PS C:\> Get-AbrAdLog -OutputFolderPath 'C:\Users\Jon\Desktop\' -IncludeErrorDetails +``` + ## :x: Known Issues - **PSWriteWord Module Conflict**: PScribo and the EvotecIT "PSWriteWord" project use conflicting cmdlets. The PSWriteWord module must be uninstalled before generating reports. - **WinRM Dependency**: This report relies heavily on remote connections via WinRM. A Windows 10 client is recommended as a jumpbox for optimal connectivity. From 82d8512bd50f8e8e04b687b6754bba5dc7b92aff Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Wed, 17 Jun 2026 20:26:03 -0400 Subject: [PATCH 08/21] Fix [255](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/255) --- .../AsBuiltReport.Microsoft.AD.psd1 | 6 +++--- .../Language/en-US/MicrosoftAD.psd1 | 2 +- .../Language/es-ES/MicrosoftAD.psd1 | 1 + .../Src/Private/Gui/Start-AsBuiltReportMSAD.ps1 | 9 +++++++-- .../Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 | 10 ++++++++-- CHANGELOG.md | 11 +++++++++-- README.md | 2 ++ 7 files changed, 31 insertions(+), 10 deletions(-) diff --git a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 index faa3f73..1716b19 100644 --- a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 +++ b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 @@ -54,15 +54,15 @@ RequiredModules = @( @{ ModuleName = 'AsBuiltReport.Core'; - ModuleVersion = '1.6.3' + ModuleVersion = '1.6.4' }, @{ ModuleName = 'AsBuiltReport.Chart'; - ModuleVersion = '0.3.2' + ModuleVersion = '0.3.3' }, @{ ModuleName = 'AsBuiltReport.Diagram'; - ModuleVersion = '1.0.7' + ModuleVersion = '1.0.8' } ) diff --git a/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 b/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 index a792eec..9ccaa40 100644 --- a/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 +++ b/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 @@ -42,7 +42,7 @@ DomainControllers = Domain Controllers Domains = Domains NoData = No data returned (Get-ADForest returned $null) -'@ + RunAsAdministrator = Please run the report with Run As Administrator priviledges. # InvokeAsBuiltReportMicrosoftAD ConvertToTextYN = ConvertFrom-StringData @' diff --git a/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 b/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 index 6d82dfe..2aa668f 100644 --- a/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 +++ b/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 @@ -42,6 +42,7 @@ DomainControllers = Controladores de Dominio Domains = Dominios NoData = No hay datos disponibles (Get-ADForest devolvió $null) + RunAsAdministrator = Please run the report with Run As Administrator priviledges. '@ # ConvertToTextYN diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Gui/Start-AsBuiltReportMSAD.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Gui/Start-AsBuiltReportMSAD.ps1 index 42a9b65..b1e3c2b 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Gui/Start-AsBuiltReportMSAD.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Gui/Start-AsBuiltReportMSAD.ps1 @@ -1,5 +1,3 @@ -#Requires -RunAsAdministrator - using namespace GliderUI using namespace GliderUI.Avalonia using namespace GliderUI.Avalonia.Controls @@ -32,6 +30,13 @@ function Start-AsBuiltReportMSAD { throw "Start-AsBuiltReportMSAD requires PowerShell 7.4+. Current version: $($PSVersionTable.PSVersion)" } + $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + + if (-not $IsAdmin) { + Write-Error -Message 'Please run the report with Run As Administrator priviledges.' + break + } + # ── Bootstrap GliderUI ────────────────────────────────────────────────────── $requiredGliderUIVersion = [version]'0.2.0' diff --git a/AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 b/AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 index a7def1b..3a7ce20 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1 @@ -5,7 +5,7 @@ function Invoke-AsBuiltReport.Microsoft.AD { .DESCRIPTION Documents the configuration of Microsoft AD in Word/HTML/Text formats using PScribo. .NOTES - Version: 1.0.0 + Version: 1.0.1 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -22,9 +22,15 @@ function Invoke-AsBuiltReport.Microsoft.AD { [PSCredential] $Credential ) - #Requires -RunAsAdministrator #Requires -Version 7.4 + $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + + if (-not $IsAdmin) { + Write-Error -Message $reportTranslate.InvokeAsBuiltReportMicrosoftAD.RunAsAdministrator + break + } + if ($psISE) { Write-Error -Message $reportTranslate.InvokeAsBuiltReportMicrosoftAD.PwshISE break diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa5dd6..9e39e9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add cmdlet Get-AbrAdLog to collect report diagnostic log +- Add cmdlet `Get-AbrAdLog` to collect report diagnostic log + +### Changed + +- Bump module version to `1.0.1` +- Upgrade AsBuiltReport.Diagram module to version `1.0.8` +- Upgrade AsBuiltReport.Chart module to version `0.3.3` ### Fixed - Add validation for ADSystem before accessing RootDomain to prevent errors -- Add success message logging for completed commands in Invoke-CommandWithTimeout function +- Add success message logging for completed commands in `Invoke-CommandWithTimeout` function - Update localization files to include NoData messages and improve key matching in tests +- Fix [255](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/255) ## [1.0.0] - 2026-05-02 diff --git a/README.md b/README.md index 3b0a6a2..56d871e 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ A Microsoft AD As Built Report can be generated with Active Directory Enterprise Due to a limitation of the WinRM component, a domain-joined machine is needed, also it is required to use the FQDN of the DC instead of it's IP address. [Reference](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote_troubleshooting?view=powershell-7.1#how-to-use-an-ip-address-in-a-remote-command) +- The report must be run in a console with administrator privileges (“Run as Administrator”). + ## :package: Module Installation ### PowerShell v5.x running on a Domain Controller server From 74622d19f1df667070f1742dcdcbc693fecb2e65 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Wed, 17 Jun 2026 20:29:01 -0400 Subject: [PATCH 09/21] Fix Requirements --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 56d871e..87d3e8b 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,8 @@ A Microsoft AD As Built Report can be generated with Active Directory Enterprise Due to a limitation of the WinRM component, a domain-joined machine is needed, also it is required to use the FQDN of the DC instead of it's IP address. [Reference](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote_troubleshooting?view=powershell-7.1#how-to-use-an-ip-address-in-a-remote-command) -- The report must be run in a console with administrator privileges (“Run as Administrator”). +> [!WARNING] +> The report must be run in a console with administrator privileges (“Run as Administrator”). ## :package: Module Installation From 5eb28fa3d58d7d03f05383c91415ac11c0791756 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Wed, 17 Jun 2026 21:29:54 -0400 Subject: [PATCH 10/21] Misc Fix --- AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 b/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 index d7c4c1d..49d29ad 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 @@ -48,6 +48,13 @@ function Get-AbrAdLog { ) begin { + $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + + if (-not $IsAdmin) { + Write-Error -Message $reportTranslate.InvokeAsBuiltReportMicrosoftAD.RunAsAdministrator + break + } + Write-Verbose 'Get-AbrAdLog: Starting diagnostic collection.' $TimeStamp = Get-Date -Format 'yyyyMMdd_HHmmss' $FileName = "AbrAdDiagnostics_$TimeStamp.json" From 69706b77250456793e44fe1f8900b40775405ac4 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 18 Jun 2026 20:59:00 -0400 Subject: [PATCH 11/21] Fix Misc text error --- AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 b/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 index 9ccaa40..76408e9 100644 --- a/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 +++ b/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 @@ -37,7 +37,7 @@ DiagramExportError = Unable to export the diagram {0}: {1} ClearPSSession = Clearing PSSession with ID {0} ClearCIMSession = Clearing CIM session with ID {0} - FinishedReport = - Finished generating the report for the forest {0}: + FinishedReport = - Finished generating the report for the forest: {0} SystemsUnreachable = The following systems could not be contacted: DomainControllers = Domain Controllers Domains = Domains From 46ca0616dc661e0c02b47dfbe6cd82d2a778d695 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 18 Jun 2026 21:02:55 -0400 Subject: [PATCH 12/21] Update SECURITY.md --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index ced17a2..ff9138c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,4 @@ -Thanks for helping make AsBuiltReport.Veeam.VBR safe for everyone. +Thanks for helping make AsBuiltReport.Microsoft.Windows safe for everyone. ## Security From baac2ec730af04782b88dc64da3beb01c3155be1 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 18 Jun 2026 21:04:13 -0400 Subject: [PATCH 13/21] Update SECURITY.md --- SECURITY.md | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index ff9138c..f3898c8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,27 +1,57 @@ -Thanks for helping make AsBuiltReport.Microsoft.Windows safe for everyone. +# Security Policy + +Thanks for helping make AsBuiltReport safe for everyone. ## Security AsBuiltReport takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [AsBuiltReport](https://github.com/AsBuiltReport). -Even though "open source repositories are outside of the scope of bug bounty program and therefore not eligible for bounty rewards", we will ensure that your finding gets passed along to the appropriate maintainers for remediation. +While AsBuiltReport is an open source project without a formal bug bounty program, we are committed to addressing security vulnerabilities promptly and will ensure that your findings are passed along to the appropriate maintainers for remediation. ## Reporting Security Issues -If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. +If you believe you have found a security vulnerability in any AsBuiltReport repository, please report it to us through coordinated disclosure. **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** -Instead, please send an email to rebelinux@gmail.com. +Instead, please send an email to support@asbuiltreport.com with the subject line "SECURITY: [Brief Description]". Please include as much of the information listed below as you can to help us better understand and resolve the issue: - * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) + * The type of issue (e.g., code injection, credential exposure, or privilege escalation) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue + * Affected versions of AsBuiltReport modules This information will help us triage your report more quickly. + +## Response Timeline + +We aim to: + * Acknowledge receipt of your report within 72 hours + * Provide an initial assessment within 7-10 days + * Keep you informed of our progress throughout the remediation process + +## Disclosure Policy + +We follow coordinated disclosure principles: + * Security issues will be addressed in a timely manner + * We will coordinate with you on the disclosure timeline + * Public disclosure will occur after a fix is available and deployed + * We will credit reporters (unless anonymity is requested) + +## Supported Versions + +Please refer to individual module repositories for information on which versions are currently supported with security updates. + +## Security Best Practices + +When using AsBuiltReport: + * Store credentials securely using appropriate credential management solutions + * Review generated reports before sharing to ensure no sensitive information is exposed + * Keep AsBuiltReport modules updated to the latest versions + * Follow the principle of least privilege when providing credentials to AsBuiltReport From 3fde4955bf5468885fac2aeb81b7e29b9500c1b7 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 18 Jun 2026 21:04:36 -0400 Subject: [PATCH 14/21] Update CONTRIBUTING.md --- CONTRIBUTING.md | 272 +++++++++++++++++++++++++++++++----------------- 1 file changed, 176 insertions(+), 96 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 098c85f..41b2159 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,142 +1,222 @@ -# Contributing to this project +# Contributing to AsBuiltReport -Please take a moment to review this document in order to make the contribution -process easy and effective for everyone involved. +Your contribution is welcomed and appreciated! Thank you for taking the time to contribute to this project. -Following these guidelines helps to communicate that you respect the time of -the developers managing and developing this open source project. In return, -they should reciprocate that respect in addressing your issue or assessing -patches and features. +Please take a moment to review this document to make the contribution process easy and effective for everyone involved. +Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features. -## Using the issue tracker +## Ways to Contribute -The issue tracker is the preferred channel for [bug reports](#bugs), -[features requests](#features) and [submitting pull -requests](#pull-requests), but please respect the following restrictions: +Contributing to this project is as easy as: -* Please **do not** use the issue tracker for personal support requests (use - [Stack Overflow](http://stackoverflow.com) or Veeam Forum). +- Reporting bugs and issues +- Proposing new features +- Discussing the current state of the code +- Submitting fixes and improvements +- Creating new report modules +- Improving documentation -* Please **do not** derail or troll issues. Keep the discussion on topic and - respect the opinions of others. +For comprehensive contribution guidelines, please visit our [Developer Guide](https://www.asbuiltreport.com/dev-guide/contributing). +## Getting Started - -## Bug reports +### Prerequisites -A bug is a _demonstrable problem_ that is caused by the code in the repository. -Good bug reports are extremely helpful - thank you! +- A [GitHub account](https://github.com/signup/free) +- Git installed on your local machine +- PowerShell 5.1 or PowerShell 7+ +- [Visual Studio Code](https://code.visualstudio.com/) (recommended) -Guidelines for bug reports: +### Learning Resources -1. **Use the GitHub issue search** — check if the issue has already been - reported. +If you're new to Git and GitHub: -2. **Check if the issue has been fixed** — try to reproduce it using the - latest `master` or development branch in the repository. +- [GitHub's guide on Forking](https://docs.github.com/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) +- [GitHub's guide on Contributing to Open Source](https://docs.github.com/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github) +- [Understanding the GitHub Flow](https://docs.github.com/get-started/quickstart/github-flow) -3. **Isolate the problem** — create a [reduced test - case](http://css-tricks.com/reduced-test-cases/) and a live example.AsBuiltReport.Microsoft.AD +## Using the Issue Tracker -A good bug report shouldn't leave others needing to chase you up for more -information. Please try to be as detailed as possible in your report. What is -your environment? What steps will reproduce the issue? What browser(s) and OS -experience the problem? What would you expect to be the outcome? All these -details will help people to fix any potential bugs. +The issue tracker is the preferred channel for bug reports, feature requests, and submitting pull requests. Please respect the following: -Example: +- **Do not** use the issue tracker for personal support requests. Use our [Discussion Board](https://github.com/orgs/AsBuiltReport/discussions) instead. +- **Do not** derail or troll issues. Keep discussions on topic and respect the opinions of others. +- Search existing issues (both open and closed) before creating a new one to avoid duplicates. -> Short and descriptive example bug report title -> -> A summary of the issue and the browser/OS environment in which it occurs. If -> suitable, include the steps required to reproduce the bug. -> -> 1. This is the first step -> 2. This is the second step -> 3. Further steps, etc. -> -> `` - a link to the reduced test case -> -> Any other information you want to share that is relevant to the issue being -> reported. This might include the lines of code that you have identified as -> causing the bug, and potential solutions (and your opinions on their -> merits). +## Reporting Bugs +A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful! - -## Feature requests +### Before Submitting a Bug Report -Feature requests are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong -case to convince the project's developers of the merits of this feature. Please -provide as much detail and context as possible. +Please perform the following due diligence: +1. **Read the documentation** - Check the `README` in the AsBuiltReport.Microsoft.Azure repository, including Supported Versions, System Requirements, and Module Installation sections. +2. **Update to the latest version** - Your issue may already be fixed in the most recent release. +3. **Check dependencies** - Try upgrading or downgrading vendor PowerShell modules if applicable. +4. **Use the `-Verbose` parameter** - This may help identify the issue. +5. **Test with InfoLevels** - Set all InfoLevels to 0 in your report config, then gradually increase them to isolate the problem. +6. **Try older versions** - If you're on the latest release, try rolling back to see if the problem exists in earlier versions. +7. **Search existing issues** - Make sure it's not a known issue. - -## Pull requests +### What to Include in Bug Reports -Good pull requests - patches, improvements, new features - are a fantastic -help. They should remain focused in scope and avoid containing unrelated -commits. +A good bug report should include: -**Please ask first** before embarking on any significant pull request (e.g. -implementing features, refactoring code, porting to a different language), -otherwise you risk spending a lot of time working on something that the -project's developers might not want to merge into the project. +- A quick summary and/or background of the issue +- Software versions: + - AsBuiltReport module versions (e.g., AsBuiltReport.Core v1.4.3) + - PowerShell version (e.g., Windows PowerShell 5.1 or PowerShell 7.4) + - Operating System (e.g., Windows Server 2022) +- Steps to reproduce: + - Be specific + - Provide the full command line you executed + - Include sample code if applicable + - Upload screenshots if helpful +- What you expected to happen +- What actually happened +- Additional notes (why you think this might be happening, troubleshooting steps you've tried) -Please adhere to the coding conventions used throughout a project (indentation, -accurate comments, etc.) and any other requirements (such as test coverage). +## Feature Requests -Follow this process if you'd like your work considered for inclusion in the -project: +Feature requests are welcome! Please provide as much detail and context as possible about: -1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, - and configure the remotes: +- The problem you're trying to solve +- Why this feature would be valuable +- How you envision it working +- Any examples from other tools or projects - ```bash - # Clone your fork of the repo into the current directory - git clone https://github.com//AsBuiltReport.Microsoft.AD - # Navigate to the newly cloned directory - cd AsBuiltReport.Microsoft.AD - # Assign the original repo to a remote called "upstream" - git remote add upstream https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD - ``` +It's up to you to make a strong case for the merits of this feature. Keep in mind that features should fit within the scope and aims of the project. + +## Pull Requests + +Good pull requests (patches, improvements, new features) are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. + +**Please ask first** before embarking on any significant pull request (e.g., implementing features, refactoring code), otherwise you risk spending time on something that might not be merged. + +### Creating Quality Pull Requests + +A good quality pull request will have: + +- **A meaningful title** describing what change you're making (not just "Fix issue #5") + - Use present tense and imperative mood: "Add support for Server 2022" not "Added support" + - "Fix connection timeout" not "Fixed for connection issue" +- **A clear description** summarizing the changes and their benefits + - Reference related issues (e.g., "Fix #11") + - First sentence should explain the benefit to end users +- **Focused scope** - Avoid PRs with too many changes; split large features into smaller PRs +- **Updated documentation**: + - Update `CHANGELOG.md` with add/remove/fix/change information + - Update `README.md` with new features, instructions, parameters, or examples +- **Well-written commits** that tell the story of the development +- **Code quality** meeting project best practices + +### Submitting Pull Requests -2. If you cloned a while ago, get the latest changes from upstream: +Always create pull requests against the `dev` branch: +1. Fork the AsBuiltReport repository + +2. Clone your fork and add the upstream remote: ```bash - git checkout - git pull upstream + git clone https://github.com//AsBuiltReport.Microsoft.Azure + cd AsBuiltReport.Microsoft.Azure + git remote add upstream https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Azure ``` -3. Create a new topic branch (off the main project development branch) to - contain your feature, change, or fix: - +3. Create a new feature branch from `dev`: ```bash - git checkout -b + git checkout dev + git pull upstream dev + git checkout -b ``` -4. Commit your changes in logical chunks. Please adhere to these [git commit - message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) - or your code is unlikely be merged into the main project. Use Git's - [interactive rebase](https://help.github.com/articles/interactive-rebase) - feature to tidy up your commits before making them public. +4. Make your changes and commit with clear messages -5. Locally merge (or rebase) the upstream development branch into your topic branch: +5. Update documentation (`CHANGELOG.md` and `README.md`) +6. Squash commits into one or two succinct commits if needed: ```bash - git pull [--rebase] upstream + git rebase -i HEAD~n # n being the number of commits to rebase ``` -6. Push your topic branch up to your fork: +7. Ensure your branch is up to date with upstream: + ```bash + git fetch upstream + git rebase upstream/dev + ``` +8. Push your branch to your fork: ```bash - git push origin + git push --force origin ``` -7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) - with a clear title and description. +9. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) against the `dev` branch + +Pull requests will be reviewed as soon as possible. Please follow the PR template provided in the repository. + +## Code Contributions + +### Code Editor + +We highly recommend using [Visual Studio Code](https://code.visualstudio.com/) for development. + +### Coding Standards + +Code contributors should follow the [PowerShell Best Practices and Style Guide](https://github.com/PoshCode/PowerShellPracticeAndStyle) to ensure consistency. + +Use [PSScriptAnalyzer](https://github.com/PowerShell/PSScriptAnalyzer) to check code quality. + +### DO + +- Use PascalCasing for all public member, type, and namespace names +- Use custom label headers within tables for readability +- Favor readability over brevity +- Use PSCustomObjects to store data for PScribo tables: + ```powershell + $myObject = [PSCustomObject]@{ + Name = 'Value' + Property = 'Value' + } + + $TableParams = @{ + Name = 'Table Name' + List = $true + ColumnWidths = 40, 60 + } + + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + + $myObject | Table @TableParams + ``` +- Set ColumnWidths for all tables (list tables typically use 40, 60) +- Sort primary object properties in alphanumeric order +- Perform all safe commands (Get-*, API calls) at the start of scripts +- Use comments in English to explain reasoning, not to describe commands +- Maintain a changelog following [Keep a Changelog](https://keepachangelog.com/) guidelines + +### DO NOT + +- Include functions within report scripts (create separate files in `\Src\Private`) +- Submit unrelated changes in the same pull request + +## Version Control Branching + +- Always create a new branch for your work +- Base your branch off `dev` +- Avoid submitting unrelated changes (bug fixes & new features) in the same branch + +## Questions and Discussion + +If you have questions or want to discuss contributions: + +- Raise an issue in the AsBuiltReport.Microsoft.Azure [repository](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Azure) +- Email us at support@asbuiltreport.com +- Visit our website at [www.asbuiltreport.com](https://www.asbuiltreport.com) + +## License -**IMPORTANT**: By submitting a patch, you agree to allow the project owner to -license your work under the same license as that used by the project. +By submitting a patch, you agree to allow the project owner to license your work under the [MIT License](https://www.asbuiltreport.com/about/license/). From 2f1edf3b4e860c703f00ec6e637c2b39e0cda2c2 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 18 Jun 2026 21:05:23 -0400 Subject: [PATCH 15/21] Update CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 2ec1c28..6fed2df 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,6 @@ -# Contributor Covenant Code of Conduct +# Code of Conduct + +The AsBuiltReport project is committed to fostering an open and welcoming environment for all contributors, users, and community members. ## Our Pledge @@ -36,6 +38,15 @@ Examples of unacceptable behavior include: * Other conduct which could reasonably be considered inappropriate in a professional setting +## Technical Disagreements + +Technical disagreements are a normal part of open source development. When disagreements arise: + +* Focus on the technical merits of the solution +* Assume good intentions from all parties +* Seek to understand different perspectives +* Escalate to maintainers if consensus cannot be reached + ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of @@ -50,8 +61,14 @@ decisions when appropriate. ## Scope -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. +This Code of Conduct applies within all AsBuiltReport community spaces, including: + +* GitHub repositories (issues, pull requests, discussions) +* Official social media channels +* Community chat platforms +* Project events and meetups + +It also applies when an individual is officially representing the AsBuiltReport community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. @@ -60,7 +77,8 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -rebelinux@gmail.com. +support@asbuiltreport.com. + All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the @@ -106,7 +124,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within From dd4ceb857107d8bfb9c1e6fcac66ce047374d11e Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Thu, 18 Jun 2026 21:06:12 -0400 Subject: [PATCH 16/21] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41b2159..b408fab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ A bug is a demonstrable problem that is caused by the code in the repository. Go Please perform the following due diligence: -1. **Read the documentation** - Check the `README` in the AsBuiltReport.Microsoft.Azure repository, including Supported Versions, System Requirements, and Module Installation sections. +1. **Read the documentation** - Check the `README` in the AsBuiltReport.Microsoft.AD repository, including Supported Versions, System Requirements, and Module Installation sections. 2. **Update to the latest version** - Your issue may already be fixed in the most recent release. 3. **Check dependencies** - Try upgrading or downgrading vendor PowerShell modules if applicable. 4. **Use the `-Verbose` parameter** - This may help identify the issue. @@ -120,9 +120,9 @@ Always create pull requests against the `dev` branch: 2. Clone your fork and add the upstream remote: ```bash - git clone https://github.com//AsBuiltReport.Microsoft.Azure - cd AsBuiltReport.Microsoft.Azure - git remote add upstream https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Azure + git clone https://github.com//AsBuiltReport.Microsoft.AD + cd AsBuiltReport.Microsoft.AD + git remote add upstream https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD ``` 3. Create a new feature branch from `dev`: @@ -213,7 +213,7 @@ Use [PSScriptAnalyzer](https://github.com/PowerShell/PSScriptAnalyzer) to check If you have questions or want to discuss contributions: -- Raise an issue in the AsBuiltReport.Microsoft.Azure [repository](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Azure) +- Raise an issue in the AsBuiltReport.Microsoft.AD [repository](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD) - Email us at support@asbuiltreport.com - Visit our website at [www.asbuiltreport.com](https://www.asbuiltreport.com) From f25e1b08ea533d4f83897a7205d0b0cefaddfcb9 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Fri, 19 Jun 2026 10:54:59 -0400 Subject: [PATCH 17/21] Fix non-printable ASCII characters from a string --- .../Src/Private/Tools/ConvertTo-HashToYN.ps1 | 32 +++++++++--- .../Src/Private/Tools/ConvertTo-TextYN.ps1 | 1 + .../Tools/Remove-NonPrintableAscii.ps1 | 49 +++++++++++++++++++ CHANGELOG.md | 1 + 4 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 AsBuiltReport.Microsoft.AD/Src/Private/Tools/Remove-NonPrintableAscii.ps1 diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Tools/ConvertTo-HashToYN.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Tools/ConvertTo-HashToYN.ps1 index 99c1f00..112c250 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Tools/ConvertTo-HashToYN.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Tools/ConvertTo-HashToYN.ps1 @@ -3,10 +3,13 @@ function ConvertTo-HashToYN { .SYNOPSIS Used by As Built Report to convert array content true or false automatically to Yes or No. .DESCRIPTION - + Used by As Built Report to convert array content true or false automatically to Yes or No. + Now also strips non-printable ASCII characters from string values while creating the array hash. + This is required for Word Document Output as PSCribo cannot create Word documents with non-ASCII characters .NOTES - Version: 0.2.0 + Version: 0.1.1 Author: Jonathan Colon + Changes: 0.1.1 - Updated to include non-unicode character string cleaning. Graham Flynn - 30/07/2025 .EXAMPLE @@ -22,14 +25,31 @@ function ConvertTo-HashToYN { ) $result = [ordered] @{} + foreach ($i in $TEXT.GetEnumerator()) { try { - $result.add($i.Key, (ConvertTo-TextYN $i.Value)) + $valueToProcess = $i.Value + + # Check if the value is a string before attempting to clean it + if ($valueToProcess -is [string]) { + $valueToProcess = $valueToProcess | Remove-NonPrintableAscii + } + + $convertedValue = ConvertTo-TextYN $valueToProcess + + $result.add($i.Key, $convertedValue) } catch { - $result.add($i.Key, ($i.Value)) + # If ConvertTo-TextYN fails, still try to clean the original value if it's a string + $originalValue = $i.Value + if ($originalValue -is [string]) { + $originalValue = $originalValue | Remove-NonPrintableAscii + } + $result.add($i.Key, ($originalValue)) # Add the (potentially cleaned) original value } } if ($result) { - $result - } else { $TEXT } + return $result + } else { + return $TEXT + } } # end \ No newline at end of file diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Tools/ConvertTo-TextYN.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Tools/ConvertTo-TextYN.ps1 index fa393e2..36c280c 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Tools/ConvertTo-TextYN.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Tools/ConvertTo-TextYN.ps1 @@ -20,6 +20,7 @@ function ConvertTo-TextYN { Position = 0, Mandatory)] [AllowEmptyString()] + [AllowNull()] [string] $TEXT ) diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Tools/Remove-NonPrintableAscii.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Tools/Remove-NonPrintableAscii.ps1 new file mode 100644 index 0000000..5f30966 --- /dev/null +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Tools/Remove-NonPrintableAscii.ps1 @@ -0,0 +1,49 @@ +function Remove-NonPrintableAscii { + <# + .SYNOPSIS + Removes non-printable ASCII characters from a string. + .DESCRIPTION + This function takes a string as input and returns a new string + where all characters outside the printable ASCII range (ASCII 32-126) + have been removed. If the input is null or empty, it returns an empty string. + .PARAMETER InputString + The string from which to remove non-printable ASCII characters. + .EXAMPLE + Remove-NonPrintableAscii -InputString "Hello`nWorld`t!" + # Output: "HelloWorld!" + + .EXAMPLE + "This string has a null character: `0" | Remove-NonPrintableAscii + # Output: "This string has a null character: " + + .EXAMPLE + $null | Remove-NonPrintableAscii + # Output: "" (empty string) + + .EXAMPLE + "" | Remove-NonPrintableAscii + # Output: "" (empty string) + #> + [CmdletBinding(SupportsShouldProcess = $true)] + [OutputType([String])] + + param ( + [Parameter(ValueFromPipeline = $true)] + [string]$InputString + ) + + process { + if ($PSCmdlet.ShouldProcess($InputString, 'Remove non-printable ASCII characters')) { + # Check if the input string is null or empty. + # If it is, return an empty string immediately to avoid errors. + if ([string]::IsNullOrEmpty($InputString)) { + return '' + } + + # Regular expression to match any character that is NOT a printable ASCII character. + # [^\x20-\x7E] matches any character that is not in the range of ASCII 32 (space) to 126 (tilde). + $cleanedString = $InputString -replace '[^\x20-\x7E]', '' + return $cleanedString + } + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e39e9b..2ed52bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add success message logging for completed commands in `Invoke-CommandWithTimeout` function - Update localization files to include NoData messages and improve key matching in tests - Fix [255](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/255) +- Fix non-printable ASCII characters from a string ## [1.0.0] - 2026-05-02 From b8f0b9160e67af256c247947fb4be5834e85f7e5 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Fri, 19 Jun 2026 21:49:53 -0400 Subject: [PATCH 18/21] Set an option to enable/disable the main logo of the diagrams --- .../AsBuiltReport.Microsoft.AD.json | 3 ++- .../Src/Private/Diagram/Get-AbrDiagrammer.ps1 | 4 ++++ .../Src/Private/Diagram/New-AbrADDiagram.ps1 | 17 ++++++++++++++++- CHANGELOG.md | 3 ++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json index 4a34316..7f55b40 100644 --- a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json +++ b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json @@ -45,7 +45,8 @@ "SignatureAuthorName": "", "SignatureCompanyName": "", "JobsTimeOut": 900, - "DCStatusPingCount": 2 + "DCStatusPingCount": 2, + "DisableDiagramMainLogo": false }, "InfoLevel": { "_comment_": "0 = Disabled, 1 = Enabled, 2 = Adv Summary, 3 = Detailed", diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagrammer.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagrammer.ps1 index ee7bca4..270fb14 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagrammer.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagrammer.ps1 @@ -115,6 +115,10 @@ function Get-AbrDiagrammer { $DiagramParams.Add('CompanyName', $Options.SignatureCompanyName) } + if ($Options.DisableDiagramMainLogo) { + $DiagramParams.Add('DisableMainDiagramLogo', $True) + } + try { foreach ($Format in $DiagramFormat) { if ($Format -eq 'base64') { diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/New-AbrADDiagram.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/New-AbrADDiagram.ps1 index 1180e5d..88d0a2e 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/New-AbrADDiagram.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/New-AbrADDiagram.ps1 @@ -53,6 +53,8 @@ function New-AbrADDiagram { Control to enable subgraph debugging ( Subgraph Lines ). .PARAMETER EnableErrorDebug Control to enable error debugging. + .PARAMETER DisableMainDiagramLogo + Switch to disable rendering the main diagram logo. .PARAMETER AuthorName Allow to set footer signature Author Name. .PARAMETER CompanyName @@ -155,6 +157,12 @@ function New-AbrADDiagram { [ValidateSet('left-to-right', 'top-to-bottom')] [string] $Direction = 'top-to-bottom', + [Parameter( + Mandatory = $false, + HelpMessage = 'Disable the Main Diagram Logo' + )] + [Switch] $DisableMainDiagramLogo, + [Parameter( Mandatory = $false, HelpMessage = 'Please provide the path to the diagram output file' @@ -510,8 +518,15 @@ function New-AbrADDiagram { Write-Verbose $reportTranslate.NewADDiagram.genDiagramSignature + # Subgraph MainGraph used to draw the main drawboard. + if ($DisableMainDiagramLogo) { + $FormatedMainLogo = '' + } else { + $FormatedMainLogo = Add-HtmlLabel -ImagesObj $Images -Label $MainGraphLabel -IconType $CustomLogo -IconDebug $IconDebug -IconWidth 250 -IconHeight 80 -Fontsize 24 -FontName 'Segoe UI Bold' -FontColor $Fontcolor -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor + } + # Main Graph SubGraph - SubGraph MainGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $MainGraphLabel -IconType $CustomLogo -IconDebug $IconDebug -IconWidth 250 -IconHeight 80 -Fontsize 24 -FontName 'Segoe UI Bold' -FontColor $Fontcolor -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor); fontsize = 22; penwidth = 0; labelloc = 't'; labeljust = 'c' } { + SubGraph MainGraph -Attributes @{Label = $FormatedMainLogo; fontsize = 22; penwidth = 0; labelloc = 't'; labeljust = 'c' } { Write-Verbose $reportTranslate.NewADDiagram.genDiagramMain $script:ForestRoot = $ADSystem.Name.ToString().ToUpper() diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed52bb..4599af7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump module version to `1.0.1` - Upgrade AsBuiltReport.Diagram module to version `1.0.8` - Upgrade AsBuiltReport.Chart module to version `0.3.3` +- Set an option to enable/disable the main logo of the diagrams ### Fixed @@ -25,7 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add success message logging for completed commands in `Invoke-CommandWithTimeout` function - Update localization files to include NoData messages and improve key matching in tests - Fix [255](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD/issues/255) -- Fix non-printable ASCII characters from a string +- Fix: remove non-printable ASCII characters from a string ## [1.0.0] - 2026-05-02 From 34f8c65c4bf8232631b3619485f103157ee5ade8 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Fri, 19 Jun 2026 22:00:47 -0400 Subject: [PATCH 19/21] Update github actions to latest releases --- .github/workflows/PSScriptAnalyzer.yml | 2 +- .github/workflows/Pester.yml | 4 ++-- .github/workflows/Release.yml | 2 +- CHANGELOG.md | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/PSScriptAnalyzer.yml b/.github/workflows/PSScriptAnalyzer.yml index 3c79c0e..7554e95 100644 --- a/.github/workflows/PSScriptAnalyzer.yml +++ b/.github/workflows/PSScriptAnalyzer.yml @@ -5,7 +5,7 @@ jobs: name: Run PSScriptAnalyzer runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: lint uses: alagoutte/github-action-psscriptanalyzer@master with: diff --git a/.github/workflows/Pester.yml b/.github/workflows/Pester.yml index 3703e67..10ad496 100644 --- a/.github/workflows/Pester.yml +++ b/.github/workflows/Pester.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up PowerShell Gallery run: | @@ -68,7 +68,7 @@ jobs: - name: Upload test results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-results-${{ matrix.os }}-${{ matrix.shell }} path: Tests/testResults.xml diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 39a2492..3f46982 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -8,7 +8,7 @@ jobs: publish-to-gallery: runs-on: windows-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set PSRepository to Trusted for PowerShell Gallery shell: pwsh run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 4599af7..5aeeb70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgrade AsBuiltReport.Diagram module to version `1.0.8` - Upgrade AsBuiltReport.Chart module to version `0.3.3` - Set an option to enable/disable the main logo of the diagrams +- Update github actions to latest releases ### Fixed From a66ca0e7dd38600b82aa0f25344106bb1a42b749 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Mon, 22 Jun 2026 09:55:42 -0400 Subject: [PATCH 20/21] Increase file version --- AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 b/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 index 49d29ad..e9c694e 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Public/Get-AbrAdLog.ps1 @@ -26,7 +26,7 @@ function Get-AbrAdLog { Saves a full diagnostic JSON (with stack traces) to C:\Logs and returns the object to the pipeline. .NOTES - Version: 0.1.0 + Version: 1.0.1 Author: Jonathan Colon Github: rebelinux .LINK From 1ebcd44e5328444e7f01558252c4e187d5b3715c Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Mon, 22 Jun 2026 09:56:43 -0400 Subject: [PATCH 21/21] Update Changelog release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aeeb70..e31e52b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ##### This project is community maintained and has no sponsorship from Microsoft, its employees or any of its affiliates. -## [1.0.1] - unreleased +## [1.0.1] - 2026-06-22 ### Added