diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4093fa2..558cea0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,352 +1,871 @@ -# Copilot Instructions for AsBuiltReport.Microsoft.AD +# AsBuiltReport.Microsoft.AD - Copilot Instructions -## What This Project Does +**Project Overview:** AsBuiltReport.Microsoft.AD is a PowerShell module that generates comprehensive as-built documentation for Microsoft Active Directory (AD) infrastructure in Word/HTML/Text formats. It's part of the larger AsBuiltReport ecosystem and works in conjunction with AsBuiltReport.Core. -A PowerShell module that generates As-Built documentation reports for Microsoft Active Directory environments (Forest, Domains, Domain Controllers, DNS, PKI/CA). It produces HTML/Word/Text output via the **PScribo** library, with optional network topology diagrams via **Diagrammer.Core**. +--- + +## 1. PROJECT STRUCTURE + +### Top-Level Directory Layout +``` +AsBuiltReport.Microsoft.AD/ +├── .github/ # CI/CD workflows and PR templates +│ ├── workflows/ # GitHub Actions workflows +│ │ ├── Pester.yml # Unit testing pipeline +│ │ ├── PSScriptAnalyzer.yml # Linting/code analysis +│ │ ├── CodeQL.yml # Security scanning +│ │ ├── Release.yml # Publishing to PSGallery + social media +│ │ └── Stale.yml # Issue/PR housekeeping +│ └── PULL_REQUEST_TEMPLATE.md +├── .vscode/ # VS Code settings for PowerShell formatting +│ └── settings.json # Formatting rules, rulers @ 115 chars +├── AsBuiltReport.Microsoft.AD/ # MAIN MODULE DIRECTORY +│ ├── AsBuiltReport.Microsoft.AD.psm1 # Module manifest (14 lines - loads all functions) +│ ├── AsBuiltReport.Microsoft.AD.psd1 # Module declaration (v0.9.11) +│ ├── AsBuiltReport.Microsoft.AD.json # Default report config (InfoLevels, HealthChecks) +│ ├── AsBuiltReport.Microsoft.AD.Style.ps1 # Document styling (20.8 KB) +│ ├── Src/ +│ │ ├── Public/ +│ │ │ └── Invoke-AsBuiltReport.Microsoft.AD.ps1 # ENTRY POINT (291 lines) +│ │ └── Private/ +│ │ ├── Get-Abr*.ps1 # 52x data gathering functions +│ │ ├── ConvertTo-*.ps1 # Format/conversion helpers +│ │ ├── Convert-*.ps1 # Data transformation utilities +│ │ ├── Get-*Diagram.ps1 # Visualization generation +│ │ └── Utility functions # Session management, timeout handling, etc. +│ ├── Language/ # Localization files +│ │ ├── en-US/MicrosoftAD.psd1 # English strings (hash of all messages) +│ │ └── es-ES/MicrosoftAD.psd1 # Spanish localization +│ └── icons/ # Image assets for reports +├── Tests/ +│ ├── Invoke-Tests.ps1 # Test runner script (204 lines) +│ ├── AsBuiltReport.Microsoft.AD.Tests.ps1 # Pester unit tests +│ ├── LocalizationData.Tests.ps1 # Localization validation +│ └── README.md +├── Samples/ # Example HTML reports +├── README.md # Project documentation +├── CONTRIBUTING.md # Contribution guidelines +├── CODE_OF_CONDUCT.md # Community standards +├── LICENSE # License file +├── CHANGELOG.md # Version history +├── SECURITY.md # Security policy +└── Todo.md # Development roadmap +``` -## Runtime Requirements +### Key Directories +- **Src/Public**: Only `Invoke-AsBuiltReport.Microsoft.AD` - the single exported public function +- **Src/Private**: 88 total functions (52 Get-Abr* for data gathering, rest are utilities) +- **Language**: Localization for multi-language support (en-US, es-ES) +- **Tests**: Pester tests + custom test runner supporting code coverage -- **Must run as Administrator** — `#Requires -RunAsAdministrator` is enforced in the entry point. -- **Target must be an FQDN** — IP addresses are explicitly rejected; always pass a fully-qualified domain name for `-Target`. -- **Cannot run in PowerShell ISE** — detected and blocked at startup; use the PowerShell console or terminal. -- **PowerShell 7+ recommended** — required for tests; the module itself targets Windows PowerShell 5.1+ on Windows only. -- **Reporting machine must be domain-joined** — required for the PKI/CA section (`Get-ComputerADDomain` check). -- **WinRM must be enabled on DCs** — all remote data collection goes through WinRM; CIMSession is supplementary. +### File Count Summary +- **Total .ps1 files**: 94 +- **Public functions**: 1 (exported) +- **Private functions**: ~88 + utilities +- **Data gathering functions (Get-Abr*)**: 52 -## Testing +--- + +## 2. BUILD, TEST, LINT COMMANDS + +### Test Execution -Run all Pester tests (requires PowerShell 7+, Windows): +**Local Test Execution:** ```powershell -cd Tests -.\Invoke-Tests.ps1 +.\Tests\Invoke-Tests.ps1 # Basic run +.\Tests\Invoke-Tests.ps1 -CodeCoverage -OutputFormat NUnitXml # With coverage +``` + +**Test Runner Details** (`Tests/Invoke-Tests.ps1`): +- Uses **Pester 5.0.0+** for testing framework +- Supports output formats: Console, NUnitXml, JUnitXml +- Includes code coverage analysis (JaCoCo format) +- Code coverage threshold: 50% minimum (warning at <50%) +- Coverage files tracked: `*.psm1`, `Src/Public/*.ps1`, `Src/Private/*.ps1` +- Test results: `Tests/testResults.xml` +- Coverage output: `Tests/coverage.xml` + +### Code Analysis + +**PSScriptAnalyzer** (`PSScriptAnalyzerSettings.psd1`): +- Linting tool configured in CI/CD +- Custom rules enforced: + - `PSAvoidExclaimOperator` - no `!` operator + - `AvoidUsingDoubleQuotesForConstantString` - use single quotes for constants + - `UseCorrectCasing` - enforce proper case + - `PSAvoidUsingCmdletAliases` - no aliases + - `PSUseConsistentWhitespace` - whitespace consistency +- Excluded rules: + - `PSUseToExportFieldsInManifest` + - `PSAvoidUsingWriteHost` (needed for reports) + +### CI/CD Pipelines + +**Pester Tests Workflow** (`.github/workflows/Pester.yml`): +- Triggers: push (main/dev/master), PR, manual +- Runs on: Windows (pwsh + powershell 5.1) +- Auto-installs: Pester 5.0.0+, PScribo 0.11.1+, PSScriptAnalyzer 1.0.0+, AsBuiltReport.Core 1.6.2+ +- Uploads test results as artifacts +- Uploads code coverage to Codecov + +**PSScriptAnalyzer Workflow** (`.github/workflows/PSScriptAnalyzer.yml`): +- Uses external action: `alagoutte/github-action-psscriptanalyzer@master` +- Fails on errors, comments inline +- Settings: `.github/workflows/PSScriptAnalyzerSettings.psd1` + +**CodeQL Workflow** (`.github/workflows/CodeQL.yml`): +- Security scanning for PowerShell + +**Release Workflow** (`.github/workflows/Release.yml`): +- Triggers on release published +- Tests module manifest +- Publishes to PowerShell Gallery (`Publish-Module`) +- Posts release announcements to Twitter & Bluesky + +**No Build/Invoke-Build found**: This is a pure PowerShell module (no compilation). + +--- + +## 3. ARCHITECTURE + +### High-Level Data Flow + +``` +Invoke-AsBuiltReport.Microsoft.AD (Entry Point) + ↓ + [Input: Target DC, Credentials] + ↓ + [Validate: Requirements, Features, Modules] + ↓ + [Connection: PSSession + CIMSession to DC] + ↓ + [Process Per Forest/Domain] + ├── Get-AbrForestSection (Forest-level data) + ├── Get-AbrDomainSection (Per-domain data) + ├── Get-AbrDnsSection (DNS configuration) + └── Get-AbrPKISection (Certificate Authority) + ↓ + [Diagram Generation: Forest, Replication, Trusts, Sites, CA] + ↓ + [Session Cleanup: Remove PSSession, CIMSession] + ↓ + [Output: HTML/Word/Text Report] ``` -Run with code coverage: +### Main Entry Point + +**File**: `AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1` (291 lines) + +**Signature**: ```powershell -.\Invoke-Tests.ps1 -CodeCoverage +function Invoke-AsBuiltReport.Microsoft.AD { + [CmdletBinding()] + param ( + [String[]] $Target, # Domain controller(s) FQDN + [PSCredential] $Credential # Credentials for remote session + ) + #Requires -RunAsAdministrator +} +``` + +**Key Responsibilities**: +1. Validate prerequisites (Windows PS >= 5.1, admin rights, not ISE) +2. Check installed modules & warn on outdated versions +3. Validate OS features (RSAT tools on workstation, features on server) +4. Load report config (JSON), InfoLevels, HealthChecks, Options +5. Establish PSSession + CIMSession to DC via WinRM +6. Collect forest/domain/DNS/PKI data via section functions +7. Generate diagrams (if enabled) +8. Build report using PScribo +9. Cleanup sessions + +**Critical Design Pattern**: +- **$Target** must be FQDN (not IP) - WinRM limitation +- Must run **-RunAsAdministrator** +- Must run from PowerShell 7+, **NOT** PowerShell ISE +- WinRM must be enabled on DC +- Uses **$Options** hash from config for behavior control + +### Core Section Builders + +These functions call data gatherers and structure output via PScribo's **Section** cmdlet: + +1. **Get-AbrForestSection**: Forest topology, schema, tombstone lifetime, global catalogs +2. **Get-AbrDomainSection**: Per-domain configuration, trusts, replication, GPOs, OUs +3. **Get-AbrDnsSection**: DNS zones, scavenging, delegation +4. **Get-AbrPKISection**: Certificate authorities, templates, security + +### Data Gathering Functions (Get-Abr*) + +**Pattern**: Each function collects specific AD object data via remote PSSession: + +Example: `Get-AbrADForest` (80 lines) +- Uses `Invoke-CommandWithTimeout` to run remote cmdlets +- Parses schema version to determine Windows Server version +- Detects anonymous access via dsHeuristics +- Returns object with translated property names +- Applies HealthCheck styling if enabled + +**All 52 Get-Abr* functions follow this pattern:** +- Accept parameters (Domain, ValidDcFromDomain, etc.) +- Start: Log collection message, start timing +- Process: Remote invocation via session, data transformation +- Output: `[System.Collections.ArrayList]` of objects +- HealthCheck: Conditionally apply styling (Warning/Critical) +- Return: Table/list output via PScribo's **Table** cmdlet + +### InfoLevel Architecture + +**Default Config** (`AsBuiltReport.Microsoft.AD.json`): +```json +"InfoLevel": { + "_comment_": "0 = Disabled, 1 = Enabled, 2 = Adv Summary, 3 = Detailed", + "Forest": 2, + "Domain": 2, + "DNS": 1, + "CA": 0 +} ``` -Run a single test file directly: +**Usage Pattern**: ```powershell -Invoke-Pester -Path .\Tests\AsBuiltReport.Microsoft.AD.Tests.ps1 -Output Detailed +if ($InfoLevel.Forest -ge 1) { ... show basic info } +if ($InfoLevel.Forest -ge 2) { ... show advanced details } +if ($InfoLevel.Forest -ge 3) { ... show comprehensive tables } ``` -Run PSScriptAnalyzer lint locally: +Enables **progressive disclosure** - users control report verbosity. + +### HealthCheck Architecture + +**Default Config**: +```json +"HealthCheck": { + "Domain": { + "GMSA": true, # Group Managed Service Accounts + "GPO": true, # Group Policy Objects + "Backup": true, # Domain backup status + "DFS": true, # DFS health + "SPN": true, # Service Principal Names + "DuplicateObject": true, + "Security": true, + "BestPractice": true + }, + "DomainController": { ... }, + "Site": { ... }, + "DNS": { ... }, + "CA": { ... } +} +``` + +**Styling Application**: ```powershell -Invoke-ScriptAnalyzer -Path .\AsBuiltReport.Microsoft.AD\Src -Settings .\.github\workflows\PSScriptAnalyzerSettings.psd1 -Recurse +if ($HealthCheck.Domain.Security) { + $OutObj | Where-Object { $_.AnonymousAccess -eq 'Enabled' } | + Set-Style -Style Critical -Property AnonymousAccess + $OutObj | Where-Object { $_.TombstoneLifetime -lt 180 } | + Set-Style -Style Warning -Property TombstoneLifetime +} ``` -PSScriptAnalyzer enforces: `UseCorrectCasing`, `PSUseConsistentWhitespace`, `PSAvoidUsingCmdletAliases`, `AvoidUsingDoubleQuotesForConstantString`, `PSAvoidExclaimOperator`. Errors fail CI; warnings do not. +Objects marked as Warning/Critical get colored highlighting in reports. -## Architecture +### Connection Management -### Module Layout +**Session Establishment** (in main entry point): +```powershell +$TempPssSession = Get-ValidPSSession -ComputerName $System -SessionName $System +$TempCIMSession = Get-ValidCIMSession -ComputerName $System -SessionName $System +``` +**Remote Command Execution**: +```powershell +Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADForest } ``` -AsBuiltReport.Microsoft.AD/ - AsBuiltReport.Microsoft.AD.psm1 # Dot-sources all Src/Public and Src/Private *.ps1 files - AsBuiltReport.Microsoft.AD.json # Default report config (InfoLevel, HealthCheck, Options) - AsBuiltReport.Microsoft.AD.psd1 # Module manifest - AsBuiltReport.Microsoft.AD.Style.ps1 # PScribo document styling - Src/ - Public/ - Invoke-AsBuiltReport.Microsoft.AD.ps1 # Entry point; sets up sessions, calls Section functions - Private/ - Get-Abr*Section.ps1 # Top-level section orchestrators - Get-AbrAD*.ps1 # Report content generators (one per AD topic) - Get-AbrDiag*.ps1 # Diagram generators - ConvertTo-*.ps1 # Data conversion helpers - Get-Valid*.ps1 # Session/DC validation helpers - Language/ - en-US/MicrosoftAD.psd1 # English string resources - es-ES/MicrosoftAD.psd1 # Spanish string resources + +**Cleanup**: +```powershell +foreach ($PSSession in $PSSTable | Where { $_.Status -ne 'Offline' }) { + Remove-PSSession -Id $PSSession.id +} ``` -### Data Flow +### Diagram Generation + +**Diagrammer Integration**: +- Uses `Diagrammer.Core` module for topology visualization +- Types: Forest, Replication, Sites, SitesInventory, Trusts, CertificateAuthority +- Controlled by `$Options.EnableDiagrams`, `$Options.DiagramType.*` +- Outputs: PDF/PNG (configurable via `$Options.ExportDiagramsFormat`) +- Theme: White/Dark (via `$Options.DiagramTheme`) -1. `Invoke-AsBuiltReport.Microsoft.AD` (Public) connects via PSSession/CIMSession to a target DC, discovers the forest/domain topology, then calls each `Get-Abr*Section` function. -2. Section functions (e.g., `Get-AbrDomainSection`, `Get-AbrForestSection`, `Get-AbrDNSSection`, `Get-AbrPKISection`) gate execution with `$InfoLevel.*` checks and iterate over domains/DCs. -3. Content functions (e.g., `Get-AbrADDomain`, `Get-AbrADDomainController`) collect AD data via `Invoke-CommandWithTimeout` (remote PSSession) and write output using PScribo DSL (`Section`, `Table`, `Paragraph`, `BlankLine`). -4. The PScribo document is assembled in memory and exported to the requested format by the AsBuiltReport.Core framework. +--- -### Key Script-Scoped Variables +## 4. KEY CONVENTIONS AND PATTERNS -These are set by `Invoke-AsBuiltReport.Microsoft.AD` and used across all Private functions: +### Function Naming Convention -| Variable | Purpose | -|---|---| -| `$script:TempPssSession` | Primary PSSession to initial target DC | -| `$script:TempCIMSession` | CIMSession to initial DC | -| `$script:InfoLevel` | Hash from JSON config — controls section depth (0–3) | -| `$script:Options` | Hash from JSON config — WinRM, exclusions, diagram settings | -| `$script:ADSystem` | `Get-ADForest` result for the target forest | -| `$script:ForestInfo` | Root domain FQDN (uppercased) | -| `$script:OrderedDomains` | Root domain first, then child domains | -| `$script:DCStatus` | ArrayList tracking reachability status per DC | -| `$reportTranslate` | Localized string resources loaded from `Language/` | +**Public Functions**: +- `Invoke-AsBuiltReport.Microsoft.AD` - single entry point (uses dot notation) -## Key Conventions +**Private Functions** - Three categories: -### Function Naming +1. **Data Gatherers** (`Get-Abr*`): + - `Get-AbrADForest` - retrieves Forest info + - `Get-AbrADDomain` - retrieves Domain info + - `Get-AbrADDomainController` - DC inventory + - `Get-AbrADCA*` - CA-specific data + - Pattern: Get-Abr[Section][Subsection] -- `Get-AbrAD*` — collects and renders a specific AD topic (domain info, DC info, GPO, trust, etc.) -- `Get-Abr*Section` — top-level orchestrators that call multiple `Get-AbrAD*` functions inside `Section {}` blocks -- `Get-AbrDiag*` — generate infrastructure diagrams -- `ConvertTo-*` — data transformation helpers (e.g., `ConvertTo-TextYN`, `ConvertTo-HashToYN`, `ConvertTo-FileSizeString`) -- `Get-Valid*` — session/connectivity helpers (`Get-ValidDCfromDomain`, `Get-ValidPSSession`, `Get-ValidCIMSession`) +2. **Section Builders** (`Get-Abr*Section`): + - `Get-AbrForestSection` - orchestrates Forest section + - `Get-AbrDomainSection` - orchestrates Domain section + - `Get-AbrDnsSection` - orchestrates DNS section + - `Get-AbrPKISection` - orchestrates PKI section + - Pattern: Get-Abr[Section]Section -### Building Report Tables +3. **Diagram Builders** (`Get-AbrDiag*`): + - `Get-AbrDiagrammer` - main diagram orchestration + - `Get-AbrDiagForest`, `Get-AbrDiagReplication`, etc. + - Pattern: Get-AbrDiag[DiagramType] -Every content function uses this pattern: +4. **Utility Functions** (various): + - `Convert-IpAddressToMaskLength` - IP/CIDR conversion + - `ConvertTo-HashToYN` - bool → Yes/No conversion + - `Invoke-CommandWithTimeout` - remote execution with timeout + - `Get-ValidPSSession` - session validation/creation + - `Test-WinRM` - WinRM connectivity check + +### Data Structure Patterns + +**Standard Data Object**: ```powershell -$OutObj = [System.Collections.ArrayList]::new() $inObj = [ordered] @{ - $reportTranslate.FunctionName.FieldKey = $value - # ... + 'Property Name' = $Value + 'Health Check Property' = $CheckResult } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) | Out-Null - -$TableParams = @{ - Name = "Table Title - $Domain" - List = $true # or $false for horizontal tables - ColumnWidths = 40, 60 -} -if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" -} -$OutObj | Table @TableParams ``` -### Localized Strings +**Conversion Helper Usage**: +```powershell +# ConvertTo-HashToYN: Converts boolean $true/$false → "Yes"/"No" +$inObj | ConvertTo-HashToYN +``` -All user-visible strings — table column headers, section headings, paragraph text, health check messages — must come from `$reportTranslate..`. Add new strings to both `Language/en-US/MicrosoftAD.psd1` and `Language/es-ES/MicrosoftAD.psd1`. String keys use PascalCase and match the function name as a top-level key. +**Style Application**: +```powershell +$OutObj | Set-Style -Style Critical -Property $PropertyName +$OutObj | Set-Style -Style Warning -Property $PropertyName +``` -### InfoLevel and HealthCheck Gating +### Report Section Structure -- `$InfoLevel.Domain` (0=Disabled, 1=Enabled, 2=Adv Summary, 3=Detailed) controls whether a section runs and how much detail it shows. -- `$HealthCheck.Domain.BestPractice` (boolean) controls whether health check styling/paragraphs are added to existing tables. -- Pattern for health checks: +**PScribo Section Hierarchy**: ```powershell -if ($HealthCheck.Domain.BestPractice) { - $OutObj | Set-Style -Style Warning -Property $reportTranslate.FunctionName.FieldKey +Section -Style Heading1 "Forest Name" { + Paragraph "Introduction..." + BlankLine + + Section -Style Heading2 "Subsection Title" { + if ($Options.ShowDefinitionInfo) { + Paragraph "Definition text..." + } + + # Call data gatherer + Get-AbrADForest + + if ($InfoLevel.Forest -ge 2) { + # Advanced details + Get-AbrADSite + } + } } ``` -### Remote Execution +**PScribo Elements Used**: +- `Section` - create section with heading levels (Heading1-Heading4) +- `Table` - display data in tabular format +- `Paragraph` - text with styling (Bold, Underline, Colors) +- `BlankLine` - spacing +- `PageBreak` - force page break in Word/PDF -Always use `Invoke-CommandWithTimeout` (not `Invoke-Command` directly) for remote PSSession calls. This respects `$Options.JobsTimeOut`: +### Translation/Localization Pattern + +**Property Names Use Translated Strings**: ```powershell -$Result = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { - Get-ADDomain -Identity $using:Domain +# From Language/en-US/MicrosoftAD.psd1 +@{ + GetAbrADForest = @{ + Collecting = 'Collecting Active Directory forest information.' + ForestName = 'Forest Name' + ForestFunctionalLevel = 'Forest Functional Level' + ... + } } + +# In function: +$reportTranslate.GetAbrADForest.Collecting # Loaded at module init ``` -### Error Handling Pattern +**Multi-Language Support**: +- Each culture has its own .psd1 file (en-US, es-ES, etc.) +- Strings loaded into `$reportTranslate` hash at module load +- Property names in output tables are localized +### HealthCheck Patterns + +**Pre-Check Pattern** (e.g., RID Pool): ```powershell -begin { - Write-PScriboMessage -Message ($reportTranslate.FunctionName.Collecting -f $Domain) - Show-AbrDebugExecutionTime -Start -TitleMessage 'Section Title' -} -process { - try { - # ... collect and render ... - } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Context Description)" +if ($HealthCheck.Domain.BestPractice) { + if ([math]::Truncate($CompleteSIDS / $RIDsRemaining) -gt 80) { + $OutObj | Set-Style -Style Warning -Property RIDProperty + Paragraph "Health check message about RID pool..." } } -end { - Show-AbrDebugExecutionTime -End -TitleMessage 'Section Title' +``` + +**28 Functions Use HealthCheck** out of 52 data gatherers (~54%): +- Focus on security, best practices, service health +- Each check compares values against thresholds +- Styling applied: Warning, Critical, or Success + +### Configuration-Driven Behavior + +**Options Hash Controls**: +```json +"Options": { + "ShowExecutionTime": false, # Show timing info + "ShowDefinitionInfo": false, # Show definition text + "PSDefaultAuthentication": "Negotiate", + "Exclude": { "Domains": [], "DCs": [] }, + "Include": { "Domains": [] }, # Only these domains + "WinRMSSL": false, + "WinRMFallbackToNoSSL": true, + "WinRMSSLPort": 5986, + "WinRMPort": 5985, + "EnableDiagrams": true, + "DiagramTheme": "White", + "JobsTimeOut": 900 # 15-minute timeout } ``` -Use `Write-PScriboMessage` (not `Write-Host`) for module logging. `Write-Host` is only allowed in the Public entry point (`Invoke-AsBuiltReport.Microsoft.AD.ps1`) for top-level user-facing progress messages. +**Usage Example**: +```powershell +if ($Options.ShowDefinitionInfo) { + Paragraph $reportTranslate.GetAbrForestSection.DefinitionText +} + +$TimeoutSeconds = $Options.JobsTimeOut +``` -### DC Connectivity Check +### Error Handling & Timeouts -Before running per-DC logic, always check WinRM reachability: +**Invoke-CommandWithTimeout Pattern**: ```powershell -if (Get-DCWinRMState -ComputerName $DC -DCStatus ([ref]$DCStatus)) { - # ... per-DC work ... +function Invoke-CommandWithTimeout { + param( + [System.Management.Automation.Runspaces.PSSession]$Session, + [scriptblock]$ScriptBlock, + [int]$TimeoutSeconds = $Options.JobsTimeOut + ) + + # Run as background job with timeout + $job = Invoke-Command -Session $Session -AsJob -ScriptBlock $ScriptBlock + Wait-Job $job -Timeout $TimeoutSeconds + Receive-Job $job } ``` -### Configuration JSON +**Try-Catch in Data Gatherers**: +```powershell +try { + Get-AbrADForest +} catch { + Write-PScriboMessage -IsWarning $_.Exception.Message +} +``` -`AsBuiltReport.Microsoft.AD.json` defines defaults for `Report`, `Options`, `InfoLevel`, and `HealthCheck`. New configurable options must be added here with sensible defaults. +### Sensitive Data Handling ---- +**No explicit redaction observed**, but patterns suggest: +- Credentials passed via `$PSCredential` object (not stored) +- Session-based execution (no inline secrets) +- Remote execution prevents data capture on local disk +- Output tables contain parsed, non-sensitive data -## Session Management +**Recommendation**: Follow AD best practices - restrict report access, don't email to untrusted parties. -### Three Parallel Session Tables +--- -All remote connectivity is tracked in three `[System.Collections.ArrayList]` caches (passed as `[ref]` throughout): +## 5. CONFIGURATION + +### Primary Config File + +**File**: `AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json` + +**Structure**: +```json +{ + "Report": { + "Name": "Microsoft Active Directory As Built Report", + "Version": "1.0", + "Status": "Released", + "ShowCoverPageImage": true, + "ShowTableOfContents": true, + "ShowHeaderFooter": true, + "ShowTableCaptions": true + }, + "Options": { ... }, // Execution behavior + "InfoLevel": { ... }, // Report verbosity + "HealthCheck": { ... } // Health check toggles +} +``` -| Variable | Cache Contents | Helper | -|---|---|---| -| `$DCStatus` | WinRM reachability per DC | `Get-DCWinRMState` | -| `$PSSTable` | PSSession objects per DC | `Get-ValidPSSession` | -| `$CIMTable` | CIMSession objects per DC | `Get-ValidCIMSession` | +### Configuration Usage -Each entry in these lists is a hashtable with at minimum: `DCName`, `Status` (`Online`/`Offline`), `Protocol`, and `Id`. +Users provide config via **-ReportConfig parameter** to AsBuiltReport.Core: -### `Get-DCWinRMState` — Reachability Gate +```powershell +$ReportConfig = Get-Content 'config.json' | ConvertFrom-Json -Always called **before** establishing a PSSession or CIMSession. It: -1. Checks `$DCStatus` cache first (avoids repeated Test-WSMan calls). -2. Falls back to `Test-WSMan` if not cached, respecting `$Options.WinRMSSL`, `$Options.WinRMSSLPort`, and `$Options.WinRMFallbackToNoSSL`. -3. Records result in `$DCStatus` and returns `$true`/`$false`. -4. Ping count controlled by `$Options.DCStatusPingCount` (default: 2). +New-AsBuiltReport -Report Microsoft.AD ` + -Target 'DC01.contoso.com' ` + -ReportConfig $ReportConfig ` + -Credential $cred ` + -Format HTML +``` +**Module Loads**: ```powershell -# Always gate DC-specific work: -if (Get-DCWinRMState -ComputerName $DC -DCStatus ([ref]$DCStatus)) { - # safe to proceed -} +$script:Report = $ReportConfig.Report +$script:InfoLevel = $ReportConfig.InfoLevel +$script:Options = $ReportConfig.Options ``` -### `Get-ValidPSSession` — PSSession Pool +### Module Manifest -Manages a pool of reusable PSSessions. Behaviour: -- If a cached `Online` session exists for the DC, returns it immediately without creating a new one. -- If `$Options.WinRMSSL` is set, tries SSL first (`$Options.WinRMSSLPort`); if it fails and `$Options.WinRMFallbackToNoSSL` is `$true`, retries on plain WinRM (`$Options.WinRMPort`). -- For the **initial forest connection** (`-InitialForrestConnection $true`), failure throws a terminating error. For per-DC connections, failure is non-terminating (logged as a warning). -- Authentication method is `$Options.PSDefaultAuthentication` (default: `Negotiate`). +**File**: `AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1` -```powershell -$DCPssSession = Get-ValidPSSession -ComputerName $DC -SessionName $DC -PSSTable ([ref]$PSSTable) -``` +**Key Settings**: +- **Version**: 0.9.11 +- **PowerShellVersion**: 5.1 (minimum, actually PS7 required) +- **CompatiblePSEditions**: Desktop, Core +- **GUID**: 0a3e1c04-13b8-418f-89bc-a5a18da07394 -### `Get-ValidCIMSession` — CIMSession Pool +**Required Modules**: +- AsBuiltReport.Core (v1.6.2+) +- AsBuiltReport.Chart (v0.2.0+) +- Diagrammer.Core (v0.2.38+) +- PSPKI (v4.3.0+) -Mirrors `Get-ValidPSSession` but for CIM. SSL uses `New-CimSessionOption -UseSsl`; plain uses `New-CimSession` with `$Options.PSDefaultAuthentication`. CIMSession entries carry an additional `InstanceId` field. +--- -### `Get-ValidDCfromDomain` — Domain DC Discovery +## 6. EXISTING AI CONFIGS -Queries `Get-ADDomain` (via the primary `$TempPssSession`) to get `ReplicaDirectoryServers`, then iterates them through `Get-DCWinRMState` and returns the first reachable DC's FQDN. Used at the start of each domain loop to obtain the `$ValidDC` variable passed to all content functions. +**None Found**. No existing files: +- `.cursorrules` ✗ +- `.clinerules` ✗ +- `.windsurfrules` ✗ +- `CLAUDE.md` ✗ +- `AGENTS.md` ✗ +- `CONVENTIONS.md` ✗ -```powershell -if ($ValidDC = Get-ValidDCfromDomain -Domain $Domain -DCStatus ([ref]$DCStatus)) { - # use $ValidDC as the -Server parameter for AD cmdlets -} -``` +--- + +## 7. README AND CONTRIBUTING + +### README Key Points + +**Project Purpose**: +- Community-maintained, no Microsoft sponsorship +- Generates as-built documentation for AD (Word/HTML/Text) +- Supports AD 2012/2016/2019/2022/2025 +- **PowerShell 7+ required** (not PS 5.1!) +- Windows only (RSAT dependency) + +**Supported Features**: +- Forest topology & schema info +- Domain configuration & replication +- DNS zones & scavenging +- PKI/Certificate Authority details +- Diagrams (Forest, Replication, Trusts, Sites, CA) +- Health checks for security/best practices +- Multi-language support (en-US, es-ES) + +**Key Disclaimer**: +> This assessment is not exhaustive. All recommendations should be reviewed and implemented by qualified personnel. The author(s) assume no liability for any damages. + +### CONTRIBUTING Guidelines + +**Process**: +1. Fork repo, clone, add remote upstream +2. Create topic branch off dev/main +3. Make changes following project conventions +4. Commit with clear messages +5. Pull upstream dev before pushing +6. Open PR with clear description + +**Requirements**: +- Follow existing code conventions (indentation, comments) +- Include test coverage (reference Pester tests) +- Respect git commit message guidelines +- No copyrighted content +- Agree to project license + +**Key Restriction**: +- Ask before embarking on large features/refactoring +- Don't use issue tracker for personal support --- -## Diagram Generation +## 8. CODE CONVENTIONS SUMMARY + +### PowerShell Code Style + +**Enforced via VSCode + PSScriptAnalyzer**: + +**Formatting** (`.vscode/settings.json`): +- Tab size: 4 spaces (insert spaces, not tabs) +- Line length: 115 characters (ruler configured) +- Trim trailing whitespace: enabled +- Code folding: enabled +- Brace style: + - Opening brace on same line: `if (...) {` + - New line after opening brace: `{\n ...` + - New line after closing brace: disabled +- Whitespace: + - Before open brace: enabled + - Before open paren: enabled + - Around operators: enabled + - After separator (;): enabled + - Around pipe: enabled + +**Linting** (`PSScriptAnalyzerSettings.psd1`): +- No single-character variable names +- No double quotes for constant strings +- Case sensitivity enforced +- No aliases (full cmdlet names) +- Consistent whitespace + +### Naming Conventions + +**Variables**: +- PascalCase for scripts/function names: `$ValidDcFromDomain` +- $script: prefix for module-level vars: `$script:Report`, `$script:InfoLevel` +- Hungarian notation for collections: `$PSSTable`, `$DCStatus` (plural hint) + +**Functions**: +- Verb-Noun format: `Get-AbrADForest`, `Invoke-CommandWithTimeout` +- Approved verbs: Get, New, Invoke, Test, Convert +- Hierarchy: `Get-[Abr][Component][Action]` + +**Constants**: +- `[ordered]` for hash ordering +- `[System.Collections.ArrayList]` for dynamic arrays (preferred over `@()`) +- `[pscustomobject]` for object creation + +### Error Handling + +- Use **try-catch** blocks +- Write warnings via `Write-PScriboMessage -IsWarning` +- Write errors via `Write-Error` or `throw` +- Log activity via `Write-PScriboMessage` +- Show timing via `Show-AbrDebugExecutionTime` + +### Documentation + +- SYNOPSIS, DESCRIPTION, NOTES (version, author, twitter, github) +- .EXAMPLE, .LINK for help +- Inline comments for complex logic +- Parameter documentation with `[Parameter(...)]` attributes -### Overview +--- -Diagrams are generated via the **Diagrammer.Core** / **Diagrammer.Microsoft.AD** ecosystem (PSGraph + Graphviz). The pipeline is: +## 9. CRITICAL DEVELOPMENT NOTES -``` -Get-AbrDiagrammer # thin wrapper, reads $Options, calls New-AbrADDiagram - └─ New-AbrADDiagram # builds Graphviz DOT graph, exports to file or base64 - └─ Get-AbrDiag* # per-diagram-type data collectors (called inside New-AbrADDiagram) -``` +### Must-Know Limitations -### Diagram Types +1. **WinRM Requirements**: + - Target must be FQDN (not IP) + - WinRM must be enabled on DC + - Domain-joined machine required to run module + - PowerShell 7+ on Windows only -Six types are supported (controlled by `$Options.DiagramType.*` booleans in the JSON config): +2. **Execution Context**: + - Must run `-RunAsAdministrator` + - Cannot run inside PowerShell ISE + - Remote execution via PSSession (not local cmdlets) -| Type | JSON Key | What it shows | -|---|---|---| -| `Forest` | `DiagramType.Forest` | Forest topology with domains | -| `Sites` | `DiagramType.Sites` | AD site links and connections | -| `SitesInventory` | `DiagramType.SitesInventory` | Sites with DC inventory per site | -| `Trusts` | `DiagramType.Trusts` | Domain trust relationships | -| `CertificateAuthority` | `DiagramType.CertificateAuthority` | PKI CA hierarchy | -| `Replication` | `DiagramType.Replication` | DC replication topology | +3. **Session Timeout**: + - Default timeout: 900 seconds (15 minutes) + - Configurable via `$Options.JobsTimeOut` + - Long operations may timeout on slow links -### How `Get-AbrDiagrammer` Works +### Development Workflow -1. Reads `$Options.DiagramTheme` (`White`/`Black`/`Neon`) and `$Options.ExportDiagramsFormat` (array: `pdf`, `png`, `svg`, `jpg`, `base64`). -2. Passes an existing `$TempPssSession` as `-PSSessionObject` (no credential re-prompt). -3. For `base64` format: returns the base64 string directly (used to embed diagrams inline in HTML reports). -4. For file formats: saves to `$OutputFolderPath` as `AsBuiltReport.Microsoft.AD-().` and returns the file path when `-ExportPath` is set. -5. Optional features toggled via `$Options`: `EnableDiagramDebug` (red edge/subgraph outlines), `EnableDiagramSignature` (footer with `SignatureAuthorName`/`SignatureCompanyName`), `DiagramWaterMark`. +1. **Make changes** to `.ps1` files in `Src/Public` or `Src/Private` +2. **Run tests** locally: `.\Tests\Invoke-Tests.ps1` +3. **Check linting**: PSScriptAnalyzer via VSCode +4. **Push to dev branch** (not master) +5. **CI/CD runs** Pester + PSScriptAnalyzer +6. **Create PR** to merge into master -### Embedding a Diagram in the Report +### Debugging Tips -The typical pattern in a Section function: +**Execution Timing**: ```powershell -if ($Options.EnableDiagrams -and $Options.DiagramType.Forest) { - $DiagramFile = Get-AbrDiagrammer -DiagramType 'Forest' -DiagramOutput 'base64' -PSSessionObject $TempPssSession - if ($DiagramFile) { - Image -Base64 $DiagramFile -Text 'Forest Diagram' -Percent 100 -Align 'Center' - BlankLine - } +if ($Options.ShowExecutionTime) { + Show-AbrDebugExecutionTime -Start/Stop -TitleMessage 'Section Name' } ``` -### `New-AbrADDiagram` Internals +**Logging Messages**: +```powershell +Write-PScriboMessage -Message "Collecting..." +Write-PScriboMessage -IsWarning "Warning message" +``` -- Requires **admin** privileges (checks `WindowsPrincipal` role). -- Builds a `Graph {}` block (PSGraph DSL) with node/edge default styles derived from `$DiagramTheme`. -- Icon images are loaded from `AsBuiltReport.Microsoft.AD/icons/` via `$script:IconPath`. -- The `$reportTranslate.NewADDiagram.*` keys supply graph label strings (supports `en-US`/`es-ES`). -- `$Options.DiagramObjDebug` enables verbose object-level debug output. -- Does **not** use `$TempPssSession` directly — it creates its own internal `$DiagramTempPssSession` or accepts one via `-PSSessionObject`. +**Remote Session Debugging**: +```powershell +$session = Get-PSSession -Name 'DC01.contoso.com' +Invoke-Command -Session $session -ScriptBlock { Get-ADForest } +``` -### Adding a New Diagram Type +### Performance Considerations -1. Add a new `Get-AbrDiag.ps1` in `Src/Private/` following the existing `Get-AbrDiagForest.ps1` / `Get-AbrDiagSite.ps1` pattern. -2. Add the type string to the `ValidateSet` in both `Get-AbrDiagrammer` and `New-AbrADDiagram`. -3. Add a `$MainGraphLabel` switch case in `New-AbrADDiagram`'s `begin` block. -4. Add the corresponding boolean key to `Options.DiagramType` in `AsBuiltReport.Microsoft.AD.json`. -5. Add localized label strings to both `Language/` psd1 files under the `NewADDiagram` key. +- Remote data collection happens sequentially (per domain) +- Large forests (100+ domains) may take 30+ minutes +- CPU-intensive: Schema analysis, trust enumeration +- Network: WinRM traffic, potentially large XML responses +- Disk: HTML/DOCX output can be 50+ MB with diagrams ---- +### Testing Strategy -## PKI / Certificate Authority Section +**Unit Tests** (`Tests/AsBuiltReport.Microsoft.AD.Tests.ps1`): +- Module manifest validation +- Function availability +- Module dependency versions +- Export validation -### Prerequisites +**Integration Tests** (Not present): +- Would require live AD environment +- Manual testing against test domains recommended -The PKI section only runs when **all** of the following are true: -- `$InfoLevel.CA -ge 1` -- The machine running the report is joined to a domain that is **part of the target forest** (`Get-ComputerADDomain` result must be in `$ADSystem.Domains`) -- `Get-CertificationAuthority -Enterprise` returns at least one CA (uses the **PSPKI** module) +**Code Coverage**: +- Current: Unknown (50% threshold enforced) +- Recommendation: Add more tests for edge cases -If the reporting machine's domain is not in the forest, a warning is logged and the section is skipped entirely. +--- -### CA Data Source +## 10. PROJECT-SPECIFIC GUIDANCE FOR AI ASSISTANTS + +### When Making Code Changes + +1. **Respect InfoLevel checks**: Wrap new sections with `if ($InfoLevel.Component -ge N)` +2. **Add HealthCheck conditionals**: Wrap checks with `if ($HealthCheck.Component.Feature)` +3. **Use localization strings**: Reference `$reportTranslate.FunctionName.PropertyName` +4. **Follow try-catch pattern**: Every data gatherer in try-catch with `-IsWarning` +5. **Apply Set-Style**: Mark warning/critical objects for report highlighting +6. **Use OrderedDictionary**: `[ordered] @{}` for property ordering +7. **Pass sessions as parameters**: Don't assume `$TempPssSession` global exists +8. **Document with `.SYNOPSIS`**: All functions need help documentation +9. **Return objects not strings**: Build arrays of `[pscustomobject]` for Table output +10. **Test with `-CodeCoverage`**: Ensure new code is covered by tests + +### Common Tasks + +**Add a new health check:** +1. Add boolean to `AsBuiltReport.Microsoft.AD.json` under `HealthCheck.Component.NewCheck` +2. In Get-Abr* function: `if ($HealthCheck.Component.NewCheck) { ... Set-Style ... }` +3. Add test case to `Tests/AsBuiltReport.Microsoft.AD.Tests.ps1` + +**Add new report section:** +1. Create `Get-AbrNewSection` function in `Src/Private/` +2. Create `Get-AbrNewSectionData` data gatherer +3. Call from main entry point: `if ($InfoLevel.NewComponent -ge 1) { Get-AbrNewSection }` +4. Add InfoLevel config: `"NewComponent": 1` to JSON +5. Add translations to `Language/en-US/MicrosoftAD.psd1` and `es-ES/` + +**Fix a timeout issue:** +1. Increase `$Options.JobsTimeOut` in JSON (default 900) +2. Or reduce data scope (disable HealthChecks or lower InfoLevel) +3. Or optimize remote query (use `-Filter` with better conditions) + +### Module Dependencies to Understand + +- **AsBuiltReport.Core**: Framework for report generation, parameter validation +- **PScribo**: Document markup (Section, Table, Paragraph, Set-Style) +- **ActiveDirectory**: AD cmdlets (Get-ADForest, Get-ADDomain, etc.) - Microsoft module +- **PSPKI**: PKI cmdlets (Get-CertificationAuthority) - community module +- **Diagrammer.Core**: Diagram generation for topology visualization +- **GroupPolicy**: GPO retrieval (Get-GPO, Get-GPOReport) +- **DnsServer**: DNS zone enumeration -The PKI section does **not** use PSSession/CIMSession for CA data. It uses **PSPKI** module cmdlets directly on the machine running the report: -- `Get-CertificationAuthority -Enterprise` — discovers all enterprise CAs -- `Get-CertificationAuthority -Enterprise -ComputerName $CA` — per-CA object -- `Get-CACryptographyConfig -CertificationAuthority $CA` -- `Get-CATemplate`, `Get-CARoleServiceStatus`, `Get-CRLDistributionPoint`, `Get-AuthorityInformationAccess` +--- -The `$script:CAs` variable is set in `Get-AbrPKISection` and used by all CA sub-functions. +## 11. QUICK REFERENCE + +### Module Entry Point +- **Location**: `AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1` +- **Exports**: Single public function (dot-notation name) +- **Parameters**: `$Target` (FQDN array), `$Credential` (PSCredential) +- **Returns**: Report file (HTML/Word/Text) via PScribo + +### Main Directories +| Directory | Purpose | Files | +|-----------|---------|-------| +| `Src/Public` | Exported functions | 1 file (entry point) | +| `Src/Private` | Internal functions | 88 functions | +| `Language` | Localization | .psd1 per culture | +| `Tests` | Unit/integration tests | Pester framework | +| `.github/workflows` | CI/CD pipelines | 5 YAML files | + +### Key Files +| File | Purpose | Size | +|------|---------|------| +| `AsBuiltReport.Microsoft.AD.psm1` | Module loader | 14 lines | +| `AsBuiltReport.Microsoft.AD.psd1` | Manifest | ~100 lines | +| `AsBuiltReport.Microsoft.AD.json` | Config template | 89 lines | +| `AsBuiltReport.Microsoft.AD.Style.ps1` | Report styling | 20 KB | + +### Test Commands +```powershell +# Basic test run +.\Tests\Invoke-Tests.ps1 -### Section Structure (`Get-AbrPKISection`) +# With coverage +.\Tests\Invoke-Tests.ps1 -CodeCoverage -OutputFormat NUnitXml -``` -PKI (Heading1) ← only when $InfoLevel.CA -ge 1 - Get-AbrADCASummary ← always (CA name, server, type, service status) - Get-AbrADCARoot ← InfoLevel.CA -ge 2 - Get-AbrADCASubordinate ← InfoLevel.CA -ge 2 - foreach accessible CA: - Details (Heading2) - Get-AbrADCASecurity ← always - Get-AbrADCACryptographyConfig ← always - Get-AbrADCAAIA ← InfoLevel.CA -ge 2 - Get-AbrADCACRLSetting ← InfoLevel.CA -ge 2 - Get-AbrADCATemplate ← InfoLevel.CA -ge 2 - Get-AbrADCAKeyRecoveryAgent ← always +# Output formats +.\Tests\Invoke-Tests.ps1 -OutputFormat JUnitXml +.\Tests\Invoke-Tests.ps1 -OutputFormat Console ``` -### HealthCheck Styles for CA +### Required Modules (Minimum Versions) +```powershell +AsBuiltReport.Core 1.6.2+ +AsBuiltReport.Chart 0.2.0+ +Diagrammer.Core 0.2.38+ +PSPKI 4.3.0+ +Pester 5.0.0+ +PScribo 0.11.1+ +PSScriptAnalyzer 1.0.0+ +``` -| Check | Config Key | Style Applied | -|---|---|---| -| CA service not Running | `HealthCheck.CA.Status` | `Critical` on Status column | -| CA statistics thresholds | `HealthCheck.CA.Statistics` | `Warning` | -| Best practice settings | `HealthCheck.CA.BestPractice` | `Warning` | +### Function Categories +| Category | Count | Examples | +|----------|-------|----------| +| Get-Abr* (data gathering) | 52 | Get-AbrADForest, Get-AbrADDomain | +| Get-Abr*Section (orchestration) | 4 | Get-AbrForestSection, Get-AbrDNSSection | +| Get-AbrDiag* (diagrams) | 8 | Get-AbrDiagrammer, Get-AbrDiagForest | +| Utility (conversion, helpers) | 24+ | ConvertTo-HashToYN, Invoke-CommandWithTimeout | -Style values are `Warning` (yellow), `Critical` (red), and `Info` (blue) — passed to `Set-Style -Style -Property `. +--- -### Adding New CA Content +**Document Version**: 1.0 +**Last Updated**: 2024 +**Project Version**: 0.9.11 +**Target PowerShell**: 7+ +**Platform**: Windows Only -CA functions receive `$CA` (a PSPKI `CertificationAuthority` object) as their only parameter. The `$ForestInfo` script variable provides the forest name for table naming. Follow the same `$inObj` → `ConvertTo-HashToYN` → `Table` pattern as all other content functions. diff --git a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json index 88f5b32..4a34316 100644 --- a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json +++ b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json @@ -79,11 +79,6 @@ "DP": true, "Zones": true, "BestPractice": true - }, - "CA": { - "Status": true, - "Statistics": true, - "BestPractice": true } } } \ No newline at end of file diff --git a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1 index bdeab47..58e336b 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 = '0.9.12' + ModuleVersion = '1.0.0' # Supported PSEditions CompatiblePSEditions = @('Core') @@ -58,11 +58,11 @@ }, @{ ModuleName = 'AsBuiltReport.Chart'; - ModuleVersion = '0.3.0' + ModuleVersion = '0.3.1' }, @{ ModuleName = 'AsBuiltReport.Diagram'; - ModuleVersion = '1.0.5' + ModuleVersion = '1.0.6' } ) @@ -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') + FunctionsToExport = @('Invoke-AsBuiltReport.Microsoft.AD', 'Start-AsBuiltReportMSAD') # 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/AsBuiltReport.Microsoft.AD.psm1 b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psm1 index c27469d..2b78072 100644 --- a/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psm1 +++ b/AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psm1 @@ -3,8 +3,9 @@ $Public = @(Get-ChildItem -Path $PSScriptRoot\Src\Public\*.ps1 -ErrorAction Sile $Diagram = @(Get-ChildItem -Path $PSScriptRoot\Src\Private\Diagram\*.ps1 -ErrorAction SilentlyContinue) $Report = @(Get-ChildItem -Path $PSScriptRoot\Src\Private\Report\*.ps1 -ErrorAction SilentlyContinue) $Tools = @(Get-ChildItem -Path $PSScriptRoot\Src\Private\Tools\*.ps1 -ErrorAction SilentlyContinue) +$Gui = @(Get-ChildItem -Path $PSScriptRoot\Src\Private\Gui\*.ps1 -ErrorAction SilentlyContinue) -foreach ($Module in @($Public + $Report + $Diagram + $Tools)) { +foreach ($Module in @($Public + $Report + $Diagram + $Tools + $Gui)) { try { . $Module.FullName } catch { @@ -15,4 +16,5 @@ foreach ($Module in @($Public + $Report + $Diagram + $Tools)) { Export-ModuleMember -Function $Public.BaseName Export-ModuleMember -Function $Report.BaseName Export-ModuleMember -Function $Diagram.BaseName -Export-ModuleMember -Function $Tools.BaseName \ No newline at end of file +Export-ModuleMember -Function $Tools.BaseName +Export-ModuleMember -Function $Gui.BaseName \ No newline at end of file diff --git a/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 b/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 index a0c3154..01b82cb 100644 --- a/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 +++ b/AsBuiltReport.Microsoft.AD/Language/en-US/MicrosoftAD.psd1 @@ -21,13 +21,13 @@ CIMSessionError = Failed to establish a CimSession ({0}) with the Domain Controller '{1}'. ConnectingForest = Connecting to retrieve forest information from the Domain Controller '{0}'. ForestError = Failed to retrieve forest information from the Domain Controller '{0}'. Ensure the provided system is a Domain Controller and the provided credentials have sufficient permissions to query Active Directory forest information. Error details: {1} - IncludeDomainsEnabled = - Include.Domains option enabled: Including only the following domains in the report: {0} - ExcludeDomainsEnabled = - Including all child domains in the report except the following excluded domains: {0} + IncludeDomainsEnabled = - Include.Domains option enabled: Including only the following domains in the report: {0} + ExcludeDomainsEnabled = - Including all child domains in the report except the following excluded domains: {0} GettingForestInfo = - Retrieving forest information {0}. - DiscoveringChildDomains = - Discovering child domains of the forest {0}: {1} + DiscoveringChildDomains = - Discovering child domains of the forest {0}: {1}. DCAvailable = - Initial configuration: A DC is available in the domain {0}. Adding domain to the report. - DCUnavailable = - Unable to obtain an available DC in the domain {0}. Removing domain from the report. - FinishingDomainList = - Finalizing the list of domains in the forest {0}: {1} + DCUnavailable = - Unable to obtain an available DC in the domain {0}. Removing domain from the report. + FinishingDomainList = - Finalizing the list of domains in the forest {0}: {1}. WorkingOnForest = - Working on the Forest section. WorkingOnDomain = - Working on the Domain section. WorkingOnDNS = - Working on the DNS section. @@ -84,6 +84,12 @@ ScopeEnabled = Enabled (Summary) ScopeAdvanced = Enabled (Advanced Summary) ScopeDetailed = Enabled (Detailed) + ErrorReportOverview = Report Brief - Report Overview + ErrorForestSummary = Report Brief - Forest Summary + ErrorDomainSummaryItem = Report Brief - Domain Summary Item + ErrorDomainSummary = Report Brief - Domain Summary + ErrorReportScope = Report Brief - Report Scope + ErrorReportBriefSection = Report Brief Section '@ # Get-AbrForestSection @@ -150,6 +156,14 @@ RecycleBinBP = Accidental deletion of Active Directory objects is a common issue for AD DS users. Enabling the Recycle Bin feature allows for the recovery of these accidentally deleted objects, helping to maintain the integrity and continuity of the Active Directory environment. RecycleBinRef = https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-ad-recycle-bin-understanding-implementing-best-practices-and/ba-p/396944 CADiagram = Certificate Authority Diagram + TableName = Forest Summary + ErrorForestDiagramGraph = Forest Diagram Graph: + ErrorForestDiagramSection = Forest Diagram Section: + NoCARootInfo = No Certificate Authority Root information found in {0}, Disabling this section. + NoCAIssuerInfo = No Certificate Authority Issuer information found, Disabling this section. + ErrorCADiagramGraph = Certificate Authority Diagram Graph: + ErrorCADiagramSection = Certificate Authority Diagram Section: + NoOptionalFeatureInfo = No Optional Feature information found in {0}, Disabling this section. '@ NewADDiagram = ConvertFrom-StringData @' @@ -272,9 +286,11 @@ DnsName = DNS Name ServerRoles = Server Roles Version = Version + ErrorExchangeItem = Exchange Item + NoExchangeInfo = No Exchange Infrastructure information found in {0}, Disabling this section. + ErrorExchangeTable = Exchange Table + ErrorExchangeServerItem = ExchangeServer: [{0}]. '@ - - # Get-AbrADSCCM GetAbrADSCCM = ConvertFrom-StringData @' Collecting = Collecting AD SCCM information of {0}. Heading = SCCM Infrastructure @@ -284,6 +300,9 @@ ManagementPoint = Management Point SiteCode = Site Code Version = Version + ErrorSCCMItem = SCCM Item + NoSCCMInfo = No SCCM Infrastructure information found in {0}, Disabling this section. + ErrorSCCMTable = SCCM Table '@ # Get-AbrDHCPinAD @@ -297,6 +316,9 @@ Yes = Yes No = No Unknown = Unknown + ErrorDHCPItem = DHCP Item + NoDHCPInfo = No DHCP Infrastructure information found in {0}, Disabling this section. + ErrorDHCPTable = DHCP Table '@ # Get-AbrADSite @@ -383,6 +405,41 @@ StatusUnknown = Unknown StatusOffline = Offline SysvolBP = SYSVOL is a special directory that resides on each domain controller (DC) within a domain. The directory comprises folders that store Group Policy objects (GPOs) and logon scripts that clients need to access and synchronize between DCs. For these logon scripts and GPOs to function properly, SYSVOL should be replicated accurately and rapidly throughout the domain. Ensure that proper SYSVOL replication is in place to ensure identical GPO/SYSVOL content for the domain controller across all Active Directory domains. + ErrorReplicationDiagramGraph = Replication Diagram Graph: + ErrorReplicationDiagramSection = Replication Diagram Section: + ErrorDomainSite = Domain Site + ErrorSiteReplicationConnectionItem = Site Replication Connection Item + NoConnectionObjectsInfo = No Connection Objects information found in {0}, Disabling this section. + ErrorConnectionObjects = Connection Objects + ErrorSiteSubnets = Site Subnets + UnableToRead = Unable to read {0} on {1} + ErrorMissingSubnetPSSession = Missing Subnet in AD Section: New-PSSession: Unable to connect to {0}: {1} + ErrorMissingSubnetItemTable = Missing Subnet in AD Item table: + NoMissingSubnetsInfo = No Missing Subnets in AD information found in {0}, Disabling this section. + ErrorMissingSubnetItemSection = Missing Subnet in AD Item Section: + NoSiteSubnetsInfo = No Site Subnets information found in {0}, Disabling this section. + ErrorSiteTopologyDiagramGraph = Site Topology Diagram Graph: + ErrorSiteTopologyDiagramSection = Site Topology Diagram Section: + ErrorInterSiteTransports = Inter-Site Transports section + ErrorIPSiteLinksTable = IP Site Links table + NoIPSiteLinksInfo = No IP Site Links information found in {0}, Disabling this section. + ErrorIPSiteLinksSection = IP Site Links Section + ErrorIPSiteLinksBridgesTable = IP Site Links Bridges table + NoIPSiteLinksBridgesInfo = No IP Site Links Bridges information found in {0}, Disabling this section. + ErrorIP = IP + ErrorSMTPSiteLinksTable = SMTP Site Links table + ErrorSMTPSiteLinksSection = SMTP Site Links Section + ErrorSMTPSiteLinksBridgesTable = SMTP Site Links Bridges table + NoSMTPSiteLinksBridgesInfo = No SMTP Site Links Bridges information found in {0}, Disabling this section. + NoSMTPSiteLinksInfo = No SMTP Site Links information found in {0}, Disabling this section. + ErrorSMTP = SMTP + ErrorSysvolReplicationItemSection = Sysvol Replication Item Section: + UnableToCollect = Unable to collect information from {0}. + ErrorDNSIPConfigItem = DNS IP Configuration Item + NoSysvolReplicationInfo = No Sysvol Replication information found in {0}, Disabling this section. + ErrorSysvolReplicationTableSection = Sysvol Replication Table Section: + NoSitesInfo = No Sites information found in {0}, Disabling this section. + ErrorDomainSiteGlobal = Domain Site Global '@ # Get-AbrDNSSection @@ -396,6 +453,7 @@ DefinitionParagraph = The Domain Name System (DNS) is a hierarchical and decentralized naming system for computers, services, or other resources connected to the Internet or a private network. It associates various information with domain names assigned to each of the participating entities. Most prominently, it translates more readily memorized domain names to the numerical IP addresses needed for locating and identifying computer services and devices with the underlying network protocols. Paragraph = The following section provides a detailed overview of the DNS infrastructure configuration and settings within the Active Directory environment. NoCIMSession = DNS infrastructure configuration data requires a CIM session and could not be collected. Verify that WinRM and CIM connectivity to the domain controllers is available. + ErrorDNSInfo = Domain Name System Information '@ # Get-AbrADDNSInfrastructure @@ -457,6 +515,21 @@ ForwarderMinBP = For redundancy reasons, more than one forwarding server should be configured. RootHintsMissingCA = A default installation of the DNS server role should have root hints unless the server has a root zone - .(root). If the server has a root zone then delete it. If the server doesn't have a root zone and there are no root servers listed on the Root Hints tab of the DNS server properties then the server may be missing the cache.dns file in the %systemroot%\\system32\\dns directory, which is where the list of root servers is loaded from. RootHintsDuplicateCA = Duplicate IP Address found in the table of the DNS root hints servers. The DNS console does not show the duplicate Root Hint servers; you can only see them using the DNS PowerShell cmdlets. While there is a dnscmd utility to replace the Root Hints file, Using PowerShell is the best way to remediate this issue. + ErrorInfrastructureSummarySection = DNS Infrastructure Summary Section: + ErrorDirectoryPartitionsItemSection = Directory Partitions Item Section: + ErrorDirectoryPartitionsTableSection = Directory Partitions Table Section: + ErrorDirectoryPartitionsSection = Directory Partitions Section: + ErrorRRLItem = Response Rate Limiting (RRL) Item + ErrorRRLTable = Response Rate Limiting (RRL) Table + ErrorScavengingItem = Scavenging Item + ErrorScavengingTable = Scavenging Table + ErrorForwarderItem = Forwarder Item + ErrorForwarderTable = Forwarder Table + ErrorRootHintsTable = Root Hints Table + ErrorRootHintsSection = Root Hints Section + ErrorZoneScopeRecursionItem = Zone Scope Recursion Item + ErrorZoneScopeRecursionTable = Zone Scope Recursion Table + ErrorDNSInfrastructureSection = DNS Infrastructure Section '@ # Get-AbrADDNSZone @@ -499,6 +572,25 @@ BestPractice = Best Practices: ZoneTransferBP = Configure all DNS zones to allow zone transfers only from trusted IP addresses. This ensures that only authorized DNS servers can receive zone data, reducing the risk of unauthorized access or data leakage. It is a best practice to specify the IP addresses of the secondary DNS servers that are allowed to receive zone transfers. ZoneAgingBP = Microsoft recommends enabling aging/scavenging on all DNS servers. However, with AD-integrated zones, ensure DNS scavenging is enabled on only one DC at the main site. The results will be replicated to other DCs. + ErrorDNSZoneItem = Domain Name System Zone Item + NoDelegationInfo = DNS Zones {0} Section: No Zone Delegation information found, Disabling this section. + ErrorZoneDelegationItem = Zone Delegation Item + NoDelegationInfoDC = DNS Zones Section: No Zone Delegation information found in {0}, Disabling this section. + ErrorZoneDelegationTable = Zone Delegation Table + ErrorZoneTransferPSSession = DNS Zones Transfers Section: New-PSSession: Unable to connect to {0}: {1} + ErrorZoneTransfersItem = Zone Transfers Item + NoZoneTransferInfo = DNS Zones Section: No Zone Transfer information found in {0}, Disabling this section. + ErrorZoneTransfersTable = Zone Transfers Table + ErrorReverseLookupZoneItem = Reverse Lookup Zone Configuration Item + NoReverseLookupZoneInfo = DNS Zones Section: No Reverse lookup zone information found in {0}, Disabling this section. + ErrorReverseLookupZoneTable = Reverse Lookup Zone Configuration Table + ErrorConditionalForwarderItem = Conditional Forwarder Item + NoConditionalForwarderInfo = DNS Zones Section: No Conditional forwarder zone information found in {0}, Disabling this section. + ErrorConditionalForwarderTable = Conditional Forwarder Table + ErrorZoneScopeAgingItem = Zone Scope Aging Item + NoZoneAgingInfo = DNS Zones Section: No Zone Aging property information found in {0}, Disabling this section. + ErrorZoneScopeAgingTable = Zone Scope Aging Table + ErrorGlobalDNSZoneInfo = Global DNS Zone Information '@ # Get-AbrPKISection @@ -516,6 +608,7 @@ # Get-AbrDomainSection GetAbrDomainSection = ConvertFrom-StringData @' Collecting = Collecting Domain information from {0}. + CollectingDomain = Collecting Domain information from {0}. Paragraph = This section provides an overview of the Active Directory domain configuration, including key settings and operational details. SectionTitle = AD Domain Configuration DefinitionText = An Active Directory domain is a collection of objects within a Microsoft Active Directory network. An object can be a single user, a group, or a hardware component such as a computer or printer. Each domain holds a database containing object identity information. Active Directory domains can be identified using a DNS name, which can be the same as an organization's public domain name, a sub-domain, or an alternate version (which may end in .local). @@ -539,6 +632,7 @@ ReplicationParagraph = The following section provides an overview of Active Directory replication connections and status between domain controllers in this domain. GPOSection = Group Policy GPOParagraph = The following section provides an overview of the Group Policy Objects (GPOs) configured and applied within this domain. + ErrorADDomain = Active Directory Domain '@ # Get-AbrADDomain @@ -570,6 +664,8 @@ Reference = Reference: RIDBestPractice = The RID Issued percentage exceeds 80%. It is recommended to evaluate the utilization of RIDs to prevent potential exhaustion and ensure the stability of the domain. The Relative Identifier (RID) is a crucial component in the SID (Security Identifier) for objects within the domain. Exhaustion of the RID pool can lead to the inability to create new security principals, such as user or computer accounts. Regular monitoring and proactive management of the RID pool are essential to maintain domain health and avoid disruptions. RIDReference = https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/managing-rid-pool-depletion/ba-p/399736 + TableName = Domain Summary + ErrorSection = AD Domain Summary Section: '@ # Get-AbrADFSMO @@ -586,6 +682,9 @@ Reference = Reference: InfraMasterBP = The infrastructure master role in the domain {0} should be held by a domain controller that is not a global catalog server. The infrastructure master is responsible for updating references from objects in its domain to objects in other domains. If the infrastructure master runs on a global catalog server, it will not function properly because the global catalog holds a partial replica of every object in the forest, and it will not update the references. This issue does not affect forests that have a single domain. InfraMasterRef = http://go.microsoft.com/fwlink/?LinkId=168841 + TableName = FSMO Roles + ErrorFSMOItem = Flexible Single Master Operations + ErrorPSSession = FSMO Roles Section: New-PSSession: Unable to connect to {0}: {1} '@ # Get-AbrADTrust @@ -627,6 +726,12 @@ BestPractice = Best Practice: AESBP = Ensure that AES Kerberos encryption is enabled on all Active Directory trusts. RC4 encryption is considered weak and vulnerable to various attacks. Enabling AES encryption on trusts enhances Kerberos security and aligns with modern security standards. Reference: https://techcommunity.microsoft.com/t5/itops-talk-blog/tough-questions-answered-can-i-disable-rc4-etype-for-kerberos-on/ba-p/382718 TrustDiagramSection = Domain and Trusts Diagram + ErrorTrustItem = Trust Item + ErrorTrustDiagramGraph = Domain and Trusts Diagram Graph: + ErrorTrustDiagramSection = Domain and Trusts Diagram Section: + NoTrustInfo = No Domain Trust information found in {0}, Disabling this section. + ErrorTrustTable = Trust Table + ErrorTrustSection = Trust Section '@ # Get-AbrADAuthenticationPolicy @@ -660,6 +765,20 @@ ServiceTGTLifetime = Service TGT Lifetime (mins) ComputerTGTLifetime = Computer TGT Lifetime (mins) PolicyBP = Authentication Policies should be set to Enforce mode to actively restrict Kerberos TGT lifetimes and account sign-in. Policies in audit mode only log events without enforcing restrictions. + ErrorSiloItem = Authentication Policy Silo Item + SiloTableName = Authentication Policy Silo + SilosTableName = Authentication Policy Silos + ErrorSiloMemberItem = Authentication Policy Silo Member Item + SiloMembersTableName = Authentication Policy Silo Members + ErrorSiloMembersTable = Authentication Policy Silo Members Table + ErrorSilosSectionA = Authentication Policy Silos Section + NoSiloInfo = No Authentication Policy Silo information found in {0}, Disabling this section. + ErrorPolicyItem = Authentication Policy Item + PolicyTableName = Authentication Policy + PoliciesTableName = Authentication Policies + ErrorPoliciesSection = Authentication Policies Section + NoPolicyInfo = No Authentication Policy information found in {0}, Disabling this section. + NoAuthPolicyOrSiloInfo = No Authentication Policy or Silo information found in {0}, Disabling this section. '@ # Get-AbrADDomainObject @@ -829,6 +948,45 @@ GMSAInactiveBP = *Regularly check for and remove inactive group managed service accounts from Active Directory. Inactive accounts can pose a security risk as they may be exploited by malicious actors. Ensuring that only active and necessary accounts exist helps maintain a secure environment and reduces the risk of unauthorized access or privilege escalation. GMSANoHostComputersBP = **No 'Host Computers' has been defined; please validate that the gMSA is currently in use. If not, it is recommended to remove these unused resources from Active Directory. GMSANoRetrieveManagedPasswordBP = ***No 'Retrieve Managed Password' has been defined; please validate that the gMSA is currently in use. If not, it is recommended to remove these unused resources from Active Directory. + PrivilegedGroupMembersTableName = Privileged Group Members: + GMSATableName = gMSA + MembersLabel = Members + TypeLabelUser = USER + TypeLabelComputer = COMPUTER + TypeLabelGroup = GROUP + TypeLabelFSP = FOREIGN SECURITY PRINCIPAL + ErrorDomainObjectStats = Domain Object Stats + ErrorUserObjectCountChart = User Object Count Chart + ErrorStatusOfUserAccounts = Status of User Accounts + ErrorStatusOfUsersAccountsChart = Status of Users Accounts Chart + ErrorUsersObjectsTable = Users Objects Table + ErrorUsersObjectsSection = Users Objects Section + ErrorGroupCategoryObjectChart = Group Category Object Chart + ErrorGroupScopesObjectChart = Group Scopes Object Chart + ErrorGroupsObjectsTable = Groups Objects Table + ErrorGroupsObjectsSection = Groups Objects Section + ErrorPrivilegedGroup = Privileged Group in Active Directory + ErrorPrivilegedGroupNonDefaultTable = Privileged Group (Non-Default) Table + ErrorPrivilegedGroupNonDefaultSection = Privileged Group (Non-Default) Section + ErrorEmptyGroupsObjectsTable = Empty Groups Objects Table + ErrorEmptyGroupsObjectsSection = Empty Groups Objects Section + ErrorCircularGroupMembershipTable = Circular Group Membership Table + ErrorCircularGroupMembershipSection = Circular Group Membership Section + ErrorPreWin2000 = Pre-Windows 2000 Compatible Access + ErrorComputersObjectCountChart = Computers Object Count Chart + ErrorStatusOfComputerAccounts = Status of Computer Accounts + ErrorStatusOfComputersAccountsChart = Status of Computers Accounts Chart + ErrorOperatingSystemsInAD = Operating Systems in Active Directory + ErrorComputersPasswordNotRequired = Computers with Password-Not-Required + ErrorComputersObjectsTable = Computers Objects Table + ErrorComputersObjectsSection = Computers Objects Section + ErrorDefaultDomainPasswordPolicy = Default Domain Password Policy + ErrorFGPP = Fine Grained Password Policies + ErrorWindowsLAPS = Windows LAPS + ErrorGMSAItem = Group Managed Service Accounts Item + ErrorGMSASection = Group Managed Service Accounts Section + ErrorFSPItem = Foreign Security Principals Item + ErrorFSPSection = Foreign Security Principals Section '@ # Get-AbrADHardening @@ -872,6 +1030,8 @@ LDAPSigningBP = LDAP signing enforcement is not configured on this domain controller. LDAP signing is a security feature that protects the integrity and confidentiality of LDAP communications by requiring data signing. Configure LDAP signing to require signing on all domain controllers. LDAPCBBindingBP = LDAP channel binding enforcement is not configured on this domain controller. LDAP channel binding is a security feature that protects against man-in-the-middle attacks by binding the LDAP session to the TLS channel, ensuring the authenticity and integrity of LDAP communications. Configure LDAP channel binding on all domain controllers. NTLMv1BP = NTLMv1 authentication is enabled on this domain controller. NTLMv1 is an outdated authentication protocol that is vulnerable to credential capture and relay attacks. Disable NTLMv1 on all systems; it has been superseded by NTLMv2, which offers significantly improved security protections. + ErrorADHardeningItem = ADHardening Item + ErrorADHardeningSection = ADHardening Section '@ # Get-AbrADDomainLastBackup @@ -890,6 +1050,8 @@ BackupBP1 = Ensure there is a recent (<180 days) Active Directory backup. BackupBP2 = Regular backups are crucial for disaster recovery and maintaining the integrity of your Active Directory environment. BackupBP3 = Consider setting up automated backup schedules and regularly verifying the backup status to prevent data loss. + ErrorDomainLastBackupItem = Domain Last Backup Item + ErrorDomainLastBackupTable = Domain Last Backup Table '@ # Get-AbrADDuplicateSPN @@ -905,6 +1067,8 @@ HealthCheck = Health Check: CorrectiveActions = Corrective Actions: SPNBP = Ensure there aren't any duplicate SPNs (other than krbtgt). Duplicate SPNs can cause authentication issues and should be resolved promptly. Use the `setspn -X` command to identify duplicate SPNs. Remove or reassign duplicate SPNs as necessary to maintain a healthy AD environment. + ErrorSPNItem = SPN Item + ErrorSPNTable = SPN Table '@ # Get-AbrADDuplicateObject @@ -921,6 +1085,8 @@ HealthCheck = Health Check: CorrectiveActions = Corrective Actions: DuplicateObjectBP = Ensure there are no duplicate objects in Active Directory. Duplicate objects can cause various issues such as authentication problems, replication conflicts, and administrative overhead. It is recommended to regularly audit and clean up any duplicate objects to maintain a healthy and efficient Active Directory environment. + ErrorDuplicateObjectItem = Duplicate Object Item + ErrorDuplicateObjectTable = Duplicate Object Table '@ # Get-AbrADDCRoleFeature @@ -933,6 +1099,9 @@ HealthCheck = Health Check: BestPractices = Best Practices: RoleBP = Domain Controllers should have limited software and agents installed including roles and services. Non-essential code running on Domain Controllers is a risk to the enterprise Active Directory environment. A Domain Controller should only run required software, services and roles critical to essential operation. + ErrorPSSession = Roles Section: New-PSSession: Unable to connect to {0}: {1} + ErrorRoleFeatureSection = Roles {0} Section: + ErrorRolesSection = Roles Section: '@ # Get-AbrADDCDiag @@ -944,6 +1113,9 @@ Description = Description TableName = DCDiag Test Status NoData = No DCDiag information found in {0}, Disabling this section. + ErrorDCDiagTestSection = Active Directory DCDiag {0} Section: + ErrorDCDiagSection = Active Directory DCDiag Section: + ErrorInvokeDcDiag = Invoke-DcDiag - Failed to get DCDiag for {0} with error: '@ # Get-AbrADInfrastructureService @@ -958,6 +1130,9 @@ CorrectiveActions = Corrective Actions: SpoolerBP = The Print Spooler service has known vulnerabilities that can be exploited by attackers to gain unauthorized access or execute malicious code. Disabling this service on Domain Controllers and other critical servers that do not require print services can reduce the attack surface and improve the overall security posture of your Active Directory environment. DHCPServerBP = Per security best practices, DHCP Server services should run on a dedicated server separate from domain controllers to minimize security risks, reduce resource contention, and ensure optimal performance of both DHCP and Active Directory services. + ErrorPSSession = Domain Controller Infrastructure Services Section: New-PSSession: Unable to connect to {0}: {1} + ErrorDCInfraServicesItem = Domain Controller Infrastructure Services Item + ErrorDCInfraServicesTable = Domain Controller Infrastructure Services Table '@ # Get-AbrADDFSHealth @@ -992,6 +1167,14 @@ ContentCorrectiveActions = Corrective Actions: ContentSysvolBP = Review the files and extensions listed above and ensure they are necessary for the operation of your domain. Remove any files that are not required or that appear suspicious. Regularly monitor the Sysvol folder to maintain a healthy and secure Active Directory environment. ContentNetlogonBP = Review the files and extensions listed above and ensure they are necessary for the operation of your domain. Remove any files that are not required or that appear suspicious. Regularly monitor the Netlogon folder to maintain a healthy and secure Active Directory environment. + ErrorSysvolReplicationStatusItemSection = Sysvol Replication Status Item Section: + ErrorSysvolReplicationStatusTableSection = Sysvol Replication Status Table Section: + ErrorSysvolContentPSSession = Sysvol Content Status Section: New-PSSession: Unable to connect to {0}: {1} + ErrorSysvolHealthSection = Sysvol Health {0} Section: + ErrorSysvolHealthTableSection = Sysvol Health Table Section: + ErrorNetlogonContentPSSession = Netlogon Content Status Section: New-PSSession: Unable to connect to {0}: {1} + ErrorNetlogonHealthSection = Netlogon Health {0} Section: + ErrorNetlogonContentStatusSection = Netlogon Content Status Section: '@ # Get-AbrADKerberosAudit @@ -1023,6 +1206,10 @@ AdminHealthCheck = Health Check: AdminBestPractice = Best Practice: AdminBP = Microsoft recommends using a unique, complex password for the built-in Administrator account and rotating it regularly (at least every 90 days). Consider renaming the account and disabling it when not actively in use to reduce the risk of brute-force or credential-stuffing attacks targeting this well-known account. + ErrorUnconstrainedKerberosItem = Unconstrained Kerberos delegation + ErrorKRBTGTAccountItem = KRBTGT account Item + ErrorAdminAccountItem = ADMIN account Item + ErrorUnconstrainedKerberosSection = Unconstrained Kerberos delegation Section '@ # Get-AbrADSiteReplication @@ -1055,6 +1242,15 @@ ReplicationStatusBestPractices = Best Practices: ReplicationStatusBP = Replication failures can lead to object inconsistencies, stale credentials, Group Policy application failures, and authentication issues across the environment. Investigate and resolve any replication errors promptly using tools such as repadmin /showrepl or the Active Directory Replication Status Tool to prevent further divergence between domain controllers. AutoGeneratedValue = + ErrorSiteReplicationConnectionItem = Site Replication Connection Item + ErrorSiteReplicationConnectionSection = Site Replication Connection Section + SiteLabel = Site: + FromLabel = From: + ToLabel = To: + ErrorReplicationConnection = Replication Connection + ErrorPSSession = Replication Status Section: New-PSSession: Unable to connect to {0}: {1} + ErrorReplicationStatus = Replication Status + ErrorSiteReplicationStatus = Site Replication Status '@ # Get-AbrADOU @@ -1079,6 +1275,10 @@ GPOBlockedHealthCheck = Health Check: GPOBlockedCorrectiveActions = Corrective Actions: GPOBlockedBP = Review the use of enforced policies and blocked policy inheritance in Active Directory. Enforced policies ensure that specific Group Policy Objects (GPOs) are applied and cannot be overridden by other GPOs. Blocked policy inheritance prevents GPOs from parent containers from being applied to the Organizational Unit (OU). While these settings can be useful for maintaining strict policy application, they can also lead to unexpected results and complicate troubleshooting. Ensure that the use of these settings aligns with your organization's policy management strategy and does not inadvertently cause issues. + ErrorOUItem = Organizational Unit Item + ErrorBlockedInheritanceGPOItem = Blocked Inheritance GPO Item + ErrorBlockedInheritanceGPOSection = Blocked Inheritance GPO Section + ErrorOUSection = Organizational Unit Section '@ # Get-AbrADSecurityAssessment @@ -1135,6 +1335,19 @@ PrivilegedUsersReference = Reference: PrivilegedUsersReferenceURL = https://www.stigviewer.com/stig/active_directory_domain/2017-12-15/finding/V-36435 ServiceAccountsAdminCountNote = ** 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 Active Directory for all user accounts with an SPN and with AdminCount=1. Ensure that there are no privileged accounts that have SPNs assigned to them. + ErrorAccountSecurityAssessmentItem = Account Security Assessment Item + ErrorUserAccountSecurityAssessmentChart = User Account Security Assessment Chart + NoUserInfo = No Domain users information found in {0}, Disabling this section. + ErrorAccountSecurityAssessmentTable = Account Security Assessment Table + ErrorPrivilegedUsersAssessmentItem = Privileged Users Assessment Item + NoPrivilegedUserInfo = No Privileged User Assessment information found in {0}, Disabling this section. + ErrorPrivilegedUsersTable = Privileged Users Table + ErrorInactivePrivilegedAccountsItem = Inactive Privileged Accounts Item + NoInactivePrivilegedInfo = No Inactive Privileged Accounts information found in {0}, Disabling this section. + ErrorInactivePrivilegedAccountsTable = Inactive Privileged Accounts Table + ErrorServiceAccountsAssessmentItem = Service Accounts Assessment Item + NoServiceAccountsInfo = No Service Accounts Assessment information found in {0}, Disabling this section. + ErrorServiceAccountsAssessmentTable = Service Accounts Assessment Table '@ # Get-AbrADGPO @@ -1232,6 +1445,23 @@ GPOSettingsParagraph = The following section provides details about Group Policy configuration resources, including WMI filters, the Central Store repository, and scripts attached to GPOs. GPOHealthTitle = GPO Health GPOHealthParagraph = The following section highlights Group Policy Objects that may require attention, including unlinked, empty, enforced, and orphaned GPOs. + ErrorGPOItem = Group Policy Objects + ErrorWMIFiltersItem = WMI Filters + ErrorWMIFiltersPSSession = WMI Filters Section: New-PSSession: Unable to connect to {0}: {1} + ErrorGPOCentralStore = GPO Central Store + ErrorGPOLogonLogoffItem = GPO with Logon/Logoff Script Item + ErrorGPOLogonLogoffSection = GPO with Logon/Logoff Script Section + ErrorGPOStartupShutdownItem = GPO with Computer Startup/Shutdown Item + ErrorGPOStartupShutdownSection = GPO with Computer Startup/Shutdown Section + ErrorUnlinkedGPOItem = Unlinked Group Policy Objects Item + ErrorUnlinkedGPOSection = Unlinked Group Policy Objects Section + ErrorEmptyGPOItem = Empty Group Policy Objects Item + ErrorEmptyGPOSection = Empty Group Policy Objects Section + ErrorEnforcedGPOItem = Enforced Group Policy Objects Item + ErrorEnforcedGPOTable = Enforced Group Policy Objects Table + ErrorOrphanedGPOPSSession = Orphaned GPO Section: New-PSSession: Unable to connect to {0}: {1} + ErrorOrphanedGPOItem = Orphaned GPO + ErrorGPOSection = Group Policy Objects Section '@ # Get-AbrADDomainController GetAbrADDomainController = ConvertFrom-StringData @' @@ -1347,6 +1577,47 @@ MissingUpdatesParagraph = The following table provides a summary of pending or missing Windows updates detected on Domain Controllers in the {0} domain. MissingUpdatesBestPractice = It is critical to install security updates to protect your systems from malicious attacks. Regularly applying updates ensures that your systems are safeguarded against newly discovered vulnerabilities. Additionally, installing software updates provides access to new features and improvements, enhancing overall system performance and stability. Neglecting updates can leave your systems exposed to potential threats and exploitation. Therefore, it is in your best interest to maintain an up-to-date environment by promptly installing all recommended updates. DCObjectChart = Domain Controller Object - Chart + ErrorNetworkInterfacesInfo = Unable to get {0} network interfaces information + ErrorDCNetSettingsPSSession = DC Net Settings Section: New-PSSession: Unable to connect to {0}: {1} + ErrorDCItem = Domain Controller Item + UnableToCollect = Unable to collect information from {0}. + ErrorDCTable = Domain Controller Table + ErrorGeneralInfoSection = General Information Section + ErrorPartitionsSection = Partitions Section + ErrorNetworkingSettingsSection = Networking Settings Section + ErrorHardwareInventoryTable = Hardware Inventory Table + ErrorDCHardwareSection = Domain Controller Hardware Section + ErrorDCSection = Domain Controller Section + ErrorDNSIPConfigPSSession = DNS IP Configuration Section: New-PSSession: Unable to connect to {0}: {1} + ErrorDNSIPConfigTableSection = Domain Controller DNS IP Configuration Table Section: + ErrorDNSIPConfigItem = DNS IP Configuration Item + ErrorDNSIPConfigSection = Domain Controller DNS IP Configuration Section: + ErrorNTDSPSSession = NTDS Section: New-PSSession: Unable to connect to {0}: {1} + ErrorNTDSItem = NTDS Item + ErrorNTDSSection = NTDS section + ErrorTimeSourcePSSession = Time Source Section: New-PSSession: Unable to connect to {0}: {1} + ErrorTimeSourceItem = Time Source Item + ErrorTimeSourceTable = Time Source Table + ErrorTimeSource = Time Source + ErrorSRVRecordsStatusItem = SRV Records Status Item + ErrorSRVRecordsStatusTable = SRV Records Status Table + ErrorSRVRecordsStatus = SRV Records Status + ErrorFileSharesPSSession = Domain Controllers File Shares Section: New-PSSession: Unable to connect to {0}: {1} + ErrorFileSharesItem = File Shares Item + ErrorFileSharesTable = File Shares Table + ErrorInstalledSoftwarePSSession = Domain Controller Installed Software Section: New-PSSession: Unable to connect to {0}: {1} + ErrorInstalledSoftwareTable = Installed Software Table + ErrorInstalledSoftwareSection = Installed Software Section + ErrorMissingPatchPSSession = Domain Controller Pending Missing Patch Section: New-PSSession: Unable to connect to {0}: {1} + ErrorMissingPatchTable = Installed Software Table + ErrorMissingPatchSection = Domain Controller Section +'@ + + # Get-AbrDiagrammer + GetAbrDiagrammer = ConvertFrom-StringData @' + GettingDiagram = Getting {0} diagram from {1}. + ErrorExportDiagram = Unable to export the {0} Diagram: + ErrorGetDiagram = Unable to get the {0} Diagram: '@ } \ No newline at end of file diff --git a/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 b/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 index bae0268..8e9ceb4 100644 --- a/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 +++ b/AsBuiltReport.Microsoft.AD/Language/es-ES/MicrosoftAD.psd1 @@ -2,17 +2,45 @@ @{ # InvokeAsBuiltReportMicrosoftAD InvokeAsBuiltReportMicrosoftAD = ConvertFrom-StringData @' - PwshISE = Este script no se puede ejecutar dentro del ISE de PowerShell. Por favor, ejecútalo desde la ventana de comandos de PowerShell. + PwshISE = Este script no se puede ejecutar dentro de PowerShell ISE. Por favor, ejecútalo desde la ventana de comandos de PowerShell. ReportModuleInfo3 = - Documentación: https://github.com/AsBuiltReport/AsBuiltReport.{0} ReportModuleInfo2 = - Informes de problemas o errores: https://github.com/AsBuiltReport/AsBuiltReport.{0}/issues - ReportModuleInfo1 = - No olvides actualizar tu archivo de configuración de informe después de cada nueva versión: https://www.asbuiltreport.com/user-guide/new-asbuiltreportconfig/ + ReportModuleInfo1 = - No olvides actualizar tu archivo de configuración de informe después de cada nuevo lanzamiento de versión: https://www.asbuiltreport.com/user-guide/new-asbuiltreportconfig/ ReportModuleInfo4 = - Para patrocinar este proyecto, por favor visita: ReportModuleInfo5 = https://ko-fi.com/F1F8DEV80 ReportModuleInfo6 = - Obteniendo información de dependencias: ProjectWebsite = - Por favor consulta el sitio web de GitHub de AsBuiltReport.Microsoft.AD para obtener información más detallada sobre este proyecto. CommunityProject = - AsBuiltReport es un proyecto de código abierto mantenido por la comunidad. No tiene patrocinio, respaldo o afiliación con ningún proveedor de tecnología, sus empleados o afiliados. - DISCLAIMER = Este informe combina análisis de datos automatizado con observaciones profesionales. Aunque estos hallazgos ofrecen información experta, esta evaluación no es exhaustiva. Todas las recomendaciones deben ser revisadas e implementadas por personal calificado. Los autores no asumen responsabilidad alguna por daños, incluidas pérdidas de ganancias, interrupciones comerciales o pérdidas financieras, derivadas del uso de este informe o sus recomendaciones. - DisclaimerSection = AVISO LEGAL + DISCLAIMER = Este informe combina análisis de datos automatizado con observaciones profesionales. Aunque estos hallazgos ofrecen información experta, esta evaluación no es exhaustiva. Todas las recomendaciones deben ser revisadas e implementadas por personal calificado. Los autores no asumen ninguna responsabilidad por daños, incluyendo pérdidas de ganancias, interrupciones comerciales o pérdidas financieras, derivados del uso de este informe o sus recomendaciones. + DisclaimerSection = DESCARGO DE RESPONSABILIDAD + ModuleInstalled = - El módulo {0} v{1} está actualmente instalado. + ModuleAvailable = - El módulo {0} v{1} está disponible. + ModuleUpdate = - Ejecuta 'Update-Module -Name {0} -Force' para instalar la versión más reciente. + IPAddressError = Por favor, usa el Nombre de Dominio Completamente Calificado (FQDN) en lugar de una dirección IP al conectar con el Controlador de Dominio: {0} + PSSessionError = Error al establecer una PSSession ({0}) con el Controlador de Dominio '{1}': {2} + CIMSessionError = Error al establecer una sesión CIM ({0}) con el Controlador de Dominio '{1}'. + ConnectingForest = Conectando para recuperar información del bosque desde el Controlador de Dominio '{0}'. + ForestError = Error al recuperar información del bosque desde el Controlador de Dominio '{0}'. Asegúrate de que el sistema proporcionado sea un Controlador de Dominio y que las credenciales proporcionadas tengan permisos suficientes para consultar información del bosque de Active Directory. Detalles del error: {1} + IncludeDomainsEnabled = - Opción Include.Domains habilitada: Incluyendo solo los siguientes dominios en el informe: {0} + ExcludeDomainsEnabled = - Incluyendo todos los dominios secundarios en el informe excepto los siguientes dominios excluidos: {0} + GettingForestInfo = - Recuperando información del bosque {0}. + DiscoveringChildDomains = - Descubriendo dominios secundarios del bosque {0}: {1}. + DCAvailable = - Configuración inicial: Un DC está disponible en el dominio {0}. Agregando dominio al informe. + DCUnavailable = - No se puede obtener un DC disponible en el dominio {0}. Removiendo dominio del informe. + FinishingDomainList = - Finalizando la lista de dominios en el bosque {0}: {1}. + WorkingOnForest = - Trabajando en la sección del Bosque. + WorkingOnDomain = - Trabajando en la sección del Dominio. + WorkingOnDNS = - Trabajando en la sección de DNS. + WorkingOnPKI = - Trabajando en la sección de PKI. + ExportDiagramsEnabled = - Opción ExportDiagrams habilitada: Exportando diagramas: + TrustsDiagramError = No se puede generar el diagrama de 'Confianzas' para el dominio '{0}': {1} + DiagramExportError = No se puede exportar el diagrama {0}: {1} + ClearPSSession = Limpiando PSSession con ID {0} + ClearCIMSession = Limpiando sesión CIM con ID {0} + FinishedReport = - Se ha terminado de generar el informe para el bosque {0}: + SystemsUnreachable = Los siguientes sistemas no pudieron ser contactados: + DomainControllers = Controladores de Dominio + Domains = Dominios '@ # ConvertToTextYN @@ -56,6 +84,12 @@ ScopeEnabled = Habilitado (Resumen) ScopeAdvanced = Habilitado (Resumen Avanzado) ScopeDetailed = Habilitado (Detallado) + ErrorReportOverview = Report Brief - Report Overview + ErrorForestSummary = Report Brief - Forest Summary + ErrorDomainSummaryItem = Report Brief - Domain Summary Item + ErrorDomainSummary = Report Brief - Domain Summary + ErrorReportScope = Report Brief - Report Scope + ErrorReportBriefSection = Report Brief Section '@ # Get-AbrForestSection @@ -122,6 +156,14 @@ RecycleBinBP = La eliminación accidental de objetos de Active Directory es un problema común para usuarios de AD DS. Habilitar la función Papelera de Reciclaje permite la recuperación de estos objetos eliminados accidentalmente, ayudando a mantener la integridad y continuidad del entorno de Active Directory. RecycleBinRef = https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-ad-recycle-bin-understanding-implementing-best-practices-and/ba-p/396944 CADiagram = Diagrama de Autoridad de Certificación + TableName = Resumen del Bosque + ErrorForestDiagramGraph = Forest Diagram Graph: + ErrorForestDiagramSection = Forest Diagram Section: + NoCARootInfo = No se encontró información de Autoridad de Certificación raíz en {0}, deshabilitando esta sección. + NoCAIssuerInfo = No se encontró información de Autoridad de Certificación emisora, deshabilitando esta sección. + ErrorCADiagramGraph = Certificate Authority Diagram Graph: + ErrorCADiagramSection = Certificate Authority Diagram Section: + NoOptionalFeatureInfo = No se encontró información de Características Opcionales en {0}, deshabilitando esta sección. '@ NewADDiagram = ConvertFrom-StringData @' @@ -245,6 +287,10 @@ DnsName = Nombre DNS ServerRoles = Roles del Servidor Version = Versión + ErrorExchangeItem = Exchange Item + NoExchangeInfo = No se encontró información de infraestructura de Exchange en {0}, deshabilitando esta sección. + ErrorExchangeTable = Exchange Table + ErrorExchangeServerItem = ExchangeServer: [{0}]. '@ # Get-AbrADSCCM @@ -257,6 +303,9 @@ ManagementPoint = Punto de Gestión SiteCode = Código de Sitio Version = Versión + ErrorSCCMItem = SCCM Item + NoSCCMInfo = No se encontró información de infraestructura de SCCM en {0}, deshabilitando esta sección. + ErrorSCCMTable = SCCM Table '@ # Get-AbrDHCPinAD @@ -270,6 +319,9 @@ Yes = Sí No = No Unknown = Desconocido + ErrorDHCPItem = DHCP Item + NoDHCPInfo = No se encontró información de infraestructura de DHCP en {0}, deshabilitando esta sección. + ErrorDHCPTable = DHCP Table '@ # Get-AbrADSite @@ -306,7 +358,7 @@ MissingSubnets = Subredes Faltantes en AD MissingSubnetsTable = Subredes Faltantes MissingSubnetsParagraph = La siguiente tabla lista las entradas NO_CLIENT_SITE encontradas en el archivo netlogon.log en cada Controlador de Dominio del bosque. Estas entradas indican direcciones IP de clientes que no pudieron ser mapeadas a un sitio de Active Directory. - DC = CD + DC = DC IP = IP MissingSubnetsBP = Asegúrate de que todas las subredes en cada sitio estén correctamente definidas. Las definiciones de subred faltantes pueden impedir que los clientes usen sus Controladores de Dominio más cercanos, resultando en mayor latencia de autenticación. InterSiteTransports = Transportes Entre Sitios @@ -343,7 +395,7 @@ SMTPParagraph = La replicación SMTP se usa para sitios que no pueden usar otros protocolos de replicación, pero como regla general, nunca debe usarse. Se reserva para escenarios donde las conexiones de red no siempre están disponibles, permitiendo que la replicación se programe en intervalos específicos. SMTPChangeNotifBP = Habilitar la notificación de cambio trata una conexión de replicación entre sitios como si fuera una conexión dentro de un sitio. La replicación entre sitios con notificación de cambio es casi instantánea. Microsoft recomienda usar un valor de Opción de 5 (Notificación de Cambio Habilitada sin Compresión). SysvolReplication = Replicación de Sysvol - DCName = Nombre del CD + DCName = Nombre del DC ReplicationStatus = Estado de Replicación Domain = Dominio StatusUninitialized = No Inicializado @@ -355,7 +407,42 @@ StatusDisabled = Deshabilitado StatusUnknown = Desconocido StatusOffline = Fuera de Línea - SysvolBP = SYSVOL es un directorio especial que reside en cada controlador de dominio (CD) dentro de un dominio. El directorio comprende carpetas que almacenan objetos de Política de Grupo (GPO) y scripts de inicio de sesión que los clientes necesitan acceder y sincronizar entre CDs. Para que estos scripts de inicio de sesión y GPO funcionen correctamente, SYSVOL debe replicarse con precisión y rapidez en todo el dominio. Asegúrate de que se implemente una replicación correcta de SYSVOL para asegurar contenido idéntico de GPO/SYSVOL para el controlador de dominio en todos los dominios de Active Directory. + SysvolBP = SYSVOL es un directorio especial que reside en cada controlador de dominio (DC) dentro de un dominio. El directorio comprende carpetas que almacenan objetos de Política de Grupo (GPO) y scripts de inicio de sesión que los clientes necesitan acceder y sincronizar entre DCs. Para que estos scripts de inicio de sesión y GPO funcionen correctamente, SYSVOL debe replicarse con precisión y rapidez en todo el dominio. Asegúrate de que se implemente una replicación correcta de SYSVOL para asegurar contenido idéntico de GPO/SYSVOL para el controlador de dominio en todos los dominios de Active Directory. + ErrorReplicationDiagramGraph = Replication Diagram Graph: + ErrorReplicationDiagramSection = Replication Diagram Section: + ErrorDomainSite = Domain Site + ErrorSiteReplicationConnectionItem = Site Replication Connection Item + NoConnectionObjectsInfo = No se encontró información de Objetos de Conexión en {0}, deshabilitando esta sección. + ErrorConnectionObjects = Connection Objects + ErrorSiteSubnets = Site Subnets + UnableToRead = No se puede leer {0} en {1} + ErrorMissingSubnetPSSession = Missing Subnet in AD Section: New-PSSession: Unable to connect to {0}: {1} + ErrorMissingSubnetItemTable = Missing Subnet in AD Item table: + NoMissingSubnetsInfo = No se encontró información de Subredes Faltantes en {0}, deshabilitando esta sección. + ErrorMissingSubnetItemSection = Missing Subnet in AD Item Section: + NoSiteSubnetsInfo = No se encontró información de Subredes de Sitio en {0}, deshabilitando esta sección. + ErrorSiteTopologyDiagramGraph = Site Topology Diagram Graph: + ErrorSiteTopologyDiagramSection = Site Topology Diagram Section: + ErrorInterSiteTransports = Inter-Site Transports section + ErrorIPSiteLinksTable = IP Site Links table + NoIPSiteLinksInfo = No se encontró información de Vínculos de Sitio IP en {0}, deshabilitando esta sección. + ErrorIPSiteLinksSection = IP Site Links Section + ErrorIPSiteLinksBridgesTable = IP Site Links Bridges table + NoIPSiteLinksBridgesInfo = No se encontró información de Puentes de Vínculos de Sitio IP en {0}, deshabilitando esta sección. + ErrorIP = IP + ErrorSMTPSiteLinksTable = SMTP Site Links table + ErrorSMTPSiteLinksSection = SMTP Site Links Section + ErrorSMTPSiteLinksBridgesTable = SMTP Site Links Bridges table + NoSMTPSiteLinksBridgesInfo = No se encontró información de Puentes de Vínculos de Sitio SMTP en {0}, deshabilitando esta sección. + NoSMTPSiteLinksInfo = No se encontró información de Vínculos de Sitio SMTP en {0}, deshabilitando esta sección. + ErrorSMTP = SMTP + ErrorSysvolReplicationItemSection = Sysvol Replication Item Section: + UnableToCollect = No se puede recopilar información de {0}. + ErrorDNSIPConfigItem = DNS IP Configuration Item + NoSysvolReplicationInfo = No se encontró información de Replicación Sysvol en {0}, deshabilitando esta sección. + ErrorSysvolReplicationTableSection = Sysvol Replication Table Section: + NoSitesInfo = No se encontró información de Sitios en {0}, deshabilitando esta sección. + ErrorDomainSiteGlobal = Domain Site Global '@ # Get-AbrDNSSection @@ -364,11 +451,12 @@ CollectingDomain = Recopilando información de DNS desde {0}. DomainParagraph = La siguiente sección proporciona una descripción general detallada de la configuración del servicio DNS y su configuración para este dominio. ExcludedDomain = {0} deshabilitado en variable Exclude.Domain - NoDCAvailable = No se puede obtener un CD disponible en el dominio {0}. Removiendo dominio de la sección DNS. + NoDCAvailable = No se puede obtener un DC disponible en el dominio {0}. Removiendo dominio de la sección DNS. Heading = Configuración de DNS DefinitionParagraph = El Sistema de Nombres de Dominio (DNS) es un sistema de nomenclatura jerárquico y descentralizado para computadoras, servicios u otros recursos conectados a Internet o a una red privada. Asocia varios tipos de información con nombres de dominio asignados a cada una de las entidades participantes. Más prominentemente, traduce nombres de dominio más fáciles de recordar a direcciones IP numéricas necesarias para localizar e identificar servicios y dispositivos de computadora dentro de los protocolos de red subyacentes. Paragraph = La siguiente sección proporciona una descripción general detallada de la configuración de infraestructura de DNS y su configuración dentro del entorno de Active Directory. NoCIMSession = La configuración de la infraestructura de DNS requiere una sesión CIM y no pudo ser recopilada. Verifica que la conectividad WinRM y CIM a los controladores de dominio esté disponible. + ErrorDNSInfo = Domain Name System Information '@ # Get-AbrADDNSInfrastructure - Continue with the rest of the translations... @@ -389,11 +477,11 @@ RootHintsParagraph = La siguiente sección proporciona información detallada sobre la configuración de Sugerencias de Raíz para cada servidor DNS en el dominio {0}. ZoneScopeRecursion = Recursión de Alcance de Zona DirectoryPartitions = Particiones de Directorio - DCName = Nombre del CD + DCName = Nombre del DC BuildNumber = Número de Compilación IPv6 = IPv6 DnsSec = DnsSec - ReadOnlyDC = CD de Solo Lectura + ReadOnlyDC = DC de Solo Lectura ListeningIP = IP de Escucha Name = Nombre State = Estado @@ -425,12 +513,27 @@ BestPractice = Mejores Prácticas: CorrectiveActions = Acciones Correctivas: Reference = Referencia: - ScavengingBP = Microsoft recomienda habilitar envejecimiento/limpieza en todos los servidores DNS. Sin embargo, con zonas integradas en AD, asegúrate de que la limpieza de DNS esté habilitada solo en un CD del sitio principal. Los resultados se replicarán a otros CDs. + ScavengingBP = Microsoft recomienda habilitar envejecimiento/limpieza en todos los servidores DNS. Sin embargo, con zonas integradas en AD, asegúrate de que la limpieza de DNS esté habilitada solo en un DC del sitio principal. Los resultados se replicarán a otros DCs. ForwarderMaxBP = Configura los servidores para usar no más de dos servidores DNS externos como Reenviadores. Usar más de dos reenviadores puede llevar a tiempos de resolución aumentados y problemas potenciales con el equilibrio de carga de consultas DNS. Se recomienda usar dos servidores DNS confiables y geográficamente diversos para asegurar redundancia y rendimiento óptimo. ForwarderRefURL = https://learn.microsoft.com/es-es/troubleshoot/windows-server/networking/forwarders-resolution-timeouts ForwarderMinBP = Por razones de redundancia, se debe configurar más de un servidor de reenvío. RootHintsMissingCA = Una instalación predeterminada del rol de servidor DNS debe tener sugerencias de raíz a menos que el servidor tenga una zona raíz - .(raíz). Si el servidor tiene una zona raíz, elimínala. Si el servidor no tiene una zona raíz y no hay servidores raíz listados en la pestaña Sugerencias de Raíz de las propiedades del servidor DNS, el servidor puede estar perdiendo el archivo cache.dns en el directorio %systemroot%\\system32\\dns, desde donde se carga la lista de servidores raíz. RootHintsDuplicateCA = Se encontró dirección IP duplicada en la tabla de servidores de sugerencias de raíz de DNS. La consola de DNS no muestra los servidores de Sugerencias de Raíz duplicados; solo puedes verlos usando cmdlets de PowerShell de DNS. Aunque existe una utilidad dnscmd para reemplazar el archivo de Sugerencias de Raíz, usar PowerShell es la mejor forma de remediar este problema. + ErrorInfrastructureSummarySection = DNS Infrastructure Summary Section: + ErrorDirectoryPartitionsItemSection = Directory Partitions Item Section: + ErrorDirectoryPartitionsTableSection = Directory Partitions Table Section: + ErrorDirectoryPartitionsSection = Directory Partitions Section: + ErrorRRLItem = Response Rate Limiting (RRL) Item + ErrorRRLTable = Response Rate Limiting (RRL) Table + ErrorScavengingItem = Scavenging Item + ErrorScavengingTable = Scavenging Table + ErrorForwarderItem = Forwarder Item + ErrorForwarderTable = Forwarder Table + ErrorRootHintsTable = Root Hints Table + ErrorRootHintsSection = Root Hints Section + ErrorZoneScopeRecursionItem = Zone Scope Recursion Item + ErrorZoneScopeRecursionTable = Zone Scope Recursion Table + ErrorDNSInfrastructureSection = DNS Infrastructure Section '@ # Get-AbrADDNSZone @@ -472,7 +575,26 @@ HealthCheck = Verificación de Salud: BestPractice = Mejores Prácticas: ZoneTransferBP = Configura todas las zonas de DNS para permitir transferencias de zona solo desde direcciones IP de confianza. Esto asegura que solo servidores DNS autorizados puedan recibir datos de zona, reduciendo el riesgo de acceso no autorizado o fuga de datos. Es una mejor práctica especificar las direcciones IP de los servidores DNS secundarios autorizados a recibir transferencias de zona. - ZoneAgingBP = Microsoft recomienda habilitar envejecimiento/limpieza en todos los servidores DNS. Sin embargo, con zonas integradas en AD, asegúrate de que la limpieza de DNS esté habilitada solo en un CD del sitio principal. Los resultados se replicarán a otros CDs. + ZoneAgingBP = Microsoft recomienda habilitar envejecimiento/limpieza en todos los servidores DNS. Sin embargo, con zonas integradas en AD, asegúrate de que la limpieza de DNS esté habilitada solo en un DC del sitio principal. Los resultados se replicarán a otros DCs. + ErrorDNSZoneItem = Domain Name System Zone Item + NoDelegationInfo = Sección de Zonas DNS {0}: No se encontró información de Delegación de Zona, deshabilitando esta sección. + ErrorZoneDelegationItem = Zone Delegation Item + NoDelegationInfoDC = Sección de Zonas DNS: No se encontró información de Delegación de Zona en {0}, deshabilitando esta sección. + ErrorZoneDelegationTable = Zone Delegation Table + ErrorZoneTransferPSSession = DNS Zones Transfers Section: New-PSSession: Unable to connect to {0}: {1} + ErrorZoneTransfersItem = Zone Transfers Item + NoZoneTransferInfo = Sección de Zonas DNS: No se encontró información de Transferencia de Zona en {0}, deshabilitando esta sección. + ErrorZoneTransfersTable = Zone Transfers Table + ErrorReverseLookupZoneItem = Reverse Lookup Zone Configuration Item + NoReverseLookupZoneInfo = Sección de Zonas DNS: No se encontró información de zona de búsqueda inversa en {0}, deshabilitando esta sección. + ErrorReverseLookupZoneTable = Reverse Lookup Zone Configuration Table + ErrorConditionalForwarderItem = Conditional Forwarder Item + NoConditionalForwarderInfo = Sección de Zonas DNS: No se encontró información de zona de reenviador condicional en {0}, deshabilitando esta sección. + ErrorConditionalForwarderTable = Conditional Forwarder Table + ErrorZoneScopeAgingItem = Zone Scope Aging Item + NoZoneAgingInfo = Sección de Zonas DNS: No se encontró información de propiedad de envejecimiento de zona en {0}, deshabilitando esta sección. + ErrorZoneScopeAgingTable = Zone Scope Aging Table + ErrorGlobalDNSZoneInfo = Global DNS Zone Information '@ # Continuing with remaining sections... @@ -563,7 +685,7 @@ ValidityPeriod = Período de Validez ACL = Lista de Control de Acceso (ACL) ACLTable = Lista de Control de Acceso - DCName = Nombre del CD + DCName = Nombre del DC Owner = Propietario Group = Grupo AccessRights = Derechos de Acceso @@ -672,22 +794,23 @@ # Get-AbrDomainSection GetAbrDomainSection = ConvertFrom-StringData @' Collecting = Recopilando información de Dominio desde {0}. + CollectingDomain = Recopilando información de Dominio desde {0}. Paragraph = Esta sección proporciona una descripción general de la configuración del dominio de Active Directory, incluyendo configuraciones clave y detalles operacionales. SectionTitle = Configuración del Dominio de AD DefinitionText = Un dominio de Active Directory es una colección de objetos dentro de una red de Microsoft Active Directory. Un objeto puede ser un usuario individual, un grupo o un componente de hardware como una computadora o impresora. Cada dominio contiene una base de datos con información de identidad de objetos. Los dominios de Active Directory se pueden identificar usando un nombre DNS, que puede ser el mismo que el nombre de dominio público de una organización, un subdominio o una versión alternativa (que puede terminar en .local). ParagraphDetail = La siguiente tabla proporciona un desglose detallado de los atributos de configuración del dominio de Active Directory. HealthChecks = Verificaciones de Salud DomainControllersSection = Controladores de Dominio - DCDefinitionText = Un controlador de dominio (CD) es una computadora servidor que responde solicitudes de autenticación de seguridad dentro de un dominio de red de computadoras. Es un servidor de red responsable de permitir el acceso del anfitrión a recursos del dominio. Autentica usuarios, almacena información de cuenta de usuario e implementa la política de seguridad para un dominio. + DCDefinitionText = Un controlador de dominio (DC) es una computadora servidor que responde solicitudes de autenticación de seguridad dentro de un dominio de red de computadoras. Es un servidor de red responsable de permitir el acceso del anfitrión a recursos del dominio. Autentica usuarios, almacena información de cuenta de usuario e implementa la política de seguridad para un dominio. DCParagraphDetail = La siguiente sección presenta una descripción general profunda de los controladores de dominio de Active Directory, incluyendo su configuración y detalles clave. DCParagraphSummary = La siguiente sección proporciona un resumen de la configuración y detalles clave de los controladores de dominio de Active Directory. RolesSection = Roles RolesParagraph = La siguiente sección proporciona una descripción general detallada de los roles y funciones instalados en controladores de dominio en {0}. - DCDiagSection = Diagnóstico de CD - DCDiagParagraph = La siguiente sección proporciona un resumen del Diagnóstico de CD de Active Directory. + DCDiagSection = Diagnóstico de DC + DCDiagParagraph = La siguiente sección proporciona un resumen del Diagnóstico de DC de Active Directory. InfraServicesSection = Servicios de Infraestructura InfraServicesParagraph = La siguiente sección proporciona una descripción general detallada del estado y configuración de servicios de infraestructura en los controladores de dominio. - NoDCAvailable = No se puede obtener un CD disponible en el dominio {0}. Removiendo dominio de la sección de Dominio. + NoDCAvailable = No se puede obtener un DC disponible en el dominio {0}. Removiendo dominio de la sección de Dominio. WinRMErrorDCDiag = Error: La conexión al servidor remoto {0} falló: WinRM no puede completar la operación. (Información de DCDiag) WinRMErrorInfraService = Error: La conexión al servidor remoto {0} falló: WinRM no puede completar la operación. (ADInfrastructureService) DomainExcluded = {0} deshabilitado en variable Exclude.Domain @@ -695,6 +818,7 @@ ReplicationParagraph = La siguiente sección proporciona una descripción general de las conexiones de replicación de Active Directory y estado entre controladores de dominio en este dominio. GPOSection = Política de Grupo GPOParagraph = La siguiente sección proporciona una descripción general de los Objetos de Política de Grupo (GPO) configurados y aplicados dentro de este dominio. + ErrorADDomain = Active Directory Domain '@ # Get-AbrADDomain @@ -726,6 +850,8 @@ Reference = Referencia: RIDBestPractice = El porcentaje de RID Emitido excede el 80%. Se recomienda evaluar la utilización de RID para prevenir posible agotamiento y asegurar la estabilidad del dominio. El Identificador Relativo (RID) es un componente crucial en el SID (Identificador de Seguridad) para objetos dentro del dominio. El agotamiento del grupo de RID puede llevar a la incapacidad de crear nuevos principales de seguridad, como cuentas de usuario o computadora. El monitoreo regular y la gestión proactiva del grupo de RID son esenciales para mantener la salud del dominio y evitar disrupciones. RIDReference = https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/managing-rid-pool-depletion/ba-p/399736 + TableName = Resumen del Dominio + ErrorSection = Sección de Resumen del Dominio de AD: '@ # Get-AbrADFSMO @@ -742,6 +868,9 @@ Reference = Referencia: InfraMasterBP = El rol maestro de infraestructura en el dominio {0} debe ser mantenido por un controlador de dominio que no sea un servidor de catálogo global. El maestro de infraestructura es responsable de actualizar referencias de objetos en su dominio a objetos en otros dominios. Si el maestro de infraestructura se ejecuta en un servidor de catálogo global, no funcionará correctamente porque el catálogo global contiene una réplica parcial de cada objeto en el bosque, y no actualizará las referencias. Este problema no afecta bosques que tienen un único dominio. InfraMasterRef = http://go.microsoft.com/fwlink/?LinkId=168841 + TableName = Roles FSMO + ErrorFSMOItem = Flexible Single Master Operations + ErrorPSSession = FSMO Roles Section: New-PSSession: Unable to connect to {0}: {1} '@ # Get-AbrADTrust @@ -783,6 +912,12 @@ BestPractice = Mejor Práctica: AESBP = Asegúrate de que la encriptación Kerberos AES esté habilitada en todas las confianzas de Active Directory. La encriptación RC4 se considera débil y vulnerable a varios ataques. Habilitar la encriptación AES en confianzas mejora la seguridad de Kerberos y se alinea con estándares de seguridad modernos. Referencia: https://techcommunity.microsoft.com/t5/itops-talk-blog/tough-questions-answered-can-i-disable-rc4-etype-for-kerberos-on/ba-p/382718 TrustDiagramSection = Diagrama de Dominios y Confianzas + ErrorTrustItem = Trust Item + ErrorTrustDiagramGraph = Domain and Trusts Diagram Graph: + ErrorTrustDiagramSection = Domain and Trusts Diagram Section: + NoTrustInfo = No se encontró información de confianza de dominio en {0}, deshabilitando esta sección. + ErrorTrustTable = Trust Table + ErrorTrustSection = Trust Section '@ # Continue with remaining sections... @@ -816,6 +951,20 @@ ServiceTGTLifetime = Tiempo de Vida de TGT del Servicio (mins) ComputerTGTLifetime = Tiempo de Vida de TGT de la Computadora (mins) PolicyBP = Las Políticas de Autenticación deben estar configuradas en modo Aplicar para restringir activamente los tiempos de vida de TGT de Kerberos e inicio de sesión de cuenta. Las políticas en modo auditoría solo registran eventos sin aplicar restricciones. + ErrorSiloItem = Authentication Policy Silo Item + SiloTableName = Silo de Política de Autenticación + SilosTableName = Silos de Políticas de Autenticación + ErrorSiloMemberItem = Authentication Policy Silo Member Item + SiloMembersTableName = Miembros del Silo de Política de Autenticación + ErrorSiloMembersTable = Authentication Policy Silo Members Table + ErrorSilosSectionA = Authentication Policy Silos Section + NoSiloInfo = No se encontró información de Silos de Política de Autenticación en {0}, deshabilitando esta sección. + ErrorPolicyItem = Authentication Policy Item + PolicyTableName = Política de Autenticación + PoliciesTableName = Políticas de Autenticación + ErrorPoliciesSection = Authentication Policies Section + NoPolicyInfo = No se encontró información de Políticas de Autenticación en {0}, deshabilitando esta sección. + NoAuthPolicyOrSiloInfo = No se encontró información de Política de Autenticación o Silo en {0}, deshabilitando esta sección. '@ # Get-AbrADDomainObject @@ -985,6 +1134,45 @@ GMSAInactiveBP = *Verifica regularmente y elimina cuentas de servicio administradas grupales inactivas de Active Directory. Las cuentas inactivas pueden ser un riesgo de seguridad ya que pueden ser explotadas por actores maliciosos. Asegurar que solo cuentas activas y necesarias existan ayuda a mantener un entorno seguro y reduce el riesgo de acceso no autorizado o escalada de privilegios. GMSANoHostComputersBP = **No se ha definido "Computadoras Anfitrión"; por favor valida que gMSA esté actualmente en uso. Si no, se recomienda eliminar estos recursos no utilizados de Active Directory. GMSANoRetrieveManagedPasswordBP = ***No se ha definido "Recuperar Contraseña Administrada"; por favor valida que gMSA esté actualmente en uso. Si no, se recomienda eliminar estos recursos no utilizados de Active Directory. + PrivilegedGroupMembersTableName = Miembros del Grupo Privilegiado: + GMSATableName = gMSA + MembersLabel = Miembros + TypeLabelUser = USUARIO + TypeLabelComputer = COMPUTADORA + TypeLabelGroup = GRUPO + TypeLabelFSP = ENTIDAD DE SEGURIDAD EXTERNA + ErrorDomainObjectStats = Domain Object Stats + ErrorUserObjectCountChart = User Object Count Chart + ErrorStatusOfUserAccounts = Status of User Accounts + ErrorStatusOfUsersAccountsChart = Status of Users Accounts Chart + ErrorUsersObjectsTable = Users Objects Table + ErrorUsersObjectsSection = Users Objects Section + ErrorGroupCategoryObjectChart = Group Category Object Chart + ErrorGroupScopesObjectChart = Group Scopes Object Chart + ErrorGroupsObjectsTable = Groups Objects Table + ErrorGroupsObjectsSection = Groups Objects Section + ErrorPrivilegedGroup = Privileged Group in Active Directory + ErrorPrivilegedGroupNonDefaultTable = Privileged Group (Non-Default) Table + ErrorPrivilegedGroupNonDefaultSection = Privileged Group (Non-Default) Section + ErrorEmptyGroupsObjectsTable = Empty Groups Objects Table + ErrorEmptyGroupsObjectsSection = Empty Groups Objects Section + ErrorCircularGroupMembershipTable = Circular Group Membership Table + ErrorCircularGroupMembershipSection = Circular Group Membership Section + ErrorPreWin2000 = Pre-Windows 2000 Compatible Access + ErrorComputersObjectCountChart = Computers Object Count Chart + ErrorStatusOfComputerAccounts = Status of Computer Accounts + ErrorStatusOfComputersAccountsChart = Status of Computers Accounts Chart + ErrorOperatingSystemsInAD = Operating Systems in Active Directory + ErrorComputersPasswordNotRequired = Computers with Password-Not-Required + ErrorComputersObjectsTable = Computers Objects Table + ErrorComputersObjectsSection = Computers Objects Section + ErrorDefaultDomainPasswordPolicy = Default Domain Password Policy + ErrorFGPP = Fine Grained Password Policies + ErrorWindowsLAPS = Windows LAPS + ErrorGMSAItem = Group Managed Service Accounts Item + ErrorGMSASection = Group Managed Service Accounts Section + ErrorFSPItem = Foreign Security Principals Item + ErrorFSPSection = Foreign Security Principals Section '@ # Get-AbrADHardening @@ -1028,6 +1216,8 @@ LDAPSigningBP = La aplicación de firmas LDAP no se configura en este controlador de dominio. La firma LDAP es una función de seguridad que protege la integridad y confidencialidad de las comunicaciones LDAP requiriendo firma de datos. Configura la firma LDAP para requerir firma en todos los controladores de dominio. LDAPCBBindingBP = La aplicación de vinculación de canal de LDAP no se configura en este controlador de dominio. La vinculación de canal de LDAP es una función de seguridad que protege contra ataques de intermediario vinculando la sesión LDAP al canal TLS, asegurando la autenticidad e integridad de las comunicaciones LDAP. Configura la vinculación de canal de LDAP en todos los controladores de dominio. NTLMv1BP = La autenticación NTLMv1 está habilitada en este controlador de dominio. NTLMv1 es un protocolo de autenticación obsoleto que es vulnerable a ataques de captura y retransmisión de credenciales. Deshabilita NTLMv1 en todos los sistemas; ha sido superado por NTLMv2, que ofrece protecciones de seguridad significativamente mejoradas. + ErrorADHardeningItem = ADHardening Item + ErrorADHardeningSection = ADHardening Section '@ # Get-AbrADDomainLastBackup @@ -1046,6 +1236,8 @@ BackupBP1 = Asegúrate de que haya un respaldo reciente de Active Directory (<180 días). BackupBP2 = Los respaldos regulares son cruciales para la recuperación ante desastres y el mantenimiento de la integridad de tu entorno de Active Directory. BackupBP3 = Considera configurar cronogramas de respaldo automatizados y verifica regularmente el estado del respaldo para prevenir pérdida de datos. + ErrorDomainLastBackupItem = Domain Last Backup Item + ErrorDomainLastBackupTable = Domain Last Backup Table '@ # Get-AbrADDuplicateSPN @@ -1061,6 +1253,8 @@ HealthCheck = Verificación de Salud: CorrectiveActions = Acciones Correctivas: SPNBP = Asegúrate de que no haya SPN duplicados (otros que krbtgt). Los SPN duplicados pueden causar problemas de autenticación y deben resolverse rápidamente. Usa el comando `setspn -X` para identificar SPN duplicados. Elimina o reasigna SPN duplicados según sea necesario para mantener un entorno de AD saludable. + ErrorSPNItem = SPN Item + ErrorSPNTable = SPN Table '@ # Get-AbrADDuplicateObject @@ -1077,11 +1271,13 @@ HealthCheck = Verificación de Salud: CorrectiveActions = Acciones Correctivas: DuplicateObjectBP = Asegúrate de que no haya objetos duplicados en Active Directory. Los objetos duplicados pueden causar varios problemas tales como problemas de autenticación, conflictos de replicación y gastos administrativos adicionales. Se recomienda auditar y limpiar regularmente cualquier objeto duplicado para mantener un entorno de Active Directory saludable y eficiente. + ErrorDuplicateObjectItem = Duplicate Object Item + ErrorDuplicateObjectTable = Duplicate Object Table '@ # Get-AbrADDCRoleFeature GetAbrADDCRoleFeature = ConvertFrom-StringData @' - Collecting = Recopilando información de Rol y Características de CD de Active Directory de {0}. + Collecting = Recopilando información de Rol y Características de DC de Active Directory de {0}. Name = Nombre Parent = Padre Description = Descripción @@ -1089,6 +1285,9 @@ HealthCheck = Verificación de Salud: BestPractices = Mejores Prácticas: RoleBP = Los Controladores de Dominio deben tener software y agentes limitados instalados incluyendo roles y servicios. El código no esencial ejecutándose en Controladores de Dominio es un riesgo para el entorno empresarial de Active Directory. Un Controlador de Dominio debe ejecutar solo software requerido, servicios y roles críticos para la operación esencial. + ErrorPSSession = Roles Section: New-PSSession: Unable to connect to {0}: {1} + ErrorRoleFeatureSection = Roles {0} Section: + ErrorRolesSection = Roles Section: '@ # Get-AbrADDCDiag @@ -1100,11 +1299,14 @@ Description = Descripción TableName = Estado de Prueba de DCDiag NoData = No se encontró información de DCDiag en {0}, deshabilitando esta sección. + ErrorDCDiagTestSection = Active Directory DCDiag {0} Section: + ErrorDCDiagSection = Active Directory DCDiag Section: + ErrorInvokeDcDiag = Invoke-DcDiag - Failed to get DCDiag for {0} with error: '@ # Get-AbrADInfrastructureService GetAbrADInfrastructureService = ConvertFrom-StringData @' - Collecting = Recopilando información de Servicios de Infraestructura de CD de Active Directory de {0}. + Collecting = Recopilando información de Servicios de Infraestructura de DC de Active Directory de {0}. DisplayName = Nombre Mostrado ShortName = Nombre Corto Status = Estado @@ -1114,6 +1316,9 @@ CorrectiveActions = Acciones Correctivas: SpoolerBP = El servicio Print Spooler tiene vulnerabilidades conocidas que pueden ser explotadas por atacantes para obtener acceso no autorizado o ejecutar código malicioso. Deshabilitar este servicio en Controladores de Dominio y otros servidores críticos que no requieren servicios de impresión puede reducir la superficie de ataque y mejorar la postura de seguridad general de tu entorno de Active Directory. DHCPServerBP = De acuerdo con las mejores prácticas de seguridad, los servicios de Servidor DHCP deben ejecutarse en un servidor dedicado separado de los controladores de dominio para minimizar riesgos de seguridad, reducir contención de recursos y asegurar rendimiento óptimo de ambos servicios DHCP y Active Directory. + ErrorPSSession = Domain Controller Infrastructure Services Section: New-PSSession: Unable to connect to {0}: {1} + ErrorDCInfraServicesItem = Domain Controller Infrastructure Services Item + ErrorDCInfraServicesTable = Domain Controller Infrastructure Services Table '@ # Get-AbrADDFSHealth @@ -1121,7 +1326,7 @@ Collecting = Recopilando información de Salud de DFS de Dominio de AD en {0}. SysvolReplicationTitle = Estado de Replicación de Sysvol SysvolReplicationParagraph = La siguiente sección proporciona el estado de replicación de la carpeta SYSVOL para el dominio {0}. - DCName = Nombre del CD + DCName = Nombre del DC ReplicationStatus = Estado de Replicación GPOCount = Conteo de GPO SysvolCount = Conteo de Sysvol @@ -1132,7 +1337,7 @@ SysvolReplicationNoData = No se encontró información de DFS en {0}, deshabilitando esta sección. SysvolReplicationHealthCheck = Verificación de Salud: SysvolReplicationCorrectiveActions = Acciones Correctivas: - SysvolReplicationBP = SYSVOL es un directorio especial que reside en cada controlador de dominio (CD) dentro de un dominio. El directorio comprende carpetas que almacenan objetos de Política de Grupo (GPO) y scripts de inicio de sesión que los clientes necesitan acceder y sincronizar entre CDs. Para que estos scripts de inicio de sesión y GPO funcionen correctamente, SYSVOL debe replicarse con precisión y rapidez en todo el dominio. Asegúrate de que se implemente una replicación correcta de SYSVOL para asegurar contenido idéntico de GPO/SYSVOL para el controlador de dominio en todos los dominios de Active Directory. + SysvolReplicationBP = SYSVOL es un directorio especial que reside en cada controlador de dominio (DC) dentro de un dominio. El directorio comprende carpetas que almacenan objetos de Política de Grupo (GPO) y scripts de inicio de sesión que los clientes necesitan acceder y sincronizar entre DCs. Para que estos scripts de inicio de sesión y GPO funcionen correctamente, SYSVOL debe replicarse con precisión y rapidez en todo el dominio. Asegúrate de que se implemente una replicación correcta de SYSVOL para asegurar contenido idéntico de GPO/SYSVOL para el controlador de dominio en todos los dominios de Active Directory. SysvolContentTitle = Estado de Contenido de Sysvol SysvolContentParagraph = La siguiente sección proporciona el estado de salud de SYSVOL para el dominio {0}. SysvolContentNoData = No se encontró información de carpeta SYSVOL en {0}, deshabilitando esta sección. @@ -1148,6 +1353,14 @@ ContentCorrectiveActions = Acciones Correctivas: ContentSysvolBP = Revisa los archivos y extensiones listados arriba y asegúrate de que sean necesarios para la operación de tu dominio. Elimina cualquier archivo que no sea requerido o que parezca sospechoso. Monitorea regularmente la carpeta Sysvol para mantener un entorno de Active Directory saludable y seguro. ContentNetlogonBP = Revisa los archivos y extensiones listados arriba y asegúrate de que sean necesarios para la operación de tu dominio. Elimina cualquier archivo que no sea requerido o que parezca sospechoso. Monitorea regularmente la carpeta Netlogon para mantener un entorno de Active Directory saludable y seguro. + ErrorSysvolReplicationStatusItemSection = Sysvol Replication Status Item Section: + ErrorSysvolReplicationStatusTableSection = Sysvol Replication Status Table Section: + ErrorSysvolContentPSSession = Sysvol Content Status Section: New-PSSession: Unable to connect to {0}: {1} + ErrorSysvolHealthSection = Sysvol Health {0} Section: + ErrorSysvolHealthTableSection = Sysvol Health Table Section: + ErrorNetlogonContentPSSession = Netlogon Content Status Section: New-PSSession: Unable to connect to {0}: {1} + ErrorNetlogonHealthSection = Netlogon Health {0} Section: + ErrorNetlogonContentStatusSection = Netlogon Content Status Section: '@ # Get-AbrADKerberosAudit @@ -1179,6 +1392,10 @@ AdminHealthCheck = Verificación de Salud: AdminBestPractice = Mejor Práctica: AdminBP = Microsoft recomienda usar una contraseña única y compleja para la cuenta de Administrador integrada y rotarla regularmente (al menos cada 90 días). Considera renombrar la cuenta y deshabilitarla cuando no esté activamente en uso para reducir el riesgo de ataques de fuerza bruta o relleno de credenciales dirigidos a esta cuenta bien conocida. + ErrorUnconstrainedKerberosItem = Unconstrained Kerberos delegation + ErrorKRBTGTAccountItem = KRBTGT account Item + ErrorAdminAccountItem = ADMIN account Item + ErrorUnconstrainedKerberosSection = Unconstrained Kerberos delegation Section '@ # Get-AbrADSiteReplication @@ -1211,6 +1428,15 @@ ReplicationStatusBestPractices = Mejores Prácticas: ReplicationStatusBP = Los fallos de replicación pueden llevar a inconsistencias de objetos, credenciales obsoletas, fallos en la aplicación de Política de Grupo e problemas de autenticación en todo el entorno. Investiga y resuelve cualquier error de replicación rápidamente usando herramientas como repadmin /showrepl o la Herramienta de Estado de Replicación de Active Directory para prevenir mayor divergencia entre controladores de dominio. AutoGeneratedValue = + ErrorSiteReplicationConnectionItem = Site Replication Connection Item + ErrorSiteReplicationConnectionSection = Site Replication Connection Section + SiteLabel = Sitio: + FromLabel = Desde: + ToLabel = Hacia: + ErrorReplicationConnection = Replication Connection + ErrorPSSession = Replication Status Section: New-PSSession: Unable to connect to {0}: {1} + ErrorReplicationStatus = Replication Status + ErrorSiteReplicationStatus = Site Replication Status '@ # Get-AbrADOU @@ -1235,6 +1461,10 @@ GPOBlockedHealthCheck = Verificación de Salud: GPOBlockedCorrectiveActions = Acciones Correctivas: GPOBlockedBP = Revisa el uso de políticas aplicadas y herencia de política bloqueada en Active Directory. Las políticas aplicadas aseguran que Objetos de Política de Grupo (GPO) específicos se apliquen y no puedan ser anulados por otros GPO. La herencia de política bloqueada previene que GPO de contenedores padres se apliquen a la Unidad Organizativa (OU). Aunque estas configuraciones pueden ser útiles para mantener la aplicación de política estricta, también pueden llevar a resultados inesperados y complicar la solución de problemas. Asegúrate de que el uso de estas configuraciones se alinee con la estrategia de gestión de políticas de tu organización y no cause inadvertidamente problemas. + ErrorOUItem = Organizational Unit Item + ErrorBlockedInheritanceGPOItem = Blocked Inheritance GPO Item + ErrorBlockedInheritanceGPOSection = Blocked Inheritance GPO Section + ErrorOUSection = Organizational Unit Section '@ # Get-AbrADSecurityAssessment @@ -1291,6 +1521,19 @@ PrivilegedUsersReference = Referencia: PrivilegedUsersReferenceURL = https://www.stigviewer.com/stig/active_directory_domain/2017-12-15/finding/V-36435 ServiceAccountsAdminCountNote = ** Los Atacantes están más interesados en Cuentas de Servicio que son miembros de grupos altamente privilegiados como Domain Admins. Una forma rápida de verificar esto es enumerar todas las cuentas de usuario con el atributo AdminCount igual a 1. Esto significa que un atacante puede simplemente pedir al Active Directory todas las cuentas de usuario con un SPN y con AdminCount=1. Asegúrate de que no haya cuentas privilegiadas que tengan SPN asignado a ellas. + ErrorAccountSecurityAssessmentItem = Account Security Assessment Item + ErrorUserAccountSecurityAssessmentChart = User Account Security Assessment Chart + NoUserInfo = No se encontró información de usuarios del dominio en {0}, deshabilitando esta sección. + ErrorAccountSecurityAssessmentTable = Account Security Assessment Table + ErrorPrivilegedUsersAssessmentItem = Privileged Users Assessment Item + NoPrivilegedUserInfo = No se encontró información de Evaluación de Usuarios Privilegiados en {0}, deshabilitando esta sección. + ErrorPrivilegedUsersTable = Privileged Users Table + ErrorInactivePrivilegedAccountsItem = Inactive Privileged Accounts Item + NoInactivePrivilegedInfo = No se encontró información de Cuentas Privilegiadas Inactivas en {0}, deshabilitando esta sección. + ErrorInactivePrivilegedAccountsTable = Inactive Privileged Accounts Table + ErrorServiceAccountsAssessmentItem = Service Accounts Assessment Item + NoServiceAccountsInfo = No se encontró información de Evaluación de Cuentas de Servicio en {0}, deshabilitando esta sección. + ErrorServiceAccountsAssessmentTable = Service Accounts Assessment Table '@ # Get-AbrADGPO @@ -1388,6 +1631,23 @@ GPOSettingsParagraph = La siguiente sección proporciona detalles sobre recursos de configuración de Política de Grupo, incluyendo filtros WMI, el repositorio de Almacenamiento Central y scripts anexados a GPO. GPOHealthTitle = Salud de GPO GPOHealthParagraph = La siguiente sección destaca Objetos de Política de Grupo que pueden requerir atención, incluyendo GPO sin vincular, vacíos, aplicados y huérfanos. + ErrorGPOItem = Group Policy Objects + ErrorWMIFiltersItem = WMI Filters + ErrorWMIFiltersPSSession = WMI Filters Section: New-PSSession: Unable to connect to {0}: {1} + ErrorGPOCentralStore = GPO Central Store + ErrorGPOLogonLogoffItem = GPO with Logon/Logoff Script Item + ErrorGPOLogonLogoffSection = GPO with Logon/Logoff Script Section + ErrorGPOStartupShutdownItem = GPO with Computer Startup/Shutdown Item + ErrorGPOStartupShutdownSection = GPO with Computer Startup/Shutdown Section + ErrorUnlinkedGPOItem = Unlinked Group Policy Objects Item + ErrorUnlinkedGPOSection = Unlinked Group Policy Objects Section + ErrorEmptyGPOItem = Empty Group Policy Objects Item + ErrorEmptyGPOSection = Empty Group Policy Objects Section + ErrorEnforcedGPOItem = Enforced Group Policy Objects Item + ErrorEnforcedGPOTable = Enforced Group Policy Objects Table + ErrorOrphanedGPOPSSession = Orphaned GPO Section: New-PSSession: Unable to connect to {0}: {1} + ErrorOrphanedGPOItem = Orphaned GPO + ErrorGPOSection = Group Policy Objects Section '@ # Get-AbrADDomainController GetAbrADDomainController = ConvertFrom-StringData @' @@ -1397,7 +1657,7 @@ BestPractices = Mejores Prácticas: CorrectiveActions = Acciones Correctivas: SecurityBestPractices = Mejores Prácticas de Seguridad: - DCName = Nombre del CD + DCName = Nombre del DC Status = Estado Online = En Línea Offline = Fuera de Línea @@ -1503,6 +1763,47 @@ MissingUpdatesParagraph = La siguiente tabla proporciona un resumen de actualizaciones de Windows pendientes o faltantes detectadas en Controladores de Dominio en el dominio {0}. MissingUpdatesBestPractice = Es crítico instalar actualizaciones de seguridad para proteger tus sistemas de ataques maliciosos. Aplicar regularmente actualizaciones asegura que tus sistemas estén protegidos contra vulnerabilidades recién descubiertos. Además, instalar actualizaciones de software proporciona acceso a nuevas características y mejoras, mejorando el rendimiento y estabilidad general del sistema. Descuidar las actualizaciones puede dejar tus sistemas expuestos a amenazas potenciales y explotación. Por lo tanto, es en tu mejor interés mantener un entorno actualizado instalando rápidamente todas las actualizaciones recomendadas. DCObjectChart = Gráfico de Objeto de Controlador de Dominio + ErrorNetworkInterfacesInfo = No se puede obtener información de interfaces de red de {0} + ErrorDCNetSettingsPSSession = DC Net Settings Section: New-PSSession: Unable to connect to {0}: {1} + ErrorDCItem = Domain Controller Item + UnableToCollect = No se puede recopilar información de {0}. + ErrorDCTable = Domain Controller Table + ErrorGeneralInfoSection = General Information Section + ErrorPartitionsSection = Partitions Section + ErrorNetworkingSettingsSection = Networking Settings Section + ErrorHardwareInventoryTable = Hardware Inventory Table + ErrorDCHardwareSection = Domain Controller Hardware Section + ErrorDCSection = Domain Controller Section + ErrorDNSIPConfigPSSession = DNS IP Configuration Section: New-PSSession: Unable to connect to {0}: {1} + ErrorDNSIPConfigTableSection = Domain Controller DNS IP Configuration Table Section: + ErrorDNSIPConfigItem = DNS IP Configuration Item + ErrorDNSIPConfigSection = Domain Controller DNS IP Configuration Section: + ErrorNTDSPSSession = NTDS Section: New-PSSession: Unable to connect to {0}: {1} + ErrorNTDSItem = NTDS Item + ErrorNTDSSection = NTDS section + ErrorTimeSourcePSSession = Time Source Section: New-PSSession: Unable to connect to {0}: {1} + ErrorTimeSourceItem = Time Source Item + ErrorTimeSourceTable = Time Source Table + ErrorTimeSource = Time Source + ErrorSRVRecordsStatusItem = SRV Records Status Item + ErrorSRVRecordsStatusTable = SRV Records Status Table + ErrorSRVRecordsStatus = SRV Records Status + ErrorFileSharesPSSession = Domain Controllers File Shares Section: New-PSSession: Unable to connect to {0}: {1} + ErrorFileSharesItem = File Shares Item + ErrorFileSharesTable = File Shares Table + ErrorInstalledSoftwarePSSession = Domain Controller Installed Software Section: New-PSSession: Unable to connect to {0}: {1} + ErrorInstalledSoftwareTable = Installed Software Table + ErrorInstalledSoftwareSection = Installed Software Section + ErrorMissingPatchPSSession = Domain Controller Pending Missing Patch Section: New-PSSession: Unable to connect to {0}: {1} + ErrorMissingPatchTable = Installed Software Table + ErrorMissingPatchSection = Domain Controller Section +'@ + + # Get-AbrDiagrammer + GetAbrDiagrammer = ConvertFrom-StringData @' + GettingDiagram = Getting {0} diagram from {1}. + ErrorExportDiagram = Unable to export the {0} Diagram: + ErrorGetDiagram = Unable to get the {0} Diagram: '@ -} \ No newline at end of file +} diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADCaInfo.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADCaInfo.ps1 index 014d8ea..399e2b9 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADCaInfo.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADCaInfo.ps1 @@ -5,7 +5,7 @@ function Get-AbrADCAInfo { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -43,7 +43,7 @@ function Get-AbrADCAInfo { $TempCAInfo = [PSCustomObject]@{ Name = Remove-SpecialCharacter -String "$($rootCA.Name)RootCA" -SpecialChars '\-. ' CAName = $rootCA.Name - Label = Add-NodeIcon -Name $rootCA.Name -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -Rows $AditionalInfo + Label = Add-NodeIcon -Name $rootCA.Name -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -Rows $AditionalInfo -FontColor $Fontcolor AditionalInfo = $AditionalInfo IsRoot = $true } @@ -64,7 +64,7 @@ function Get-AbrADCAInfo { $TempCAInfo = [PSCustomObject]@{ Name = Remove-SpecialCharacter -String $RootCAName -SpecialChars '\-. ' CAName = $RootCAName - Label = Add-NodeIcon -Name $RootCAName -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -Rows $AditionalInfo + Label = Add-NodeIcon -Name $RootCAName -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -Rows $AditionalInfo -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor AditionalInfo = $AditionalInfo IsRoot = $true } @@ -86,7 +86,7 @@ function Get-AbrADCAInfo { $TempCAInfo = [PSCustomObject]@{ Name = Remove-SpecialCharacter -String $subordinateCA.Name -SpecialChars '\-. ' CAName = $subordinateCA.Name - Label = Add-NodeIcon -Name $subordinateCA.dNSHostName -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -Rows $AditionalInfo + Label = Add-NodeIcon -Name $subordinateCA.dNSHostName -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -Rows $AditionalInfo -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor AditionalInfo = $AditionalInfo IsRoot = $false } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADForestInfo.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADForestInfo.ps1 index f830c5e..41c1595 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADForestInfo.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADForestInfo.ps1 @@ -5,7 +5,7 @@ function Get-AbrADForestInfo { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -131,9 +131,9 @@ function Get-AbrADForestInfo { $TempForestInfo = [PSCustomObject]@{ Name = Remove-SpecialCharacter -String "$($ChildDomain)ChildDomain" -SpecialChars '\-. ' ChildDomainLabel = $ChildDomain - Label = Add-NodeIcon -Name $ChildDomain -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -AditionalInfo $AditionalDomainInfo -FontSize 18 + Label = Add-NodeIcon -Name $ChildDomain -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -AditionalInfo $AditionalDomainInfo -FontSize 18 -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor RootDomain = $ForestObj.RootDomain - RootDomainLabel = Add-NodeIcon -Name $ForestObj.RootDomain -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -AditionalInfo $AditionalForestInfo -FontSize 18 + RootDomainLabel = Add-NodeIcon -Name $ForestObj.RootDomain -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -AditionalInfo $AditionalForestInfo -FontSize 18 -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor ChildDomain = $ChildDomain ParentDomain = Remove-SpecialCharacter -String "$($Childs.Parent)ChildDomain" -SpecialChars '\-. ' AditionalInfo = $AditionalDomainInfo @@ -181,7 +181,7 @@ function Get-AbrADForestInfo { $TempForestInfo = [PSCustomObject]@{ Name = Remove-SpecialCharacter -String "$($ForestObj.Name)RootDomain" -SpecialChars '\-. ' - Label = Add-NodeIcon -Name $ForestObj.RootDomain -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -AditionalInfo $AditionalForestInfo -FontSize 18 + Label = Add-NodeIcon -Name $ForestObj.RootDomain -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -AditionalInfo $AditionalForestInfo -FontSize 18 -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor AditionalInfo = $AditionalForestInfo } $ForestInfo.Add($TempForestInfo) diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADTrustInfo.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADTrustInfo.ps1 index 7d4e407..5b37274 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADTrustInfo.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrADTrustInfo.ps1 @@ -5,7 +5,7 @@ function Get-AbrADTrustsInfo { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -71,9 +71,9 @@ function Get-AbrADTrustsInfo { } $TempTrustsInfo = [PSCustomObject]@{ Name = Remove-SpecialCharacter -String "$($Trust.Target)Trusts" -SpecialChars '\-. ' - Label = Add-NodeIcon -Name $Trust.Target -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -RowsOrdered $AditionalInfo + Label = Add-NodeIcon -Name $Trust.Target -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -RowsOrdered $AditionalInfo -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor Source = $Trust.CanonicalName.split('/')[0] - SourceLabel = Add-NodeIcon -Name $Trust.CanonicalName.split('/')[0] -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug + SourceLabel = Add-NodeIcon -Name $Trust.CanonicalName.split('/')[0] -IconType 'AD_Domain' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor Direction = $TrustDirectionID[[int]$Trust.TrustDirection] } $TrustsInfo.Add($TempTrustsInfo) diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagCertificateAuthority.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagCertificateAuthority.ps1 index 20d5e9f..e8e96b5 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagCertificateAuthority.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagCertificateAuthority.ps1 @@ -5,7 +5,7 @@ function Get-AbrDiagCertificateAuthority { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -32,7 +32,7 @@ function Get-AbrDiagCertificateAuthority { $CAInfo = Get-AbrADCAInfo if ($CAInfo) { - SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { + SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { SubGraph MainSubGraph -Attributes @{Label = ' ' ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } { if ($CAInfo | Where-Object { $_.IsRoot }) { @@ -42,16 +42,16 @@ function Get-AbrDiagCertificateAuthority { $CALabel = $reportTranslate.NewADDiagram.caEntRootCA } - $CARootNodes = Add-HtmlNodeTable -Name CARootNodes -ImagesObj $Images -inputObject ($CAInfo | Where-Object { $_.IsRoot }).CAName -Align 'Center' -iconType 'AD_Certificate' -ColumnSize 4 -IconDebug $IconDebug -MultiIcon -AditionalInfo ($CAInfo | Where-Object { $_.IsRoot }).AditionalInfo -FontSize 18 -TableBorderColor $Edgecolor + $CARootNodes = Add-HtmlNodeTable -Name CARootNodes -ImagesObj $Images -inputObject ($CAInfo | Where-Object { $_.IsRoot }).CAName -Align 'Center' -iconType 'AD_Certificate' -ColumnSize 4 -IconDebug $IconDebug -MultiIcon -AditionalInfo ($CAInfo | Where-Object { $_.IsRoot }).AditionalInfo -FontSize 18 -TableBorderColor $Edgecolor -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor - Node -Name 'RootCA' -Attributes @{Label = (Add-HtmlSubGraph -Name RootCA -ImagesObj $Images -TableArray $CARootNodes -Align 'Center' -IconDebug $IconDebug -Label $CALabel -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -IconType 'AD_PKI_Logo' -FontColor $Fontcolor -FontSize 24 -FontBold -TableBorderColor $Edgecolor); shape = 'plain'; fillColor = 'transparent'; fontsize = 18; fontname = 'Segoe Ui' } + Node -Name 'RootCA' -Attributes @{Label = (Add-HtmlSubGraph -Name RootCA -ImagesObj $Images -TableArray $CARootNodes -Align 'Center' -IconDebug $IconDebug -Label $CALabel -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -IconType 'AD_PKI_Logo' -FontColor $Fontcolor -FontSize 24 -FontBold -TableBorderColor $Edgecolor -TableBackgroundColor $MainGraphBGColor); shape = 'plain'; fillColor = 'transparent'; fontsize = 18; fontname = 'Segoe Ui' } } if ($CAInfo | Where-Object { -not $_.IsRoot }) { - $CASubordinateNodes = Add-HtmlNodeTable -Name CASubordinateNodes -ImagesObj $Images -inputObject ($CAInfo | Where-Object { -not $_.IsRoot }).CAName -Align 'Center' -iconType 'AD_Certificate' -ColumnSize 4 -IconDebug $IconDebug -MultiIcon -AditionalInfo ($CAInfo | Where-Object { -not $_.IsRoot }).AditionalInfo -FontSize 18 -TableBorderColor $Edgecolor + $CASubordinateNodes = Add-HtmlNodeTable -Name CASubordinateNodes -ImagesObj $Images -inputObject ($CAInfo | Where-Object { -not $_.IsRoot }).CAName -Align 'Center' -iconType 'AD_Certificate' -ColumnSize 4 -IconDebug $IconDebug -MultiIcon -AditionalInfo ($CAInfo | Where-Object { -not $_.IsRoot }).AditionalInfo -FontSize 18 -TableBorderColor $Edgecolor -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor - Node -Name 'SubordinateCA' -Attributes @{Label = (Add-HtmlSubGraph -Name SubordinateCA -ImagesObj $Images -TableArray $CASubordinateNodes -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.caEntSubCA -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -IconType 'AD_PKI_Logo' -FontColor $Fontcolor -FontSize 24 -FontBold -TableBorderColor $Edgecolor); shape = 'plain'; fillColor = 'transparent'; fontsize = 18; fontname = 'Segoe Ui' } + Node -Name 'SubordinateCA' -Attributes @{Label = (Add-HtmlSubGraph -Name SubordinateCA -ImagesObj $Images -TableArray $CASubordinateNodes -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.caEntSubCA -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -IconType 'AD_PKI_Logo' -FontColor $Fontcolor -FontSize 24 -FontBold -TableBorderColor $Edgecolor -TableBackgroundColor $MainGraphBGColor); shape = 'plain'; fillColor = 'transparent'; fontsize = 18; fontname = 'Segoe Ui' } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagForest.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagForest.ps1 index a44e6bc..8c3c83c 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagForest.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagForest.ps1 @@ -5,7 +5,7 @@ function Get-AbrDiagForest { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -32,7 +32,7 @@ function Get-AbrDiagForest { $ForestInfo = Get-AbrADForestInfo if ($ForestInfo) { - SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { + SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { SubGraph MainSubGraph -Attributes @{Label = ' ' ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } { if ($ForestInfo.ChildDomain ) { diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagReplication.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagReplication.ps1 index bcf6b9a..af21f1e 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagReplication.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagReplication.ps1 @@ -5,7 +5,7 @@ function Get-AbrDiagReplication { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -33,7 +33,7 @@ function Get-AbrDiagReplication { Write-Verbose -Message ($reportTranslate.NewADDiagram.buildingReplication -f $($ForestRoot)) $HTMLLegend = ('
{0} {1}
' -f $reportTranslate.NewADDiagram.replIntraSite, $reportTranslate.NewADDiagram.replInterSite) if ($ReplInfo) { - SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } { + SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } { SubGraph MainSubGraph -Attributes @{Label = $HTMLLegend ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } { # Collect unique sites and DCs from replication data $Sites = ($ReplInfo | Select-Object -ExpandProperty FromSite) + ($ReplInfo | Select-Object -ExpandProperty ToSite) | Select-Object -Unique | Where-Object { $_ -ne 'Unknown' } @@ -49,10 +49,10 @@ function Get-AbrDiagReplication { ($ReplInfo | Where-Object { ($_.FromServer -eq $DC -and $_.FromSite -eq $Site) -or ($_.ToServer -eq $DC -and $_.ToSite -eq $Site) }) } | Select-Object -Unique - SubGraph $SiteNodeName -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $Site -IconType 'AD_Site' -IconDebug $IconDebug -SubgraphLabel -IconWidth 35 -IconHeight 35 -Fontsize 18 -FontName 'Segoe UI' -FontColor $Fontcolor); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded'; color = 'gray' } { + SubGraph $SiteNodeName -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $Site -IconType 'AD_Site' -IconDebug $IconDebug -SubgraphLabel -IconWidth 35 -IconHeight 35 -Fontsize 18 -FontName 'Segoe UI' -FontColor $Fontcolor -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded'; color = 'gray' } { foreach ($DC in $SiteDCs) { $DCNodeName = Remove-SpecialCharacter -String $DC -SpecialChars '\-. ' - Node -Name $DCNodeName -Attributes @{Label = (Add-NodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18); shape = 'plain'; fillColor = 'transparent' } + Node -Name $DCNodeName -Attributes @{Label = (Add-NodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18 -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor); shape = 'plain'; fillColor = 'transparent' } } } } @@ -63,10 +63,10 @@ function Get-AbrDiagReplication { -not ($ReplInfo | Where-Object { ($_.FromServer -eq $DC -and $_.FromSite -ne 'Unknown') -or ($_.ToServer -eq $DC -and $_.ToSite -ne 'Unknown') }) } if ($UnknownSiteDCs) { - SubGraph UnknownSite -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $reportTranslate.NewADDiagram.replUnknownSite -IconType 'AD_Site' -IconDebug $IconDebug -SubgraphLabel -IconWidth 35 -IconHeight 35 -Fontsize 18 -FontName 'Segoe UI' -FontColor $Fontcolor); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded'; color = 'gray' } { + SubGraph UnknownSite -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $reportTranslate.NewADDiagram.replUnknownSite -IconType 'AD_Site' -IconDebug $IconDebug -SubgraphLabel -IconWidth 35 -IconHeight 35 -Fontsize 18 -FontName 'Segoe UI' -FontColor $Fontcolor -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded'; color = 'gray' } { foreach ($DC in $UnknownSiteDCs) { $DCNodeName = Remove-SpecialCharacter -String $DC -SpecialChars '\-. ' - Node -Name $DCNodeName -Attributes @{Label = (Add-NodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18); shape = 'plain'; fillColor = 'transparent' } + Node -Name $DCNodeName -Attributes @{Label = (Add-NodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18 -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor); shape = 'plain'; fillColor = 'transparent' } } } } @@ -74,7 +74,7 @@ function Get-AbrDiagReplication { # No site information - draw all DCs without grouping foreach ($DC in $AllDCs) { $DCNodeName = Remove-SpecialCharacter -String $DC -SpecialChars '\-. ' - Node -Name $DCNodeName -Attributes @{Label = (Add-NodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18); shape = 'plain'; fillColor = 'transparent' } + Node -Name $DCNodeName -Attributes @{Label = (Add-NodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18 -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor -FontColor $Fontcolor); shape = 'plain'; fillColor = 'transparent' } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagSite.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagSite.ps1 index 36bd60f..bd88d2b 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagSite.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagSite.ps1 @@ -5,7 +5,7 @@ function Get-AbrDiagSite { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -32,7 +32,7 @@ function Get-AbrDiagSite { $SitesInfo = Get-AbrADSitesInfo if ($SitesInfo) { - SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { + SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { SubGraph MainSubGraph -Attributes @{Label = ' ' ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } { if ($SitesInfo.Site) { foreach ($SitesObj in $SitesInfo) { @@ -41,7 +41,7 @@ function Get-AbrDiagSite { foreach ($Link in $SitesObj.SiteLink) { # Start - Information for each SiteLink. Example: "Name: (Pharmax-to-Acad) SiteLink (Cost: 10) (Frequency: 15 minutes)" $SiteLink = Remove-SpecialCharacter -String $Link.Name -SpecialChars '\-. ' - Node -Name $SiteLink -Attributes @{Label = (Add-HtmlTable -Name SiteLink -ALIGN 'Center' -IconDebug $IconDebug -Rows ($Link.AditionalInfo.GetEnumerator() | ForEach-Object { "$($_.key): $($_.value)" }) -ColumnSize 1 -FontSize 12); shape = 'plain'; fillColor = 'transparent' } + Node -Name $SiteLink -Attributes @{Label = (Add-HtmlTable -Name SiteLink -ALIGN 'Center' -IconDebug $IconDebug -Rows ($Link.AditionalInfo.GetEnumerator() | ForEach-Object { "$($_.key): $($_.value)" }) -ColumnSize 1 -FontSize 12 -FontColor $Fontcolor -TableBackgroundColor $MainGraphBGColor); shape = 'plain'; fillColor = 'transparent' } Edge -From $Site -To $SiteLink @{minlen = 2; arrowtail = 'none'; arrowhead = 'none' } # End - Information for each SiteLink foreach ($SiteLinkSite in $Link.Sites) { diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagSiteInventory.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagSiteInventory.ps1 index 21ae9a7..2f9f905 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagSiteInventory.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagSiteInventory.ps1 @@ -5,7 +5,7 @@ function Get-AbrDiagSiteInventory { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -32,7 +32,7 @@ function Get-AbrDiagSiteInventory { $SitesGroups = Get-AbrADSitesInventoryInfo if ($SitesGroups) { - SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { + SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { SubGraph MainSubGraph -Attributes @{Label = ' ' ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } { if (($SitesGroups | Measure-Object).Count -ge 1) { $ChildSiteSubgraphArray = [System.Collections.Generic.List[object]]::new() @@ -40,23 +40,23 @@ function Get-AbrDiagSiteInventory { if ($SiteGroupOBJ.DomainControllers.DCsArray) { - $ChildDCsNodes = Add-HtmlTable -Name ChildDCsNodes -ImagesObj $Images -Rows $SiteGroupOBJ.DomainControllers.DCsArray -ALIGN 'Center' -ColumnSize 3 -IconDebug $IconDebug -TableStyle 'dashed,rounded' -NoFontBold -FontSize 18 + $ChildDCsNodes = Add-HtmlTable -Name ChildDCsNodes -ImagesObj $Images -Rows $SiteGroupOBJ.DomainControllers.DCsArray -ALIGN 'Center' -ColumnSize 3 -IconDebug $IconDebug -TableStyle 'dashed,rounded' -NoFontBold -FontSize 18 -TableBackgroundColor $MainGraphBGColor -FontColor $Fontcolor - $ChildDCsNodesSubgraph = Add-HtmlSubGraph -Name ChildDCsNodesSubgraph -ImagesObj $Images -TableArray $ChildDCsNodes -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.DomainControllers -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -IconType 'AD_DC' -FontSize 18 + $ChildDCsNodesSubgraph = Add-HtmlSubGraph -Name ChildDCsNodesSubgraph -ImagesObj $Images -TableArray $ChildDCsNodes -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.DomainControllers -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -IconType 'AD_DC' -FontSize 18 -TableBackgroundColor $MainGraphBGColor } else { - $ChildDCsNodesSubgraph = Add-HtmlSubGraph -Name ChildDCsNodesSubgraph -ImagesObj $Images -TableArray $reportTranslate.NewADDiagram.NoSiteDC -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.DomainControllers -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -IconType 'AD_DC' -FontSize 22 + $ChildDCsNodesSubgraph = Add-HtmlSubGraph -Name ChildDCsNodesSubgraph -ImagesObj $Images -TableArray $reportTranslate.NewADDiagram.NoSiteDC -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.DomainControllers -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -IconType 'AD_DC' -FontSize 22 -TableBackgroundColor $MainGraphBGColor } if ($SiteGroupOBJ.Subnets.SubnetArray) { - $ChildSubnetsNodes = Add-HtmlTable -Name ChildSubnetsNodes -ImagesObj $Images -Rows $SiteGroupOBJ.Subnets.SubnetArray -ALIGN 'Center' -ColumnSize 3 -IconDebug $IconDebug -TableStyle 'dashed,rounded' -NoFontBold -FontSize 18 + $ChildSubnetsNodes = Add-HtmlTable -Name ChildSubnetsNodes -ImagesObj $Images -Rows $SiteGroupOBJ.Subnets.SubnetArray -ALIGN 'Center' -ColumnSize 3 -IconDebug $IconDebug -TableStyle 'dashed,rounded' -NoFontBold -FontSize 18 -TableBackgroundColor $MainGraphBGColor -FontColor $Fontcolor - $ChildSubnetsNodesSubgraph = Add-HtmlSubGraph -Name ChildSubnetsNodesSubgraph -ImagesObj $Images -TableArray $ChildSubnetsNodes -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.Subnets -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -IconType 'AD_Site_Subnet' -FontSize 22 + $ChildSubnetsNodesSubgraph = Add-HtmlSubGraph -Name ChildSubnetsNodesSubgraph -ImagesObj $Images -TableArray $ChildSubnetsNodes -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.Subnets -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -IconType 'AD_Site_Subnet' -FontSize 22 -TableBackgroundColor $MainGraphBGColor } else { - $ChildSubnetsNodesSubgraph = Add-HtmlSubGraph -Name ChildSubnetsNodesSubgraph -ImagesObj $Images -TableArray $reportTranslate.NewADDiagram.NoSiteSubnet -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.Subnets -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -IconType 'AD_Site_Subnet' -FontSize 22 + $ChildSubnetsNodesSubgraph = Add-HtmlSubGraph -Name ChildSubnetsNodesSubgraph -ImagesObj $Images -TableArray $reportTranslate.NewADDiagram.NoSiteSubnet -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.Subnets -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -IconType 'AD_Site_Subnet' -FontSize 22 -TableBackgroundColor $MainGraphBGColor } $ChildSiteSubgraph = [System.Collections.Generic.List[object]]::new() @@ -65,11 +65,11 @@ function Get-AbrDiagSiteInventory { $ChildSiteSubgraph.Add($ChildSubnetsNodesSubgraph) $ChildSiteSubgraphArray.Add( - (Add-HtmlSubGraph -Name ChildSiteSubgraphArray -ImagesObj $Images -TableArray $ChildSiteSubgraph -Align 'Center' -IconType 'AD_Site' -IconDebug $IconDebug -Label $SiteGroupOBJ.Name -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -FontSize 22) + (Add-HtmlSubGraph -Name ChildSiteSubgraphArray -ImagesObj $Images -TableArray $ChildSiteSubgraph -Align 'Center' -IconType 'AD_Site' -IconDebug $IconDebug -Label $SiteGroupOBJ.Name -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -FontSize 22 -TableBackgroundColor $MainGraphBGColor) ) } - Node -Name 'SitesTopology' -Attributes @{Label = (Add-HtmlSubGraph -Name SitesTopology -ImagesObj $Images -TableArray $ChildSiteSubgraphArray -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.Sites -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -FontSize 22); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = 'Segoe Ui' } + Node -Name 'SitesTopology' -Attributes @{Label = (Add-HtmlSubGraph -Name SitesTopology -ImagesObj $Images -TableArray $ChildSiteSubgraphArray -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.Sites -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -TableBorderColor 'gray' -FontColor $Fontcolor -FontSize 22 -TableBackgroundColor $MainGraphBGColor); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = 'Segoe Ui' } } else { Node -Name NoSites -Attributes @{Label = $reportTranslate.NewADDiagram.NoSites; shape = 'rectangle'; labelloc = 'c'; fixedsize = $true; width = '3'; height = '2'; fillColor = 'transparent'; penwidth = 0 } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagTrust.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagTrust.ps1 index 6cbc704..d623288 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagTrust.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagTrust.ps1 @@ -5,7 +5,7 @@ function Get-AbrDiagTrust { .DESCRIPTION Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -32,13 +32,13 @@ function Get-AbrDiagTrust { $TrustsInfo = Get-AbrADTrustsInfo if ($TrustsInfo) { - SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold); fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { + SubGraph ForestSubGraph -Attributes @{Label = (Add-HtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold -TableBackgroundColor $MainGraphBGColor -CellBackgroundColor $MainGraphBGColor); fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } { SubGraph MainSubGraph -Attributes @{Label = ' ' ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } { if (($TrustsInfo.Name | Measure-Object).count -gt 10) { $ChildDomainsNodes = $TrustsInfo.Label - Node -Name 'TrustDestinations' -Attributes @{Label = (Add-HtmlSubGraph -Name TrustDestinations -ImagesObj $Images -TableArray $ChildDomainsNodes -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.TrustRelationships -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -FontSize 22 -FontName 'Segoe UI' -TableBorderColor $Edgecolor -FontColor $Fontcolor); shape = 'plain'; fillColor = 'transparent'; fontsize = 18; fontname = 'Segoe Ui' } + Node -Name 'TrustDestinations' -Attributes @{Label = (Add-HtmlSubGraph -Name TrustDestinations -ImagesObj $Images -TableArray $ChildDomainsNodes -Align 'Center' -IconDebug $IconDebug -Label $reportTranslate.NewADDiagram.TrustRelationships -LabelPos 'top' -TableStyle 'dashed,rounded' -TableBorder '1' -ColumnSize 3 -FontSize 22 -FontName 'Segoe UI' -TableBorderColor $Edgecolor -FontColor $Fontcolor -TableBackgroundColor $MainGraphBGColor); shape = 'plain'; fillColor = 'transparent'; fontsize = 18; fontname = 'Segoe Ui' } $ForestRootDomain = Remove-SpecialCharacter -String "$($TrustsInfo.Source[0])ForestRoot" -SpecialChars '\-. ' Node -Name $ForestRootDomain -Attributes @{Label = $TrustsInfo.SourceLabel[0]; shape = 'plain'; fillColor = 'transparent' } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagrammer.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagrammer.ps1 index 4885b3a..ee7bca4 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagrammer.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/Get-AbrDiagrammer.ps1 @@ -61,7 +61,7 @@ function Get-AbrDiagrammer { ) begin { - Write-PScriboMessage -Message "Getting $($DiagramType) diagram from $DomainController ." + Write-PScriboMessage -Message ($reportTranslate.GetAbrDiagrammer.GettingDiagram -f $DiagramType, $DomainController) } process { @@ -136,7 +136,7 @@ function Get-AbrDiagrammer { if (Test-Path -Path $FilePath -PathType Leaf) { $FilePath } else { - Write-PScriboMessage -IsWarning -Message "Unable to export the $DiagramType Diagram: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrDiagrammer.ErrorExportDiagram -f $DiagramType) $($_.Exception.Message)" } } else { Write-Information "Saved '$FileName' diagram to '$($OutputFolderPath)'." -InformationAction Continue @@ -145,10 +145,10 @@ function Get-AbrDiagrammer { } } } catch { - Write-PScriboMessage -IsWarning -Message "Unable to export the $DiagramType Diagram: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrDiagrammer.ErrorExportDiagram -f $DiagramType) $($_.Exception.Message)" } } catch { - Write-PScriboMessage -IsWarning -Message "Unable to get the $DiagramType Diagram: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrDiagrammer.ErrorGetDiagram -f $DiagramType) $($_.Exception.Message)" } } end {} diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/New-AbrADDiagram.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/New-AbrADDiagram.ps1 index c8e9aa8..3896270 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/New-AbrADDiagram.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Diagram/New-AbrADDiagram.ps1 @@ -71,7 +71,7 @@ function New-AbrADDiagram { .PARAMETER WatermarkColor Allow to specified the color used for the watermark text. Default: #565656. .NOTES - Version: 0.9.12 + Version: 1.0.0 Author(s): Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -511,7 +511,7 @@ function New-AbrADDiagram { Write-Verbose $reportTranslate.NewADDiagram.genDiagramSignature # 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 ); fontsize = 22; penwidth = 0; labelloc = 't'; labeljust = 'c' } { + 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' } { Write-Verbose $reportTranslate.NewADDiagram.genDiagramMain $script:ForestRoot = $ADSystem.Name.ToString().ToUpper() diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Gui/Start-AsBuiltReportMSAD.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Gui/Start-AsBuiltReportMSAD.ps1 new file mode 100644 index 0000000..42a9b65 --- /dev/null +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Gui/Start-AsBuiltReportMSAD.ps1 @@ -0,0 +1,1408 @@ +#Requires -RunAsAdministrator + +using namespace GliderUI +using namespace GliderUI.Avalonia +using namespace GliderUI.Avalonia.Controls +using namespace GliderUI.Avalonia.Platform.Storage +using namespace GliderUI.Avalonia.Media + +function Start-AsBuiltReportMSAD { + <# + .SYNOPSIS + GUI launcher for AsBuiltReport.Microsoft.AD — runs entirely in PowerShell 7. + .DESCRIPTION + A PowerShell 7.4+ desktop GUI (GliderUI / Avalonia) that collects connection, + output and report options, then generates the Microsoft AD As-Built Report by + calling New-AsBuiltReport directly — no child PS5.1 process required. + .NOTES + Requirements: + PowerShell 7.4+ — to run this script + GliderUI 0.2.0+ (auto-installed on first run) — Install-PSResource -Name GliderUI -Version 0.2.0 -Scope CurrentUser -TrustRepository + AsBuiltReport.Core — Install-PSResource -Name AsBuiltReport.Core + AsBuiltReport.Microsoft.AD — Install-PSResource -Name AsBuiltReport.Microsoft.AD + #> + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope = 'Function')] + + [CmdletBinding()] + param() + + if ($PSVersionTable.PSVersion.Major -lt 7 -or ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -lt 4)) { + throw "Start-AsBuiltReportMSAD requires PowerShell 7.4+. Current version: $($PSVersionTable.PSVersion)" + } + + # ── Bootstrap GliderUI ────────────────────────────────────────────────────── + $requiredGliderUIVersion = [version]'0.2.0' + + if (-not (Get-Module -ListAvailable -Name GliderUI)) { + Write-Host 'GliderUI not found — installing from PSGallery…' -ForegroundColor Cyan + Install-PSResource -Name GliderUI -Version $requiredGliderUIVersion -Scope CurrentUser -TrustRepository + } + + $gliderMod = Get-Module -ListAvailable -Name GliderUI | + Sort-Object Version -Descending | + Select-Object -First 1 + + if ($null -eq $gliderMod -or $gliderMod.Version -lt $requiredGliderUIVersion) { + $found = if ($null -eq $gliderMod) { 'not installed' } else { "v$($gliderMod.Version)" } + Write-Error ("GliderUI v{0} or later is required (found: {1}).`nInstall with: Install-PSResource -Name GliderUI -Version {0} -Scope CurrentUser -TrustRepository`nThen restart PowerShell." -f $requiredGliderUIVersion, $found) + return + } + + Import-Module GliderUI -Force + + # Thread-safe store shared between the main runspace and the report runspace + $syncHash = [Hashtable]::Synchronized(@{ + CancelRequested = $false + IsBusy = $false + }) + + # ── UI Helper Functions ───────────────────────────────────────────────────── + function New-SectionTitle ([string]$Text) { + $tb = [TextBlock]::new() + $tb.Text = $Text + $tb.FontSize = 13 + $tb.FontWeight = 'SemiBold' + $tb.Margin = '0,18,0,6' + return $tb + } + + function New-FormRow ([string]$Label, $Control, [int]$LabelWidth = 185) { + $row = [StackPanel]::new() + $row.Orientation = 'Horizontal' + $row.Spacing = 10 + $row.Margin = '0,3,0,3' + + $lbl = [TextBlock]::new() + $lbl.Text = $Label + $lbl.Width = $LabelWidth + $lbl.VerticalAlignment = 'Center' + $lbl.FontSize = 12 + + $row.Children.Add($lbl) + $row.Children.Add($Control) + return $row + } + + function New-InlineLabel ([string]$Text) { + $tb = [TextBlock]::new() + $tb.Text = $Text + $tb.VerticalAlignment = 'Center' + $tb.Margin = '8,0,0,0' + $tb.FontSize = 12 + return $tb + } + + # Wraps a password TextBox with an eye-toggle button. + function New-PasswordRow ($PasswordTextBox) { + $btn = [Button]::new() + $btn.Content = '👁' + $btn.Padding = '6,2,6,2' + $btn.VerticalAlignment = 'Center' + $btn.AddClick({ + if ($PasswordTextBox.PasswordChar -eq [char]0) { + $PasswordTextBox.PasswordChar = [char]'●' + } else { + $PasswordTextBox.PasswordChar = [char]0 + } + }.GetNewClosure()) + + $row = [StackPanel]::new() + $row.Orientation = 'Horizontal' + $row.Spacing = 6 + $row.Children.Add($PasswordTextBox) + $row.Children.Add($btn) + return $row + } + + function New-DrawerMenuItem ([string]$Title, [string]$IconGeometry, $Page, $NavigationPage) { + $icon = [PathIcon]::new() + $icon.Data = [Geometry]::Parse($IconGeometry) + + $textBlock = [TextBlock]::new() + $textBlock.Text = $Title + $textBlock.VerticalAlignment = 'Center' + + $panel = [StackPanel]::new() + $panel.Orientation = 'Horizontal' + $panel.Spacing = 8 + $panel.Children.Add($icon) + $panel.Children.Add($textBlock) + + $button = [Button]::new() + $button.HorizontalAlignment = 'Stretch' + $button.Padding = 12 + $button.Background = [SolidColorBrush]::new([Colors]::Transparent, 1) + $button.Content = $panel + $button.AddClick({ + param($argumentList) + $targetPage, $navPage = $argumentList + $navPage.ReplaceAsync($targetPage) | Out-Null + }, @($Page, $NavigationPage)) + return $button + } + + # ── Connection Controls ───────────────────────────────────────────────────── + $txtServer = [TextBox]::new() + $txtServer.Width = 240 + $txtServer.Watermark = 'dc01.contoso.com' + + $txtUser = [TextBox]::new() + $txtUser.Width = 200 + $txtUser.Watermark = 'DOMAIN\username or user@domain' + + $txtPass = [TextBox]::new() + $txtPass.Width = 200 + $txtPass.Watermark = 'Password' + try { $txtPass.PasswordChar = [char]'●' } catch { Out-Null } + + # ── Saved Connections ─────────────────────────────────────────────────────── + $savedConnPath = if ($IsWindows) { + [System.IO.Path]::Combine($env:USERPROFILE, 'AsBuiltReport', 'MSAD-SavedConnections.json') + } else { + [System.IO.Path]::Combine($env:HOME, 'AsBuiltReport', 'MSAD-SavedConnections.json') + } + + $loadSavedConns = { + if (Test-Path $savedConnPath) { + try { + $raw = Get-Content -Path $savedConnPath -Raw -Encoding UTF8 | ConvertFrom-Json + if ($null -eq $raw) { return @() } + return @($raw) + } catch { return @() } + } + return @() + }.GetNewClosure() + + $saveSavedConns = { + param ([array]$Connections) + $dir = Split-Path $savedConnPath -Parent + if (-not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null } + if ($Connections.Count -eq 0) { + '[]' | Set-Content -Path $savedConnPath -Encoding UTF8 + } else { + $Connections | ConvertTo-Json -Depth 3 | Set-Content -Path $savedConnPath -Encoding UTF8 + } + }.GetNewClosure() + + $cboSavedConn = [ComboBox]::new() + $cboSavedConn.Width = 262 + + $refreshSavedConnCombo = { + $cboSavedConn.Items.Clear() + foreach ($c in (& $loadSavedConns)) { + $cboSavedConn.Items.Add("$($c.Server) ($($c.Username))") | Out-Null + } + }.GetNewClosure() + & $refreshSavedConnCombo + + $cboSavedConn.AddSelectionChanged({ + $idx = $cboSavedConn.SelectedIndex + if ($idx -lt 0) { return } + $conns = & $loadSavedConns + if ($idx -ge $conns.Count) { return } + $sel = $conns[$idx] + $txtServer.Text = $sel.Server + $txtUser.Text = $sel.Username + $txtPass.Text = '' + }) + + $btnSaveConn = [Button]::new() + $btnSaveConn.Content = '💾 Save Connection' + $btnSaveConn.AddClick({ + $srv = $txtServer.Text.Trim() + $usr = $txtUser.Text.Trim() + if ([string]::IsNullOrWhiteSpace($srv) -or [string]::IsNullOrWhiteSpace($usr)) { + $syncHash.lblConfigStatus.Text = '⚠ Enter a Domain Controller FQDN and username before saving.' + return + } + $conns = [System.Collections.ArrayList]@() + foreach ($c in (& $loadSavedConns)) { $conns.Add($c) | Out-Null } + $dup = $conns | Where-Object { $_.Server -eq $srv -and $_.Username -eq $usr } + if (-not $dup) { + $conns.Add([PSCustomObject]@{ Server = $srv; Username = $usr }) | Out-Null + & $saveSavedConns -Connections @($conns) + & $refreshSavedConnCombo + $syncHash.lblConfigStatus.Text = "✅ Connection saved: $srv ($usr)" + } else { + $syncHash.lblConfigStatus.Text = "ℹ Connection already exists: $srv ($usr)" + } + }) + + $btnDeleteConn = [Button]::new() + $btnDeleteConn.Content = '🗑 Delete' + $btnDeleteConn.AddClick({ + $idx = $cboSavedConn.SelectedIndex + if ($idx -lt 0) { + $syncHash.lblConfigStatus.Text = '⚠ Select a saved connection to delete.' + return + } + $conns = [System.Collections.ArrayList]@() + foreach ($c in (& $loadSavedConns)) { $conns.Add($c) | Out-Null } + if ($idx -ge $conns.Count) { return } + $removed = $conns[$idx] + $conns.RemoveAt($idx) + & $saveSavedConns -Connections @($conns) + $cboSavedConn.SelectedIndex = -1 + & $refreshSavedConnCombo + $syncHash.lblConfigStatus.Text = "🗑 Deleted: $($removed.Server) ($($removed.Username))" + }) + + $savedConnActionsRow = [StackPanel]::new() + $savedConnActionsRow.Orientation = 'Horizontal' + $savedConnActionsRow.Spacing = 6 + $savedConnActionsRow.Children.Add($btnSaveConn) + $savedConnActionsRow.Children.Add($btnDeleteConn) + + # ── Output Controls ───────────────────────────────────────────────────────── + $chkHTML = [CheckBox]::new(); $chkHTML.Content = 'HTML'; $chkHTML.IsChecked = $true + $chkWord = [CheckBox]::new(); $chkWord.Content = 'Word'; $chkWord.IsChecked = $false + $chkText = [CheckBox]::new(); $chkText.Content = 'Text'; $chkText.IsChecked = $false + + $fmtPanel = [StackPanel]::new() + $fmtPanel.Orientation = 'Horizontal' + $fmtPanel.Spacing = 20 + $fmtPanel.Children.Add($chkHTML) + $fmtPanel.Children.Add($chkWord) + $fmtPanel.Children.Add($chkText) + + $txtOutput = [TextBox]::new() + $txtOutput.Width = 240 + $txtOutput.Text = if ($IsWindows) { + [System.IO.Path]::Combine($env:USERPROFILE, 'Documents', 'AsBuiltReport') + } else { + [System.IO.Path]::Combine($env:HOME, 'AsBuiltReport') + } + + $btnBrowse = [Button]::new() + $btnBrowse.Content = 'Browse…' + $btnBrowse.AddClick({ + try { + $btnBrowse.IsEnabled = $false + $storageProvider = [Window]::GetTopLevel($btnBrowse).StorageProvider + if ($null -eq $storageProvider) { + Write-Host 'Storage provider not available.' -ForegroundColor Yellow + return + } + $options = [FolderPickerOpenOptions]::new() + $options.Title = 'Select Output Folder Path' + $folders = $storageProvider.OpenFolderPickerAsync($options).WaitForCompleted() + if ($folders -and $folders.Count -gt 0) { + $txtOutput.Text = $folders[0].Path.LocalPath + } + } catch { + Write-Host "Folder picker error: $_" -ForegroundColor Red + } finally { + $btnBrowse.IsEnabled = $true + } + }) + + $outputPathRow = [StackPanel]::new() + $outputPathRow.Orientation = 'Horizontal' + $outputPathRow.Spacing = 8 + $outputPathRow.Children.Add($txtOutput) + $outputPathRow.Children.Add($btnBrowse) + + $cboLang = [ComboBox]::new() + $cboLang.Width = 100 + $cboLang.Items.Add('en-US') | Out-Null + $cboLang.Items.Add('es-ES') | Out-Null + $cboLang.SelectedIndex = 0 + + # ── Report Name ───────────────────────────────────────────────────────────── + $txtReportName = [TextBox]::new() + $txtReportName.Width = 300 + $txtReportName.Text = 'Microsoft Active Directory As Built Report' + $txtReportName.Watermark = 'Output filename (without extension)' + + # ── Options Controls ──────────────────────────────────────────────────────── + # Options matching AsBuiltReport.Microsoft.AD.json > Options + $swDiagrams = [ToggleSwitch]::new(); $swDiagrams.IsChecked = $true + $swExportDiagrams = [ToggleSwitch]::new(); $swExportDiagrams.IsChecked = $true + $swTimestamp = [ToggleSwitch]::new(); $swTimestamp.IsChecked = $false + $swWinRMSSL = [ToggleSwitch]::new(); $swWinRMSSL.IsChecked = $false + $swWinRMFallback = [ToggleSwitch]::new(); $swWinRMFallback.IsChecked = $true + + $cboDiagramTheme = [ComboBox]::new() + $cboDiagramTheme.Width = 120 + @('White', 'Black', 'Neon') | ForEach-Object { $cboDiagramTheme.Items.Add($_) | Out-Null } + $cboDiagramTheme.SelectedIndex = 0 + + $cboPSDefaultAuth = [ComboBox]::new() + $cboPSDefaultAuth.Width = 160 + @('Negotiate', 'Kerberos', 'NTLM', 'Default') | ForEach-Object { $cboPSDefaultAuth.Items.Add($_) | Out-Null } + $cboPSDefaultAuth.SelectedIndex = 0 + + # ── InfoLevel Controls — matching AsBuiltReport.Microsoft.AD.json > InfoLevel ─ + function New-LevelCombo { + $cbo = [ComboBox]::new() + $cbo.Width = 160 + @('0 - Off', '1 - Enabled', '2 - Adv Summary', '3 - Detailed') | ForEach-Object { $cbo.Items.Add($_) | Out-Null } + $cbo.SelectedIndex = 1 + return $cbo + } + + $cboLvlForest = New-LevelCombo; $cboLvlForest.SelectedIndex = 2 # default 2 per JSON + $cboLvlDomain = New-LevelCombo; $cboLvlDomain.SelectedIndex = 2 # default 2 per JSON + $cboLvlDNS = New-LevelCombo; $cboLvlDNS.SelectedIndex = 1 # default 1 per JSON + + # ── Progress Bar & Log ────────────────────────────────────────────────────── + $progressBar = [ProgressBar]::new() + $progressBar.IsIndeterminate = $true + $progressBar.IsVisible = $false + $progressBar.Margin = '0,8,0,4' + $syncHash.progressBar = $progressBar + + $txtLog = [TextBox]::new() + $txtLog.IsReadOnly = $true + $txtLog.AcceptsReturn = $true + $txtLog.Height = 220 + $txtLog.FontSize = 16 + $txtLog.TextWrapping = 'Wrap' + $txtLog.Watermark = 'Output log will appear here…' + try { $txtLog.FontFamily = 'Consolas,Courier New,Monospace' } catch { Out-Null } + $syncHash.txtLog = $txtLog + + $chkVerbose = [CheckBox]::new() + $chkVerbose.Content = '🔍Verbose' + $chkVerbose.IsChecked = $false + $chkVerbose.HorizontalAlignment = 'Right' + $chkVerbose.VerticalAlignment = 'Center' + $chkVerbose.Margin = '0,0,8,0' + $syncHash.chkVerbose = $chkVerbose + + # ── Action Buttons ────────────────────────────────────────────────────────── + $btnCancel = [Button]::new() + $btnCancel.Content = '✕ Cancel' + $btnCancel.IsVisible = $false + $btnCancel.Margin = '0,0,0,0' + $btnCancel.AddClick({ + $syncHash.CancelRequested = $true + $rps = $syncHash.reportPS + if ($null -ne $rps) { $rps.Stop() } + }) + $syncHash.btnCancel = $btnCancel + + $btnExportLog = [Button]::new() + $btnExportLog.Content = '💾 Export Log' + $btnExportLog.Margin = '0,0,0,0' + $btnExportLog.AddClick({ + try { + $btnExportLog.IsEnabled = $false + $logText = $syncHash.txtLog.Text + if ([string]::IsNullOrWhiteSpace($logText)) { + $syncHash.lblConfigStatus.Text = '⚠ Log is empty — nothing to export.' + return + } + $storageProvider = [Window]::GetTopLevel($btnExportLog).StorageProvider + if ($null -eq $storageProvider) { return } + $saveOpts = [FilePickerSaveOptions]::new() + $saveOpts.Title = 'Export Output Log' + $saveOpts.SuggestedFileName = "MSAD-AsBuiltReport-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" + $file = $storageProvider.SaveFilePickerAsync($saveOpts).WaitForCompleted() + if ($null -ne $file) { + $logText | Set-Content -Path $file.Path.LocalPath -Encoding UTF8 + $syncHash.lblConfigStatus.Text = "✅ Log exported: $(Split-Path $file.Path.LocalPath -Leaf)" + } + } catch { + $syncHash.lblConfigStatus.Text = "❌ Log export failed: $_" + } finally { + $btnExportLog.IsEnabled = $true + } + }) + + $btnGenerate = [Button]::new() + $btnGenerate.Content = '▶ Generate Report' + $btnGenerate.HorizontalAlignment = 'Stretch' + $btnGenerate.HorizontalContentAlignment = 'Center' + $btnGenerate.FontSize = 14 + $btnGenerate.FontWeight = 'SemiBold' + $btnGenerate.Margin = '0,22,0,0' + $btnGenerate.Classes.Add('accent') + $syncHash.btnGenerate = $btnGenerate + + # ── Generate Callback ──────────────────────────────────────────────────────── + $generateCallback = [EventCallback]::new() + $generateCallback.RunspaceMode = 'RunspacePoolAsyncUI' + $generateCallback.DisabledControlsWhileProcessing = $btnGenerate + + $generateCallback.ArgumentList = @{ + SyncHash = $syncHash + Server = $txtServer + Username = $txtUser + Password = $txtPass + ReportName = $txtReportName + OutPath = $txtOutput + FmtHTML = $chkHTML + FmtWord = $chkWord + FmtText = $chkText + Lang = $cboLang + DiagramTheme = $cboDiagramTheme + PSDefaultAuth = $cboPSDefaultAuth + Diagrams = $swDiagrams + ExportDiagrams = $swExportDiagrams + Timestamp = $swTimestamp + WinRMSSL = $swWinRMSSL + WinRMFallback = $swWinRMFallback + LvlForest = $cboLvlForest + LvlDomain = $cboLvlDomain + LvlDNS = $cboLvlDNS + Verbose = $chkVerbose + # ConfigPath and AbrConfigPath are late-bound below after TextBox creation + } + + $generateCallback.ScriptBlock = { + param ($ui) + + $sh = $ui.SyncHash + if ($sh.IsBusy) { + $sh.lblConfigStatus.Text = '⚠ Another operation is already running. Please wait.' + return + } + $sh.IsBusy = $true + $sh.CancelRequested = $false + $sh.progressBar.IsVisible = $true + $sh.btnCancel.IsVisible = $true + $sh.txtLog.Text = '' + + $verboseEnabled = $ui.Verbose.IsChecked -eq $true + + function Write-Logging ([string]$Msg, [string]$Level = '', [bool]$AddTimestamp = $false) { + $ts = Get-Date -Format 'HH:mm:ss' + if ($Level -eq '') { + if ($AddTimestamp) { + $sh.txtLog.Text += "[$ts] $Msg`n" + } else { + $sh.txtLog.Text += "$Msg`n" + } + } else { + if ($AddTimestamp) { + $sh.txtLog.Text += "[$ts][$Level] $Msg`n" + } else { + $sh.txtLog.Text += "[$Level] $Msg`n" + } + } + $sh.txtLog.CaretIndex = $sh.txtLog.Text.Length + } + + function Build-MSADConfigObject { + param ( + [string]$ReportName, + [string]$Lang, + [string]$Theme, + [bool]$EnableDiagrams, + [bool]$ExportDiagrams, + [string]$PSDefaultAuthentication, + [bool]$WinRMSSL, + [bool]$WinRMFallbackToNoSSL, + [int]$LvlForest, + [int]$LvlDomain, + [int]$LvlDNS + ) + return [ordered]@{ + Report = [ordered]@{ + Name = $ReportName + Version = '1.0' + Status = 'Released' + Language = $Lang + ShowCoverPageImage = $true + ShowTableOfContents = $true + ShowHeaderFooter = $true + ShowTableCaptions = $true + } + Options = [ordered]@{ + ShowExecutionTime = $false + ShowDefinitionInfo = $false + PSDefaultAuthentication = $PSDefaultAuthentication + Exclude = [ordered]@{ Domains = @(); DCs = @() } + Include = [ordered]@{ Domains = @() } + WinRMSSL = $WinRMSSL + WinRMFallbackToNoSSL = $WinRMFallbackToNoSSL + WinRMSSLPort = 5986 + WinRMPort = 5985 + EnableDiagrams = $EnableDiagrams + EnableDiagramDebug = $false + DiagramTheme = $Theme + DiagramObjDebug = $false + DiagramWaterMark = '' + DiagramType = [ordered]@{ + CertificateAuthority = $true + Forest = $true + Replication = $true + Sites = $true + SitesInventory = $true + Trusts = $true + } + ExportDiagrams = $ExportDiagrams + ExportDiagramsFormat = @('pdf') + EnableDiagramSignature = $false + SignatureAuthorName = '' + SignatureCompanyName = '' + JobsTimeOut = 900 + DCStatusPingCount = 2 + } + InfoLevel = [ordered]@{ + Forest = $LvlForest + Domain = $LvlDomain + DNS = $LvlDNS + } + HealthCheck = [ordered]@{ + Domain = [ordered]@{ + GMSA = $true + GPO = $true + Backup = $true + DFS = $true + SPN = $true + DuplicateObject = $true + Security = $true + BestPractice = $true + } + DomainController = [ordered]@{ + Diagnostic = $true + Services = $true + Software = $true + BestPractice = $true + } + Site = [ordered]@{ + Replication = $true + BestPractice = $true + } + DNS = [ordered]@{ + Aging = $true + DP = $true + Zones = $true + BestPractice = $true + } + CA = [ordered]@{ + Status = $true + Statistics = $true + BestPractice = $true + } + } + } + } + + # ── Collect values ──────────────────────────────────────────────────────── + $server = $ui.Server.Text.Trim() + $username = $ui.Username.Text.Trim() + $password = $ui.Password.Text + $reportName = $ui.ReportName.Text.Trim() + $outPath = $ui.OutPath.Text.Trim() + $lang = [string]$ui.Lang.SelectedItem + $configPath = $ui.ConfigPath.Text.Trim() + $abrConfigPath = $ui.AbrConfigPath.Text.Trim() + + $formats = @() + if ($ui.FmtHTML.IsChecked -eq $true) { $formats += 'Html' } + if ($ui.FmtWord.IsChecked -eq $true) { $formats += 'Word' } + if ($ui.FmtText.IsChecked -eq $true) { $formats += 'Text' } + if ($formats.Count -eq 0) { $formats = @('Html') } + + $enableDiagrams = [bool]$ui.Diagrams.IsChecked + $exportDiagrams = [bool]$ui.ExportDiagrams.IsChecked + $addTimestamp = [bool]$ui.Timestamp.IsChecked + $winRMSSL = [bool]$ui.WinRMSSL.IsChecked + $winRMFallback = [bool]$ui.WinRMFallback.IsChecked + $psDefaultAuth = [string]$ui.PSDefaultAuth.SelectedItem + $diagramTheme = [string]$ui.DiagramTheme.SelectedItem + + # Parse InfoLevel (first char = number) + $lvlForest = [int]([string]$ui.LvlForest.SelectedItem).Substring(0, 1) + $lvlDomain = [int]([string]$ui.LvlDomain.SelectedItem).Substring(0, 1) + $lvlDNS = [int]([string]$ui.LvlDNS.SelectedItem).Substring(0, 1) + + # ── Validation ──────────────────────────────────────────────────────────── + if ([string]::IsNullOrWhiteSpace($server)) { + Write-Logging 'Domain Controller FQDN is required.' 'ERROR' + $sh.progressBar.IsVisible = $false; $sh.btnCancel.IsVisible = $false; $sh.IsBusy = $false; return + } + if ([string]::IsNullOrWhiteSpace($username)) { + Write-Logging 'Username is required.' 'ERROR' + $sh.progressBar.IsVisible = $false; $sh.btnCancel.IsVisible = $false; $sh.IsBusy = $false; return + } + if ([string]::IsNullOrWhiteSpace($password)) { + Write-Logging 'Password is required.' 'ERROR' + $sh.progressBar.IsVisible = $false; $sh.btnCancel.IsVisible = $false; $sh.IsBusy = $false; return + } + if ([string]::IsNullOrWhiteSpace($outPath)) { + $outPath = if ($IsWindows) { + [System.IO.Path]::Combine($env:USERPROFILE, 'Documents', 'AsBuiltReport') + } else { + [System.IO.Path]::Combine($env:HOME, 'AsBuiltReport') + } + } + if (-not (Test-Path $outPath)) { + New-Item -Path $outPath -ItemType Directory -Force | Out-Null + Write-Logging "Created output folder: $outPath" + } + if ([string]::IsNullOrWhiteSpace($reportName)) { $reportName = 'Microsoft Active Directory As Built Report' } + if ([string]::IsNullOrWhiteSpace($abrConfigPath)) { + Write-Logging 'AsBuiltReport config file path is required. Use the "⚙️ AsBuiltReport Global Settings" expander to create one.' 'ERROR' + $sh.progressBar.IsVisible = $false; $sh.btnCancel.IsVisible = $false; $sh.IsBusy = $false; return + } + if (-not (Test-Path $abrConfigPath)) { + Write-Logging "AsBuiltReport config file not found: $abrConfigPath" 'ERROR' + $sh.progressBar.IsVisible = $false; $sh.btnCancel.IsVisible = $false; $sh.IsBusy = $false; return + } + + Write-Logging "Target : $server" + Write-Logging "User : $username" + Write-Logging "Formats : $($formats -join ', ')" + Write-Logging "Output : $outPath" + + # ── Import modules in this runspace ─────────────────────────────────────── + Write-Logging 'Loading AsBuiltReport modules…' + try { + Import-Module AsBuiltReport.Core, AsBuiltReport.Microsoft.AD -Force -ErrorAction Stop + } catch { + Write-Logging "Failed to load modules: $_" 'ERROR' + $sh.progressBar.IsVisible = $false; $sh.btnCancel.IsVisible = $false; $sh.IsBusy = $false; return + } + + # ── Resolve ReportConfigFilePath ────────────────────────────────────────── + # Use the saved config file from Config Management if provided; + # otherwise build a temp config from the current UI control values. + $tempConfig = $null + if (-not [string]::IsNullOrWhiteSpace($configPath) -and (Test-Path $configPath)) { + $reportConfigFilePath = $configPath + Write-Logging "Using config file: $(Split-Path $configPath -Leaf)" + } else { + $configObj = Build-MSADConfigObject ` + -ReportName $reportName ` + -Lang $lang ` + -Theme $diagramTheme ` + -EnableDiagrams $enableDiagrams ` + -ExportDiagrams $exportDiagrams ` + -PSDefaultAuthentication $psDefaultAuth ` + -WinRMSSL $winRMSSL ` + -WinRMFallbackToNoSSL $winRMFallback ` + -LvlForest $lvlForest ` + -LvlDomain $lvlDomain ` + -LvlDNS $lvlDNS + + $tempConfig = [System.IO.Path]::Combine($env:TEMP, "MSAD_cfg_$(New-Guid).json") + $configObj | ConvertTo-Json -Depth 6 | Set-Content -Path $tempConfig -Encoding UTF8 + $reportConfigFilePath = $tempConfig + Write-Logging 'Using config built from UI controls.' + } + + # ── Invoke New-AsBuiltReport ────────────────────────────────────────────── + try { + if ($sh.CancelRequested) { Write-Logging 'Cancelled before start.' 'WARN'; return } + + Write-Logging 'Starting report generation…' + + $securePassword = ConvertTo-SecureString $password -AsPlainText -Force + $credential = [PSCredential]::new($username, $securePassword) + + $params = @{ + Report = 'Microsoft.AD' + Target = $server + Credential = $credential + OutputFolderPath = $outPath + Format = $formats + ReportConfigFilePath = $reportConfigFilePath + AsBuiltConfigFilePath = $abrConfigPath + } + + if ($addTimestamp) { $params['Timestamp'] = $true } + if ($verboseEnabled) { $params['Verbose'] = $true } + + Write-Logging "Using AsBuiltReport config: $(Split-Path $abrConfigPath -Leaf)" + + New-AsBuiltReport @params *>&1 | ForEach-Object { + $line = if ($_ -is [System.Management.Automation.ErrorRecord]) { + Write-Logging "$($_.Exception.Message)" 'ERROR' + return + } elseif ($_ -is [System.Management.Automation.WarningRecord]) { + Write-Logging "$($_.Message)" 'WARN' + return + } elseif ($_ -is [System.Management.Automation.VerboseRecord]) { + if ($verboseEnabled) { + Write-Logging "$($_.Message)" 'VERBOSE' + } + return + } elseif ($_ -is [System.Management.Automation.InformationRecord]) { + "$($_.MessageData)" + } else { + "$_" + } + if (-not [string]::IsNullOrWhiteSpace($line)) { + Write-Logging $line + } + } + Write-Logging -Msg "✅ Report generation completed. Files saved to: $outPath" -Level '' -AddTimestamp $true + } catch { + Write-Logging $_.Exception.Message 'ERROR' + if ($_.ScriptStackTrace) { Write-Logging $_.ScriptStackTrace 'ERROR' } + } finally { + if ($null -ne $tempConfig) { + Remove-Item -Path $tempConfig -Force -ErrorAction SilentlyContinue + } + $sh.progressBar.IsVisible = $false + $sh.btnCancel.IsVisible = $false + $sh.IsBusy = $false + } + } + + $btnGenerate.AddClick($generateCallback) + + # ── Config Management Controls ─────────────────────────────────────────────── + $txtConfigPath = [TextBox]::new() + $txtConfigPath.Width = 298 + $txtConfigPath.Watermark = 'Path to AsBuiltReport.Microsoft.AD.json (optional)' + $txtConfigPath.Text = if ($IsWindows) { + [System.IO.Path]::Combine($env:USERPROFILE, 'AsBuiltReport', 'AsBuiltReport.Microsoft.AD.json') + } else { + [System.IO.Path]::Combine($env:HOME, 'AsBuiltReport', 'AsBuiltReport.Microsoft.AD.json') + } + + $btnBrowseConfig = [Button]::new() + $btnBrowseConfig.Content = 'Browse…' + $btnBrowseConfig.AddClick({ + try { + $btnBrowseConfig.IsEnabled = $false + $storageProvider = [Window]::GetTopLevel($btnBrowseConfig).StorageProvider + if ($null -eq $storageProvider) { + Write-Host 'Storage provider not available.' -ForegroundColor Yellow + return + } + $options = [FilePickerOpenOptions]::new() + $options.Title = 'Select AsBuiltReport.Microsoft.AD JSON Config File' + $JsonConfigFile = $storageProvider.OpenFilePickerAsync($options).WaitForCompleted() + if ($JsonConfigFile -and $JsonConfigFile.Count -gt 0) { + $txtConfigPath.Text = $JsonConfigFile[0].Path.LocalPath + } + } catch { + Write-Host "File picker error: $_" -ForegroundColor Red + } finally { + $btnBrowseConfig.IsEnabled = $true + } + }) + + $configPathRow = [StackPanel]::new() + $configPathRow.Orientation = 'Horizontal' + $configPathRow.Spacing = 8 + $configPathRow.Children.Add($txtConfigPath) + $configPathRow.Children.Add($btnBrowseConfig) + + $lblConfigStatus = [TextBlock]::new() + $lblConfigStatus.FontSize = 11 + $lblConfigStatus.Margin = '0,4,0,0' + $lblConfigStatus.Text = '' + $syncHash.lblConfigStatus = $lblConfigStatus + + # ── AsBuiltReport Global Config (AsBuiltReport.json) ───────────────────────── + $txtAbrConfigPath = [TextBox]::new() + $txtAbrConfigPath.Width = 298 + $txtAbrConfigPath.Watermark = 'Required: path to AsBuiltReport.json' + + $btnBrowseAbrConfig = [Button]::new() + $btnBrowseAbrConfig.Content = 'Browse…' + $btnBrowseAbrConfig.AddClick({ + try { + $btnBrowseAbrConfig.IsEnabled = $false + $storageProvider = [Window]::GetTopLevel($btnBrowseAbrConfig).StorageProvider + if ($null -eq $storageProvider) { return } + $options = [FilePickerOpenOptions]::new() + $options.Title = 'Select AsBuiltReport.json' + $options.AllowMultiple = $false + $picked = $storageProvider.OpenFilePickerAsync($options).WaitForCompleted() + if ($picked -and $picked.Count -gt 0) { + $txtAbrConfigPath.Text = $picked[0].Path.LocalPath + $syncHash.lblConfigStatus.Text = "📄 AsBuiltReport config: $(Split-Path $txtAbrConfigPath.Text -Leaf)" + } + } catch { + $syncHash.lblConfigStatus.Text = "❌ Browse error: $_" + } finally { + $btnBrowseAbrConfig.IsEnabled = $true + } + }) + + $abrConfigPathRow = [StackPanel]::new() + $abrConfigPathRow.Orientation = 'Horizontal' + $abrConfigPathRow.Spacing = 8 + $abrConfigPathRow.Children.Add($txtAbrConfigPath) + $abrConfigPathRow.Children.Add($btnBrowseAbrConfig) + + # Late-bind after TextBox objects exist + $generateCallback.ArgumentList['ConfigPath'] = $txtConfigPath + $generateCallback.ArgumentList['AbrConfigPath'] = $txtAbrConfigPath + + # ── AsBuiltReport Global Settings (AsBuiltReport.json editor) ──────────────── + $txtAbrCoFullName = [TextBox]::new(); $txtAbrCoFullName.Width = 298; $txtAbrCoFullName.Watermark = 'e.g. Acme Corporation' + $txtAbrCoShortName = [TextBox]::new(); $txtAbrCoShortName.Width = 298; $txtAbrCoShortName.Watermark = 'e.g. ACME' + $txtAbrCoContact = [TextBox]::new(); $txtAbrCoContact.Width = 298; $txtAbrCoContact.Watermark = 'Contact person' + $txtAbrCoPhone = [TextBox]::new(); $txtAbrCoPhone.Width = 298; $txtAbrCoPhone.Watermark = 'e.g. +1-800-555-0100' + $txtAbrCoAddress = [TextBox]::new(); $txtAbrCoAddress.Width = 298; $txtAbrCoAddress.Watermark = 'Street, City, Country' + $txtAbrCoEmail = [TextBox]::new(); $txtAbrCoEmail.Width = 298; $txtAbrCoEmail.Watermark = 'company@example.com' + $txtAbrRptAuthor = [TextBox]::new(); $txtAbrRptAuthor.Width = 298; $txtAbrRptAuthor.Watermark = 'Report author' + $txtAbrMailServer = [TextBox]::new(); $txtAbrMailServer.Width = 298; $txtAbrMailServer.Watermark = 'smtp.example.com' + $txtAbrMailPort = [TextBox]::new(); $txtAbrMailPort.Width = 298; $txtAbrMailPort.Watermark = '587' + $txtAbrMailFrom = [TextBox]::new(); $txtAbrMailFrom.Width = 298; $txtAbrMailFrom.Watermark = 'from@example.com' + $txtAbrMailTo = [TextBox]::new(); $txtAbrMailTo.Width = 298; $txtAbrMailTo.Watermark = 'to@example.com, other@example.com' + $txtAbrMailBody = [TextBox]::new(); $txtAbrMailBody.Width = 298; $txtAbrMailBody.Watermark = 'Email body text' + $swAbrMailUseSSL = [ToggleSwitch]::new(); $swAbrMailUseSSL.IsChecked = $true + $swAbrMailCreds = [ToggleSwitch]::new(); $swAbrMailCreds.IsChecked = $true + $txtAbrFolderPath = [TextBox]::new(); $txtAbrFolderPath.Width = 298; $txtAbrFolderPath.Watermark = '.\AsBuiltReport' + + $loadAbrFields = { + param ([hashtable]$j) + $txtAbrCoFullName.Text = if ($j.Company.FullName) { $j.Company.FullName } else { '' } + $txtAbrCoShortName.Text = if ($j.Company.ShortName) { $j.Company.ShortName } else { '' } + $txtAbrCoContact.Text = if ($j.Company.Contact) { $j.Company.Contact } else { '' } + $txtAbrCoPhone.Text = if ($j.Company.Phone) { $j.Company.Phone } else { '' } + $txtAbrCoAddress.Text = if ($j.Company.Address) { $j.Company.Address } else { '' } + $txtAbrCoEmail.Text = if ($j.Company.Email) { $j.Company.Email } else { '' } + $txtAbrRptAuthor.Text = if ($j.Report.Author) { $j.Report.Author } else { '' } + $txtAbrMailServer.Text = if ($j.Email.Server) { $j.Email.Server } else { '' } + $txtAbrMailPort.Text = if ($j.Email.Port) { $j.Email.Port } else { '' } + $txtAbrMailFrom.Text = if ($j.Email.From) { $j.Email.From } else { '' } + $txtAbrMailTo.Text = if ($j.Email.To) { ($j.Email.To -join ', ') } else { '' } + $txtAbrMailBody.Text = if ($j.Email.Body) { $j.Email.Body } else { '' } + $swAbrMailUseSSL.IsChecked = if ($null -ne $j.Email.UseSSL) { [bool]$j.Email.UseSSL } else { $true } + $swAbrMailCreds.IsChecked = if ($null -ne $j.Email.Credentials) { [bool]$j.Email.Credentials } else { $true } + $txtAbrFolderPath.Text = if ($j.UserFolder.Path) { $j.UserFolder.Path } else { + if ($IsWindows) { [System.IO.Path]::Combine($env:USERPROFILE, 'Documents', 'AsBuiltReport') } else { [System.IO.Path]::Combine($env:HOME, 'AsBuiltReport') } + } + } + + $buildAbrConfig = { + $toList = ([string]$txtAbrMailTo.Text).Trim() -split '\s*,\s*' | Where-Object { $_ -ne '' } + $portRaw = ([string]$txtAbrMailPort.Text).Trim() + $portVal = if ($portRaw -match '^\d+$') { [int]$portRaw } else { $null } + return [ordered]@{ + Company = [ordered]@{ + FullName = ([string]$txtAbrCoFullName.Text).Trim() + Phone = ([string]$txtAbrCoPhone.Text).Trim() + Address = ([string]$txtAbrCoAddress.Text).Trim() + ShortName = ([string]$txtAbrCoShortName.Text).Trim() + Contact = ([string]$txtAbrCoContact.Text).Trim() + Email = ([string]$txtAbrCoEmail.Text).Trim() + } + Email = [ordered]@{ + Credentials = [bool]$swAbrMailCreds.IsChecked + Body = ([string]$txtAbrMailBody.Text).Trim() + From = ([string]$txtAbrMailFrom.Text).Trim() + UseSSL = [bool]$swAbrMailUseSSL.IsChecked + Server = ([string]$txtAbrMailServer.Text).Trim() + To = if ($toList.Count -gt 0) { @($toList) } else { @() } + Port = $portVal + } + Report = [ordered]@{ Author = ([string]$txtAbrRptAuthor.Text).Trim() } + UserFolder = [ordered]@{ Path = ([string]$txtAbrFolderPath.Text).Trim() } + } + }.GetNewClosure() + $syncHash.buildAbrConfig = $buildAbrConfig + + $validateAbrRequired = { + $missing = @() + if ([string]::IsNullOrWhiteSpace($txtAbrCoFullName.Text)) { $missing += 'Full Name' } + if ([string]::IsNullOrWhiteSpace($txtAbrCoShortName.Text)) { $missing += 'Short Name' } + if ([string]::IsNullOrWhiteSpace($txtAbrCoContact.Text)) { $missing += 'Contact' } + if ([string]::IsNullOrWhiteSpace($txtAbrCoEmail.Text)) { $missing += 'Email' } + if ([string]::IsNullOrWhiteSpace($txtAbrRptAuthor.Text)) { $missing += 'Author' } + if ([string]::IsNullOrWhiteSpace($txtAbrFolderPath.Text)) { $missing += 'Path' } + if ($missing.Count -gt 0) { + return "⚠ Required fields missing: $($missing -join ', ')" + } + return $null + }.GetNewClosure() + $syncHash.validateAbrRequired = $validateAbrRequired + + $btnAbrNew = [Button]::new() + $btnAbrNew.Content = '🆕 Create New' + $btnAbrNew.Margin = '0,0,8,0' + $btnAbrNew.AddClick({ + try { + $btnAbrNew.IsEnabled = $false + $storageProvider = [Window]::GetTopLevel($btnAbrNew).StorageProvider + if ($null -eq $storageProvider) { + $syncHash.lblConfigStatus.Text = '⚠ Cannot open save dialog.' + return + } + $saveOpts = [FilePickerSaveOptions]::new() + $saveOpts.Title = 'Create New AsBuiltReport Config File' + $saveOpts.SuggestedFileName = 'AsBuiltReport.json' + $saveOpts.DefaultExtension = 'json' + $file = $storageProvider.SaveFilePickerAsync($saveOpts).WaitForCompleted() + if ($null -eq $file) { return } + if ($null -eq $file.Path) { + $syncHash.lblConfigStatus.Text = '⚠ Could not resolve file path from dialog.' + return + } + $validationError = & $syncHash.validateAbrRequired + if ($null -ne $validationError) { + $syncHash.lblConfigStatus.Text = $validationError + return + } + $dest = $file.Path.LocalPath + $cfg = & $syncHash.buildAbrConfig + $destDir = Split-Path $dest -Parent + if (-not (Test-Path $destDir)) { New-Item -Path $destDir -ItemType Directory -Force | Out-Null } + $cfg | ConvertTo-Json -Depth 4 | Set-Content -Path $dest -Encoding UTF8 + $txtAbrConfigPath.Text = $dest + $syncHash.lblConfigStatus.Text = "✅ Created: $(Split-Path $dest -Leaf)" + } catch { + $syncHash.lblConfigStatus.Text = "❌ Create failed: $_" + } finally { + $btnAbrNew.IsEnabled = $true + } + }) + + $btnAbrLoad = [Button]::new() + $btnAbrLoad.Content = '📂 Load from File' + $btnAbrLoad.Margin = '0,0,8,0' + $btnAbrLoad.AddClick({ + try { + $btnAbrLoad.IsEnabled = $false + $src = if ($txtAbrConfigPath.Text) { $txtAbrConfigPath.Text.Trim() } else { '' } + if ([string]::IsNullOrWhiteSpace($src) -or -not (Test-Path $src)) { + $syncHash.lblConfigStatus.Text = '⚠ Set a valid AsBuiltReport.json path first.' + return + } + $j = Get-Content -Path $src -Raw | ConvertFrom-Json -AsHashtable + & $loadAbrFields $j + $syncHash.lblConfigStatus.Text = "✅ Loaded: $(Split-Path $src -Leaf)" + } catch { + $syncHash.lblConfigStatus.Text = "❌ Load failed: $_" + } finally { + $btnAbrLoad.IsEnabled = $true + } + }) + + $btnAbrSave = [Button]::new() + $btnAbrSave.Content = '💾 Save to File' + $btnAbrSave.AddClick({ + try { + $btnAbrSave.IsEnabled = $false + $validationError = & $syncHash.validateAbrRequired + if ($null -ne $validationError) { + $syncHash.lblConfigStatus.Text = $validationError + return + } + if ([string]::IsNullOrWhiteSpace($txtAbrConfigPath.Text)) { + $syncHash.lblConfigStatus.Text = '❌ Please provide a config file path before saving.' + return + } + $dest = $txtAbrConfigPath.Text.Trim() + $cfg = & $syncHash.buildAbrConfig + $destDir = Split-Path $dest -Parent + if (-not (Test-Path $destDir)) { New-Item -Path $destDir -ItemType Directory -Force | Out-Null } + $cfg | ConvertTo-Json -Depth 4 | Set-Content -Path $dest -Encoding UTF8 + $syncHash.lblConfigStatus.Text = "✅ Saved: $(Split-Path $dest -Leaf)" + } catch { + $syncHash.lblConfigStatus.Text = "❌ Save failed: $_" + } finally { + $btnAbrSave.IsEnabled = $true + } + }) + + $abrActionRow = [StackPanel]::new() + $abrActionRow.Orientation = 'Horizontal' + $abrActionRow.Margin = '0,10,0,0' + $abrActionRow.Children.Add($btnAbrNew) + $abrActionRow.Children.Add($btnAbrLoad) + $abrActionRow.Children.Add($btnAbrSave) + + $abrRequiredNote = [TextBlock]::new() + $abrRequiredNote.Text = '* Required' + $abrRequiredNote.FontSize = 12 + $abrRequiredNote.Margin = '0,0,0,8' + $abrRequiredNote.TextAlignment = 'Right' + + $abrInnerPanel = [StackPanel]::new() + $abrInnerPanel.Spacing = 2 + $abrInnerPanel.Margin = '4,4,4,8' + $abrInnerPanel.Children.Add($abrRequiredNote) + $abrInnerPanel.Children.Add((New-SectionTitle '🏢 Company')) + $abrInnerPanel.Children.Add((New-FormRow -Label '* Full Name' -Control $txtAbrCoFullName)) + $abrInnerPanel.Children.Add((New-FormRow -Label '* Short Name' -Control $txtAbrCoShortName)) + $abrInnerPanel.Children.Add((New-FormRow -Label '* Contact' -Control $txtAbrCoContact)) + $abrInnerPanel.Children.Add((New-FormRow -Label 'Phone' -Control $txtAbrCoPhone)) + $abrInnerPanel.Children.Add((New-FormRow -Label 'Address' -Control $txtAbrCoAddress)) + $abrInnerPanel.Children.Add((New-FormRow -Label '* Email' -Control $txtAbrCoEmail)) + $abrInnerPanel.Children.Add((New-SectionTitle '📝 Report')) + $abrInnerPanel.Children.Add((New-FormRow -Label '* Author' -Control $txtAbrRptAuthor)) + $abrInnerPanel.Children.Add((New-SectionTitle '📧 Email')) + $abrInnerPanel.Children.Add((New-FormRow -Label 'SMTP Server' -Control $txtAbrMailServer)) + $abrInnerPanel.Children.Add((New-FormRow -Label 'Port' -Control $txtAbrMailPort)) + $abrInnerPanel.Children.Add((New-FormRow -Label 'From' -Control $txtAbrMailFrom)) + $abrInnerPanel.Children.Add((New-FormRow -Label 'To (comma-sep.)' -Control $txtAbrMailTo)) + $abrInnerPanel.Children.Add((New-FormRow -Label 'Body' -Control $txtAbrMailBody)) + $abrInnerPanel.Children.Add((New-FormRow -Label 'Use SSL' -Control $swAbrMailUseSSL)) + $abrInnerPanel.Children.Add((New-FormRow -Label 'Credentials' -Control $swAbrMailCreds)) + $abrInnerPanel.Children.Add((New-SectionTitle '📁 User Folder')) + $abrInnerPanel.Children.Add((New-FormRow -Label '* Path' -Control $txtAbrFolderPath)) + $abrInnerPanel.Children.Add($abrActionRow) + + $abrExpander = [Expander]::new() + $abrExpander.Header = '⚙️ AsBuiltReport Global Settings' + $abrExpander.IsExpanded = $false + $abrExpander.Margin = '0,8,0,0' + $abrExpander.Content = $abrInnerPanel + + # ── Save Config Button ───────────────────────────────────────────────────── + function Build-MSADConfigForSave { + param ( + [string]$ReportName, [string]$Lang, [string]$Theme, + [bool]$EnableDiagrams, [bool]$ExportDiagrams, + [string]$PSDefaultAuthentication, [bool]$WinRMSSL, [bool]$WinRMFallbackToNoSSL, + [int]$LvlForest, [int]$LvlDomain, [int]$LvlDNS + ) + return [ordered]@{ + Report = [ordered]@{ + Name = $ReportName + Version = '1.0' + Status = 'Released' + Language = $Lang + ShowCoverPageImage = $true + ShowTableOfContents = $true + ShowHeaderFooter = $true + ShowTableCaptions = $true + } + Options = [ordered]@{ + ShowExecutionTime = $false + ShowDefinitionInfo = $false + PSDefaultAuthentication = $PSDefaultAuthentication + Exclude = [ordered]@{ Domains = @(); DCs = @() } + Include = [ordered]@{ Domains = @() } + WinRMSSL = $WinRMSSL + WinRMFallbackToNoSSL = $WinRMFallbackToNoSSL + WinRMSSLPort = 5986 + WinRMPort = 5985 + EnableDiagrams = $EnableDiagrams + EnableDiagramDebug = $false + DiagramTheme = $Theme + DiagramObjDebug = $false + DiagramWaterMark = '' + DiagramType = [ordered]@{ + CertificateAuthority = $true + Forest = $true + Replication = $true + Sites = $true + SitesInventory = $true + Trusts = $true + } + ExportDiagrams = $ExportDiagrams + ExportDiagramsFormat = @('pdf') + EnableDiagramSignature = $false + SignatureAuthorName = '' + SignatureCompanyName = '' + JobsTimeOut = 900 + DCStatusPingCount = 2 + } + InfoLevel = [ordered]@{ + Forest = $LvlForest + Domain = $LvlDomain + DNS = $LvlDNS + } + HealthCheck = [ordered]@{ + Domain = [ordered]@{ + GMSA = $true; GPO = $true; Backup = $true; DFS = $true + SPN = $true; DuplicateObject = $true; Security = $true; BestPractice = $true + } + DomainController = [ordered]@{ + Diagnostic = $true; Services = $true; Software = $true; BestPractice = $true + } + Site = [ordered]@{ Replication = $true; BestPractice = $true } + DNS = [ordered]@{ Aging = $true; DP = $true; Zones = $true; BestPractice = $true } + CA = [ordered]@{ Status = $true; Statistics = $true; BestPractice = $true } + } + } + } + + $btnSaveConfig = [Button]::new() + $btnSaveConfig.Content = '💾 Save Config' + $btnSaveConfig.HorizontalAlignment = 'Stretch' + $btnSaveConfig.HorizontalContentAlignment = 'Center' + $btnSaveConfig.Width = 196 + $btnSaveConfig.Margin = '0,0,4,0' + $btnSaveConfig.AddClick({ + $destPath = $txtConfigPath.Text.Trim() + if ([string]::IsNullOrWhiteSpace($destPath)) { + $syncHash.lblConfigStatus.Text = '⚠ Please enter a destination path first.' + return + } + try { + $parent = Split-Path $destPath -Parent + if (-not [string]::IsNullOrEmpty($parent) -and -not (Test-Path $parent)) { + New-Item -Path $parent -ItemType Directory -Force | Out-Null + } + function Get-LevelVal ($cbo) { [int]([string]$cbo.SelectedItem).Substring(0, 1) } + $configObj = Build-MSADConfigForSave ` + -ReportName ($txtReportName.Text.Trim()) ` + -Lang ([string]$cboLang.SelectedItem) ` + -Theme ([string]$cboDiagramTheme.SelectedItem) ` + -EnableDiagrams ([bool]$swDiagrams.IsChecked) ` + -ExportDiagrams ([bool]$swExportDiagrams.IsChecked) ` + -PSDefaultAuthentication ([string]$cboPSDefaultAuth.SelectedItem) ` + -WinRMSSL ([bool]$swWinRMSSL.IsChecked) ` + -WinRMFallbackToNoSSL ([bool]$swWinRMFallback.IsChecked) ` + -LvlForest (Get-LevelVal $cboLvlForest) ` + -LvlDomain (Get-LevelVal $cboLvlDomain) ` + -LvlDNS (Get-LevelVal $cboLvlDNS) + $configObj | ConvertTo-Json -Depth 6 | Set-Content -Path $destPath -Encoding UTF8 + $syncHash.lblConfigStatus.Text = "✅ Config saved: $(Split-Path $destPath -Leaf)" + } catch { + $syncHash.lblConfigStatus.Text = "❌ Save failed: $_" + } + }) + + $btnLoadConfig = [Button]::new() + $btnLoadConfig.Content = '📂 Load Config' + $btnLoadConfig.HorizontalAlignment = 'Stretch' + $btnLoadConfig.HorizontalContentAlignment = 'Center' + $btnLoadConfig.Width = 196 + $btnLoadConfig.Margin = '0,0,4,0' + $btnLoadConfig.AddClick({ + $srcPath = $txtConfigPath.Text.Trim() + if ([string]::IsNullOrWhiteSpace($srcPath) -or -not (Test-Path $srcPath)) { + $syncHash.lblConfigStatus.Text = '⚠ Config file path not found.' + return + } + try { + $j = Get-Content -Path $srcPath -Raw | ConvertFrom-Json + if ($j.Report.Name) { $txtReportName.Text = $j.Report.Name } + if ($j.Report.Language) { $idx = $cboLang.Items.IndexOf($j.Report.Language); if ($idx -ge 0) { $cboLang.SelectedIndex = $idx } } + if ($null -ne $j.Options.EnableDiagrams) { $swDiagrams.IsChecked = [bool]$j.Options.EnableDiagrams } + if ($null -ne $j.Options.ExportDiagrams) { $swExportDiagrams.IsChecked = [bool]$j.Options.ExportDiagrams } + if ($null -ne $j.Options.ShowExecutionTime) { Out-Null } + if ($null -ne $j.Options.ShowDefinitionInfo) { Out-Null } + if ($null -ne $j.Options.WinRMSSL) { $swWinRMSSL.IsChecked = [bool]$j.Options.WinRMSSL } + if ($null -ne $j.Options.WinRMFallbackToNoSSL) { $swWinRMFallback.IsChecked = [bool]$j.Options.WinRMFallbackToNoSSL } + if ($j.Options.DiagramTheme) { $idx = $cboDiagramTheme.Items.IndexOf($j.Options.DiagramTheme); if ($idx -ge 0) { $cboDiagramTheme.SelectedIndex = $idx } } + if ($j.Options.PSDefaultAuthentication) { $idx = $cboPSDefaultAuth.Items.IndexOf($j.Options.PSDefaultAuthentication); if ($idx -ge 0) { $cboPSDefaultAuth.SelectedIndex = $idx } } + if ($null -ne $j.InfoLevel.Forest) { $cboLvlForest.SelectedIndex = [int]$j.InfoLevel.Forest } + if ($null -ne $j.InfoLevel.Domain) { $cboLvlDomain.SelectedIndex = [int]$j.InfoLevel.Domain } + if ($null -ne $j.InfoLevel.DNS) { $cboLvlDNS.SelectedIndex = [int]$j.InfoLevel.DNS } + $syncHash.lblConfigStatus.Text = "✅ Config loaded: $(Split-Path $srcPath -Leaf)" + } catch { + $syncHash.lblConfigStatus.Text = "❌ Load failed: $_" + } + }) + + $btnOpenConfig = [Button]::new() + $btnOpenConfig.Content = '📄 Open File' + $btnOpenConfig.HorizontalAlignment = 'Stretch' + $btnOpenConfig.HorizontalContentAlignment = 'Center' + $btnOpenConfig.Width = 196 + $btnOpenConfig.AddClick({ + $filePath = $txtConfigPath.Text.Trim() + if ([string]::IsNullOrWhiteSpace($filePath) -or -not (Test-Path $filePath)) { + $syncHash.lblConfigStatus.Text = '⚠ Config file not found.' + return + } + try { Start-Process $filePath } catch { $syncHash.lblConfigStatus.Text = "❌ Could not open file: $_" } + }) + + $cfgBtnRow = [StackPanel]::new() + $cfgBtnRow.Orientation = 'Horizontal' + $cfgBtnRow.Margin = '0,4,0,0' + $cfgBtnRow.Children.Add($btnSaveConfig) + $cfgBtnRow.Children.Add($btnLoadConfig) + $cfgBtnRow.Children.Add($btnOpenConfig) + + # ── Assemble Main Panel (Report Page) ─────────────────────────────────────── + $mainPanel = [StackPanel]::new() + $mainPanel.Margin = '28,20,28,24' + $mainPanel.Spacing = 2 + + $headerPanel = [StackPanel]::new() + $headerPanel.HorizontalAlignment = 'Center' + $headerPanel.Spacing = 4 + $headerPanel.Margin = '0,0,0,4' + + $hTitle = [TextBlock]::new() + $hTitle.Text = 'Microsoft Active Directory' + $hTitle.FontSize = 22 + $hTitle.FontWeight = 'Bold' + $hTitle.HorizontalAlignment = 'Center' + + $hSub = [TextBlock]::new() + $hSub.Text = 'As-Built Report Generator' + $hSub.FontSize = 13 + $hSub.HorizontalAlignment = 'Center' + + $headerPanel.Children.Add($hTitle) + $headerPanel.Children.Add($hSub) + $mainPanel.Children.Add($headerPanel) + + # Row 1: Server Connection | Report Output + $topGrid = [Grid]::new() + $topGrid.ColumnDefinitions = [ColumnDefinitions]::Parse('*, *') + $topGrid.ColumnSpacing = 24 + $topGrid.Margin = '0,4,0,0' + + $connPanel = [StackPanel]::new() + $connPanel.Spacing = 2 + $connPanel.Children.Add((New-SectionTitle '🔌 Server Connection')) + $connPanel.Children.Add((New-FormRow -Label 'Saved Connections' -Control $cboSavedConn -LabelWidth 150)) + $connPanel.Children.Add((New-FormRow -Label 'Domain Controller' -Control $txtServer -LabelWidth 150)) + $connPanel.Children.Add((New-FormRow -Label 'Username' -Control $txtUser -LabelWidth 150)) + $connPanel.Children.Add((New-FormRow -Label 'Password' -Control (New-PasswordRow $txtPass) -LabelWidth 150)) + $connPanel.Children.Add((New-FormRow -Label '' -Control $savedConnActionsRow -LabelWidth 150)) + [Grid]::SetColumn($connPanel, 0) + $topGrid.Children.Add($connPanel) + + $outPanel = [StackPanel]::new() + $outPanel.Spacing = 2 + $outPanel.Children.Add((New-SectionTitle '📄 Report Output')) + $outPanel.Children.Add((New-FormRow -Label 'Report Name' -Control $txtReportName -LabelWidth 130)) + $outPanel.Children.Add((New-FormRow -Label 'Format' -Control $fmtPanel -LabelWidth 130)) + $outPanel.Children.Add((New-FormRow -Label 'Output Folder' -Control $outputPathRow -LabelWidth 130)) + $outPanel.Children.Add((New-FormRow -Label 'Language' -Control $cboLang -LabelWidth 130)) + $outPanel.Children.Add((New-FormRow -Label 'Add Timestamp' -Control $swTimestamp -LabelWidth 130)) + [Grid]::SetColumn($outPanel, 1) + $topGrid.Children.Add($outPanel) + + $mainPanel.Children.Add($topGrid) + + # Row 2: Options | Info Level + $bottomGrid = [Grid]::new() + $bottomGrid.ColumnDefinitions = [ColumnDefinitions]::Parse('*, *') + $bottomGrid.ColumnSpacing = 24 + $bottomGrid.Margin = '0,4,0,0' + + $optPanel = [StackPanel]::new() + $optPanel.Spacing = 2 + $optPanel.Children.Add((New-SectionTitle '⚙️ Options')) + $optPanel.Children.Add((New-FormRow -Label 'Enable Diagrams' -Control $swDiagrams -LabelWidth 185)) + $optPanel.Children.Add((New-FormRow -Label 'Export Diagrams' -Control $swExportDiagrams -LabelWidth 185)) + $optPanel.Children.Add((New-FormRow -Label 'Diagram Theme' -Control $cboDiagramTheme -LabelWidth 185)) + $optPanel.Children.Add((New-FormRow -Label 'WinRM SSL' -Control $swWinRMSSL -LabelWidth 185)) + $optPanel.Children.Add((New-FormRow -Label 'WinRM Fallback' -Control $swWinRMFallback -LabelWidth 185)) + $optPanel.Children.Add((New-FormRow -Label 'PS Authentication' -Control $cboPSDefaultAuth -LabelWidth 185)) + [Grid]::SetColumn($optPanel, 0) + $bottomGrid.Children.Add($optPanel) + + $lvlPanel = [StackPanel]::new() + $lvlPanel.Spacing = 2 + $lvlPanel.Children.Add((New-SectionTitle '📊 Info Level')) + $lvlPanel.Children.Add((New-FormRow -Label 'Forest' -Control $cboLvlForest)) + $lvlPanel.Children.Add((New-FormRow -Label 'Domain' -Control $cboLvlDomain)) + $lvlPanel.Children.Add((New-FormRow -Label 'DNS' -Control $cboLvlDNS)) + [Grid]::SetColumn($lvlPanel, 1) + $bottomGrid.Children.Add($lvlPanel) + + $mainPanel.Children.Add($bottomGrid) + + $mainPanel.Children.Add((New-SectionTitle '🗂️ Config Management')) + $mainPanel.Children.Add((New-FormRow -Label '📄 MSAD Config File' -Control $configPathRow)) + $mainPanel.Children.Add($cfgBtnRow) + $mainPanel.Children.Add((New-FormRow -Label '📄 AsBuiltReport Config File' -Control $abrConfigPathRow)) + $mainPanel.Children.Add($abrExpander) + + $mainPanel.Children.Add($btnGenerate) + + # Log area header + $logTitle = [TextBlock]::new() + $logTitle.Text = '📋 Output Log' + $logTitle.FontSize = 13 + $logTitle.FontWeight = 'SemiBold' + $logTitle.VerticalAlignment = 'Center' + + $logHeaderGrid = [Grid]::new() + $logHeaderGrid.Margin = '0,14,0,6' + $logHeaderGrid.ColumnDefinitions.Add( + [ColumnDefinition]::new([GridLength]::new(1, [GridUnitType]::Star))) + $logHeaderGrid.ColumnDefinitions.Add( + [ColumnDefinition]::new([GridLength]::new(0, [GridUnitType]::Auto))) + $logHeaderGrid.ColumnDefinitions.Add( + [ColumnDefinition]::new([GridLength]::new(0, [GridUnitType]::Auto))) + [Grid]::SetColumn($logTitle, 0) + [Grid]::SetColumn($chkVerbose, 1) + [Grid]::SetColumn($btnExportLog, 2) + $logHeaderGrid.Children.Add($logTitle) + $logHeaderGrid.Children.Add($chkVerbose) + $logHeaderGrid.Children.Add($btnExportLog) + + $btnOpenOutputFolder = [Button]::new() + $btnOpenOutputFolder.Content = '📁 Open Output Folder' + $btnOpenOutputFolder.Margin = '0,0,8,0' + $btnOpenOutputFolder.AddClick({ + $path = $txtOutput.Text.Trim() + if ([string]::IsNullOrWhiteSpace($path)) { + $syncHash.lblConfigStatus.Text = '⚠ No output folder set.' + return + } + if (-not (Test-Path $path)) { + $syncHash.lblConfigStatus.Text = "⚠ Output folder not found: $path" + return + } + try { Start-Process $path } catch { $syncHash.lblConfigStatus.Text = "❌ Could not open folder: $_" } + }) + + $logActionsRow = [StackPanel]::new() + $logActionsRow.Orientation = 'Horizontal' + $logActionsRow.HorizontalAlignment = 'Right' + $logActionsRow.Margin = '0,6,0,0' + $logActionsRow.Children.Add($btnOpenOutputFolder) + $logActionsRow.Children.Add($btnCancel) + + $scrollView = [ScrollViewer]::new() + $scrollView.Content = $mainPanel + + # ── Drawer Pages ──────────────────────────────────────────────────────────── + $reportPage = [ContentPage]::new() + $reportPage.Header = 'Report' + $reportPage.Content = $scrollView + + $navigationPage = [NavigationPage]::new() + $navigationPage.Content = $reportPage + + # MDI path geometry for nav icons + $reportGeometry = 'M6,2A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6M6,4H13V9H18V20H6V4M8,12V14H16V12H8M8,16V18H13V16H8Z' + + $btnNavReport = New-DrawerMenuItem -Title 'Report' -IconGeometry $reportGeometry -Page $reportPage -NavigationPage $navigationPage + + $drawerMenuPanel = [StackPanel]::new() + $drawerMenuPanel.Margin = 12 + $drawerMenuPanel.Children.Add($btnNavReport) + + $drawerMenu = [ContentPage]::new() + $drawerMenu.Content = $drawerMenuPanel + + $drawerHeader = [TextBlock]::new() + $drawerHeader.Text = 'Navigation' + $drawerHeader.FontSize = 16 + $drawerHeader.FontWeight = 'SemiBold' + $drawerHeader.VerticalAlignment = 'Center' + $drawerHeader.Padding = '16,10,12,10' + + $drawerPage = [DrawerPage]::new() + $drawerPage.DrawerHeader = $drawerHeader + $drawerPage.Drawer = $drawerMenu + $drawerPage.Content = $navigationPage + + # ── Shared bottom strip (log + status — visible from all drawer pages) ──────── + $sharedBottomPanel = [StackPanel]::new() + $sharedBottomPanel.Margin = '28,4,28,16' + $sharedBottomPanel.Children.Add($progressBar) + $sharedBottomPanel.Children.Add($logHeaderGrid) + $sharedBottomPanel.Children.Add($txtLog) + $sharedBottomPanel.Children.Add($logActionsRow) + $sharedBottomPanel.Children.Add($lblConfigStatus) + + # ── Outer grid: drawer (fills space) above shared log strip ────────────────── + $outerGrid = [Grid]::new() + $outerGrid.RowDefinitions.Add([RowDefinition]::new([GridLength]::new(1, [GridUnitType]::Star))) + $outerGrid.RowDefinitions.Add([RowDefinition]::new([GridLength]::new(0, [GridUnitType]::Auto))) + [Grid]::SetRow($drawerPage, 0) + [Grid]::SetRow($sharedBottomPanel, 1) + $outerGrid.Children.Add($drawerPage) + $outerGrid.Children.Add($sharedBottomPanel) + + # ── Window ────────────────────────────────────────────────────────────────── + $win = [Window]::new() + $win.Title = 'Microsoft AD — As-Built Report Generator' + $win.Width = 1050 + $win.Height = 920 + $win.MinWidth = 880 + $win.MinHeight = 500 + $win.Content = $outerGrid + + $win.Show() + $win.WaitForClosed() +} diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-ADExchangeServer.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-ADExchangeServer.ps1 index 51d3171..d12c181 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-ADExchangeServer.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-ADExchangeServer.ps1 @@ -59,7 +59,7 @@ function Get-ADExchangeServer { ServerRoles = $roles; } } catch { - Write-PScriboMessage -IsWarning -Message "ExchangeServer: [$($server.Name)]. $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADExchange.ErrorExchangeServerItem -f $server.Name) $($_.Exception.Message)" } } } \ No newline at end of file diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADAuthenticationPolicy.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADAuthenticationPolicy.ps1 index 1d255b6..b2f1625 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADAuthenticationPolicy.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADAuthenticationPolicy.ps1 @@ -61,7 +61,7 @@ function Get-AbrADAuthenticationPolicy { } $SiloInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Authentication Policy Silo Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADAuthenticationPolicy.ErrorSiloItem) $($_.Exception.Message)" } } @@ -73,7 +73,7 @@ function Get-AbrADAuthenticationPolicy { foreach ($Silo in $SiloInfo) { Section -Style NOTOCHeading5 -ExcludeFromTOC "$($Silo.Name)" { $TableParams = @{ - Name = "Authentication Policy Silo - $($Silo.Name)" + Name = "$($reportTranslate.GetAbrADAuthenticationPolicy.SiloTableName) - $($Silo.Name)" List = $true ColumnWidths = 40, 60 } @@ -85,7 +85,7 @@ function Get-AbrADAuthenticationPolicy { } } else { $TableParams = @{ - Name = "Authentication Policy Silos - $($Domain.DNSRoot.ToString().ToUpper())" + Name = "$($reportTranslate.GetAbrADAuthenticationPolicy.SilosTableName) - $($Domain.DNSRoot.ToString().ToUpper())" List = $false Columns = $reportTranslate.GetAbrADAuthenticationPolicy.SiloName, $reportTranslate.GetAbrADAuthenticationPolicy.SiloEnforce, $reportTranslate.GetAbrADAuthenticationPolicy.UserAuthPolicy, $reportTranslate.GetAbrADAuthenticationPolicy.ServiceAuthPolicy, $reportTranslate.GetAbrADAuthenticationPolicy.ComputerAuthPolicy ColumnWidths = 20, 12, 23, 23, 22 @@ -126,7 +126,7 @@ function Get-AbrADAuthenticationPolicy { $SiloMemberInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Authentication Policy Silo Member Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADAuthenticationPolicy.ErrorSiloMemberItem) $($_.Exception.Message)" } } } @@ -135,7 +135,7 @@ function Get-AbrADAuthenticationPolicy { Paragraph ($reportTranslate.GetAbrADAuthenticationPolicy.SiloMembersParagraph -f $Domain.DNSRoot.ToString().ToUpper()) BlankLine $TableParams = @{ - Name = "Authentication Policy Silo Members - $($Domain.DNSRoot.ToString().ToUpper())" + Name = "$($reportTranslate.GetAbrADAuthenticationPolicy.SiloMembersTableName) - $($Domain.DNSRoot.ToString().ToUpper())" List = $false ColumnWidths = 20, 20, 15, 45 } @@ -146,14 +146,14 @@ function Get-AbrADAuthenticationPolicy { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Authentication Policy Silo Members Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADAuthenticationPolicy.ErrorSiloMembersTable) $($_.Exception.Message)" } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Authentication Policy Silos Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADAuthenticationPolicy.ErrorSilosSectionA) $($_.Exception.Message)" } } else { - Write-PScriboMessage -Message "No Authentication Policy Silo information found in $($Domain.DNSRoot), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADAuthenticationPolicy.NoSiloInfo -f $Domain.DNSRoot) } if ($AuthPolicies) { try { @@ -181,7 +181,7 @@ function Get-AbrADAuthenticationPolicy { } $PolicyInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Authentication Policy Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADAuthenticationPolicy.ErrorPolicyItem) $($_.Exception.Message)" } } @@ -193,7 +193,7 @@ function Get-AbrADAuthenticationPolicy { foreach ($Policy in $PolicyInfo) { Section -Style NOTOCHeading5 -ExcludeFromTOC "$($Policy.Name)" { $TableParams = @{ - Name = "Authentication Policy - $($Policy.Name)" + Name = "$($reportTranslate.GetAbrADAuthenticationPolicy.PolicyTableName) - $($Policy.Name)" List = $true ColumnWidths = 40, 60 } @@ -205,7 +205,7 @@ function Get-AbrADAuthenticationPolicy { } } else { $TableParams = @{ - Name = "Authentication Policies - $($Domain.DNSRoot.ToString().ToUpper())" + Name = "$($reportTranslate.GetAbrADAuthenticationPolicy.PoliciesTableName) - $($Domain.DNSRoot.ToString().ToUpper())" List = $false Columns = $reportTranslate.GetAbrADAuthenticationPolicy.PolicyName, $reportTranslate.GetAbrADAuthenticationPolicy.PolicyEnforce, $reportTranslate.GetAbrADAuthenticationPolicy.UserTGTLifetime, $reportTranslate.GetAbrADAuthenticationPolicy.ServiceTGTLifetime, $reportTranslate.GetAbrADAuthenticationPolicy.ComputerTGTLifetime ColumnWidths = 20, 12, 23, 23, 22 @@ -227,17 +227,17 @@ function Get-AbrADAuthenticationPolicy { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Authentication Policies Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADAuthenticationPolicy.ErrorPoliciesSection) $($_.Exception.Message)" } } else { - Write-PScriboMessage -Message "No Authentication Policy information found in $($Domain.DNSRoot), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADAuthenticationPolicy.NoPolicyInfo -f $Domain.DNSRoot) } } } else { - Write-PScriboMessage -Message "No Authentication Policy or Silo information found in $($Domain.DNSRoot), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADAuthenticationPolicy.NoAuthPolicyOrSiloInfo -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Authentication Policy Silos Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADAuthenticationPolicy.ErrorSilosSectionA) $($_.Exception.Message)" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDCDiag.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDCDiag.ps1 index a116853..117783c 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDCDiag.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDCDiag.ps1 @@ -69,7 +69,7 @@ function Get-AbrADDCDiag { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "Active Directory DCDiag $($Result.TestName) Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDCDiag.ErrorDCDiagTestSection -f $Result.TestName) $($_.Exception.Message)" } } if ($HealthCheck.DomainController.Diagnostic) { @@ -89,7 +89,7 @@ function Get-AbrADDCDiag { Write-PScriboMessage -Message ($reportTranslate.GetAbrADDCDiag.NoData -f $DC) } } catch { - Write-PScriboMessage -IsWarning -Message "Active Directory DCDiag Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDCDiag.ErrorDCDiagSection) $($_.Exception.Message)" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDCRoleFeature.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDCRoleFeature.ps1 index c827c01..e6aefea 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDCRoleFeature.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDCRoleFeature.ps1 @@ -33,7 +33,7 @@ function Get-AbrADDCRoleFeature { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Roles Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDCRoleFeature.ErrorPSSession -f $DC, $ErrorMessage) } if ($Features) { Section -ExcludeFromTOC -Style NOTOCHeading5 $($DC.ToString().ToUpper().Split('.')[0]) { @@ -47,7 +47,7 @@ function Get-AbrADDCRoleFeature { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "Roles $($Feature.DisplayName) Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDCRoleFeature.ErrorRoleFeatureSection -f $Feature.DisplayName) $($_.Exception.Message)" } } @@ -78,7 +78,7 @@ function Get-AbrADDCRoleFeature { } } } catch { - Write-PScriboMessage -IsWarning -Message "Roles Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDCRoleFeature.ErrorRolesSection) $($_.Exception.Message)" } } end { diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDFSHealth.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDFSHealth.ps1 index c270c2e..1c1a924 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDFSHealth.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDFSHealth.ps1 @@ -71,7 +71,7 @@ } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "Sysvol Replication Status Iten Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDFSHealth.ErrorSysvolReplicationStatusItemSection) $($_.Exception.Message)" } } @@ -117,7 +117,7 @@ Write-PScriboMessage -Message ($reportTranslate.GetAbrADDFSHealth.SysvolReplicationNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "Sysvol Replication Status Table Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDFSHealth.ErrorSysvolReplicationStatusTableSection) $($_.Exception.Message)" } try { @@ -134,7 +134,7 @@ if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Sysvol Content Status Section: New-PSSession: Unable to connect to $($ValidDcFromDomain): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDFSHealth.ErrorSysvolContentPSSession -f $ValidDcFromDomain, $ErrorMessage) } if ($SYSVOLFolder) { Section -ExcludeFromTOC -Style NOTOCHeading4 $reportTranslate.GetAbrADDFSHealth.SysvolContentTitle { @@ -150,7 +150,7 @@ } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "Sysvol Health $($Extension.Extension) Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDFSHealth.ErrorSysvolHealthSection -f $Extension.Extension) $($_.Exception.Message)" } } @@ -181,7 +181,7 @@ Write-PScriboMessage -Message ($reportTranslate.GetAbrADDFSHealth.SysvolContentNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "Sysvol Health Table Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDFSHealth.ErrorSysvolHealthTableSection) $($_.Exception.Message)" } try { $DCPssSession = Get-ValidPSSession -ComputerName $ValidDcFromDomain -SessionName $($ValidDcFromDomain) -PSSTable ([ref]$PSSTable) @@ -197,7 +197,7 @@ if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Netlogon Content Status Section: New-PSSession: Unable to connect to $($ValidDcFromDomain): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDFSHealth.ErrorNetlogonContentPSSession -f $ValidDcFromDomain, $ErrorMessage) } if ($NetlogonFolder) { Section -ExcludeFromTOC -Style NOTOCHeading4 $reportTranslate.GetAbrADDFSHealth.NetlogonContentTitle { @@ -213,7 +213,7 @@ } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "Netlogon Health $($Extension.Extension) Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDFSHealth.ErrorNetlogonHealthSection -f $Extension.Extension) $($_.Exception.Message)" } } @@ -244,7 +244,7 @@ Write-PScriboMessage -Message ($reportTranslate.GetAbrADDFSHealth.NetlogonContentNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "Netlogon Content Status Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDFSHealth.ErrorNetlogonContentStatusSection) $($_.Exception.Message)" } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDNSInfrastructure.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDNSInfrastructure.ps1 index 8681b6f..c47f808 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDNSInfrastructure.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDNSInfrastructure.ps1 @@ -47,7 +47,7 @@ function Get-AbrADDNSInfrastructure { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "DNS Infrastructure Summary Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorInfrastructureSummarySection) $($_.Exception.Message)" } } } @@ -93,7 +93,7 @@ function Get-AbrADDNSInfrastructure { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "Directory Partitions Item Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorDirectoryPartitionsItemSection) $($_.Exception.Message)" } } $TableParams = @{ @@ -107,13 +107,13 @@ function Get-AbrADDNSInfrastructure { $OutObj | Sort-Object -Property $reportTranslate.GetAbrADDNSInfrastructure.Name | Table @TableParams } } catch { - Write-PScriboMessage -IsWarning -Message "Directory Partitions Table Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorDirectoryPartitionsTableSection) $($_.Exception.Message)" } } } } } catch { - Write-PScriboMessage -IsWarning -Message "Directory Partitions Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorDirectoryPartitionsSection) $($_.Exception.Message)" } } #---------------------------------------------------------------------------------------------# @@ -140,7 +140,7 @@ function Get-AbrADDNSInfrastructure { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Response Rate Limiting (RRL) Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorRRLItem) $($_.Exception.Message)" } } } @@ -156,7 +156,7 @@ function Get-AbrADDNSInfrastructure { $OutObj | Sort-Object -Property $reportTranslate.GetAbrADDNSInfrastructure.DCName | Table @TableParams } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Response Rate Limiting (RRL) Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorRRLTable) $($_.Exception.Message)" } } #---------------------------------------------------------------------------------------------# @@ -189,7 +189,7 @@ function Get-AbrADDNSInfrastructure { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Scavenging Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorScavengingItem) $($_.Exception.Message)" } } } @@ -217,7 +217,7 @@ function Get-AbrADDNSInfrastructure { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Scavenging Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorScavengingTable) $($_.Exception.Message)" } } #---------------------------------------------------------------------------------------------# @@ -241,7 +241,7 @@ function Get-AbrADDNSInfrastructure { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Forwarder Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorForwarderItem) $($_.Exception.Message)" } } } @@ -285,7 +285,7 @@ function Get-AbrADDNSInfrastructure { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Forwarder Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorForwarderTable) $($_.Exception.Message)" } #---------------------------------------------------------------------------------------------# # DNS Root Hints Section # @@ -383,13 +383,13 @@ function Get-AbrADDNSInfrastructure { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Root Hints Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorRootHintsTable) $($_.Exception.Message)" } } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Root Hints Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorRootHintsSection) $($_.Exception.Message)" } } #---------------------------------------------------------------------------------------------# @@ -415,7 +415,7 @@ function Get-AbrADDNSInfrastructure { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Zone Scope Recursion Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorZoneScopeRecursionItem) $($_.Exception.Message)" } } } @@ -431,13 +431,13 @@ function Get-AbrADDNSInfrastructure { $OutObj | Sort-Object -Property $reportTranslate.GetAbrADDNSInfrastructure.DCName | Table @TableParams } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Zone Scope Recursion Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorZoneScopeRecursionTable) $($_.Exception.Message)" } } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (DNS Infrastructure Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSInfrastructure.ErrorDNSInfrastructureSection) $($_.Exception.Message)" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDNSZone.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDNSZone.ps1 index f055409..dfb5221 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDNSZone.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDNSZone.ps1 @@ -47,7 +47,7 @@ function Get-AbrADDNSZone { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Name System Zone Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorDNSZoneItem) $($_.Exception.Message)" } } @@ -83,14 +83,14 @@ function Get-AbrADDNSZone { } } } else { - Write-PScriboMessage -Message "DNS Zones $($Zone) Section: No Zone Delegation information found, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDNSZone.NoDelegationInfo -f $Zone) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Zone Delegation Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorZoneDelegationItem) $($_.Exception.Message)" } } } else { - Write-PScriboMessage -Message "DNS Zones Section: No Zone Delegation information found in $DC, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDNSZone.NoDelegationInfoDC -f $DC) } if ($OutObj) { @@ -108,7 +108,7 @@ function Get-AbrADDNSZone { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Zone Delegation Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorZoneDelegationTable) $($_.Exception.Message)" } } @@ -122,7 +122,7 @@ function Get-AbrADDNSZone { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "DNS Zones Transfers Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDNSZone.ErrorZoneTransferPSSession -f $DC, $ErrorMessage) } if ($DNSSetting) { Section -Style Heading4 $reportTranslate.GetAbrADDNSZone.ZoneTransfers { @@ -147,7 +147,7 @@ function Get-AbrADDNSZone { $OutObj | Where-Object { $_.$($reportTranslate.GetAbrADDNSZone.SecureSecondaries) -eq $reportTranslate.GetAbrADDNSZone.SecureSecondariesAll } | Set-Style -Style Warning -Property $reportTranslate.GetAbrADDNSZone.SecureSecondaries } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Zone Transfers Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorZoneTransfersItem) $($_.Exception.Message)" } } @@ -170,10 +170,10 @@ function Get-AbrADDNSZone { } } } else { - Write-PScriboMessage -Message "DNS Zones Section: No Zone Transfer information found in $DC, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDNSZone.NoZoneTransferInfo -f $DC) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Zone Transfers Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorZoneTransfersTable) $($_.Exception.Message)" } } try { @@ -194,7 +194,7 @@ function Get-AbrADDNSZone { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Reverse Lookup Zone Configuration Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorReverseLookupZoneItem) $($_.Exception.Message)" } } @@ -209,10 +209,10 @@ function Get-AbrADDNSZone { $OutObj | Sort-Object -Property $reportTranslate.GetAbrADDNSZone.ZoneName | Table @TableParams } } else { - Write-PScriboMessage -Message "DNS Zones Section: No Reverse lookup zone information found in $DC, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDNSZone.NoReverseLookupZoneInfo -f $DC) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Reverse Lookup Zone Configuration Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorReverseLookupZoneTable) $($_.Exception.Message)" } try { $DNSSetting = Get-DnsServerZone -CimSession $TempCIMSession -ComputerName $DC | Where-Object { $_.IsReverseLookupZone -like 'False' -and $_.ZoneType -like 'Forwarder' } @@ -230,7 +230,7 @@ function Get-AbrADDNSZone { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Conditional Forwarder Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorConditionalForwarderItem) $($_.Exception.Message)" } } @@ -245,10 +245,10 @@ function Get-AbrADDNSZone { $OutObj | Sort-Object -Property $reportTranslate.GetAbrADDNSZone.ZoneName | Table @TableParams } } else { - Write-PScriboMessage -Message "DNS Zones Section: No Conditional forwarder zone information found in $DC, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDNSZone.NoConditionalForwarderInfo -f $DC) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Conditional Forwarder Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorConditionalForwarderTable) $($_.Exception.Message)" } if ($InfoLevel.DNS -ge 2) { try { @@ -272,7 +272,7 @@ function Get-AbrADDNSZone { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Zone Scope Aging Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorZoneScopeAgingItem) $($_.Exception.Message)" } } @@ -299,16 +299,16 @@ function Get-AbrADDNSZone { } } } else { - Write-PScriboMessage -Message "DNS Zones Section: No Zone Aging property information found in $DC, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDNSZone.NoZoneAgingInfo -f $DC) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Zone Scope Aging Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorZoneScopeAgingTable) $($_.Exception.Message)" } } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Global DNS Zone Information)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDNSZone.ErrorGlobalDNSZoneInfo) $($_.Exception.Message)" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomain.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomain.ps1 index 22880a7..c866ea4 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomain.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomain.ps1 @@ -68,7 +68,7 @@ function Get-AbrADDomain { } $TableParams = @{ - Name = "Domain Summary - $($Domain.DNSRoot.ToString().ToUpper())" + Name = "$($reportTranslate.GetAbrADDomain.TableName) - $($Domain.DNSRoot.ToString().ToUpper())" List = $true ColumnWidths = 40, 60 } @@ -91,7 +91,7 @@ function Get-AbrADDomain { } } } catch { - Write-PScriboMessage -IsWarning -Message "AD Domain Summary Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomain.ErrorSection) $($_.Exception.Message)" } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainController.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainController.ps1 index 0cd679d..3180504 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainController.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainController.ps1 @@ -1,4 +1,4 @@ -function Get-AbrADDomainController { +function Get-AbrADDomainController { <# .SYNOPSIS Used by As Built Report to retrieve Microsoft AD Domain Controller information. @@ -34,12 +34,12 @@ function Get-AbrADDomainController { $DCPssSession = Get-ValidPSSession -ComputerName $DC -SessionName $($DC) -PSSTable ([ref]$PSSTable) if ($DCPssSession ) { - $DCNetSettings = try { Invoke-CommandWithTimeout -Session $DCPssSession -ScriptBlock { Get-NetIPAddress } } catch { Write-PScriboMessage -IsWarning -Message "Unable to get $DC network interfaces information" } + $DCNetSettings = try { Invoke-CommandWithTimeout -Session $DCPssSession -ScriptBlock { Get-NetIPAddress } } catch { Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorNetworkInterfacesInfo -f $DC) } } else { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "DC Net Settings Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorDCNetSettingsPSSession -f $DC, $ErrorMessage) } try { $inObj = [ordered] @{ @@ -60,11 +60,11 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDCItem) $($_.Exception.Message)" } } else { try { - Write-PScriboMessage -Message "Unable to collect infromation from $DC." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDomainController.UnableToCollect -f $DC) $inObj = [ordered] @{ $($reportTranslate.GetAbrADDomainController.DCName) = $DC.ToString().ToUpper().Split('.')[0] $($reportTranslate.GetAbrADDomainController.Status) = $reportTranslate.GetAbrADDomainController.Offline @@ -75,7 +75,7 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDCItem) $($_.Exception.Message)" } } } @@ -103,7 +103,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDCTable) $($_.Exception.Message)" } try { $OutObj = [System.Collections.Generic.List[object]]::new() @@ -151,7 +151,7 @@ function Get-AbrADDomainController { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "DC Net Settings Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorDCNetSettingsPSSession -f $DC, $ErrorMessage) } try { Section -Style Heading5 $DCInfo.Name { @@ -204,7 +204,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (General Information Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorGeneralInfoSection) $($_.Exception.Message)" } try { Section -ExcludeFromTOC -Style NOTOCHeading6 $reportTranslate.GetAbrADDomainController.PartitionsTitle { @@ -227,7 +227,7 @@ function Get-AbrADDomainController { $OutObj | Table @TableParams } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Partitions Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorPartitionsSection) $($_.Exception.Message)" } try { if ($DCNetSettings) { @@ -274,7 +274,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Networking Settings Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorNetworkingSettingsSection) $($_.Exception.Message)" } try { $DCHWInfo = [System.Collections.Generic.List[object]]::new() @@ -343,14 +343,14 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Hardware Inventory Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorHardwareInventoryTable) $($_.Exception.Message)" } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Hardware Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDCHardwareSection) $($_.Exception.Message)" } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDCSection) $($_.Exception.Message)" } } } @@ -360,7 +360,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDCSection) $($_.Exception.Message)" } } #---------------------------------------------------------------------------------------------# @@ -381,7 +381,7 @@ function Get-AbrADDomainController { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "DNS IP Configuration Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorDNSIPConfigPSSession -f $DC, $ErrorMessage) } foreach ($DNSServer in $DNSSettings.ServerAddresses) { if ($DCPssSession) { @@ -403,15 +403,15 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($DC.ToString().ToUpper().Split('.')[0]) DNS IP Configuration Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDNSIPConfigItem) $($_.Exception.Message)" } } } catch { - Write-PScriboMessage -IsWarning -Message "Domain Controller DNS IP Configuration Table Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDNSIPConfigTableSection) $($_.Exception.Message)" } } else { try { - Write-PScriboMessage -Message "Unable to collect infromation from $DC." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDomainController.UnableToCollect -f $DC) $inObj = [ordered] @{ $($reportTranslate.GetAbrADDomainController.DCName) = $DC.ToString().ToUpper().Split('.')[0] $($reportTranslate.GetAbrADDomainController.Interface) = '--' @@ -422,7 +422,7 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (DNS IP Configuration Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDNSIPConfigItem) $($_.Exception.Message)" } } } @@ -482,7 +482,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "Domain Controller DNS IP Configuration Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDNSIPConfigSection) $($_.Exception.Message)" } try { @@ -501,7 +501,7 @@ function Get-AbrADDomainController { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "NTDS Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorNTDSPSSession -f $DC, $ErrorMessage) } if ( $NTDS -and $size ) { $inObj = [ordered] @{ @@ -514,11 +514,11 @@ function Get-AbrADDomainController { $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (NTDS Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorNTDSItem) $($_.Exception.Message)" } } else { try { - Write-PScriboMessage -Message "Unable to collect infromation from $DC." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDomainController.UnableToCollect -f $DC) $inObj = [ordered] @{ $($reportTranslate.GetAbrADDomainController.DCName) = $DC.ToString().ToUpper().Split('.')[0] $($reportTranslate.GetAbrADDomainController.DatabaseFile) = '--' @@ -528,7 +528,7 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (NTDS Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorNTDSItem) $($_.Exception.Message)" } } } @@ -547,7 +547,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (NTDS section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorNTDSSection) $($_.Exception.Message)" } try { $OutObj = [System.Collections.Generic.List[object]]::new() @@ -563,7 +563,7 @@ function Get-AbrADDomainController { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Time Source Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorTimeSourcePSSession -f $DC, $ErrorMessage) } if ( $NtpServer -and $SourceType ) { try { @@ -583,15 +583,15 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Time Source Item)" + Write-PScriboMessage -IsWarning "$($reportTranslate.GetAbrADDomainController.ErrorTimeSourceItem) $($_.Exception.Message)" } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Time Source Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorTimeSourceTable) $($_.Exception.Message)" } } else { try { - Write-PScriboMessage -Message "Unable to collect infromation from $DC." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDomainController.UnableToCollect -f $DC) $inObj = [ordered] @{ $($reportTranslate.GetAbrADDomainController.Name) = $DC.ToString().ToUpper().Split('.')[0] $($reportTranslate.GetAbrADDomainController.TimeServer) = '--' @@ -599,7 +599,7 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (NTDS Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorNTDSItem) $($_.Exception.Message)" } } } @@ -619,7 +619,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Time Source)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorTimeSource) $($_.Exception.Message)" } if ($HealthCheck.DomainController.Diagnostic) { try { @@ -691,7 +691,7 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning "$($_.Exception.Message) (SRV Records Status Item)" + Write-PScriboMessage -IsWarning "$($reportTranslate.GetAbrADDomainController.ErrorSRVRecordsStatusItem) $($_.Exception.Message)" } if ($HealthCheck.DomainController.Diagnostic) { $OutObj | Where-Object { $_.$($reportTranslate.GetAbrADDomainController.ARecord) -eq $reportTranslate.GetAbrADDomainController.Fail } | Set-Style -Style Critical -Property $reportTranslate.GetAbrADDomainController.ARecord @@ -703,11 +703,11 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SRV Records Status Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorSRVRecordsStatusTable) $($_.Exception.Message)" } } else { try { - Write-PScriboMessage -Message "Unable to collect infromation from $DC." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADDomainController.UnableToCollect -f $DC) $inObj = [ordered] @{ $($reportTranslate.GetAbrADDomainController.Name) = $DC.ToString().ToUpper().Split('.')[0] $($reportTranslate.GetAbrADDomainController.ARecord) = '--' @@ -718,7 +718,7 @@ function Get-AbrADDomainController { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (NTDS Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorNTDSItem) $($_.Exception.Message)" } } } @@ -746,7 +746,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SRV Records Status)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorSRVRecordsStatus) $($_.Exception.Message)" } } try { @@ -763,7 +763,7 @@ function Get-AbrADDomainController { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Domain Controllers File Shares Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorFileSharesPSSession -f $DC, $ErrorMessage) } if ($Shares) { Section -ExcludeFromTOC -Style NOTOCHeading5 $($DC.ToString().ToUpper().Split('.')[0]) { @@ -794,7 +794,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (File Shares Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorFileSharesItem) $($_.Exception.Message)" } } } @@ -813,7 +813,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (File Shares Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorFileSharesTable) $($_.Exception.Message)" } if ($HealthCheck.DomainController.Software) { try { @@ -832,7 +832,7 @@ function Get-AbrADDomainController { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Domain Controller Installed Software Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorInstalledSoftwarePSSession -f $DC, $ErrorMessage) } if ($SoftwareX64) { @@ -897,7 +897,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Installed Software Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorInstalledSoftwareTable) $($_.Exception.Message)" } } } @@ -909,7 +909,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Installed Software Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorInstalledSoftwareSection) $($_.Exception.Message)" } try { # Todo: Fix arraylist issue with foreach @@ -926,7 +926,7 @@ function Get-AbrADDomainController { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Domain Controller Pending Missing Patch Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADDomainController.ErrorMissingPatchPSSession -f $DC, $ErrorMessage) } if ( $Updates ) { @@ -967,7 +967,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Installed Software Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorInstalledSoftwareTable) $($_.Exception.Message)" } } } @@ -979,7 +979,7 @@ function Get-AbrADDomainController { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainController.ErrorDCSection) $($_.Exception.Message)" } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainLastBackup.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainLastBackup.ps1 index ac50a68..d78e2d9 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainLastBackup.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainLastBackup.ps1 @@ -49,7 +49,7 @@ function Get-AbrADDomainLastBackup { $OutObj | Where-Object { [int]$_.$($reportTranslate.GetAbrADDomainLastBackup.LastBackupInDays) -gt 180 } | Set-Style -Style Warning -Property $reportTranslate.GetAbrADDomainLastBackup.LastBackupInDays } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Last Backup Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADDomainLastBackup.ErrorDomainLastBackupItem))" } } @@ -78,7 +78,7 @@ function Get-AbrADDomainLastBackup { Write-PScriboMessage -Message ($reportTranslate.GetAbrADDomainLastBackup.NoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Last Backup Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADDomainLastBackup.ErrorDomainLastBackupTable))" } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainObject.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainObject.ps1 index dea976f..f70662a 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainObject.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDomainObject.ps1 @@ -65,10 +65,10 @@ function Get-AbrADDomainObject { $ADObjects = $Users + $GroupObj } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Object Stats)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorDomainObjectStats) $($_.Exception.Message)" } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Object Stats)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorDomainObjectStats) $($_.Exception.Message)" } try { Section -Style Heading4 $reportTranslate.GetAbrADDomainObject.UserObjectsSection { @@ -94,7 +94,7 @@ function Get-AbrADDomainObject { $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Category' $Chart = New-PieChart -Values $sampleData.Value -Labels $sampleData.Name -Title "$($reportTranslate.GetAbrADDomainObject.UserObjectsSection)" -EnableLegend -LegendOrientation Horizontal -LegendAlignment UpperCenter -Width 600 -Height 400 -Format base64 -TitleFontSize 20 -TitleFontBold -EnableCustomColorPalette -CustomColorPalette $AbrCustomPalette -EnableChartBorder -ChartBorderStyle DenselyDashed -ChartBorderColor DarkBlue } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (User Object Count Chart)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorUserObjectCountChart) $($_.Exception.Message)" } if ($OutObj) { @@ -170,7 +170,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Status of User Accounts)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorStatusOfUserAccounts) $($_.Exception.Message)" } } @@ -186,7 +186,7 @@ function Get-AbrADDomainObject { $sampleData = $OutObj $Chart = New-PieChart -Values $sampleData.$($reportTranslate.GetAbrADDomainObject.Total) -Labels $sampleData.$($reportTranslate.GetAbrADDomainObject.Category) -Title "$($reportTranslate.GetAbrADDomainObject.StatusOfUsersSection)" -EnableLegend -LegendOrientation Horizontal -LegendAlignment UpperCenter -Width 600 -Height 800 -Format base64 -TitleFontSize 20 -TitleFontBold -EnableCustomColorPalette -CustomColorPalette $AbrCustomPalette -EnableChartBorder -ChartBorderStyle DenselyDashed -ChartBorderColor DarkBlue } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Status of Users Accounts Chart)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorStatusOfUsersAccountsChart) $($_.Exception.Message)" } } if ($OutObj) { @@ -223,7 +223,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Users Objects Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorUsersObjectsTable) $($_.Exception.Message)" } } @@ -241,7 +241,7 @@ function Get-AbrADDomainObject { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Users Objects Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorUsersObjectsSection) $($_.Exception.Message)" } } Show-AbrDebugExecutionTime -End -TitleMessage 'User Objects' @@ -272,7 +272,7 @@ function Get-AbrADDomainObject { $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Name' $Chart = New-PieChart -Values $sampleData.Value -Labels $sampleData.Name -Title $reportTranslate.GetAbrADDomainObject.GroupCategoriesSubSection -EnableLegend -LegendOrientation Horizontal -LegendAlignment UpperCenter -Width 600 -Height 400 -Format base64 -TitleFontSize 20 -TitleFontBold -EnableCustomColorPalette -CustomColorPalette $AbrCustomPalette -EnableChartBorder -ChartBorderStyle DenselyDashed -ChartBorderColor DarkBlue } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Group Category Object Chart)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorGroupCategoryObjectChart) $($_.Exception.Message)" } if ($OutObj) { Section -ExcludeFromTOC -Style NOTOCHeading4 $reportTranslate.GetAbrADDomainObject.GroupCategoriesSubSection { @@ -306,7 +306,7 @@ function Get-AbrADDomainObject { $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Name' $Chart = New-PieChart -Values $sampleData.Value -Labels $sampleData.Name -Title $reportTranslate.GetAbrADDomainObject.GroupScopesSubSection -EnableLegend -LegendOrientation Horizontal -LegendAlignment UpperCenter -Width 600 -Height 400 -Format base64 -TitleFontSize 20 -TitleFontBold -EnableCustomColorPalette -CustomColorPalette $AbrCustomPalette -EnableChartBorder -ChartBorderStyle DenselyDashed -ChartBorderColor DarkBlue } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Group Scopes Object Chart)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorGroupScopesObjectChart) $($_.Exception.Message)" } if ($OutObj) { Section -ExcludeFromTOC -Style NOTOCHeading4 $reportTranslate.GetAbrADDomainObject.GroupScopesSubSection { @@ -335,7 +335,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Groups Objects Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorGroupsObjectsTable) $($_.Exception.Message)" } } @@ -353,7 +353,7 @@ function Get-AbrADDomainObject { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Groups Objects Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorGroupsObjectsSection) $($_.Exception.Message)" } } if ($GroupOBj) { @@ -379,7 +379,7 @@ function Get-AbrADDomainObject { $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Privileged Group in Active Directory item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorPrivilegedGroup) $($_.Exception.Message)" } } @@ -438,7 +438,7 @@ function Get-AbrADDomainObject { if ($Group = ($GroupOBj | Where-Object { $_.SID -like $GroupSID })) { $GroupObjects = $Group.Members if ($GroupObjFilter = $ADObjects | Where-Object { $_.distinguishedName -in $GroupObjects }) { - Section -ExcludeFromTOC -Style NOTOCHeading4 "$($Group.Name) ($(($GroupObjects | Measure-Object).count) Members)" { + Section -ExcludeFromTOC -Style NOTOCHeading4 "$($Group.Name) ($(($GroupObjects | Measure-Object).count) $($reportTranslate.GetAbrADDomainObject.MembersLabel))" { $OutObj = [System.Collections.Generic.List[object]]::new() foreach ($GroupObject in $GroupObjFilter) { try { @@ -454,7 +454,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Privileged Group in Active Directory item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorPrivilegedGroup) $($_.Exception.Message)" } } @@ -472,7 +472,7 @@ function Get-AbrADDomainObject { } $TableParams = @{ - Name = "$($Group.Name) - $($Domain.DNSRoot.ToString().ToUpper())" + Name = "$($reportTranslate.GetAbrADDomainObject.PrivilegedGroupMembersTableName) $($Group.Name) - $($Domain.DNSRoot.ToString().ToUpper())" List = $false ColumnWidths = 50, 20, 15, 15 } @@ -520,13 +520,13 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Privileged Group in Active Directory item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorPrivilegedGroup) $($_.Exception.Message)" } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Privileged Group in Active Directory)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorPrivilegedGroup) $($_.Exception.Message)" } Show-AbrDebugExecutionTime -End -TitleMessage 'Privileged Groups (Built-in)' } @@ -545,7 +545,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Privileged Group (Non-Default) Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorPrivilegedGroupNonDefaultTable) $($_.Exception.Message)" } } } @@ -576,7 +576,7 @@ function Get-AbrADDomainObject { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Privileged Group (Non-Default) Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorPrivilegedGroupNonDefaultSection) $($_.Exception.Message)" } } if ($HealthCheck.Domain.BestPractice -and ($EmptyGroupOBj)) { @@ -594,7 +594,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Empty Groups Objects Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorEmptyGroupsObjectsTable) $($_.Exception.Message)" } } } @@ -620,7 +620,7 @@ function Get-AbrADDomainObject { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Empty Groups Objects Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorEmptyGroupsObjectsSection) $($_.Exception.Message)" } } if ($HealthCheck.Domain.BestPractice -and $InfoLevel.Domain -ge 2) { @@ -648,7 +648,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Circular Group Membership Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorCircularGroupMembershipTable) $($_.Exception.Message)" } } } @@ -686,7 +686,7 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Circular Group Membership Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorCircularGroupMembershipSection) $($_.Exception.Message)" } } if ($HealthCheck.Domain.Security) { @@ -699,15 +699,15 @@ function Get-AbrADDomainObject { foreach ($MemberDN in $GroupMembers) { try { if ($MemberUser = $Users | Where-Object { $_.DistinguishedName -eq $MemberDN }) { - $MemberName = "$($MemberUser.SamAccountName) (USER)" + $MemberName = "$($MemberUser.SamAccountName) ($($reportTranslate.GetAbrADDomainObject.TypeLabelUser))" } elseif ($MemberComputer = $Computers | Where-Object { $_.DistinguishedName -eq $MemberDN }) { - $MemberName = "$($MemberComputer.Name) (COMPUTER)" + $MemberName = "$($MemberComputer.Name) ($($reportTranslate.GetAbrADDomainObject.TypeLabelComputer))" } elseif ($MemberGroup = $GroupOBj | Where-Object { $_.DistinguishedName -eq $MemberDN }) { - $MemberName = "$($MemberGroup.Name) (GROUP)" + $MemberName = "$($MemberGroup.Name) ($($reportTranslate.GetAbrADDomainObject.TypeLabelGroup))" } elseif ($MemberFSP = $FSP | Where-Object { $_.DistinguishedName -eq $MemberDN }) { - $MemberName = "$($MemberFSP.'msds-principalname') (FOREIGN SECURITY PRINCIPAL)" + $MemberName = "$($MemberFSP.'msds-principalname') ($($reportTranslate.GetAbrADDomainObject.TypeLabelFSP))" } elseif ($MemberDN -match 'ForeignSecurityPrincipals') { - $MemberName = "$(($MemberDN -split ',')[0] -replace '^CN=') (FOREIGN SECURITY PRINCIPAL)" + $MemberName = "$(($MemberDN -split ',')[0] -replace '^CN=') ($($reportTranslate.GetAbrADDomainObject.TypeLabelFSP))" } else { $MemberName = ($MemberDN -split ',')[0] -replace '^CN=' } @@ -717,7 +717,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Pre-Windows 2000 Compatible Access Group Member)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorPreWin2000) $($_.Exception.Message)" } } if ($OutObj) { @@ -745,7 +745,7 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Pre-Windows 2000 Compatible Access Group Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorPreWin2000) $($_.Exception.Message)" } Show-AbrDebugExecutionTime -End -TitleMessage 'Pre-Windows 2000 Compatible Access Group' } @@ -776,7 +776,7 @@ function Get-AbrADDomainObject { $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Name'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } | Sort-Object -Property 'Name' $Chart = New-PieChart -Values $sampleData.Value -Labels $sampleData.Name -Title "$($reportTranslate.GetAbrADDomainObject.ComputersCount)" -EnableLegend -LegendOrientation Horizontal -LegendAlignment UpperCenter -Width 600 -Height 400 -Format base64 -TitleFontSize 20 -TitleFontBold -EnableCustomColorPalette -CustomColorPalette $AbrCustomPalette -EnableChartBorder -ChartBorderStyle DenselyDashed -ChartBorderColor DarkBlue } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Computers Object Count Chart)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorComputersObjectCountChart) $($_.Exception.Message)" } if ($OutObj) { Section -ExcludeFromTOC -Style NOTOCHeading4 $reportTranslate.GetAbrADDomainObject.ComputersSubSection { @@ -838,7 +838,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Status of Computer Accounts)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorStatusOfComputerAccounts) $($_.Exception.Message)" } } @@ -854,7 +854,7 @@ function Get-AbrADDomainObject { $sampleData = $OutObj $Chart = New-PieChart -Values $sampleData.$($reportTranslate.GetAbrADDomainObject.Total) -Labels $sampleData.$($reportTranslate.GetAbrADDomainObject.Category) -Title "$($reportTranslate.GetAbrADDomainObject.StatusOfComputerAccountsSection)" -EnableLegend -LegendOrientation Horizontal -LegendAlignment UpperCenter -Width 600 -Height 400 -Format base64 -TitleFontSize 20 -TitleFontBold -EnableCustomColorPalette -CustomColorPalette $AbrCustomPalette -EnableChartBorder -ChartBorderStyle DenselyDashed -ChartBorderColor DarkBlue } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Status of Computers Accounts Chart)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorStatusOfComputersAccountsChart) $($_.Exception.Message)" } if ($OutObj) { @@ -910,7 +910,7 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Operating Systems in Active Directory)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorOperatingSystemsInAD) $($_.Exception.Message)" } Show-AbrDebugExecutionTime -End -TitleMessage 'Operating Systems Count' } @@ -951,14 +951,14 @@ function Get-AbrADDomainObject { Text $reportTranslate.GetAbrADDomainObject.PasswordNotRequiredBP } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Computers with Password-Not-Required table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorComputersPasswordNotRequired) $($_.Exception.Message)" } } Show-AbrDebugExecutionTime -End -TitleMessage 'Computers with Password-Not-Required Attribute Set' } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Computers with Password-Not-Required section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorComputersPasswordNotRequired) $($_.Exception.Message)" } if ($InfoLevel.Domain -ge 4) { try { @@ -979,7 +979,7 @@ function Get-AbrADDomainObject { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Computers Objects Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorComputersObjectsTable) $($_.Exception.Message)" } } @@ -997,7 +997,7 @@ function Get-AbrADDomainObject { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Computers Objects Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorComputersObjectsSection) $($_.Exception.Message)" } } Show-AbrDebugExecutionTime -End -TitleMessage 'Computer Objects' @@ -1051,7 +1051,7 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Default Domain Password Policy)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorDefaultDomainPasswordPolicy) $($_.Exception.Message)" } Show-AbrDebugExecutionTime -End -TitleMessage 'Default Domain Password Policy' } @@ -1123,7 +1123,7 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Fined Grained Password Policies)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorFGPP) $($_.Exception.Message)" } try { @@ -1192,7 +1192,7 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Windows LAPS)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorWindowsLAPS) $($_.Exception.Message)" } try { @@ -1231,7 +1231,7 @@ function Get-AbrADDomainObject { $GMSAInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Group Managed Service Accounts Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorGMSAItem) $($_.Exception.Message)" } } @@ -1264,7 +1264,7 @@ function Get-AbrADDomainObject { foreach ($Account in $GMSAInfo) { Section -Style NOTOCHeading4 -ExcludeFromTOC "$($Account.$($reportTranslate.GetAbrADDomainObject.GMSAName))" { $TableParams = @{ - Name = "gMSA - $($Account.$($reportTranslate.GetAbrADDomainObject.GMSAName))" + Name = "$($reportTranslate.GetAbrADDomainObject.GMSATableName) - $($Account.$($reportTranslate.GetAbrADDomainObject.GMSAName))" List = $true ColumnWidths = 40, 60 } @@ -1299,7 +1299,7 @@ function Get-AbrADDomainObject { } } else { $TableParams = @{ - Name = "gMSA - $($Domain.DNSRoot.ToString().ToUpper())" + Name = "$($reportTranslate.GetAbrADDomainObject.GMSATableName) - $($Domain.DNSRoot.ToString().ToUpper())" List = $false Columns = $reportTranslate.GetAbrADDomainObject.GMSAName, $reportTranslate.GetAbrADDomainObject.GMSALogonCount, $reportTranslate.GetAbrADDomainObject.GMSALockedOut, $reportTranslate.GetAbrADDomainObject.GMSALastLogonDate, $reportTranslate.GetAbrADDomainObject.GMSAPasswordLastSet, $reportTranslate.GetAbrADDomainObject.GMSAEnabled ColumnWidths = 25, 15, 15, 15, 15, 15 @@ -1323,7 +1323,7 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Group Managed Service Accounts Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorGMSASection) $($_.Exception.Message)" } } catch { Write-PScriboMessage -IsWarning $($_.Exception.Message) @@ -1349,7 +1349,7 @@ function Get-AbrADDomainObject { $FSPInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Foreign Security Principals Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorFSPItem) $($_.Exception.Message)" } } @@ -1366,7 +1366,7 @@ function Get-AbrADDomainObject { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Foreign Security Principals Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADDomainObject.ErrorFSPSection) $($_.Exception.Message)" } } catch { Write-PScriboMessage -IsWarning $($_.Exception.Message) diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDuplicateObject.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDuplicateObject.ps1 index e1b7660..d13417c 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDuplicateObject.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDuplicateObject.ps1 @@ -47,7 +47,7 @@ function Get-AbrADDuplicateObject { $OutObj | Set-Style -Style Warning } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Duplicate Object Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADDuplicateObject.ErrorDuplicateObjectItem))" } } @@ -72,7 +72,7 @@ function Get-AbrADDuplicateObject { Write-PScriboMessage -Message ($reportTranslate.GetAbrADDuplicateObject.NoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Duplicate Object Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADDuplicateObject.ErrorDuplicateObjectTable))" } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDuplicateSPN.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDuplicateSPN.ps1 index f0306c5..4389cf8 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDuplicateSPN.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADDuplicateSPN.ps1 @@ -46,7 +46,7 @@ function Get-AbrADDuplicateSPN { $OutObj | Set-Style -Style Warning } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SPN Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADDuplicateSPN.ErrorSPNItem))" } } @@ -73,7 +73,7 @@ function Get-AbrADDuplicateSPN { Write-PScriboMessage -Message ($reportTranslate.GetAbrADDuplicateSPN.NoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SPN Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADDuplicateSPN.ErrorSPNTable))" } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADExchange.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADExchange.ps1 index fea6e8d..8a8c723 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADExchange.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADExchange.ps1 @@ -41,7 +41,7 @@ function Get-AbrADExchange { } $EXInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Exchange Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADExchange.ErrorExchangeItem))" } } @@ -73,12 +73,12 @@ function Get-AbrADExchange { } } } else { - Write-PScriboMessage -Message "No Exchange Infrastructure information found in $($ForestInfo.toUpper()), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADExchange.NoExchangeInfo -f $ForestInfo.toUpper()) Paragraph $reportTranslate.GetAbrADExchange.NotFound BlankLine } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Exchabge Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADExchange.ErrorExchangeTable))" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADFSMO.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADFSMO.ps1 index 48efead..dcb2a2c 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADFSMO.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADFSMO.ps1 @@ -45,7 +45,7 @@ function Get-AbrADFSMO { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Flexible Single Master Operations)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADFSMO.ErrorFSMOItem))" } if ($HealthCheck.Domain.BestPractice) { @@ -55,7 +55,7 @@ function Get-AbrADFSMO { } $TableParams = @{ - Name = "FSMO Roles - $($Domain.DNSRoot)" + Name = "$($reportTranslate.GetAbrADFSMO.TableName) - $($Domain.DNSRoot)" List = $true ColumnWidths = 40, 60 } @@ -81,12 +81,12 @@ function Get-AbrADFSMO { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "FSMO Roles Section: New-PSSession: Unable to connect to $($Domain.DNSRoot): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADFSMO.ErrorPSSession -f $Domain.DNSRoot, $ErrorMessage) } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Flexible Single Master Operations)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADFSMO.ErrorFSMOItem))" } } end { diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADForest.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADForest.ps1 index 76d07c6..81d81ef 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADForest.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADForest.ps1 @@ -79,7 +79,7 @@ function Get-AbrADForest { } $TableParams = @{ - Name = "Forest Summary - $($ForestInfo)" + Name = "$($reportTranslate.GetAbrADForest.TableName) - $($ForestInfo)" List = $true ColumnWidths = 40, 60 } @@ -112,7 +112,7 @@ function Get-AbrADForest { try { $Graph = Get-AbrDiagrammer -DiagramType 'Forest' -DiagramOutput base64 -PSSessionObject $TempPssSession } catch { - Write-PScriboMessage -IsWarning -Message "Forest Diagram Graph: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADForest.ErrorForestDiagramGraph) $($_.Exception.Message)" } if ($Graph) { @@ -124,7 +124,7 @@ function Get-AbrADForest { } } } catch { - Write-PScriboMessage -IsWarning -Message "Forest Diagram Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADForest.ErrorForestDiagramSection) $($_.Exception.Message)" } } } @@ -183,7 +183,7 @@ function Get-AbrADForest { } } } else { - Write-PScriboMessage -Message "No Certificate Authority Root information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADForest.NoCARootInfo -f $ForestInfo) } if ($subordinateCA) { @@ -212,7 +212,7 @@ function Get-AbrADForest { $OutObj | Sort-Object -Property $reportTranslate.GetAbrADForest.CAName | Table @TableParams } } else { - Write-PScriboMessage -Message 'No Certificate Authority Issuer information found, Disabling this section.' + Write-PScriboMessage -Message $reportTranslate.GetAbrADForest.NoCAIssuerInfo } } if ($Options.EnableDiagrams) { @@ -220,7 +220,7 @@ function Get-AbrADForest { try { $Graph = Get-AbrDiagrammer -DiagramType 'CertificateAuthority' -DiagramOutput base64 -PSSessionObject $TempPssSession } catch { - Write-PScriboMessage -IsWarning -Message "Certificate Authority Diagram Graph: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADForest.ErrorCADiagramGraph) $($_.Exception.Message)" } if ($Graph) { @@ -232,7 +232,7 @@ function Get-AbrADForest { } } } catch { - Write-PScriboMessage -IsWarning -Message "Certificate Authority Diagram Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADForest.ErrorCADiagramSection) $($_.Exception.Message)" } } } @@ -289,7 +289,7 @@ function Get-AbrADForest { } } } else { - Write-PScriboMessage -Message "No Optional Feature information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADForest.NoOptionalFeatureInfo -f $ForestInfo) } } } catch { diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADGPO.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADGPO.ps1 index 02d228c..11829a4 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADGPO.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADGPO.ps1 @@ -56,7 +56,7 @@ function Get-AbrADGPO { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Group Policy Objects)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOItem) $($_.Exception.Message)" } } @@ -102,7 +102,7 @@ function Get-AbrADGPO { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Group Policy Objects)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOItem) $($_.Exception.Message)" } } if ($InfoLevel.Domain -ge 2) { @@ -187,12 +187,12 @@ function Get-AbrADGPO { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Group Policy Objects)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOItem) $($_.Exception.Message)" } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (WMI Filters)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorWMIFiltersItem) $($_.Exception.Message)" } } } @@ -210,7 +210,7 @@ function Get-AbrADGPO { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Wmi Filters Section: New-PSSession: Unable to connect to $($ValidDCFromDomain): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADGPO.ErrorWMIFiltersPSSession -f $ValidDCFromDomain, $ErrorMessage) } if ($WmiFilters) { @@ -245,7 +245,7 @@ function Get-AbrADGPO { Write-PScriboMessage -Message ($reportTranslate.GetAbrADGPO.WMINoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (WMI Filters)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorWMIFiltersItem) $($_.Exception.Message)" } } try { @@ -288,7 +288,7 @@ function Get-AbrADGPO { Write-PScriboMessage -Message ($reportTranslate.GetAbrADGPO.CentralStoreNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (GPO Central Store)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOCentralStore) $($_.Exception.Message)" } try { if ($GPOs) { @@ -313,7 +313,7 @@ function Get-AbrADGPO { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (GPO with Logon/Logoff Script Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOLogonLogoffItem) $($_.Exception.Message)" } } } @@ -338,7 +338,7 @@ function Get-AbrADGPO { Write-PScriboMessage -Message ($reportTranslate.GetAbrADGPO.LogonLogoffNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (GPO with Logon/Logoff Script Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOLogonLogoffSection) $($_.Exception.Message)" } try { if ($GPOs) { @@ -358,12 +358,12 @@ function Get-AbrADGPO { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (GPO with Computer Startup/Shutdown Script Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOStartupShutdownItem) $($_.Exception.Message)" } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (GPO with Computer Startup/Shutdown Script)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOStartupShutdownItem) $($_.Exception.Message)" } } } @@ -389,7 +389,7 @@ function Get-AbrADGPO { Write-PScriboMessage -Message ($reportTranslate.GetAbrADGPO.StartupShutdownNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (GPO with Computer Startup/Shutdown Script Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOStartupShutdownSection) $($_.Exception.Message)" } } } @@ -414,7 +414,7 @@ function Get-AbrADGPO { $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Unlinked Group Policy Objects Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorUnlinkedGPOItem) $($_.Exception.Message)" } } } @@ -445,7 +445,7 @@ function Get-AbrADGPO { Write-PScriboMessage -Message ($reportTranslate.GetAbrADGPO.UnlinkedGPONoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Unlinked Group Policy Objects Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorUnlinkedGPOSection) $($_.Exception.Message)" } try { $OutObj = [System.Collections.Generic.List[object]]::new() @@ -463,7 +463,7 @@ function Get-AbrADGPO { $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Empty Group Policy Objects Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorEmptyGPOItem) $($_.Exception.Message)" } } } @@ -494,7 +494,7 @@ function Get-AbrADGPO { Write-PScriboMessage -Message ($reportTranslate.GetAbrADGPO.EmptyGPONoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Empty Group Policy Objects Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorEmptyGPOSection) $($_.Exception.Message)" } try { $OutObj = [System.Collections.Generic.List[object]]::new() @@ -518,7 +518,7 @@ function Get-AbrADGPO { } } } catch { - Write-PScriboMessage -IsWarning -Message "OU: $($OU): $($_.Exception.Message) (Enforced Group Policy Objects Item)" + Write-PScriboMessage -IsWarning -Message "OU: $($OU): $($reportTranslate.GetAbrADGPO.ErrorEnforcedGPOItem) $($_.Exception.Message)" } } } @@ -551,7 +551,7 @@ function Get-AbrADGPO { Write-PScriboMessage -Message ($reportTranslate.GetAbrADGPO.EnforcedGPONoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Enforced Group Policy Objects Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorEnforcedGPOTable) $($_.Exception.Message)" } # Code taken from Jeremy Saunders # https://github.com/jeremyts/ActiveDirectoryDomainServices/blob/master/Audit/FindOrphanedGPOs.ps1 @@ -564,7 +564,7 @@ function Get-AbrADGPO { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Orphaned GPO Section: New-PSSession: Unable to connect to $($ValidDCFromDomain): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADGPO.ErrorOrphanedGPOPSSession -f $ValidDCFromDomain, $ErrorMessage) } $GPOPoliciesSYSVOLUNC = "\\$($Domain.DNSRoot)\SYSVOL\$($Domain.DNSRoot)\Policies" $OrphanGPOs = [System.Collections.Generic.List[object]]::new() @@ -658,13 +658,13 @@ function Get-AbrADGPO { Write-PScriboMessage -Message ($reportTranslate.GetAbrADGPO.OrphanedGPONoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Orphaned GPO)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorOrphanedGPOItem) $($_.Exception.Message)" } } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Group Policy Objects Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADGPO.ErrorGPOSection) $($_.Exception.Message)" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADHardening.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADHardening.ps1 index 1fd7668..7494f08 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADHardening.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADHardening.ps1 @@ -175,11 +175,11 @@ function Get-AbrADHardening { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (ADHardening Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADHardening.ErrorADHardeningItem))" } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (ADHardening Section)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADHardening.ErrorADHardeningSection))" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADInfrastructureService.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADInfrastructureService.ps1 index a0fd536..72304e6 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADInfrastructureService.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADInfrastructureService.ps1 @@ -33,7 +33,7 @@ function Get-AbrADInfrastructureService { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Domain Controller Infrastructure Services Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADInfrastructureService.ErrorPSSession -f $DC, $ErrorMessage) } if ($Available) { $OutObj = [System.Collections.Generic.List[object]]::new() @@ -50,7 +50,7 @@ function Get-AbrADInfrastructureService { $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Infrastructure Services Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADInfrastructureService.ErrorDCInfraServicesItem))" } } @@ -96,7 +96,7 @@ function Get-AbrADInfrastructureService { Write-PScriboMessage -Message ($reportTranslate.GetAbrADInfrastructureService.NoData -f $DC) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Controller Infrastructure Services Section)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADInfrastructureService.ErrorDCInfraServicesTable))" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADKerberosAudit.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADKerberosAudit.ps1 index 6ffe642..44080e1 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADKerberosAudit.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADKerberosAudit.ps1 @@ -42,7 +42,7 @@ function Get-AbrADKerberosAudit { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Unconstrained Kerberos delegation Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADKerberosAudit.ErrorUnconstrainedKerberosItem))" } } @@ -86,7 +86,7 @@ function Get-AbrADKerberosAudit { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (KRBTGT account Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADKerberosAudit.ErrorKRBTGTAccountItem))" } if ($HealthCheck.Domain.Security) { @@ -114,7 +114,7 @@ function Get-AbrADKerberosAudit { Write-PScriboMessage -Message ($reportTranslate.GetAbrADKerberosAudit.KRBTGTNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Unconstrained Kerberos delegation Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADKerberosAudit.ErrorUnconstrainedKerberosItem))" } try { $SID = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { "$($($using:Domain).domainsid.ToString())-500" } @@ -134,7 +134,7 @@ function Get-AbrADKerberosAudit { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (ADMIN account Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADKerberosAudit.ErrorAdminAccountItem))" } if ($HealthCheck.Domain.Security) { @@ -162,10 +162,10 @@ function Get-AbrADKerberosAudit { Write-PScriboMessage -Message ($reportTranslate.GetAbrADKerberosAudit.AdminNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Unconstrained Kerberos delegation Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADKerberosAudit.ErrorUnconstrainedKerberosItem))" } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Unconstrained Kerberos delegation Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADKerberosAudit.ErrorUnconstrainedKerberosSection))" } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADOU.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADOU.ps1 index eb33870..f1eb319 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADOU.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADOU.ps1 @@ -52,7 +52,7 @@ function Get-AbrADOU { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Organizational Unit Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADOU.ErrorOUItem))" } } @@ -94,7 +94,7 @@ function Get-AbrADOU { $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Blocked Inheritance GPO Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADOU.ErrorBlockedInheritanceGPOItem))" } } } @@ -124,7 +124,7 @@ function Get-AbrADOU { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Blocked Inheritance GPO Section)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADOU.ErrorBlockedInheritanceGPOSection))" } } } @@ -132,7 +132,7 @@ function Get-AbrADOU { Write-PScriboMessage -Message ($reportTranslate.GetAbrADOU.OUNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Organizational Unit Section)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADOU.ErrorOUSection))" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADReportBrief.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADReportBrief.ps1 index fe11695..899ffd1 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADReportBrief.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADReportBrief.ps1 @@ -51,7 +51,7 @@ function Get-AbrADReportBrief { } $OutObj | Table @TableParams } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Report Brief - Report Overview)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADReportBrief.ErrorReportOverview))" } BlankLine @@ -79,7 +79,7 @@ function Get-AbrADReportBrief { } $OutObj | Table @TableParams } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Report Brief - Forest Summary)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADReportBrief.ErrorForestSummary))" } BlankLine @@ -103,7 +103,7 @@ function Get-AbrADReportBrief { } $OutObj.Add([pscustomobject]$inObj) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Report Brief - Domain Summary - $Domain)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADReportBrief.ErrorDomainSummaryItem))" } } @@ -121,7 +121,7 @@ function Get-AbrADReportBrief { $OutObj | Table @TableParams } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Report Brief - Domain Summary)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADReportBrief.ErrorDomainSummary))" } BlankLine @@ -162,12 +162,12 @@ function Get-AbrADReportBrief { } $OutObj | Table @TableParams } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Report Brief - Report Scope)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADReportBrief.ErrorReportScope))" } } PageBreak } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Report Brief Section)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADReportBrief.ErrorReportBriefSection))" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSCCM.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSCCM.ps1 index 375886c..cf9604f 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSCCM.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSCCM.ps1 @@ -42,7 +42,7 @@ function Get-AbrADSCCM { } $SCCMInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.SCCMception.Message) (SCCM Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSCCM.ErrorSCCMItem) $($_.Exception.Message)" } } @@ -74,12 +74,12 @@ function Get-AbrADSCCM { } } } else { - Write-PScriboMessage -Message "No SCCM Infrastructure information found in $($ForestInfo.toUpper()), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSCCM.NoSCCMInfo -f $ForestInfo.toUpper()) Paragraph $reportTranslate.GetAbrADSCCM.NotFound BlankLine } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.SCCMception.Message) (SCCM Table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSCCM.ErrorSCCMTable) $($_.Exception.Message)" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSecurityAssessment.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSecurityAssessment.ps1 index 99789ea..9e6f2df 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSecurityAssessment.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSecurityAssessment.ps1 @@ -56,7 +56,7 @@ function Get-AbrADSecurityAssessment { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Account Security Assessment Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorAccountSecurityAssessmentItem))" } if ($HealthCheck.Domain.Security) { @@ -83,7 +83,7 @@ function Get-AbrADSecurityAssessment { $sampleData = $inObj.GetEnumerator() | Select-Object @{ Name = 'Category'; Expression = { $_.key } }, @{ Name = 'Value'; Expression = { $_.value } } $Chart = New-PieChart -Values $sampleData.Value -Labels $sampleData.Category -Title $reportTranslate.GetAbrADSecurityAssessment.UserAccountTitle -EnableLegend -LegendOrientation Horizontal -LegendAlignment UpperCenter -Width 600 -Height 600 -Format base64 -TitleFontSize 20 -TitleFontBold -EnableCustomColorPalette -CustomColorPalette $AbrCustomPalette -EnableChartBorder -ChartBorderStyle DenselyDashed -ChartBorderColor DarkBlue } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (User Account Security Assessment Chart)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorUserAccountSecurityAssessmentChart))" } if ($OutObj) { Section -ExcludeFromTOC -Style NOTOCHeading4 $reportTranslate.GetAbrADSecurityAssessment.UserAccountTitle { @@ -101,10 +101,10 @@ function Get-AbrADSecurityAssessment { } } } else { - Write-PScriboMessage -Message "No Domain users information found in $($Domain.DNSRoot), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSecurityAssessment.NoUserInfo -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Account Security Assessment Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorAccountSecurityAssessmentTable))" } if ($InfoLevel.Domain -ge 2) { try { @@ -139,7 +139,7 @@ function Get-AbrADSecurityAssessment { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Privileged Users Assessment Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorPrivilegedUsersAssessmentItem))" } } @@ -186,10 +186,10 @@ function Get-AbrADSecurityAssessment { } } } else { - Write-PScriboMessage -Message "No Privileged User Assessment information found in $($Domain.DNSRoot), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSecurityAssessment.NoPrivilegedUserInfo -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Privileged Users Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorPrivilegedUsersTable))" } try { $InactivePrivilegedUsers = $PrivilegedUsers | Where-Object { ($_.LastLogonDate -le (Get-Date).AddDays(-30)) -and ($_.PasswordLastSet -le (Get-Date).AddDays(-365)) -and ($_.SamAccountName -ne 'krbtgt') -and ($_.SamAccountName -ne 'Administrator') } @@ -217,7 +217,7 @@ function Get-AbrADSecurityAssessment { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Inactive Privileged Accounts Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorInactivePrivilegedAccountsItem))" } } @@ -243,10 +243,10 @@ function Get-AbrADSecurityAssessment { } } } else { - Write-PScriboMessage -Message "No Inactive Privileged Accounts information found in $($Domain.DNSRoot), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSecurityAssessment.NoInactivePrivilegedInfo -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Inactive Privileged Accounts Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorInactivePrivilegedAccountsTable))" } try { $UserSPNs = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADUser -ResultPageSize 1000 -Server ($using:Domain).DNSRoot -Filter { ServicePrincipalName -like '*' } -Properties AdminCount, PasswordLastSet, LastLogonDate, ServicePrincipalName, TrustedForDelegation, TrustedtoAuthForDelegation } @@ -273,7 +273,7 @@ function Get-AbrADSecurityAssessment { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Service Accounts Assessment Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorServiceAccountsAssessmentItem))" } } @@ -305,10 +305,10 @@ function Get-AbrADSecurityAssessment { } } } else { - Write-PScriboMessage -Message "No Service Accounts Assessment information found in $($Domain.DNSRoot), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSecurityAssessment.NoServiceAccountsInfo -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Service Accounts Assessment Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADSecurityAssessment.ErrorServiceAccountsAssessmentTable))" } } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSite.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSite.ps1 index 9cfc160..3064a6f 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSite.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSite.ps1 @@ -36,7 +36,7 @@ function Get-AbrADSite { try { $Graph = Get-AbrDiagrammer -DiagramType 'Replication' -DiagramOutput base64 -PSSessionObject $TempPssSession } catch { - Write-PScriboMessage -IsWarning -Message "Replication Diagram Graph: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorReplicationDiagramGraph) $($_.Exception.Message)" } if ($Graph) { @@ -48,7 +48,7 @@ function Get-AbrADSite { } } } catch { - Write-PScriboMessage -IsWarning -Message "Replication Diagram Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorReplicationDiagramSection) $($_.Exception.Message)" } } Section -Style Heading4 $reportTranslate.GetAbrADSite.Sites { @@ -82,7 +82,7 @@ function Get-AbrADSite { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Site)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorDomainSite) $($_.Exception.Message)" } } @@ -154,12 +154,12 @@ function Get-AbrADSite { $OutObj | Where-Object { $_.$($reportTranslate.GetAbrADSite.Name) -ne $reportTranslate.GetAbrADSite.AutoGenerated } | Set-Style -Style Warning -Property $reportTranslate.GetAbrADSite.Name } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Site Replication Connection Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSiteReplicationConnectionItem) $($_.Exception.Message)" } } $TableParams = @{ - Name = "$($reportTranslate.GetAbrADSite.ConnectionObjects) - $($ForestInfo)" + Name = "$($reportTranslate.GetAbrADSite.ConnectionObjects)- $($ForestInfo)" List = $false ColumnWidths = 25, 25, 25, 25 } @@ -179,10 +179,10 @@ function Get-AbrADSite { } } } else { - Write-PScriboMessage -Message "No Connection Objects information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoConnectionObjectsInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Connection Objects)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorConnectionObjects) $($_.Exception.Message)" } try { $Subnet = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADReplicationSubnet -Filter * -Properties * } @@ -207,7 +207,7 @@ function Get-AbrADSite { $OutObj | Where-Object { $_.$($reportTranslate.GetAbrADSite.Sites) -eq $reportTranslate.GetAbrADSite.NoSiteAssigned } | Set-Style -Style Warning -Property $reportTranslate.GetAbrADSite.Sites } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Site Subnets)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSiteSubnets) $($_.Exception.Message)" } } @@ -222,7 +222,7 @@ function Get-AbrADSite { } $List.Add($reportTranslate.GetAbrADSite.DescBP) } - if ($OutObj | Where-Object { $_.$($reportTranslate.GetAbrADSite.Sites) -eq $reportTranslate.GetAbrADSite.NoSiteAssigned }) { + if ($OutObj | Where-Object { $_.$($reportTranslate.GetAbrADSite.Sites) -eq $reportTranslate.GetAbrADSite.NoSiteAssigned }){ $OutObj | Where-Object { $_.$($reportTranslate.GetAbrADSite.Sites) -eq $reportTranslate.GetAbrADSite.NoSiteAssigned } | Set-Style -Style Warning -Property $reportTranslate.GetAbrADSite.Sites $Num++ foreach ( $OBJ in ($OutObj | Where-Object { $_.$($reportTranslate.GetAbrADSite.Sites) -eq $reportTranslate.GetAbrADSite.NoSiteAssigned }) ) { @@ -273,16 +273,16 @@ function Get-AbrADSite { } } } else { - Write-PScriboMessage -Message "Unable to read $Path on $DC" + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.UnableToRead -f $Path, $DC) } } else { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Missing Subnet in AD Section: New-PSSession: Unable to connect to $($DC): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADSite.ErrorMissingSubnetPSSession -f $DC, $ErrorMessage) } } catch { - Write-PScriboMessage -IsWarning -Message "Missing Subnet in AD Item table: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorMissingSubnetItemTable) $($_.Exception.Message)" } } } @@ -313,25 +313,25 @@ function Get-AbrADSite { } } } else { - Write-PScriboMessage -Message "No Missing Subnets in AD information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoMissingSubnetsInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "Missing Subnet in AD Item Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorMissingSubnetItemSection) $($_.Exception.Message)" } } } } else { - Write-PScriboMessage -Message "No Site Subnets information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoSiteSubnetsInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Site Subnets)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSiteSubnets) $($_.Exception.Message)" } if ($Options.EnableDiagrams) { try { try { $Graph = Get-AbrDiagrammer -DiagramType 'Sites' -DiagramOutput base64 -PSSessionObject $TempPssSession } catch { - Write-PScriboMessage -IsWarning -Message "Site Topology Diagram Graph: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSiteTopologyDiagramGraph) $($_.Exception.Message)" } if ($Graph) { @@ -343,7 +343,7 @@ function Get-AbrADSite { } } } catch { - Write-PScriboMessage -IsWarning -Message "Site Topology Diagram Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSiteTopologyDiagramSection) $($_.Exception.Message)" } } try { @@ -402,7 +402,7 @@ function Get-AbrADSite { } $OutObj | Sort-Object -Property $reportTranslate.GetAbrADSite.Name | Table @TableParams } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Inter-Site Transports section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorInterSiteTransports) $($_.Exception.Message)" } try { Section -Style Heading4 $reportTranslate.GetAbrADSite.IPSection { @@ -484,15 +484,15 @@ function Get-AbrADSite { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (IP Site Links table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorIPSiteLinksTable) $($_.Exception.Message)" } } } } else { - Write-PScriboMessage -Message "No IP Site Links information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoIPSiteLinksInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (IP Site Links Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorIPSiteLinksSection) $($_.Exception.Message)" } try { $IPLinkBridges = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADReplicationSiteLinkBridge -Filter * -Properties * | Where-Object { $_.InterSiteTransportProtocol -eq 'IP' } } @@ -550,19 +550,19 @@ function Get-AbrADSite { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (IP Site Links Bridges table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorIPSiteLinksBridgesTable) $($_.Exception.Message)" } } } } else { - Write-PScriboMessage -Message "No IP Site Links Bridges information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoIPSiteLinksBridgesInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (IP Site Links Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorIPSiteLinksSection) $($_.Exception.Message)" } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (IP)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorIP) $($_.Exception.Message)" } try { $IPLink = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADReplicationSiteLink -Filter * -Properties * | Where-Object { $_.InterSiteTransportProtocol -eq 'SMTP' } } @@ -645,12 +645,12 @@ function Get-AbrADSite { } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SMTP Site Links table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSMTPSiteLinksTable) $($_.Exception.Message)" } } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SMTP Site Links Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSMTPSiteLinksSection) $($_.Exception.Message)" } try { $IPLinkBridges = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADReplicationSiteLinkBridge -Filter * -Properties * | Where-Object { $_.InterSiteTransportProtocol -eq 'SMTP' } } @@ -707,29 +707,29 @@ function Get-AbrADSite { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SMTP Site Links Bridges table)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSMTPSiteLinksBridgesTable) $($_.Exception.Message)" } } } } else { - Write-PScriboMessage -Message "No SMTP Site Links Bridges information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoSMTPSiteLinksBridgesInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SMTP Site Links Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSMTPSiteLinksSection) $($_.Exception.Message)" } } } else { - Write-PScriboMessage -Message "No SMTP Site Links information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoSMTPSiteLinksInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (SMTP)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSMTP) $($_.Exception.Message)" } } } else { - Write-PScriboMessage -Message "No SMTP Site Links information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoSMTPSiteLinksInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Site Subnets)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSiteSubnets) $($_.Exception.Message)" } try { $OutObj = [System.Collections.Generic.List[object]]::new() @@ -760,7 +760,7 @@ function Get-AbrADSite { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "Sysvol Replication Item Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSysvolReplicationItemSection) $($_.Exception.Message)" } if ($HealthCheck.Site.BestPractice) { @@ -782,7 +782,7 @@ function Get-AbrADSite { } } else { try { - Write-PScriboMessage -Message "Unable to collect infromation from $DC." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.UnableToCollect -f $DC) $inObj = [ordered] @{ $reportTranslate.GetAbrADSite.DCName = $DC.split('.', 2)[0] $reportTranslate.GetAbrADSite.ReplicationStatus = $reportTranslate.GetAbrADSite.StatusUnknown @@ -790,7 +790,7 @@ function Get-AbrADSite { } $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (DNS IP Configuration Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorDNSIPConfigItem) $($_.Exception.Message)" } } } @@ -819,17 +819,17 @@ function Get-AbrADSite { } } } else { - Write-PScriboMessage -Message "No Sysvol Replication information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoSysvolReplicationInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "Sysvol Replication Table Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorSysvolReplicationTableSection) $($_.Exception.Message)" } } } else { - Write-PScriboMessage -Message "No Sites information found in $ForestInfo, Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADSite.NoSitesInfo -f $ForestInfo) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Site Global)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSite.ErrorDomainSiteGlobal) $($_.Exception.Message)" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSiteReplication.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSiteReplication.ps1 index 10443eb..f743e9c 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSiteReplication.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADSiteReplication.ps1 @@ -64,11 +64,11 @@ function Get-AbrADSiteReplication { $ReplInfo | Where-Object { $_.$($reportTranslate.GetAbrADSiteReplication.AutoGenerated) -ne 'Yes' } | Set-Style -Style Warning -Property $reportTranslate.GetAbrADSiteReplication.AutoGenerated } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Site Replication Connection Item)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSiteReplication.ErrorSiteReplicationConnectionItem) $($_.Exception.Message)" } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Site Replication Connection Section)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSiteReplication.ErrorSiteReplicationConnectionSection) $($_.Exception.Message)" } } } @@ -79,7 +79,7 @@ function Get-AbrADSiteReplication { Paragraph $reportTranslate.GetAbrADSiteReplication.ReplicationConnectionParagraph BlankLine foreach ($Repl in ($ReplInfo | Sort-Object -Property 'Replicate From Directory Server')) { - Section -Style NOTOCHeading4 -ExcludeFromTOC "Site: $($Repl.$($reportTranslate.GetAbrADSiteReplication.FromSite)): From: $($Repl.$($reportTranslate.GetAbrADSiteReplication.FromServer)) To: $($Repl.$($reportTranslate.GetAbrADSiteReplication.ToServer))" { + Section -Style NOTOCHeading4 -ExcludeFromTOC "$($reportTranslate.GetAbrADSiteReplication.SiteLabel) $($Repl.$($reportTranslate.GetAbrADSiteReplication.FromSite)): $($reportTranslate.GetAbrADSiteReplication.FromLabel) $($Repl.$($reportTranslate.GetAbrADSiteReplication.FromServer)) $($reportTranslate.GetAbrADSiteReplication.ToLabel) $($Repl.$($reportTranslate.GetAbrADSiteReplication.ToServer))" { $TableParams = @{ Name = "$($reportTranslate.GetAbrADSiteReplication.ReplicationConnectionTableName) - $($Repl.$($reportTranslate.GetAbrADSiteReplication.ToServer))" List = $true @@ -112,7 +112,7 @@ function Get-AbrADSiteReplication { Write-PScriboMessage -Message ($reportTranslate.GetAbrADSiteReplication.ReplicationConnectionNoData -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Replication Connection)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSiteReplication.ErrorReplicationConnection) $($_.Exception.Message)" } } try { @@ -125,7 +125,7 @@ function Get-AbrADSiteReplication { if (-not $_.Exception.MessageId) { $ErrorMessage = $_.FullyQualifiedErrorId } else { $ErrorMessage = $_.Exception.MessageId } - Write-PScriboMessage -IsWarning -Message "Replication Status Section: New-PSSession: Unable to connect to $($ValidDCFromDomain): $ErrorMessage" + Write-PScriboMessage -IsWarning -Message ($reportTranslate.GetAbrADSiteReplication.ErrorPSSession -f $ValidDCFromDomain, $ErrorMessage) } if ($RepStatus) { Section -Style Heading4 $reportTranslate.GetAbrADSiteReplication.ReplicationStatusTitle { @@ -144,7 +144,7 @@ function Get-AbrADSiteReplication { $OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Replication Status)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSiteReplication.ErrorReplicationStatus) $($_.Exception.Message)" } } if ($HealthCheck.Site.Replication) { @@ -175,7 +175,7 @@ function Get-AbrADSiteReplication { } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Site Replication Status)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADSiteReplication.ErrorSiteReplicationStatus) $($_.Exception.Message)" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADTrust.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADTrust.ps1 index fa7e28f..69b7715 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADTrust.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrADTrust.ps1 @@ -75,7 +75,7 @@ function Get-AbrADTrust { } $TrustInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Trust Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADTrust.ErrorTrustItem))" } } @@ -123,7 +123,7 @@ function Get-AbrADTrust { try { $Graph = Get-AbrDiagrammer -DiagramType 'Trusts' -DiagramOutput base64 -DomainController $ValidDCFromDomain } catch { - Write-PScriboMessage -IsWarning -Message "Domain and Trusts Diagram Graph: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADTrust.ErrorTrustDiagramGraph) $($_.Exception.Message)" } if ($Graph) { @@ -135,19 +135,19 @@ function Get-AbrADTrust { } } } catch { - Write-PScriboMessage -IsWarning -Message "Domain and Trusts Diagram Section: $($_.Exception.Message)" + Write-PScriboMessage -IsWarning -Message "$($reportTranslate.GetAbrADTrust.ErrorTrustDiagramSection) $($_.Exception.Message)" } } } } else { - Write-PScriboMessage -Message "No Domain Trust information found in $($Domain.DNSRoot), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrADTrust.NoTrustInfo -f $Domain.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Trust Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADTrust.ErrorTrustTable))" } } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Trust Section)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrADTrust.ErrorTrustSection))" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDHCPinAD.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDHCPinAD.ps1 index 581bec5..4ae7023 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDHCPinAD.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDHCPinAD.ps1 @@ -60,7 +60,7 @@ function Get-AbrDHCPinAD { } $DCHPInfo.Add([pscustomobject](ConvertTo-HashToYN $inObj)) } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (DHCP Item)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrDHCPinAD.ErrorDHCPItem))" } } @@ -75,12 +75,12 @@ function Get-AbrDHCPinAD { $DCHPInfo | Sort-Object -Property $reportTranslate.GetAbrDHCPinAD.ServerName | Table @TableParams } } else { - Write-PScriboMessage -Message "No DHCP Infrastructure information found in $($ForestInfo.toUpper()), Disabling this section." + Write-PScriboMessage -Message ($reportTranslate.GetAbrDHCPinAD.NoDHCPInfo -f $ForestInfo.toUpper()) Paragraph $reportTranslate.GetAbrDHCPinAD.NotFound BlankLine } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (DHCP Table)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrDHCPinAD.ErrorDHCPTable))" } } diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDNSSection.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDNSSection.ps1 index 7899718..c442e15 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDNSSection.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDNSSection.ps1 @@ -54,7 +54,7 @@ function Get-AbrDNSSection { Write-PScriboMessage -Message ([string]::Format($reportTranslate.GetAbrDNSSection.ExcludedDomain, $DomainInfo.DNSRoot)) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Domain Name System Information)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrDNSSection.ErrorDNSInfo))" } } else { Write-PScriboMessage -IsWarning -Message ([string]::Format($reportTranslate.GetAbrDNSSection.NoDCAvailable, $DomainInfo.DNSRoot)) diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDomainSection.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDomainSection.ps1 index 127a644..c791f57 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDomainSection.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Get-AbrDomainSection.ps1 @@ -5,7 +5,7 @@ function Get-AbrDomainSection { .DESCRIPTION .NOTES - Version: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -32,7 +32,7 @@ function Get-AbrDomainSection { # Define Filter option for Domain variable try { if ($DomainInfo = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADDomain -Identity $using:Domain }) { - Write-Host " - Collecting Domain information from $Domain." + Write-Host ([string]::Format(" - $($reportTranslate.GetAbrDomainSection.CollectingDomain)", $Domain)) $DCs = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADDomain -Identity $using:Domain | Select-Object -ExpandProperty ReplicaDirectoryServers | Where-Object { $_ -notin ($using:Options).Exclude.DCs } } | Sort-Object Section -Style Heading2 "$($DomainInfo.DNSRoot.ToString().ToUpper())" { Paragraph $reportTranslate.GetAbrDomainSection.Paragraph @@ -78,7 +78,7 @@ function Get-AbrDomainSection { try { $DCDiagObj = foreach ($DC in $DCs) { if (Get-DCWinRMState -ComputerName $DC -DCStatus ([ref]$DCStatus)) { - # Get-AbrADDCDiag -Domain $Domain -DC $DC + Get-AbrADDCDiag -Domain $Domain -DC $DC } } if ($DCDiagObj) { @@ -140,7 +140,7 @@ function Get-AbrDomainSection { Write-PScriboMessage -Message ($reportTranslate.GetAbrDomainSection.DomainExcluded -f $DomainInfo.DNSRoot) } } catch { - Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) (Active Directory Domain)" + Write-PScriboMessage -IsWarning -Message "$($_.Exception.Message) ($($reportTranslate.GetAbrDomainSection.ErrorADDomain))" } } else { $DomainStatus.Value.Add( diff --git a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Invoke-DcDiag.ps1 b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Invoke-DcDiag.ps1 index 0cfdaa3..8249bf8 100644 --- a/AsBuiltReport.Microsoft.AD/Src/Private/Report/Invoke-DcDiag.ps1 +++ b/AsBuiltReport.Microsoft.AD/Src/Private/Report/Invoke-DcDiag.ps1 @@ -25,7 +25,7 @@ function Invoke-DcDiag { try { $result = Invoke-CommandWithTimeout -Session $DCPssSessionDCDiag -ScriptBlock { dcdiag /c /s:$using:DomainController } } catch { - Write-PScriboMessage -Message "Invoke-DcDiag - Failed to get DCDiag for $DomainController with error: $($_.Exception.Message)" + Write-PScriboMessage -Message "$($reportTranslate.GetAbrADDCDiag.ErrorInvokeDcDiag -f $DomainController) $($_.Exception.Message)" return } 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 78c02a6..b943c1a 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: 0.9.12 + Version: 1.0.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -44,11 +44,11 @@ function Invoke-AsBuiltReport.Microsoft.AD { $InstalledVersion = Get-Module -ListAvailable -Name $Module -ErrorAction SilentlyContinue | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version if ($InstalledVersion) { - Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ModuleInstalled -f $Module, $InstalledVersion.ToString()) + Write-Host (" $($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ModuleInstalled)" -f $Module, $InstalledVersion.ToString()) $LatestVersion = Find-Module -Name $Module -Repository PSGallery -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Version if ($InstalledVersion -lt $LatestVersion) { - Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ModuleAvailable -f $Module, $LatestVersion.ToString()) -ForegroundColor Red - Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ModuleUpdate -f $Module) -ForegroundColor Red + Write-Host (" $($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ModuleAvailable)" -f $Module, $LatestVersion.ToString()) -ForegroundColor Red + Write-Host (" $($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ModuleUpdate)" -f $Module) -ForegroundColor Red } } } catch { @@ -163,16 +163,16 @@ function Invoke-AsBuiltReport.Microsoft.AD { if ($ChildDomains) { $OrderedDomains.Add($ChildDomains) - Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.DiscoveringChildDomains -f $RootDomains, ($OrderedDomains -join ', ')) + Write-Host (" $($reportTranslate.InvokeAsBuiltReportMicrosoftAD.DiscoveringChildDomains)" -f $RootDomains, ($OrderedDomains -join ', ')) } # Set initial connection to childs domains to find out if there is an available DC to fulfill the requests foreach ($Domain in $OrderedDomains) { try { if (Get-ValidDCfromDomain -Domain $Domain -DCStatus ([ref]$DCStatus)) { - Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.DCAvailable -f $Domain) + Write-Host (" $($reportTranslate.InvokeAsBuiltReportMicrosoftAD.DCAvailable)" -f $Domain) } else { - Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.DCUnavailable -f $Domain) + Write-Host (" $($reportTranslate.InvokeAsBuiltReportMicrosoftAD.DCUnavailable)" -f $Domain) $DomainStatus.Add( @{ Name = $Domain @@ -183,7 +183,7 @@ function Invoke-AsBuiltReport.Microsoft.AD { } } catch { $null } } - Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.FinishingDomainList -f $RootDomains, ($OrderedDomains -join ', ')) + Write-Host (" $($reportTranslate.InvokeAsBuiltReportMicrosoftAD.FinishingDomainList)" -f $RootDomains, ($OrderedDomains -join ', ')) # Report Overview Get-AbrADReportBrief diff --git a/CHANGELOG.md b/CHANGELOG.md index c4a9708..e2335f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ 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.0] - 2026-04-24 + +### Added + +- Add support for GliderUI graphical interface to generate reports via an intuitive user interface instead of command-line execution. The GUI enables users to select target domains, customize report options (InfoLevel, HealthChecks, diagram settings), and initiate report generation with a streamlined, user-friendly experience, reducing friction for non-technical stakeholders. + +### :arrows_clockwise: Changed + +- Improved multi-language support by refactoring localization strings and enhancing documentation clarity in MicrosoftAD.psd1 for English and Spanish languages. + This includes improved grammar, punctuation, and readability across various best practice descriptions related to Active Directory configurations +- Bump module version to `1.0.0` +- Upgrade AsBuiltReport.Diagram module to version `1.0.6` +- Upgrade AsBuiltReport.Chart module to version `0.3.1` + +## :bug: Fixed + +- Fix diagram theme generation not respecting the selected theme in the configuration file, ensuring that diagrams are rendered with the correct visual style as defined by the user. + ## [0.9.12] - 2026-04-02 ### :toolbox: Added diff --git a/README.md b/README.md index 6df76a2..80afbf6 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,19 @@ PS C:\> New-AsBuiltReport -Report Microsoft.AD -Target 'admin-dc-01v.contoso.loc PS C:\> New-AsBuiltReport -Report Microsoft.AD -Target 'admin-dc-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -SendEmail ``` +## :computer: GUI Examples + +The Microsoft AD As Built Report GUI can be used to generate reports without using the console. The GUI provides the same functionality as the console, but with a user-friendly interface. To launch the GUI, run the following command in a PowerShell terminal window: + +```powershell +# Launch the Microsoft AD As Built Report GUI +PS C:\> Start-AsBuiltReportMSAD +``` + +**Beta** versions of the GUI may contain bugs and issues. If you encounter any problems while using the GUI, please report them on the project's GitHub Issues page. + +![alt text](Samples/Sample-Gui.png) + ## :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. diff --git a/Samples/Sample-Gui.png b/Samples/Sample-Gui.png new file mode 100644 index 0000000..72b195e Binary files /dev/null and b/Samples/Sample-Gui.png differ diff --git a/copilot-instructions.md b/copilot-instructions.md deleted file mode 100644 index 558cea0..0000000 --- a/copilot-instructions.md +++ /dev/null @@ -1,871 +0,0 @@ -# AsBuiltReport.Microsoft.AD - Copilot Instructions - -**Project Overview:** AsBuiltReport.Microsoft.AD is a PowerShell module that generates comprehensive as-built documentation for Microsoft Active Directory (AD) infrastructure in Word/HTML/Text formats. It's part of the larger AsBuiltReport ecosystem and works in conjunction with AsBuiltReport.Core. - ---- - -## 1. PROJECT STRUCTURE - -### Top-Level Directory Layout -``` -AsBuiltReport.Microsoft.AD/ -├── .github/ # CI/CD workflows and PR templates -│ ├── workflows/ # GitHub Actions workflows -│ │ ├── Pester.yml # Unit testing pipeline -│ │ ├── PSScriptAnalyzer.yml # Linting/code analysis -│ │ ├── CodeQL.yml # Security scanning -│ │ ├── Release.yml # Publishing to PSGallery + social media -│ │ └── Stale.yml # Issue/PR housekeeping -│ └── PULL_REQUEST_TEMPLATE.md -├── .vscode/ # VS Code settings for PowerShell formatting -│ └── settings.json # Formatting rules, rulers @ 115 chars -├── AsBuiltReport.Microsoft.AD/ # MAIN MODULE DIRECTORY -│ ├── AsBuiltReport.Microsoft.AD.psm1 # Module manifest (14 lines - loads all functions) -│ ├── AsBuiltReport.Microsoft.AD.psd1 # Module declaration (v0.9.11) -│ ├── AsBuiltReport.Microsoft.AD.json # Default report config (InfoLevels, HealthChecks) -│ ├── AsBuiltReport.Microsoft.AD.Style.ps1 # Document styling (20.8 KB) -│ ├── Src/ -│ │ ├── Public/ -│ │ │ └── Invoke-AsBuiltReport.Microsoft.AD.ps1 # ENTRY POINT (291 lines) -│ │ └── Private/ -│ │ ├── Get-Abr*.ps1 # 52x data gathering functions -│ │ ├── ConvertTo-*.ps1 # Format/conversion helpers -│ │ ├── Convert-*.ps1 # Data transformation utilities -│ │ ├── Get-*Diagram.ps1 # Visualization generation -│ │ └── Utility functions # Session management, timeout handling, etc. -│ ├── Language/ # Localization files -│ │ ├── en-US/MicrosoftAD.psd1 # English strings (hash of all messages) -│ │ └── es-ES/MicrosoftAD.psd1 # Spanish localization -│ └── icons/ # Image assets for reports -├── Tests/ -│ ├── Invoke-Tests.ps1 # Test runner script (204 lines) -│ ├── AsBuiltReport.Microsoft.AD.Tests.ps1 # Pester unit tests -│ ├── LocalizationData.Tests.ps1 # Localization validation -│ └── README.md -├── Samples/ # Example HTML reports -├── README.md # Project documentation -├── CONTRIBUTING.md # Contribution guidelines -├── CODE_OF_CONDUCT.md # Community standards -├── LICENSE # License file -├── CHANGELOG.md # Version history -├── SECURITY.md # Security policy -└── Todo.md # Development roadmap -``` - -### Key Directories -- **Src/Public**: Only `Invoke-AsBuiltReport.Microsoft.AD` - the single exported public function -- **Src/Private**: 88 total functions (52 Get-Abr* for data gathering, rest are utilities) -- **Language**: Localization for multi-language support (en-US, es-ES) -- **Tests**: Pester tests + custom test runner supporting code coverage - -### File Count Summary -- **Total .ps1 files**: 94 -- **Public functions**: 1 (exported) -- **Private functions**: ~88 + utilities -- **Data gathering functions (Get-Abr*)**: 52 - ---- - -## 2. BUILD, TEST, LINT COMMANDS - -### Test Execution - -**Local Test Execution:** -```powershell -.\Tests\Invoke-Tests.ps1 # Basic run -.\Tests\Invoke-Tests.ps1 -CodeCoverage -OutputFormat NUnitXml # With coverage -``` - -**Test Runner Details** (`Tests/Invoke-Tests.ps1`): -- Uses **Pester 5.0.0+** for testing framework -- Supports output formats: Console, NUnitXml, JUnitXml -- Includes code coverage analysis (JaCoCo format) -- Code coverage threshold: 50% minimum (warning at <50%) -- Coverage files tracked: `*.psm1`, `Src/Public/*.ps1`, `Src/Private/*.ps1` -- Test results: `Tests/testResults.xml` -- Coverage output: `Tests/coverage.xml` - -### Code Analysis - -**PSScriptAnalyzer** (`PSScriptAnalyzerSettings.psd1`): -- Linting tool configured in CI/CD -- Custom rules enforced: - - `PSAvoidExclaimOperator` - no `!` operator - - `AvoidUsingDoubleQuotesForConstantString` - use single quotes for constants - - `UseCorrectCasing` - enforce proper case - - `PSAvoidUsingCmdletAliases` - no aliases - - `PSUseConsistentWhitespace` - whitespace consistency -- Excluded rules: - - `PSUseToExportFieldsInManifest` - - `PSAvoidUsingWriteHost` (needed for reports) - -### CI/CD Pipelines - -**Pester Tests Workflow** (`.github/workflows/Pester.yml`): -- Triggers: push (main/dev/master), PR, manual -- Runs on: Windows (pwsh + powershell 5.1) -- Auto-installs: Pester 5.0.0+, PScribo 0.11.1+, PSScriptAnalyzer 1.0.0+, AsBuiltReport.Core 1.6.2+ -- Uploads test results as artifacts -- Uploads code coverage to Codecov - -**PSScriptAnalyzer Workflow** (`.github/workflows/PSScriptAnalyzer.yml`): -- Uses external action: `alagoutte/github-action-psscriptanalyzer@master` -- Fails on errors, comments inline -- Settings: `.github/workflows/PSScriptAnalyzerSettings.psd1` - -**CodeQL Workflow** (`.github/workflows/CodeQL.yml`): -- Security scanning for PowerShell - -**Release Workflow** (`.github/workflows/Release.yml`): -- Triggers on release published -- Tests module manifest -- Publishes to PowerShell Gallery (`Publish-Module`) -- Posts release announcements to Twitter & Bluesky - -**No Build/Invoke-Build found**: This is a pure PowerShell module (no compilation). - ---- - -## 3. ARCHITECTURE - -### High-Level Data Flow - -``` -Invoke-AsBuiltReport.Microsoft.AD (Entry Point) - ↓ - [Input: Target DC, Credentials] - ↓ - [Validate: Requirements, Features, Modules] - ↓ - [Connection: PSSession + CIMSession to DC] - ↓ - [Process Per Forest/Domain] - ├── Get-AbrForestSection (Forest-level data) - ├── Get-AbrDomainSection (Per-domain data) - ├── Get-AbrDnsSection (DNS configuration) - └── Get-AbrPKISection (Certificate Authority) - ↓ - [Diagram Generation: Forest, Replication, Trusts, Sites, CA] - ↓ - [Session Cleanup: Remove PSSession, CIMSession] - ↓ - [Output: HTML/Word/Text Report] -``` - -### Main Entry Point - -**File**: `AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1` (291 lines) - -**Signature**: -```powershell -function Invoke-AsBuiltReport.Microsoft.AD { - [CmdletBinding()] - param ( - [String[]] $Target, # Domain controller(s) FQDN - [PSCredential] $Credential # Credentials for remote session - ) - #Requires -RunAsAdministrator -} -``` - -**Key Responsibilities**: -1. Validate prerequisites (Windows PS >= 5.1, admin rights, not ISE) -2. Check installed modules & warn on outdated versions -3. Validate OS features (RSAT tools on workstation, features on server) -4. Load report config (JSON), InfoLevels, HealthChecks, Options -5. Establish PSSession + CIMSession to DC via WinRM -6. Collect forest/domain/DNS/PKI data via section functions -7. Generate diagrams (if enabled) -8. Build report using PScribo -9. Cleanup sessions - -**Critical Design Pattern**: -- **$Target** must be FQDN (not IP) - WinRM limitation -- Must run **-RunAsAdministrator** -- Must run from PowerShell 7+, **NOT** PowerShell ISE -- WinRM must be enabled on DC -- Uses **$Options** hash from config for behavior control - -### Core Section Builders - -These functions call data gatherers and structure output via PScribo's **Section** cmdlet: - -1. **Get-AbrForestSection**: Forest topology, schema, tombstone lifetime, global catalogs -2. **Get-AbrDomainSection**: Per-domain configuration, trusts, replication, GPOs, OUs -3. **Get-AbrDnsSection**: DNS zones, scavenging, delegation -4. **Get-AbrPKISection**: Certificate authorities, templates, security - -### Data Gathering Functions (Get-Abr*) - -**Pattern**: Each function collects specific AD object data via remote PSSession: - -Example: `Get-AbrADForest` (80 lines) -- Uses `Invoke-CommandWithTimeout` to run remote cmdlets -- Parses schema version to determine Windows Server version -- Detects anonymous access via dsHeuristics -- Returns object with translated property names -- Applies HealthCheck styling if enabled - -**All 52 Get-Abr* functions follow this pattern:** -- Accept parameters (Domain, ValidDcFromDomain, etc.) -- Start: Log collection message, start timing -- Process: Remote invocation via session, data transformation -- Output: `[System.Collections.ArrayList]` of objects -- HealthCheck: Conditionally apply styling (Warning/Critical) -- Return: Table/list output via PScribo's **Table** cmdlet - -### InfoLevel Architecture - -**Default Config** (`AsBuiltReport.Microsoft.AD.json`): -```json -"InfoLevel": { - "_comment_": "0 = Disabled, 1 = Enabled, 2 = Adv Summary, 3 = Detailed", - "Forest": 2, - "Domain": 2, - "DNS": 1, - "CA": 0 -} -``` - -**Usage Pattern**: -```powershell -if ($InfoLevel.Forest -ge 1) { ... show basic info } -if ($InfoLevel.Forest -ge 2) { ... show advanced details } -if ($InfoLevel.Forest -ge 3) { ... show comprehensive tables } -``` - -Enables **progressive disclosure** - users control report verbosity. - -### HealthCheck Architecture - -**Default Config**: -```json -"HealthCheck": { - "Domain": { - "GMSA": true, # Group Managed Service Accounts - "GPO": true, # Group Policy Objects - "Backup": true, # Domain backup status - "DFS": true, # DFS health - "SPN": true, # Service Principal Names - "DuplicateObject": true, - "Security": true, - "BestPractice": true - }, - "DomainController": { ... }, - "Site": { ... }, - "DNS": { ... }, - "CA": { ... } -} -``` - -**Styling Application**: -```powershell -if ($HealthCheck.Domain.Security) { - $OutObj | Where-Object { $_.AnonymousAccess -eq 'Enabled' } | - Set-Style -Style Critical -Property AnonymousAccess - $OutObj | Where-Object { $_.TombstoneLifetime -lt 180 } | - Set-Style -Style Warning -Property TombstoneLifetime -} -``` - -Objects marked as Warning/Critical get colored highlighting in reports. - -### Connection Management - -**Session Establishment** (in main entry point): -```powershell -$TempPssSession = Get-ValidPSSession -ComputerName $System -SessionName $System -$TempCIMSession = Get-ValidCIMSession -ComputerName $System -SessionName $System -``` - -**Remote Command Execution**: -```powershell -Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADForest } -``` - -**Cleanup**: -```powershell -foreach ($PSSession in $PSSTable | Where { $_.Status -ne 'Offline' }) { - Remove-PSSession -Id $PSSession.id -} -``` - -### Diagram Generation - -**Diagrammer Integration**: -- Uses `Diagrammer.Core` module for topology visualization -- Types: Forest, Replication, Sites, SitesInventory, Trusts, CertificateAuthority -- Controlled by `$Options.EnableDiagrams`, `$Options.DiagramType.*` -- Outputs: PDF/PNG (configurable via `$Options.ExportDiagramsFormat`) -- Theme: White/Dark (via `$Options.DiagramTheme`) - ---- - -## 4. KEY CONVENTIONS AND PATTERNS - -### Function Naming Convention - -**Public Functions**: -- `Invoke-AsBuiltReport.Microsoft.AD` - single entry point (uses dot notation) - -**Private Functions** - Three categories: - -1. **Data Gatherers** (`Get-Abr*`): - - `Get-AbrADForest` - retrieves Forest info - - `Get-AbrADDomain` - retrieves Domain info - - `Get-AbrADDomainController` - DC inventory - - `Get-AbrADCA*` - CA-specific data - - Pattern: Get-Abr[Section][Subsection] - -2. **Section Builders** (`Get-Abr*Section`): - - `Get-AbrForestSection` - orchestrates Forest section - - `Get-AbrDomainSection` - orchestrates Domain section - - `Get-AbrDnsSection` - orchestrates DNS section - - `Get-AbrPKISection` - orchestrates PKI section - - Pattern: Get-Abr[Section]Section - -3. **Diagram Builders** (`Get-AbrDiag*`): - - `Get-AbrDiagrammer` - main diagram orchestration - - `Get-AbrDiagForest`, `Get-AbrDiagReplication`, etc. - - Pattern: Get-AbrDiag[DiagramType] - -4. **Utility Functions** (various): - - `Convert-IpAddressToMaskLength` - IP/CIDR conversion - - `ConvertTo-HashToYN` - bool → Yes/No conversion - - `Invoke-CommandWithTimeout` - remote execution with timeout - - `Get-ValidPSSession` - session validation/creation - - `Test-WinRM` - WinRM connectivity check - -### Data Structure Patterns - -**Standard Data Object**: -```powershell -$inObj = [ordered] @{ - 'Property Name' = $Value - 'Health Check Property' = $CheckResult -} -$OutObj.Add([pscustomobject](ConvertTo-HashToYN $inObj)) | Out-Null -``` - -**Conversion Helper Usage**: -```powershell -# ConvertTo-HashToYN: Converts boolean $true/$false → "Yes"/"No" -$inObj | ConvertTo-HashToYN -``` - -**Style Application**: -```powershell -$OutObj | Set-Style -Style Critical -Property $PropertyName -$OutObj | Set-Style -Style Warning -Property $PropertyName -``` - -### Report Section Structure - -**PScribo Section Hierarchy**: -```powershell -Section -Style Heading1 "Forest Name" { - Paragraph "Introduction..." - BlankLine - - Section -Style Heading2 "Subsection Title" { - if ($Options.ShowDefinitionInfo) { - Paragraph "Definition text..." - } - - # Call data gatherer - Get-AbrADForest - - if ($InfoLevel.Forest -ge 2) { - # Advanced details - Get-AbrADSite - } - } -} -``` - -**PScribo Elements Used**: -- `Section` - create section with heading levels (Heading1-Heading4) -- `Table` - display data in tabular format -- `Paragraph` - text with styling (Bold, Underline, Colors) -- `BlankLine` - spacing -- `PageBreak` - force page break in Word/PDF - -### Translation/Localization Pattern - -**Property Names Use Translated Strings**: -```powershell -# From Language/en-US/MicrosoftAD.psd1 -@{ - GetAbrADForest = @{ - Collecting = 'Collecting Active Directory forest information.' - ForestName = 'Forest Name' - ForestFunctionalLevel = 'Forest Functional Level' - ... - } -} - -# In function: -$reportTranslate.GetAbrADForest.Collecting # Loaded at module init -``` - -**Multi-Language Support**: -- Each culture has its own .psd1 file (en-US, es-ES, etc.) -- Strings loaded into `$reportTranslate` hash at module load -- Property names in output tables are localized - -### HealthCheck Patterns - -**Pre-Check Pattern** (e.g., RID Pool): -```powershell -if ($HealthCheck.Domain.BestPractice) { - if ([math]::Truncate($CompleteSIDS / $RIDsRemaining) -gt 80) { - $OutObj | Set-Style -Style Warning -Property RIDProperty - Paragraph "Health check message about RID pool..." - } -} -``` - -**28 Functions Use HealthCheck** out of 52 data gatherers (~54%): -- Focus on security, best practices, service health -- Each check compares values against thresholds -- Styling applied: Warning, Critical, or Success - -### Configuration-Driven Behavior - -**Options Hash Controls**: -```json -"Options": { - "ShowExecutionTime": false, # Show timing info - "ShowDefinitionInfo": false, # Show definition text - "PSDefaultAuthentication": "Negotiate", - "Exclude": { "Domains": [], "DCs": [] }, - "Include": { "Domains": [] }, # Only these domains - "WinRMSSL": false, - "WinRMFallbackToNoSSL": true, - "WinRMSSLPort": 5986, - "WinRMPort": 5985, - "EnableDiagrams": true, - "DiagramTheme": "White", - "JobsTimeOut": 900 # 15-minute timeout -} -``` - -**Usage Example**: -```powershell -if ($Options.ShowDefinitionInfo) { - Paragraph $reportTranslate.GetAbrForestSection.DefinitionText -} - -$TimeoutSeconds = $Options.JobsTimeOut -``` - -### Error Handling & Timeouts - -**Invoke-CommandWithTimeout Pattern**: -```powershell -function Invoke-CommandWithTimeout { - param( - [System.Management.Automation.Runspaces.PSSession]$Session, - [scriptblock]$ScriptBlock, - [int]$TimeoutSeconds = $Options.JobsTimeOut - ) - - # Run as background job with timeout - $job = Invoke-Command -Session $Session -AsJob -ScriptBlock $ScriptBlock - Wait-Job $job -Timeout $TimeoutSeconds - Receive-Job $job -} -``` - -**Try-Catch in Data Gatherers**: -```powershell -try { - Get-AbrADForest -} catch { - Write-PScriboMessage -IsWarning $_.Exception.Message -} -``` - -### Sensitive Data Handling - -**No explicit redaction observed**, but patterns suggest: -- Credentials passed via `$PSCredential` object (not stored) -- Session-based execution (no inline secrets) -- Remote execution prevents data capture on local disk -- Output tables contain parsed, non-sensitive data - -**Recommendation**: Follow AD best practices - restrict report access, don't email to untrusted parties. - ---- - -## 5. CONFIGURATION - -### Primary Config File - -**File**: `AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.json` - -**Structure**: -```json -{ - "Report": { - "Name": "Microsoft Active Directory As Built Report", - "Version": "1.0", - "Status": "Released", - "ShowCoverPageImage": true, - "ShowTableOfContents": true, - "ShowHeaderFooter": true, - "ShowTableCaptions": true - }, - "Options": { ... }, // Execution behavior - "InfoLevel": { ... }, // Report verbosity - "HealthCheck": { ... } // Health check toggles -} -``` - -### Configuration Usage - -Users provide config via **-ReportConfig parameter** to AsBuiltReport.Core: - -```powershell -$ReportConfig = Get-Content 'config.json' | ConvertFrom-Json - -New-AsBuiltReport -Report Microsoft.AD ` - -Target 'DC01.contoso.com' ` - -ReportConfig $ReportConfig ` - -Credential $cred ` - -Format HTML -``` - -**Module Loads**: -```powershell -$script:Report = $ReportConfig.Report -$script:InfoLevel = $ReportConfig.InfoLevel -$script:Options = $ReportConfig.Options -``` - -### Module Manifest - -**File**: `AsBuiltReport.Microsoft.AD/AsBuiltReport.Microsoft.AD.psd1` - -**Key Settings**: -- **Version**: 0.9.11 -- **PowerShellVersion**: 5.1 (minimum, actually PS7 required) -- **CompatiblePSEditions**: Desktop, Core -- **GUID**: 0a3e1c04-13b8-418f-89bc-a5a18da07394 - -**Required Modules**: -- AsBuiltReport.Core (v1.6.2+) -- AsBuiltReport.Chart (v0.2.0+) -- Diagrammer.Core (v0.2.38+) -- PSPKI (v4.3.0+) - ---- - -## 6. EXISTING AI CONFIGS - -**None Found**. No existing files: -- `.cursorrules` ✗ -- `.clinerules` ✗ -- `.windsurfrules` ✗ -- `CLAUDE.md` ✗ -- `AGENTS.md` ✗ -- `CONVENTIONS.md` ✗ - ---- - -## 7. README AND CONTRIBUTING - -### README Key Points - -**Project Purpose**: -- Community-maintained, no Microsoft sponsorship -- Generates as-built documentation for AD (Word/HTML/Text) -- Supports AD 2012/2016/2019/2022/2025 -- **PowerShell 7+ required** (not PS 5.1!) -- Windows only (RSAT dependency) - -**Supported Features**: -- Forest topology & schema info -- Domain configuration & replication -- DNS zones & scavenging -- PKI/Certificate Authority details -- Diagrams (Forest, Replication, Trusts, Sites, CA) -- Health checks for security/best practices -- Multi-language support (en-US, es-ES) - -**Key Disclaimer**: -> This assessment is not exhaustive. All recommendations should be reviewed and implemented by qualified personnel. The author(s) assume no liability for any damages. - -### CONTRIBUTING Guidelines - -**Process**: -1. Fork repo, clone, add remote upstream -2. Create topic branch off dev/main -3. Make changes following project conventions -4. Commit with clear messages -5. Pull upstream dev before pushing -6. Open PR with clear description - -**Requirements**: -- Follow existing code conventions (indentation, comments) -- Include test coverage (reference Pester tests) -- Respect git commit message guidelines -- No copyrighted content -- Agree to project license - -**Key Restriction**: -- Ask before embarking on large features/refactoring -- Don't use issue tracker for personal support - ---- - -## 8. CODE CONVENTIONS SUMMARY - -### PowerShell Code Style - -**Enforced via VSCode + PSScriptAnalyzer**: - -**Formatting** (`.vscode/settings.json`): -- Tab size: 4 spaces (insert spaces, not tabs) -- Line length: 115 characters (ruler configured) -- Trim trailing whitespace: enabled -- Code folding: enabled -- Brace style: - - Opening brace on same line: `if (...) {` - - New line after opening brace: `{\n ...` - - New line after closing brace: disabled -- Whitespace: - - Before open brace: enabled - - Before open paren: enabled - - Around operators: enabled - - After separator (;): enabled - - Around pipe: enabled - -**Linting** (`PSScriptAnalyzerSettings.psd1`): -- No single-character variable names -- No double quotes for constant strings -- Case sensitivity enforced -- No aliases (full cmdlet names) -- Consistent whitespace - -### Naming Conventions - -**Variables**: -- PascalCase for scripts/function names: `$ValidDcFromDomain` -- $script: prefix for module-level vars: `$script:Report`, `$script:InfoLevel` -- Hungarian notation for collections: `$PSSTable`, `$DCStatus` (plural hint) - -**Functions**: -- Verb-Noun format: `Get-AbrADForest`, `Invoke-CommandWithTimeout` -- Approved verbs: Get, New, Invoke, Test, Convert -- Hierarchy: `Get-[Abr][Component][Action]` - -**Constants**: -- `[ordered]` for hash ordering -- `[System.Collections.ArrayList]` for dynamic arrays (preferred over `@()`) -- `[pscustomobject]` for object creation - -### Error Handling - -- Use **try-catch** blocks -- Write warnings via `Write-PScriboMessage -IsWarning` -- Write errors via `Write-Error` or `throw` -- Log activity via `Write-PScriboMessage` -- Show timing via `Show-AbrDebugExecutionTime` - -### Documentation - -- SYNOPSIS, DESCRIPTION, NOTES (version, author, twitter, github) -- .EXAMPLE, .LINK for help -- Inline comments for complex logic -- Parameter documentation with `[Parameter(...)]` attributes - ---- - -## 9. CRITICAL DEVELOPMENT NOTES - -### Must-Know Limitations - -1. **WinRM Requirements**: - - Target must be FQDN (not IP) - - WinRM must be enabled on DC - - Domain-joined machine required to run module - - PowerShell 7+ on Windows only - -2. **Execution Context**: - - Must run `-RunAsAdministrator` - - Cannot run inside PowerShell ISE - - Remote execution via PSSession (not local cmdlets) - -3. **Session Timeout**: - - Default timeout: 900 seconds (15 minutes) - - Configurable via `$Options.JobsTimeOut` - - Long operations may timeout on slow links - -### Development Workflow - -1. **Make changes** to `.ps1` files in `Src/Public` or `Src/Private` -2. **Run tests** locally: `.\Tests\Invoke-Tests.ps1` -3. **Check linting**: PSScriptAnalyzer via VSCode -4. **Push to dev branch** (not master) -5. **CI/CD runs** Pester + PSScriptAnalyzer -6. **Create PR** to merge into master - -### Debugging Tips - -**Execution Timing**: -```powershell -if ($Options.ShowExecutionTime) { - Show-AbrDebugExecutionTime -Start/Stop -TitleMessage 'Section Name' -} -``` - -**Logging Messages**: -```powershell -Write-PScriboMessage -Message "Collecting..." -Write-PScriboMessage -IsWarning "Warning message" -``` - -**Remote Session Debugging**: -```powershell -$session = Get-PSSession -Name 'DC01.contoso.com' -Invoke-Command -Session $session -ScriptBlock { Get-ADForest } -``` - -### Performance Considerations - -- Remote data collection happens sequentially (per domain) -- Large forests (100+ domains) may take 30+ minutes -- CPU-intensive: Schema analysis, trust enumeration -- Network: WinRM traffic, potentially large XML responses -- Disk: HTML/DOCX output can be 50+ MB with diagrams - -### Testing Strategy - -**Unit Tests** (`Tests/AsBuiltReport.Microsoft.AD.Tests.ps1`): -- Module manifest validation -- Function availability -- Module dependency versions -- Export validation - -**Integration Tests** (Not present): -- Would require live AD environment -- Manual testing against test domains recommended - -**Code Coverage**: -- Current: Unknown (50% threshold enforced) -- Recommendation: Add more tests for edge cases - ---- - -## 10. PROJECT-SPECIFIC GUIDANCE FOR AI ASSISTANTS - -### When Making Code Changes - -1. **Respect InfoLevel checks**: Wrap new sections with `if ($InfoLevel.Component -ge N)` -2. **Add HealthCheck conditionals**: Wrap checks with `if ($HealthCheck.Component.Feature)` -3. **Use localization strings**: Reference `$reportTranslate.FunctionName.PropertyName` -4. **Follow try-catch pattern**: Every data gatherer in try-catch with `-IsWarning` -5. **Apply Set-Style**: Mark warning/critical objects for report highlighting -6. **Use OrderedDictionary**: `[ordered] @{}` for property ordering -7. **Pass sessions as parameters**: Don't assume `$TempPssSession` global exists -8. **Document with `.SYNOPSIS`**: All functions need help documentation -9. **Return objects not strings**: Build arrays of `[pscustomobject]` for Table output -10. **Test with `-CodeCoverage`**: Ensure new code is covered by tests - -### Common Tasks - -**Add a new health check:** -1. Add boolean to `AsBuiltReport.Microsoft.AD.json` under `HealthCheck.Component.NewCheck` -2. In Get-Abr* function: `if ($HealthCheck.Component.NewCheck) { ... Set-Style ... }` -3. Add test case to `Tests/AsBuiltReport.Microsoft.AD.Tests.ps1` - -**Add new report section:** -1. Create `Get-AbrNewSection` function in `Src/Private/` -2. Create `Get-AbrNewSectionData` data gatherer -3. Call from main entry point: `if ($InfoLevel.NewComponent -ge 1) { Get-AbrNewSection }` -4. Add InfoLevel config: `"NewComponent": 1` to JSON -5. Add translations to `Language/en-US/MicrosoftAD.psd1` and `es-ES/` - -**Fix a timeout issue:** -1. Increase `$Options.JobsTimeOut` in JSON (default 900) -2. Or reduce data scope (disable HealthChecks or lower InfoLevel) -3. Or optimize remote query (use `-Filter` with better conditions) - -### Module Dependencies to Understand - -- **AsBuiltReport.Core**: Framework for report generation, parameter validation -- **PScribo**: Document markup (Section, Table, Paragraph, Set-Style) -- **ActiveDirectory**: AD cmdlets (Get-ADForest, Get-ADDomain, etc.) - Microsoft module -- **PSPKI**: PKI cmdlets (Get-CertificationAuthority) - community module -- **Diagrammer.Core**: Diagram generation for topology visualization -- **GroupPolicy**: GPO retrieval (Get-GPO, Get-GPOReport) -- **DnsServer**: DNS zone enumeration - ---- - -## 11. QUICK REFERENCE - -### Module Entry Point -- **Location**: `AsBuiltReport.Microsoft.AD/Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1` -- **Exports**: Single public function (dot-notation name) -- **Parameters**: `$Target` (FQDN array), `$Credential` (PSCredential) -- **Returns**: Report file (HTML/Word/Text) via PScribo - -### Main Directories -| Directory | Purpose | Files | -|-----------|---------|-------| -| `Src/Public` | Exported functions | 1 file (entry point) | -| `Src/Private` | Internal functions | 88 functions | -| `Language` | Localization | .psd1 per culture | -| `Tests` | Unit/integration tests | Pester framework | -| `.github/workflows` | CI/CD pipelines | 5 YAML files | - -### Key Files -| File | Purpose | Size | -|------|---------|------| -| `AsBuiltReport.Microsoft.AD.psm1` | Module loader | 14 lines | -| `AsBuiltReport.Microsoft.AD.psd1` | Manifest | ~100 lines | -| `AsBuiltReport.Microsoft.AD.json` | Config template | 89 lines | -| `AsBuiltReport.Microsoft.AD.Style.ps1` | Report styling | 20 KB | - -### Test Commands -```powershell -# Basic test run -.\Tests\Invoke-Tests.ps1 - -# With coverage -.\Tests\Invoke-Tests.ps1 -CodeCoverage -OutputFormat NUnitXml - -# Output formats -.\Tests\Invoke-Tests.ps1 -OutputFormat JUnitXml -.\Tests\Invoke-Tests.ps1 -OutputFormat Console -``` - -### Required Modules (Minimum Versions) -```powershell -AsBuiltReport.Core 1.6.2+ -AsBuiltReport.Chart 0.2.0+ -Diagrammer.Core 0.2.38+ -PSPKI 4.3.0+ -Pester 5.0.0+ -PScribo 0.11.1+ -PSScriptAnalyzer 1.0.0+ -``` - -### Function Categories -| Category | Count | Examples | -|----------|-------|----------| -| Get-Abr* (data gathering) | 52 | Get-AbrADForest, Get-AbrADDomain | -| Get-Abr*Section (orchestration) | 4 | Get-AbrForestSection, Get-AbrDNSSection | -| Get-AbrDiag* (diagrams) | 8 | Get-AbrDiagrammer, Get-AbrDiagForest | -| Utility (conversion, helpers) | 24+ | ConvertTo-HashToYN, Invoke-CommandWithTimeout | - ---- - -**Document Version**: 1.0 -**Last Updated**: 2024 -**Project Version**: 0.9.11 -**Target PowerShell**: 7+ -**Platform**: Windows Only -