From 29b1795c1526d4716aa21da6b4f4c35209e13525 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 24 Mar 2026 10:37:38 -0400 Subject: [PATCH 1/5] feat: Update AsBuiltReport.NetApp.ONTAP to version 0.6.14 - Added Copilot instructions for the project. - Bumped module version to 0.6.14. - Updated AsBuiltReport.Diagram module requirement to 1.0.5. - Enhanced report section titles for clarity and consistency. - Improved diagram export functionality with aspect ratio adjustments. - Corrected SnapLock capitalization in various function outputs and messages. - Updated health check section titles for better readability. - Refined iSCSI and NVMe service section titles for consistency. --- .github/copilot-instructions.md | 199 ++++++++++++++++++ .../AsBuiltReport.NetApp.ONTAP.psd1 | 4 +- .../Src/Private/Export-AbrOntapDiagram.ps1 | 2 +- .../Private/Get-AbrOntapEfficiencyAggr.ps1 | 2 +- .../Get-AbrOntapSecuritySnapLockAggr.ps1 | 6 +- .../Get-AbrOntapSecuritySnapLockClock.ps1 | 4 +- .../Get-AbrOntapSecuritySnapLockVol.ps1 | 6 +- .../Get-AbrOntapSecuritySnapLockVollAttr.ps1 | 6 +- .../Private/Get-AbrOntapStorageFabricPool.ps1 | 6 +- .../Get-AbrOntapStorageFabricPoolConfig.ps1 | 6 +- .../Get-AbrOntapVserverIscsiInitiator.ps1 | 4 +- .../Get-AbrOntapVserverIscsiInterface.ps1 | 6 +- .../Get-AbrOntapVserverIscsiSummary.ps1 | 6 +- .../Get-AbrOntapVserverNonMappedLun.ps1 | 4 +- .../Get-AbrOntapVserverNonMappedNamespace.ps1 | 2 +- .../Get-AbrOntapVserverNvmeFcAdapter.ps1 | 6 +- .../Get-AbrOntapVserverNvmeTcpAdapter.ps1 | 6 +- ...et-AbrOntapVserverVolumeSnapshotHealth.ps1 | 2 +- .../Invoke-AsBuiltReport.NetApp.ONTAP.ps1 | 42 ++-- CHANGELOG.md | 12 ++ 20 files changed, 271 insertions(+), 60 deletions(-) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..5e9e44d --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,199 @@ +# Copilot Instructions for AsBuiltReport.NetApp.ONTAP + +## Project Overview + +PowerShell 7 module that generates as-built documentation (HTML, Word, Text) for NetApp ONTAP storage clusters. It is part of the broader **AsBuiltReport** framework and uses **PScribo** for document rendering. + +**Requires PowerShell 7+ (Core only — not compatible with Windows PowerShell 5.1).** + +## Linting + +Run PSScriptAnalyzer locally (mirrors the CI check): + +```powershell +Invoke-ScriptAnalyzer -Path ./AsBuiltReport.NetApp.ONTAP/Src ` + -Recurse ` + -Settings ./.github/workflows/PSScriptAnalyzerSettings.psd1 +``` + +Excluded rules (defined in `.github/workflows/PSScriptAnalyzerSettings.psd1`): +- `PSUseToExportFieldsInManifest` +- `PSReviewUnusedParameter` +- `PSUseDeclaredVarsMoreThanAssignments` +- `PSAvoidGlobalVars` + +CI linting is in `.github/workflows/PSScriptAnalyzer.yml` (runs on every push/PR). + +## Running the Report (Manual Testing) + +There are no automated Pester tests. Testing is done by generating a real report against an ONTAP cluster: + +```powershell +$Cred = Get-Credential +New-AsBuiltReport -Report NetApp.ONTAP ` + -Target ` + -Credential $Cred ` + -Format HTML ` + -OutputFolderPath "$env:HOME/reports" +``` + +With health checks and all formats: + +```powershell +New-AsBuiltReport -Report NetApp.ONTAP ` + -Target ` + -Credential $Cred ` + -Format HTML,Word ` + -EnableHealthCheck ` + -OutputFolderPath "$env:HOME/reports" +``` + +## Architecture + +### Module Layout + +``` +AsBuiltReport.NetApp.ONTAP/ +├── AsBuiltReport.NetApp.ONTAP.psd1 # Module manifest (required deps, version) +├── AsBuiltReport.NetApp.ONTAP.psm1 # Root module — auto-loads all Src/ files +├── AsBuiltReport.NetApp.ONTAP.json # Report configuration template (user-facing) +├── AsBuiltReport.NetApp.ONTAP.Style.ps1 # PScribo document styles +├── icons/ # PNG icons embedded in diagrams +└── Src/ + ├── Public/ + │ └── Invoke-AsBuiltReport.NetApp.ONTAP.ps1 # Single exported entry point (985 lines) + └── Private/ + ├── Get-AbrOntap*.ps1 # 100+ data collection & rendering functions + ├── ConvertTo-TextYN.ps1 # Boolean → Yes/No helper + ├── ConvertTo-HashToYN.ps1 # Hashtable value → Yes/No helper + ├── Export-AbrOntapDiagram.ps1 # Diagram export helper + └── Get-NetAppOntapAPI.ps1 # REST API wrapper +``` + +### Data Flow + +1. **Entry point** (`Invoke-AsBuiltReport.NetApp.ONTAP.ps1`) validates PS version, connects to ONTAP via `Connect-NcController`, reads the JSON config, then calls each `Get-AbrOntap*` function in section order. +2. **Data collection** — each `Get-AbrOntap*` calls NetApp.ONTAP cmdlets (`Get-NcCluster`, `Get-NcAggr`, etc.) and populates `[ordered]` hashtables. +3. **Rendering** — collected data is passed to PScribo (`Table`, `Paragraph`, `Section`, `Heading*`). Tables use `Set-Style` for health-check color coding. +4. **Diagrams** — `Get-AbrOntapClusterDiagram` and similar functions use `AsBuiltReport.Diagram` to produce topology graphs; `Export-AbrOntapDiagram` writes them to disk. +5. **Output** — PScribo serializes to HTML/Word/Text via `New-Document`. + +### Script-Scoped Variables (set by the framework, available in all Private functions) + +| Variable | Content | +|---|---| +| `$Array` | Active `NcController` connection object | +| `$script:Report` | Report config from JSON (`ShowTableCaptions`, etc.) | +| `$script:InfoLevel` | Per-section detail level (0 = disabled, 1 = summary, 2 = advanced) | +| `$script:Options` | User options (diagram theme, excluded Vservers, etc.) | +| `$script:Healthcheck` | Health check enable/disable flags per component | +| `$script:TextInfo` | `CultureInfo` for text casing | +| `$script:Images` | Hashtable of icon paths for diagrams | + +### InfoLevel Behavior + +Every section is gated on `$InfoLevel.
`: +- `0` — Skip entirely +- `1` — Summary view (key fields only) +- `2` — Advanced view (adds metrics, extended attributes, charts) + +## Key Conventions + +### Function Template + +Every `Get-AbrOntap*` private function follows this structure: + +```powershell +function Get-AbrOntap{Component}{Feature} { + <# + .SYNOPSIS + Used by As Built Report to retrieve NetApp ONTAP {description} + .NOTES + Version: 0.6.x + Author: Jonathan Colon + #> + [CmdletBinding()] + param () + + begin { + Write-PScriboMessage 'Collecting ONTAP {feature} information.' + } + + process { + try { + $Data = Get-Nc{Feature} -Controller $Array + if ($Data) { + $OutObj = @() + $inObj = [ordered] @{ + 'Friendly Label' = $Data.Property ?? '--' + } + $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj) + + # Health check styling + if ($Healthcheck.{Component}.{Feature}) { + $OutObj | Where-Object { $_.'Status' -ne 'OK' } | Set-Style -Style Critical -Property 'Status' + } + + $TableParams = @{ + Name = "Table Title - $($Array.Name)" + List = $true + ColumnWidths = 25, 75 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OutObj | Table @TableParams + } + } catch { + Write-PScriboMessage -IsWarning $_.Exception.Message + } + } +} +``` + +### Naming + +- **Private functions**: `Get-AbrOntap{Category}{Feature}` (e.g., `Get-AbrOntapStorageAGGR`, `Get-AbrOntapVserverVolumes`) +- **One function per file** — filename matches function name exactly +- **Local variables**: PascalCase (`$ClusterInfo`, `$OutObj`, `$inObj`) +- **Arrays**: plural PascalCase (`$Nodes`, `$Vservers`) +- Use `?? '--'` for null-coalescing to display placeholder in tables + +### Formatting (enforced by `.vscode/settings.json`) + +- 4-space indentation (no tabs) +- Opening brace on the same line (`if ($x) {`) +- Whitespace around operators, pipes, and separators +- Max line length: 115 characters +- No trailing whitespace +- Use full cmdlet names (no aliases — `ForEach-Object`, not `%`) +- Use correct casing for cmdlets (`Get-NcCluster`, not `get-nccluster`) + +### PScribo Patterns + +- `Section` / `Heading1`-`Heading5` for document structure +- `Table` with `ColumnWidths` always summing to 100 +- `Set-Style` values: `OK`, `Warning`, `Critical`, `Info` +- `List = $true` for single-object detail views; `List = $false` for multi-row tables +- Always wrap table creation in `if ($Report.ShowTableCaptions)` before setting `Caption` + +### Error Handling + +- Wrap all data collection in `try/catch` +- Use `Write-PScriboMessage -IsWarning $_.Exception.Message` to log errors without aborting the report +- Non-fatal: missing data should result in the section being silently skipped (check with `if ($Data)`) + +## Key Dependencies + +| Module | Version | Role | +|---|---|---| +| `AsBuiltReport.Core` | ≥ 1.6.2 | Framework, PScribo integration | +| `NetApp.ONTAP` | ≥ 9.18.1.2601 | ONTAP API cmdlets (`Get-Nc*`) | +| `AsBuiltReport.Diagram` | ≥ 1.0.3 | Topology diagram generation | +| `AsBuiltReport.Chart` | ≥ 0.3.0 | Chart/graph generation | + +Install all dependencies: + +```powershell +Install-Module AsBuiltReport.Core, NetApp.ONTAP, AsBuiltReport.Diagram, AsBuiltReport.Chart +``` diff --git a/AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1 b/AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1 index b367d34..01b113b 100755 --- a/AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1 +++ b/AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1 @@ -12,7 +12,7 @@ RootModule = 'AsBuiltReport.NetApp.ONTAP.psm1' # Version number of this module. - ModuleVersion = '0.6.13' + ModuleVersion = '0.6.14' # Supported PSEditions # CompatiblePSEditions = @() @@ -66,7 +66,7 @@ }, @{ ModuleName = 'AsBuiltReport.Diagram'; - ModuleVersion = '1.0.3' + ModuleVersion = '1.0.5' } ) diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Export-AbrOntapDiagram.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Export-AbrOntapDiagram.ps1 index a6508ea..ea9b172 100644 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Export-AbrOntapDiagram.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Export-AbrOntapDiagram.ps1 @@ -122,7 +122,7 @@ function Export-AbrOntapDiagram { $Graph = $DiagramObject $Diagram = New-AbrDiagram @DiagramParams -InputObject $Graph if ($Diagram) { - $BestAspectRatio = Get-BestImageAspectRatio -GraphObj $Diagram -MaxWidth 600 + $BestAspectRatio = Get-BestImageAspectRatio -GraphObj $Diagram -MaxWidth 600 -MaxHeight 600 Section -Style Heading2 $MainDiagramLabel { Image -Base64 $Diagram -Text 'NetApp Ontap Diagram' -Width $BestAspectRatio.Width -Height $BestAspectRatio.Height -Align Center } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapEfficiencyAggr.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapEfficiencyAggr.ps1 index a1d7a0e..08238e4 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapEfficiencyAggr.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapEfficiencyAggr.ps1 @@ -81,7 +81,7 @@ function Get-AbrOntapEfficiencyAggr { } $TableParams = @{ - Name = "HealthCheck - Volume without deduplication - $($ClusterInfo.ClusterName)" + Name = "Volumes Without Deduplication - $($ClusterInfo.ClusterName)" List = $false ColumnWidths = 45, 55 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockAggr.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockAggr.ps1 index aa1a234..6a24f2e 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockAggr.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockAggr.ps1 @@ -19,7 +19,7 @@ function Get-AbrOntapSecuritySnapLockAggr { ) begin { - Write-PScriboMessage 'Collecting ONTAP Security Aggregate Snaplock Type information.' + Write-PScriboMessage 'Collecting ONTAP Security Aggregate SnapLock Type information.' } process { @@ -31,7 +31,7 @@ function Get-AbrOntapSecuritySnapLockAggr { try { $inObj = [ordered] @{ 'Aggregate Name' = $Item.Name - 'Snaplock Type' = $TextInfo.ToTitleCase((Get-NcAggr $Item.Name -Controller $Array | Select-Object -ExpandProperty AggrSnaplockAttributes).SnaplockType) + 'SnapLock Type' = $TextInfo.ToTitleCase((Get-NcAggr $Item.Name -Controller $Array | Select-Object -ExpandProperty AggrSnaplockAttributes).SnaplockType) } $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj) } catch { @@ -40,7 +40,7 @@ function Get-AbrOntapSecuritySnapLockAggr { } $TableParams = @{ - Name = "Aggregate Snaplock Type - $($ClusterInfo.ClusterName)" + Name = "Aggregate SnapLock Type - $($ClusterInfo.ClusterName)" List = $false ColumnWidths = 40, 60 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockClock.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockClock.ps1 index 847f8c5..3aebd10 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockClock.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockClock.ps1 @@ -19,7 +19,7 @@ function Get-AbrOntapSecuritySnapLockClock { ) begin { - Write-PScriboMessage 'Collecting ONTAP Security Snaplock compliance clock information.' + Write-PScriboMessage 'Collecting ONTAP Security SnapLock compliance clock information.' } process { @@ -44,7 +44,7 @@ function Get-AbrOntapSecuritySnapLockClock { } $TableParams = @{ - Name = "Snaplock Compliance Clock - $($ClusterInfo.ClusterName)" + Name = "SnapLock Compliance Clock - $($ClusterInfo.ClusterName)" List = $false ColumnWidths = 40, 60 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockVol.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockVol.ps1 index c34cba4..00972c9 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockVol.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockVol.ps1 @@ -19,7 +19,7 @@ function Get-AbrOntapSecuritySnapLockVol { ) begin { - Write-PScriboMessage 'Collecting ONTAP Security Volume Snaplock Type information.' + Write-PScriboMessage 'Collecting ONTAP Security Volume SnapLock Type information.' } process { @@ -32,7 +32,7 @@ function Get-AbrOntapSecuritySnapLockVol { $inObj = [ordered] @{ 'Volume' = $Item.Name 'Aggregate' = $Item.Aggregate - 'Snaplock Type' = $TextInfo.ToTitleCase((Get-NcVol $Item.Name -Controller $Array | Select-Object -ExpandProperty VolumeSnaplockAttributes).SnaplockType) + 'SnapLock Type' = $TextInfo.ToTitleCase((Get-NcVol $Item.Name -Controller $Array | Select-Object -ExpandProperty VolumeSnaplockAttributes).SnaplockType) } $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj) } catch { @@ -41,7 +41,7 @@ function Get-AbrOntapSecuritySnapLockVol { } $TableParams = @{ - Name = "Volume Snaplock Type - $($ClusterInfo.ClusterName)" + Name = "Volume SnapLock Type - $($ClusterInfo.ClusterName)" List = $false ColumnWidths = 45, 35, 20 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockVollAttr.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockVollAttr.ps1 index a55de44..3cd157e 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockVollAttr.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapSecuritySnapLockVollAttr.ps1 @@ -19,7 +19,7 @@ function Get-AbrOntapSecuritySnapLockVollAttr { ) begin { - Write-PScriboMessage 'Collecting ONTAP Security Snaplock volume attributes information.' + Write-PScriboMessage 'Collecting ONTAP Security SnapLock volume attributes information.' } process { @@ -36,7 +36,7 @@ function Get-AbrOntapSecuritySnapLockVollAttr { $inObj = [ordered] @{ 'Volume' = $vol.Name 'Aggregate' = $vol.Aggregate - 'Snaplock Type' = $TextInfo.ToTitleCase($SnapLockVolAttr.Type) + 'SnapLock Type' = $TextInfo.ToTitleCase($SnapLockVolAttr.Type) 'Maximum Retention Period' = $SnapLockVolAttr.MaximumRetentionPeriod 'Minimum Retention Period' = $SnapLockVolAttr.MinimumRetentionPeriod 'Privileged Delete State' = $SnapLockVolAttr.PrivilegedDeleteState @@ -49,7 +49,7 @@ function Get-AbrOntapSecuritySnapLockVollAttr { $OutObj = [pscustomobject](ConvertTo-HashToYN $inObj) $TableParams = @{ - Name = "Snaplock Volume Attributes - $($vol.Name)" + Name = "SnapLock Volume Attributes - $($vol.Name)" List = $true ColumnWidths = 40, 60 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapStorageFabricPool.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapStorageFabricPool.ps1 index 60c087d..0623a96 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapStorageFabricPool.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapStorageFabricPool.ps1 @@ -1,7 +1,7 @@ function Get-AbrOntapStorageFabricPool { <# .SYNOPSIS - Used by As Built Report to retrieve NetApp ONTAP Aggregate FabriPool information from the Cluster Management Network + Used by As Built Report to retrieve NetApp ONTAP Aggregate FabricPool information from the Cluster Management Network .DESCRIPTION .NOTES @@ -19,7 +19,7 @@ function Get-AbrOntapStorageFabricPool { ) begin { - Write-PScriboMessage 'Collecting ONTAP Aggregate FabriPool information.' + Write-PScriboMessage 'Collecting ONTAP Aggregate FabricPool information.' } process { @@ -47,7 +47,7 @@ function Get-AbrOntapStorageFabricPool { } $TableParams = @{ - Name = "Aggregate FabriPool - $($ClusterInfo.ClusterName)" + Name = "Aggregate FabricPool - $($ClusterInfo.ClusterName)" List = $false ColumnWidths = 30, 20, 20, 15, 15 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapStorageFabricPoolConfig.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapStorageFabricPoolConfig.ps1 index 50091e9..310bc17 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapStorageFabricPoolConfig.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapStorageFabricPoolConfig.ps1 @@ -1,7 +1,7 @@ function Get-AbrOntapEfficiencyAggrConfig { <# .SYNOPSIS - Used by As Built Report to retrieve NetApp ONTAP Aggregate FabriPool Object Store Configuration from the Cluster Management Network + Used by As Built Report to retrieve NetApp ONTAP Aggregate FabricPool Object Store Configuration from the Cluster Management Network .DESCRIPTION .NOTES @@ -19,7 +19,7 @@ function Get-AbrOntapEfficiencyAggrConfig { ) begin { - Write-PScriboMessage 'Collecting ONTAP Aggregate FabriPool Object Store information.' + Write-PScriboMessage 'Collecting ONTAP Aggregate FabricPool Object Store information.' } process { @@ -41,7 +41,7 @@ function Get-AbrOntapEfficiencyAggrConfig { $OutObj = [pscustomobject](ConvertTo-HashToYN $inObj) $TableParams = @{ - Name = "Aggregate FabriPool Object Store Configuration - $($Item.ObjectStoreName)" + Name = "Aggregate FabricPool Object Store Configuration - $($Item.ObjectStoreName)" List = $true ColumnWidths = 30, 70 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiInitiator.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiInitiator.ps1 index a53cd7e..0fe2fc3 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiInitiator.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiInitiator.ps1 @@ -23,7 +23,7 @@ function Get-AbrOntapVserverIscsiInitiator { ) begin { - Write-PScriboMessage 'Collecting ONTAP Vserver ISCSI Client Initiators information.' + Write-PScriboMessage 'Collecting ONTAP Vserver iSCSI Client Initiators information.' } process { @@ -44,7 +44,7 @@ function Get-AbrOntapVserverIscsiInitiator { } $TableParams = @{ - Name = "ISCSI Client Initiator - $($Vserver)" + Name = "iSCSI Client Initiator - $($Vserver)" List = $false ColumnWidths = 60, 40 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiInterface.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiInterface.ps1 index 7564713..75954a1 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiInterface.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiInterface.ps1 @@ -23,7 +23,7 @@ function Get-AbrOntapVserverIscsiInterface { ) begin { - Write-PScriboMessage 'Collecting ONTAP Vserver ISCSI interface information.' + Write-PScriboMessage 'Collecting ONTAP Vserver iSCSI interface information.' } process { @@ -49,7 +49,7 @@ function Get-AbrOntapVserverIscsiInterface { } $TableParams = @{ - Name = "ISCSI Interface - $($Vserver)" + Name = "iSCSI Interface - $($Vserver)" List = $false ColumnWidths = 40, 30, 15, 15 } @@ -62,7 +62,7 @@ function Get-AbrOntapVserverIscsiInterface { BlankLine Paragraph { Text 'Best Practice:' -Bold - Text 'Ensure that all ISCSI interfaces are operational to maintain optimal storage connectivity.' + Text 'Ensure that all iSCSI interfaces are operational to maintain optimal storage connectivity.' } BlankLine } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiSummary.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiSummary.ps1 index 3e7007d..fabb382 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiSummary.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverIscsiSummary.ps1 @@ -23,7 +23,7 @@ function Get-AbrOntapVserverIscsiSummary { ) begin { - Write-PScriboMessage 'Collecting ONTAP Vserver ISCSI information.' + Write-PScriboMessage 'Collecting ONTAP Vserver iSCSI information.' } process { @@ -52,7 +52,7 @@ function Get-AbrOntapVserverIscsiSummary { } $TableParams = @{ - Name = "ISCSI Service - $($Vserver)" + Name = "iSCSI Service - $($Vserver)" List = $true ColumnWidths = 30, 70 } @@ -65,7 +65,7 @@ function Get-AbrOntapVserverIscsiSummary { BlankLine Paragraph { Text 'Best Practice:' -Bold - Text 'Ensure that all ISCSI services are operational to maintain optimal storage connectivity.' + Text 'Ensure that all iSCSI services are operational to maintain optimal storage connectivity.' } BlankLine } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNonMappedLun.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNonMappedLun.ps1 index d6eb756..8cabf41 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNonMappedLun.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNonMappedLun.ps1 @@ -23,7 +23,7 @@ function Get-AbrOntapVserverNonMappedLun { ) begin { - Write-PScriboMessage 'Collecting ONTAP ISCSI/FCP Non Mapped Lun information.' + Write-PScriboMessage 'Collecting ONTAP iSCSI/FCP non-mapped LUN information.' } process { @@ -50,7 +50,7 @@ function Get-AbrOntapVserverNonMappedLun { } $TableParams = @{ - Name = "HealthCheck - Non-Mapped Lun - $($Vserver)" + Name = "Non-Mapped LUNs - $($Vserver)" List = $false ColumnWidths = 30, 30, 10, 10, 20 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNonMappedNamespace.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNonMappedNamespace.ps1 index 29fac33..2f2ee12 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNonMappedNamespace.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNonMappedNamespace.ps1 @@ -50,7 +50,7 @@ function Get-AbrOntapVserverNonMappedNamespace { } $TableParams = @{ - Name = "HealthCheck - Non-Mapped Namespace - $($Vserver)" + Name = "Non-Mapped Namespaces - $($Vserver)" List = $false ColumnWidths = 30, 30, 10, 10, 20 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNvmeFcAdapter.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNvmeFcAdapter.ps1 index ecfa5d1..0446520 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNvmeFcAdapter.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNvmeFcAdapter.ps1 @@ -23,7 +23,7 @@ function Get-AbrOntapVserverNvmeFcAdapter { ) begin { - Write-PScriboMessage 'Collecting ONTAP Vserver Nvme FC adapter information.' + Write-PScriboMessage 'Collecting ONTAP Vserver NVMe FC adapter information.' } process { @@ -51,7 +51,7 @@ function Get-AbrOntapVserverNvmeFcAdapter { } $TableParams = @{ - Name = "Nvme FC Physical Adapter - $($Vserver)" + Name = "NVMe FC Physical Adapter - $($Vserver)" List = $false ColumnWidths = 25, 12, 15, 18, 18, 12 @@ -65,7 +65,7 @@ function Get-AbrOntapVserverNvmeFcAdapter { BlankLine Paragraph { Text 'Best Practice:' -Bold - Text "Ensure all Nvme FC adapters are in 'Up' status to maintain optimal connectivity and performance." + Text "Ensure all NVMe FC adapters are in 'Up' status to maintain optimal connectivity and performance." } BlankLine } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNvmeTcpAdapter.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNvmeTcpAdapter.ps1 index 863e86c..2f6af0c 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNvmeTcpAdapter.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverNvmeTcpAdapter.ps1 @@ -23,7 +23,7 @@ function Get-AbrOntapVserverNvmeTcpAdapter { ) begin { - Write-PScriboMessage 'Collecting ONTAP Vserver Nvme TCP adapter information.' + Write-PScriboMessage 'Collecting ONTAP Vserver NVMe TCP adapter information.' } process { @@ -50,7 +50,7 @@ function Get-AbrOntapVserverNvmeTcpAdapter { } $TableParams = @{ - Name = "Nvme TCP Physical Adapter - $($Vserver)" + Name = "NVMe TCP Physical Adapter - $($Vserver)" List = $false ColumnWidths = 30, 17, 17, 20, 16 @@ -64,7 +64,7 @@ function Get-AbrOntapVserverNvmeTcpAdapter { BlankLine Paragraph { Text 'Best Practice:' -Bold - Text "Ensure all Nvme TCP adapters are in 'Up' status to maintain optimal connectivity and performance." + Text "Ensure all NVMe TCP adapters are in 'Up' status to maintain optimal connectivity and performance." } BlankLine } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverVolumeSnapshotHealth.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverVolumeSnapshotHealth.ps1 index efd6fd7..6e5a73e 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverVolumeSnapshotHealth.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverVolumeSnapshotHealth.ps1 @@ -52,7 +52,7 @@ function Get-AbrOntapVserverVolumeSnapshotHealth { } $TableParams = @{ - Name = "HealthCheck - Volume Snapshot over $($SnapshotDays) days - $($Vserver)" + Name = "Volume Snapshot Age (over $($SnapshotDays) days) - $($Vserver)" List = $false ColumnWidths = 25, 35, 25, 15 } diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 index 822c9f0..2a56608 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 @@ -51,7 +51,7 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { if ($Options.UpdateCheck) { Write-Host ' - Getting dependency information:' # Check the version of the dependency modules - $ModuleArray = @('AsBuiltReport.Core', 'AsBuiltReport.Diagram', 'NetApp.ONTAP') + $ModuleArray = @('AsBuiltReport.Core', 'AsBuiltReport.Diagram', 'AsBuiltReport.Chart', 'NetApp.ONTAP') foreach ($Module in $ModuleArray) { try { @@ -202,7 +202,7 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { Get-AbrOntapStorageFabricPool if ($InfoLevel.Storage -ge 2) { if (Get-NcAggrObjectStoreConfig -Controller $Array) { - Section -Style Heading5 'FabriPool Object Store Configuration' { + Section -Style Heading5 'FabricPool Object Store Configuration' { Get-AbrOntapEfficiencyAggrConfig } } @@ -241,7 +241,7 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { } } if (Get-NcStorageShelf -Controller $Array -ErrorAction SilentlyContinue) { - Section -Style Heading3 ' Disk Shelf' { + Section -Style Heading3 'Disk Shelf' { Get-AbrOntapDiskShelfStorage } } @@ -265,7 +265,7 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { BlankLine Get-AbrOntapClusterLicense if ($InfoLevel.License -ge 2) { - Section -Style Heading4 'License Features' { + Section -Style Heading3 'License Features' { Get-AbrOntapClusterLicenseUsage } } @@ -518,7 +518,7 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { # CIFS Section # #---------------------------------------------------------------------------------------------# if (Get-NcVserver -VserverContext $SVM -Controller $Array | Where-Object { $_.VserverType -eq 'data' -and $_.AllowedProtocols -eq 'cifs' -and $_.State -eq 'running' } | Get-NcCifsServerStatus -Controller $Array -ErrorAction SilentlyContinue) { - Section -Style Heading5 'CIFS Services Information' { + Section -Style Heading5 'CIFS Services' { Paragraph "The following section provides the CIFS Service Information in $($SVM)." BlankLine Get-AbrOntapVserverCIFSSummary -Vserver $SVM @@ -566,17 +566,17 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { # NVME Section # #---------------------------------------------------------------------------------------------# if ( Get-NcNvme -Controller $Array | Where-Object { $_.Vserver -eq $SVM } ) { - Section -Style Heading5 'Nvme Services Information' { + Section -Style Heading5 'NVMe Services' { Paragraph "The following section provides the Nvme Service Information in $($SVM)." BlankLine # Get-AbrOntapVserverNvmeSummary -Vserver $SVM if (Get-NcNvmeInterface -VserverContext $Vserver -Controller $Array | Where-Object { $_.PhysicalProtocol -eq 'fibre_channel' }) { - Section -ExcludeFromTOC -Style Heading6 'Nvme FC Physical Adapter' { + Section -ExcludeFromTOC -Style Heading6 'NVMe FC Physical Adapter' { Get-AbrOntapVserverNvmeFcAdapter -Vserver $SVM } } if (Get-NcNvmeInterface -VserverContext $Vserver -Controller $Array | Where-Object { $_.PhysicalProtocol -eq 'ethernet' }) { - Section -ExcludeFromTOC -Style Heading6 'Nvme TCP Physical Adapter' { + Section -ExcludeFromTOC -Style Heading6 'NVMe TCP Physical Adapter' { Get-AbrOntapVserverNvmeTcpAdapter -Vserver $SVM } } @@ -589,17 +589,17 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { # ISCSI Section # #---------------------------------------------------------------------------------------------# if ( Get-NcIscsiService -Controller $Array | Where-Object { $_.Vserver -eq $SVM } ) { - Section -Style Heading5 'ISCSI Services' { + Section -Style Heading5 'iSCSI Services' { Paragraph "The following section provides the ISCSI Service Information in $($SVM)." BlankLine Get-AbrOntapVserverIscsiSummary -Vserver $SVM - Section -ExcludeFromTOC -Style Heading6 'ISCSI Interfaces' { + Section -ExcludeFromTOC -Style Heading6 'iSCSI Interfaces' { Get-AbrOntapVserverIscsiInterface -Vserver $SVM } $ISCSIClientInitiators = Get-AbrOntapVserverIscsiInitiator -Vserver $SVM if ($ISCSIClientInitiators) { - Section -ExcludeFromTOC -Style Heading6 'ISCSI Client Initiators' { + Section -ExcludeFromTOC -Style Heading6 'iSCSI Client Initiators' { $ISCSIClientInitiators } } @@ -609,7 +609,7 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { # FCP Section # #---------------------------------------------------------------------------------------------# if ( Get-NcFcpService -Controller $Array | Where-Object { $_.Vserver -eq $SVM } ) { - Section -Style Heading5 'FCP Services Information' { + Section -Style Heading5 'FCP Services' { Paragraph "The following section provides the FCP Service Information in $($SVM)." BlankLine Get-AbrOntapVserverFcpSummary -Vserver $SVM @@ -636,8 +636,8 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { } $NonMappedLun = Get-AbrOntapVserverNonMappedLun -Vserver $SVM if ($Healthcheck.Vserver.Status -and $NonMappedLun) { - Section -ExcludeFromTOC -Style Heading6 'HealthCheck - Non-Mapped Lun Information' { - Paragraph "The following section provides information of Non Mapped Lun in $($SVM)." + Section -ExcludeFromTOC -Style Heading6 'Non-Mapped LUNs' { + Paragraph "The following section provides information of non-mapped LUNs in $($SVM)." BlankLine $NonMappedLun } @@ -659,8 +659,8 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { } $NonMappedNamespace = Get-AbrOntapVserverNonMappedNamespace -Vserver $SVM if ($Healthcheck.Vserver.Status -and $NonMappedNamespace) { - Section -ExcludeFromTOC -Style Heading6 'HealthCheck - Non-Mapped Namespace Information' { - Paragraph "The following table provides information about Non Mapped Namespace in $($SVM)." + Section -ExcludeFromTOC -Style Heading6 'Non-Mapped Namespaces' { + Paragraph "The following table provides information about non-mapped namespaces in $($SVM)." BlankLine $NonMappedNamespace } @@ -871,17 +871,17 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { } } } - Section -Style Heading3 'Snaplock Compliance Clock' { - Paragraph "The following section provides the Snaplock Compliance Clock information in $($ClusterInfo.ClusterName)." + Section -Style Heading3 'SnapLock Compliance Clock' { + Paragraph "The following section provides the SnapLock Compliance Clock information in $($ClusterInfo.ClusterName)." BlankLine Get-AbrOntapSecuritySnapLockClock - Section -Style Heading4 'Aggregate Snaplock Type' { + Section -Style Heading4 'Aggregate SnapLock Type' { Get-AbrOntapSecuritySnapLockAggr - Section -Style Heading5 'Volume Snaplock Type' { + Section -Style Heading5 'Volume SnapLock Type' { Get-AbrOntapSecuritySnapLockVol if ($InfoLevel.Security -ge 2) { if (Get-NcVol -Controller $Array | Where-Object { $_.VolumeSnaplockAttributes.SnaplockType -in 'enterprise', 'compliance' }) { - Section -ExcludeFromTOC -Style Heading6 'Snaplock Volume Attributes' { + Section -ExcludeFromTOC -Style Heading6 'SnapLock Volume Attributes' { Get-AbrOntapSecuritySnapLockVollAttr } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9442f..cd0a35f 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.14] - 2026-03-?? + +### Changed + +- Bump module version to v0.6.14 +- Bump AsBuiltReport.Diagram module requirement to v1.0.5 +- Enhanced report section titles for clarity and consistency +- Improved diagram export functionality with aspect ratio adjustments +- Corrected SnapLock capitalization in various function outputs and messages +- Updated health check section titles for better readability +- Refined iSCSI and NVMe service section titles for consistency + ## [0.6.13] - 2026-03-17 ### Fixed From 352b2366d1db6377b2f19531b3032291b003eca8 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sat, 16 May 2026 13:26:16 -0400 Subject: [PATCH 2/5] feat: Update to version 0.6.14, add Get-AbrOntapLog cmdlet, and enhance report sections --- .github/workflows/Release.yml | 104 +++--- .../AsBuiltReport.NetApp.ONTAP.psd1 | 8 +- .../Get-AbrOntapClusterReplicationDiagram.ps1 | 2 +- .../Private/Get-AbrOntapVserverCGSummary.ps1 | 5 +- .../Private/Get-AbrOntapVserverCGVolume.ps1 | 63 ++++ .../Private/Get-AbrOntapVserverSubsystem.ps1 | 4 +- .../Src/Public/Get-AbrOntapLog.ps1 | 320 ++++++++++++++++++ .../Invoke-AsBuiltReport.NetApp.ONTAP.ps1 | 20 +- CHANGELOG.md | 12 +- 9 files changed, 463 insertions(+), 75 deletions(-) create mode 100755 AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverCGVolume.ps1 create mode 100644 AsBuiltReport.NetApp.ONTAP/Src/Public/Get-AbrOntapLog.ps1 diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 285fa65..6a49350 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -1,65 +1,49 @@ name: Publish PowerShell Module on: - release: - types: [published] + release: + types: [published] jobs: - publish-to-gallery: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Set PSRepository to Trusted for PowerShell Gallery - shell: pwsh - run: | - Set-PSRepository -Name PSGallery -InstallationPolicy Trusted - - name: Install AsBuiltReport.Core module - shell: pwsh - run: | - Install-Module -Name AsBuiltReport.Core -Repository PSGallery -Force - - name: Install NetApp.ONTAP module - shell: pwsh - run: | - Install-Module -Name NetApp.ONTAP -Repository PSGallery -Force - - name: Install AsBuiltReport.Chart module - shell: pwsh - run: | - Install-Module -Name AsBuiltReport.Chart -Repository PSGallery -Force - - name: Install AsBuiltReport.Diagram module - shell: pwsh - run: | - Install-Module -Name AsBuiltReport.Diagram -Repository PSGallery -Force - - name: Test Module Manifest - shell: pwsh - run: | - Test-ModuleManifest .\AsBuiltReport.NetApp.ONTAP\AsBuiltReport.NetApp.ONTAP.psd1 - - name: Publish module to PowerShell Gallery - shell: pwsh - run: | - Publish-Module -Path ./AsBuiltReport.NetApp.ONTAP -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose - tweet: - needs: publish-to-gallery - runs-on: ubuntu-latest - steps: - - uses: Eomm/why-don-t-you-tweet@v2 - # We don't want to tweet if the repository is not a public one - if: ${{ !github.event.repository.private }} - with: - # GitHub event payload - # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release - tweet-message: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Netapp #AsBuiltReport #PowerShell #Ontap #NetAppATeam" - env: - TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }} - TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} - TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - bsky-post: - needs: publish-to-gallery - runs-on: ubuntu-latest - steps: - - uses: zentered/bluesky-post-action@v0.3.0 - with: - post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Netapp #AsBuiltReport #PowerShell #Ontap #NetAppATeam" - env: - BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }} - BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }} + publish-to-gallery: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Set PSRepository to Trusted for PowerShell Gallery + shell: pwsh + run: | + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + - name: Install AsBuiltReport.Core module + shell: pwsh + run: | + Install-Module -Name AsBuiltReport.Core -Repository PSGallery -Force + - name: Install NetApp.ONTAP module + shell: pwsh + run: | + Install-Module -Name NetApp.ONTAP -Repository PSGallery -Force + - name: Install AsBuiltReport.Chart module + shell: pwsh + run: | + Install-Module -Name AsBuiltReport.Chart -Repository PSGallery -Force + - name: Install AsBuiltReport.Diagram module + shell: pwsh + run: | + Install-Module -Name AsBuiltReport.Diagram -Repository PSGallery -Force + - name: Test Module Manifest + shell: pwsh + run: | + Test-ModuleManifest .\AsBuiltReport.NetApp.ONTAP\AsBuiltReport.NetApp.ONTAP.psd1 + - name: Publish module to PowerShell Gallery + shell: pwsh + run: | + Publish-Module -Path ./AsBuiltReport.NetApp.ONTAP -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose + bsky-post: + needs: publish-to-gallery + runs-on: ubuntu-latest + steps: + - uses: zentered/bluesky-post-action@v0.4.0 + with: + post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Netapp #AsBuiltReport #PowerShell #Ontap #NetAppATeam" + env: + BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }} + BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }} diff --git a/AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1 b/AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1 index 01b113b..aadba5e 100755 --- a/AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1 +++ b/AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1 @@ -54,11 +54,11 @@ RequiredModules = @( @{ ModuleName = 'AsBuiltReport.Core'; - ModuleVersion = '1.6.2' + ModuleVersion = '1.6.4' }, @{ ModuleName = 'AsBuiltReport.Chart'; - ModuleVersion = '0.3.0' + ModuleVersion = '0.3.2' }, @{ ModuleName = 'NetApp.ONTAP'; @@ -66,7 +66,7 @@ }, @{ ModuleName = 'AsBuiltReport.Diagram'; - ModuleVersion = '1.0.5' + ModuleVersion = '1.0.7' } ) @@ -86,7 +86,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.NetApp.ONTAP') + FunctionsToExport = @('Invoke-AsBuiltReport.NetApp.ONTAP', 'Get-AbrOntapLog') # 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.NetApp.ONTAP/Src/Private/Get-AbrOntapClusterReplicationDiagram.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapClusterReplicationDiagram.ps1 index 4b10045..95d88b1 100644 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapClusterReplicationDiagram.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapClusterReplicationDiagram.ps1 @@ -124,7 +124,7 @@ function Get-AbrOntapClusterReplicationDiagram { 'SourceVserver' = $VserverPeer.Vserver 'RemoteCluster' = $VserverPeer.PeerCluster 'RemoteVserver' = $VserverPeer.PeerVserver - 'Color' = Get-RandomPastelColorHex + 'Color' = Get-RandomPastelColorInHex 'SourceAdditionalInfo' = [PSCustomObject][ordered]@{ 'Peer Vserver' = $VserverPeer.PeerVserver 'Peer Cluster' = $VserverPeer.PeerCluster diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverCGSummary.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverCGSummary.ps1 index 9ec9b78..2d3b36b 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverCGSummary.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverCGSummary.ps1 @@ -5,7 +5,7 @@ function Get-AbrOntapVserverCGSummary { .DESCRIPTION .NOTES - Version: 0.6.12 + Version: 0.6.14 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -39,7 +39,6 @@ function Get-AbrOntapVserverCGSummary { 'Available' = ($Item.space.available | ConvertTo-FormattedNumber -ErrorAction SilentlyContinue -NumberFormatString 0.0 -Type Datasize) ?? '--' 'Used' = ($Item.space.used | ConvertTo-FormattedNumber -ErrorAction SilentlyContinue -NumberFormatString 0.0 -Type Datasize) ?? '--' 'Replicated' = $Item.replicated - 'Lun Count' = ($Item.luns.name).count } $VserverObj += [pscustomobject](ConvertTo-HashToYN $inObj) } catch { @@ -50,7 +49,7 @@ function Get-AbrOntapVserverCGSummary { $TableParams = @{ Name = "Consistency Groups - $($Vserver)" List = $false - ColumnWidths = 40, 12, 12, 12, 12, 12 + ColumnWidths = 40, 15, 15, 15, 15 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverCGVolume.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverCGVolume.ps1 new file mode 100755 index 0000000..cfa812b --- /dev/null +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverCGVolume.ps1 @@ -0,0 +1,63 @@ +function Get-AbrOntapVserverCGVolume { + <# + .SYNOPSIS + Used by As Built Report to retrieve NetApp ONTAP Vserver Consistency Groups Volume information from the Cluster Management Network + .DESCRIPTION + + .NOTES + Version: 0.6.14 + Author: Jonathan Colon + Twitter: @jcolonfzenpr + Github: rebelinux + .EXAMPLE + + .LINK + + #> + param ( + [Parameter ( + Position = 0, + Mandatory)] + $CGObj + ) + + begin { + Write-PScriboMessage 'Collecting ONTAP Vserver Consistency Groups volume information.' + } + + process { + try { + $VolumeData = $CGObj.volumes + $CGVolumeObj = @() + if ($VolumeData) { + foreach ($Item in $VolumeData) { + try { + $inObj = [ordered] @{ + 'Name' = $Item.Name + 'Capacity' = ($Item.space.size | ConvertTo-FormattedNumber -ErrorAction SilentlyContinue -NumberFormatString 0.0 -Type Datasize) ?? '--' + 'Used' = ($Item.space.used | ConvertTo-FormattedNumber -ErrorAction SilentlyContinue -NumberFormatString 0.0 -Type Datasize) ?? '--' + } + $CGVolumeObj += [pscustomobject](ConvertTo-HashToYN $inObj) + } catch { + Write-PScriboMessage -IsWarning $_.Exception.Message + } + } + + $TableParams = @{ + Name = "Consistency Group Volume - $($CGObj.Name)" + List = $false + ColumnWidths = 33, 33, 34 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $CGVolumeObj | Sort-Object -Property Name | Table @TableParams + } + } catch { + Write-PScriboMessage -IsWarning $_.Exception.Message + } + } + + end {} + +} \ No newline at end of file diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverSubsystem.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverSubsystem.ps1 index 38ddb93..357c5d3 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverSubsystem.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapVserverSubsystem.ps1 @@ -5,7 +5,7 @@ function Get-AbrOntapVserverSubsystem { .DESCRIPTION .NOTES - Version: 0.6.12 + Version: 0.6.14 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux @@ -48,7 +48,7 @@ function Get-AbrOntapVserverSubsystem { 'Type' = $Item.Ostype 'Target NQN' = $Item.TargetNqn 'Host NQN' = $Item.Hosts.Nqn - 'Mapped Namespace' = (($MappedNamespace).count -eq 0) ? 'None': $MappedNamespaces + 'Mapped Namespace' = ($MappedNamespace ? $MappedNamespace : 'None') } $VserverObj = [pscustomobject](ConvertTo-HashToYN $inObj) if ($Healthcheck.Vserver.Status) { diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Public/Get-AbrOntapLog.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Public/Get-AbrOntapLog.ps1 new file mode 100644 index 0000000..f3f83ba --- /dev/null +++ b/AsBuiltReport.NetApp.ONTAP/Src/Public/Get-AbrOntapLog.ps1 @@ -0,0 +1,320 @@ + +function Get-AbrOntapLog { + <# + .SYNOPSIS + Collects diagnostic information for AsBuiltReport.NetApp.ONTAP troubleshooting. + .DESCRIPTION + Gathers environment, module, PowerShell session, and error information from + the current session and the machine running the report. Output is written to + a structured JSON file, and a status message is written to the host when the + collection completes successfully. + .PARAMETER OutputFolderPath + Directory where the diagnostic bundle (JSON file) is saved. + Defaults to the system temporary folder. + .PARAMETER IncludeErrorDetails + When specified, captures the full $Error collection including stack traces. + By default only the most recent 25 errors are included (without stack traces). + .PARAMETER PassThru + Returns the diagnostic object to the pipeline in addition to writing the file. + .EXAMPLE + Get-AbrOntapLog + + Saves a diagnostic JSON to the system temp folder. + .EXAMPLE + Get-AbrOntapLog -OutputFolderPath 'C:\Logs' -IncludeErrorDetails -PassThru + + Saves a full diagnostic JSON (with stack traces) to C:\Logs and returns the + object to the pipeline. + .NOTES + Version: 0.1.0 + Author: Jonathan Colon + Github: rebelinux + .LINK + https://github.com/AsBuiltReport/AsBuiltReport.NetApp.ONTAP + #> + + [CmdletBinding()] + [OutputType([PSCustomObject])] + param ( + [Parameter(Mandatory = $false, HelpMessage = 'Directory where the diagnostic bundle is saved.')] + [ValidateScript({ Test-Path $_ -PathType Container })] + [String] $OutputFolderPath = ([System.IO.Path]::GetTempPath()), + + [Parameter(Mandatory = $false, HelpMessage = 'Include full stack traces for every error in $Error.')] + [Switch] $IncludeErrorDetails, + + [Parameter(Mandatory = $false, HelpMessage = 'Return the diagnostic object to the pipeline.')] + [Switch] $PassThru + ) + + begin { + Write-Verbose 'Get-AbrOntapLog: Starting diagnostic collection.' + $TimeStamp = Get-Date -Format 'yyyyMMdd_HHmmss' + $FileName = "AbrOntapDiagnostics_$TimeStamp.json" + $OutputFile = Join-Path -Path $OutputFolderPath -ChildPath $FileName + + # Compute platform once; used throughout process block. + # PS 5.1 (Desktop) lacks $PSVersionTable.Platform, so fall back to env/API. + $IsWindowsPlatform = if ($PSVersionTable.ContainsKey('Platform') -and $PSVersionTable.Platform) { + $PSVersionTable.Platform -eq 'Win32NT' + } else { + ($env:OS -eq 'Windows_NT') -or ([System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT) + } + + $Platform = if ($PSVersionTable.ContainsKey('Platform') -and $PSVersionTable.Platform) { + $PSVersionTable.Platform + } elseif ($IsWindowsPlatform) { + 'Win32NT' + } else { + [System.Environment]::OSVersion.Platform.ToString() + } + } + + process { + $Diag = [ordered] @{} + + # --- Collection timestamp ----------------------------------------------- + $Diag['CollectedAt'] = (Get-Date -Format 'o') + + # --- PowerShell session info -------------------------------------------- + try { + $Diag['PowerShellSession'] = [ordered] @{ + PSVersion = $PSVersionTable.PSVersion.ToString() + PSEdition = $PSVersionTable.PSEdition + CLRVersion = if ($PSVersionTable.CLRVersion) { $PSVersionTable.CLRVersion.ToString() } else { 'N/A' } + WSManStackVersion = if ($PSVersionTable.WSManStackVersion) { $PSVersionTable.WSManStackVersion.ToString() } else { 'N/A' } + OS = $PSVersionTable.OS + Platform = $Platform + ExecutionPolicy = (Get-ExecutionPolicy -Scope Process).ToString() + CurrentPrincipal = if ($IsWindowsPlatform) { + [Security.Principal.WindowsIdentity]::GetCurrent().Name + } else { + $EnvUser = [System.Environment]::GetEnvironmentVariable('USER') + if ($EnvUser) { $EnvUser } else { [System.Environment]::GetEnvironmentVariable('LOGNAME') } + } + IsAdministrator = if ($IsWindowsPlatform) { + ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + } else { + try { (& id -u).Trim() -eq '0' } catch { 'N/A' } + } + HostName = $Host.Name + HostVersion = $Host.Version.ToString() + PID = $PID + } + } catch { + $Diag['PowerShellSession'] = "Error collecting PowerShell session info: $($_.Exception.Message)" + } + + # --- Machine / OS info -------------------------------------------------- + if ($IsWindowsPlatform) { + try { + $OS = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop + $CS = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop + $CPU = Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop | Select-Object -First 1 + $Diag['Machine'] = [ordered] @{ + ComputerName = $env:COMPUTERNAME + Domain = $CS.Domain + Manufacturer = $CS.Manufacturer + Model = $CS.Model + TotalMemoryGB = [math]::Round($CS.TotalPhysicalMemory / 1GB, 2) + OSCaption = $OS.Caption + OSVersion = $OS.Version + OSBuildNumber = $OS.BuildNumber + OSArchitecture = $OS.OSArchitecture + OSLastBootUpTime = $OS.LastBootUpTime.ToString('o') + CPUName = $CPU.Name + CPUCores = $CPU.NumberOfCores + CPULogicalProc = $CPU.NumberOfLogicalProcessors + TimeZone = (Get-TimeZone).DisplayName + } + } catch { + $Diag['Machine'] = "Error collecting machine info: $($_.Exception.Message)" + } + } else { + # Unix (Linux / macOS) + try { + $KernelName = try { (& uname -s).Trim() } catch { 'N/A' } + $KernelRelease = try { (& uname -r).Trim() } catch { 'N/A' } + $Architecture = try { (& uname -m).Trim() } catch { 'N/A' } + $HostName = [System.Net.Dns]::GetHostName() + $EnvUser = [System.Environment]::GetEnvironmentVariable('USER') + $CurrentUser = if ($EnvUser) { $EnvUser } else { [System.Environment]::GetEnvironmentVariable('LOGNAME') } + $IsRoot = try { (& id -u).Trim() -eq '0' } catch { 'N/A' } + + if ($KernelName -eq 'Linux') { + $OSDescription = try { + $Release = Get-Content '/etc/os-release' -ErrorAction Stop + ($Release | Where-Object { $_ -match '^PRETTY_NAME=' } | Select-Object -First 1) -replace '^PRETTY_NAME=|"', '' + } catch { 'N/A' } + + $MemInfo = Get-Content '/proc/meminfo' -ErrorAction SilentlyContinue + $MemKB = ($MemInfo | Where-Object { $_ -match '^MemTotal:' } | Select-Object -First 1) -replace '\D', '' + $MemGB = if ($MemKB) { [math]::Round([long]$MemKB / 1MB, 2) } else { 'N/A' } + + $CpuInfo = Get-Content '/proc/cpuinfo' -ErrorAction SilentlyContinue + $CpuName = ($CpuInfo | Where-Object { $_ -match '^model name' } | Select-Object -First 1) -replace '^model name\s*:\s*', '' + $CpuCores = ($CpuInfo | Where-Object { $_ -match '^cpu cores' } | Select-Object -First 1) -replace '\D', '' + $CpuLogical = ($CpuInfo | Where-Object { $_ -match '^processor' }).Count + } elseif ($KernelName -eq 'Darwin') { + $OSDescription = try { "$(& sw_vers -productName) $(& sw_vers -productVersion)".Trim() } catch { 'N/A' } + $MemBytes = try { [long](& sysctl -n hw.memsize) } catch { $null } + $MemGB = if ($null -ne $MemBytes) { [math]::Round($MemBytes / 1GB, 2) } else { 'N/A' } + $CpuName = try { (& sysctl -n machdep.cpu.brand_string).Trim() } catch { 'N/A' } + $CpuCores = try { (& sysctl -n hw.physicalcpu).Trim() } catch { 'N/A' } + $CpuLogical = try { (& sysctl -n hw.logicalcpu).Trim() } catch { 'N/A' } + } else { + $OSDescription = "Unknown Unix ($KernelName)" + $MemGB = $CpuName = $CpuCores = $CpuLogical = 'N/A' + } + + $Diag['Machine'] = [ordered] @{ + ComputerName = $HostName + CurrentUser = $CurrentUser + IsRoot = $IsRoot + KernelName = $KernelName + KernelRelease = $KernelRelease + OSDescription = $OSDescription + Architecture = $Architecture + TotalMemoryGB = $MemGB + CPUName = if ($CpuName) { $CpuName } else { 'N/A' } + CPUCores = if ($CpuCores) { $CpuCores } else { 'N/A' } + CPULogicalProc = if ($CpuLogical) { $CpuLogical } else { 'N/A' } + TimeZone = (Get-TimeZone).DisplayName + } + } catch { + $Diag['Machine'] = "Error collecting machine info: $($_.Exception.Message)" + } + } + + # --- Relevant installed modules ----------------------------------------- + try { + $RelevantModuleNames = @( + 'AsBuiltReport.NetApp.ONTAP', + 'AsBuiltReport.Core', + 'AsBuiltReport.Chart', + 'AsBuiltReport.Diagram', + 'NetApp.ONTAP', + 'PScribo', + 'PSGraph' + ) + $ModuleInfo = foreach ($ModName in $RelevantModuleNames) { + $Mods = Get-Module -ListAvailable -Name $ModName -ErrorAction SilentlyContinue | + Sort-Object -Property Version -Descending + if ($Mods) { + foreach ($Mod in $Mods) { + [ordered] @{ + Name = $Mod.Name + Version = $Mod.Version.ToString() + Path = $Mod.ModuleBase + Description = $Mod.Description + } + } + } else { + [ordered] @{ + Name = $ModName + Version = 'Not installed' + Path = $null + Description = $null + } + } + } + $Diag['InstalledModules'] = @($ModuleInfo) + } catch { + $Diag['InstalledModules'] = "Error collecting module info: $($_.Exception.Message)" + } + + # --- Currently loaded modules in session -------------------------------- + try { + $Diag['LoadedModules'] = @( + Get-Module | Sort-Object -Property Name | ForEach-Object { + [ordered] @{ + Name = $_.Name + Version = $_.Version.ToString() + Path = $_.ModuleBase + } + } + ) + } catch { + $Diag['LoadedModules'] = "Error collecting loaded modules: $($_.Exception.Message)" + } + + # --- $Error variable collection ----------------------------------------- + try { + $MaxErrors = if ($IncludeErrorDetails) { $global:Error.Count } else { [math]::Min(25, $global:Error.Count) } + $ErrorCollection = for ($i = 0; $i -lt $MaxErrors; $i++) { + $Err = $global:Error[$i] + if ($null -eq $Err) { continue } + $ErrObj = [ordered] @{ + Index = $i + Message = $Err.Exception.Message + FullyQualifiedErrorId = $Err.FullyQualifiedErrorId + Type = $Err.Exception.GetType().FullName + Category = $Err.CategoryInfo.Category.ToString() + CategoryReason = $Err.CategoryInfo.Reason + TargetName = $Err.CategoryInfo.TargetName + ErrorDetails = if ($Err.ErrorDetails) { $Err.ErrorDetails.Message } else { $null } + ScriptName = $Err.InvocationInfo.ScriptName + LineNumber = $Err.InvocationInfo.ScriptLineNumber + Line = $Err.InvocationInfo.Line -replace '\s+', ' ' + CommandName = $Err.InvocationInfo.MyCommand.Name + } + if ($IncludeErrorDetails) { + $ErrObj['StackTrace'] = $Err.Exception.StackTrace + # Build full inner exception chain + $InnerChain = [System.Collections.Generic.List[string]]::new() + $Inner = $Err.Exception.InnerException + while ($null -ne $Inner) { + $InnerChain.Add("[$($Inner.GetType().FullName)] $($Inner.Message)") + $Inner = $Inner.InnerException + } + $ErrObj['InnerExceptions'] = if ($InnerChain.Count -gt 0) { $InnerChain.ToArray() } else { $null } + } + $ErrObj + } + $Diag['ErrorLog'] = [ordered] @{ + TotalErrors = $global:Error.Count + CapturedErrors = $MaxErrors + FullDetails = $IncludeErrorDetails.IsPresent + Errors = @($ErrorCollection) + } + } catch { + $Diag['ErrorLog'] = "Error collecting `$Error log: $($_.Exception.Message)" + } + + # --- Environment variables (safe subset) -------------------------------- + try { + $SafeEnvVars = if ($IsWindowsPlatform) { + @('COMPUTERNAME', 'USERNAME', 'USERDOMAIN', 'USERDNSDOMAIN', + 'OS', 'PROCESSOR_ARCHITECTURE', 'NUMBER_OF_PROCESSORS', + 'TEMP', 'TMP', 'APPDATA', 'LOCALAPPDATA', 'PSModulePath') + } else { + @('USER', 'LOGNAME', 'HOME', 'SHELL', 'HOSTNAME', 'TMPDIR', 'TEMP', 'TMP', + 'XDG_DATA_HOME', 'XDG_CONFIG_HOME', 'PSModulePath') + } + $EnvInfo = [ordered] @{} + foreach ($VarName in $SafeEnvVars) { + $EnvInfo[$VarName] = [System.Environment]::GetEnvironmentVariable($VarName) + } + $Diag['EnvironmentVariables'] = $EnvInfo + } catch { + $Diag['EnvironmentVariables'] = "Error collecting environment variables: $($_.Exception.Message)" + } + + # --- Write output file -------------------------------------------------- + $DiagObject = [pscustomobject] $Diag + try { + $DiagObject | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputFile -Encoding UTF8 -Force + Write-Host " [Get-AbrOntapLog] Diagnostic bundle saved to: $OutputFile" -ForegroundColor Green + } catch { + Write-Warning "Get-AbrOntapLog : Failed to write diagnostic file '$OutputFile': $($_.Exception.Message)" + } + + if ($PassThru) { + $DiagObject + } + } + + end { + Write-Verbose 'Get-AbrOntapLog: Diagnostic collection complete.' + } +} diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 index 2a56608..50284d2 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 @@ -5,7 +5,7 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { .DESCRIPTION Documents the configuration of NetApp ONTAP in Word/HTML/Text formats using PScribo. .NOTES - Version: 0.6.12 + Version: 0.6.14 Author: Jonathan Colon Feliciano Twitter: @jcolonfzenpr Github: rebelinux @@ -129,6 +129,13 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { Section -Style Heading1 "$($ClusterInfo.ClusterName) Cluster Report" { Paragraph "The following section provides a summary of the array configuration for $($ClusterInfo.ClusterName)." BlankLine + #---------------------------------------------------------------------------------------------# + # Report Brief Section # + #---------------------------------------------------------------------------------------------# + Section -Style Heading2 'Report Brief' { + Paragraph "The following section provides a high-level overview of the infrastructure and installed licenses for $($ClusterInfo.ClusterName)." + BlankLine + } #region Cluster Section $ClusterDiagram = Get-AbrOntapClusterDiagram if ($ClusterDiagram) { @@ -593,7 +600,7 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { Paragraph "The following section provides the ISCSI Service Information in $($SVM)." BlankLine Get-AbrOntapVserverIscsiSummary -Vserver $SVM - Section -ExcludeFromTOC -Style Heading6 'iSCSI Interfaces' { + Section -ExcludeFromTOC -Style Heading6 'iSCSI Interfaces' { Get-AbrOntapVserverIscsiInterface -Vserver $SVM } @@ -677,13 +684,18 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { BlankLine Get-AbrOntapVserverCGSummary -Vserver $SVM foreach ($CG in $CGs) { + if ($CG.volumes) { + Section -ExcludeFromTOC -Style Heading6 "$($CG.name) Consistency Group Volumes" { + Get-AbrOntapVserverCGVolume -CGObj $CG + } + } if ($CG.luns) { - Section -ExcludeFromTOC -Style Heading6 "$($CG.name) Luns" { + Section -ExcludeFromTOC -Style Heading6 "$($CG.name) Consistency Group Luns" { Get-AbrOntapVserverCGLun -CGObj $CG } } if ($CG.namespaces) { - Section -ExcludeFromTOC -Style Heading6 "$($CG.name) Namespaces" { + Section -ExcludeFromTOC -Style Heading6 "$($CG.name) Consistency Group Namespaces" { Get-AbrOntapVserverCGNamespace -CGObj $CG } } diff --git a/CHANGELOG.md b/CHANGELOG.md index cd0a35f..4e3a2a6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.6.14] - 2026-03-?? +### Added + +- Add Get-AbrOntapLog cmdlet to retrieve diagnostic logs about the report generation process for troubleshooting purposes + ### Changed - Bump module version to v0.6.14 -- Bump AsBuiltReport.Diagram module requirement to v1.0.5 +- Bump AsBuiltReport.Diagram module requirement to v1.0.7 +- Bump AsBuiltReport.Chart module requirement to v0.3.2 - Enhanced report section titles for clarity and consistency - Improved diagram export functionality with aspect ratio adjustments - Corrected SnapLock capitalization in various function outputs and messages - Updated health check section titles for better readability - Refined iSCSI and NVMe service section titles for consistency +- Improved Consistency Group section + +### Fixed + +- Fix issue in Get-AbrOntapVserverSubsystem where subsystem information may not be retrieved correctly for certain ONTAP versions ## [0.6.13] - 2026-03-17 From 31c82de6557651ae18b3608f966abc77d5a4b185 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sun, 24 May 2026 19:28:54 -0400 Subject: [PATCH 3/5] Improved Network diagram to better display Ifgrp ports and their associated Vlan\LIFs --- .../workflows/PSScriptAnalyzerSettings.psd1 | 2 - .vscode/launch.json | 6 ++ .../Get-AbrOntapNodeNetworkDiagram.ps1 | 66 ++++++++++++------- .../Invoke-AsBuiltReport.NetApp.ONTAP.ps1 | 1 - CHANGELOG.md | 1 + 5 files changed, 51 insertions(+), 25 deletions(-) diff --git a/.github/workflows/PSScriptAnalyzerSettings.psd1 b/.github/workflows/PSScriptAnalyzerSettings.psd1 index c053761..dc51543 100755 --- a/.github/workflows/PSScriptAnalyzerSettings.psd1 +++ b/.github/workflows/PSScriptAnalyzerSettings.psd1 @@ -1,8 +1,6 @@ @{ ExcludeRules = @( 'PSUseToExportFieldsInManifest', - 'PSReviewUnusedParameter', - 'PSUseDeclaredVarsMoreThanAssignments', 'PSAvoidGlobalVars' ) } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index bd155eb..6111830 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,12 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "PowerShell: Module Interactive Session", + "type": "PowerShell", + "request": "launch", + "script": "import-Module -Name ./AsBuiltReport.NetApp.ONTAP/AsBuiltReport.NetApp.ONTAP.psd1" + }, { "name": "PowerShell: Interactive Session", "type": "PowerShell", diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 index 718c1f0..69f3100 100644 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 @@ -53,7 +53,6 @@ function Get-AbrOntapNodeNetworkDiagram { process { try { - $ClusterInfo = Get-NcCluster -Controller $Array $NodeSum = Get-NcNode -Controller $Array try { @@ -119,6 +118,16 @@ function Get-AbrOntapNodeNetworkDiagram { } 'Mac' = $NodePort.MacAddress 'Mtu' = $NodePort.Mtu + 'Mode' = switch ([string]::IsNullOrEmpty($NodePort.IfgrpMode)) { + $true { 'Unknown' } + $false { $NodePort.IfgrpMode } + default { 'Unknown' } + } + 'DistributionFunction' = switch ([string]::IsNullOrEmpty($NodePort.IfgrpDistributionFunction)) { + $true { 'Unknown' } + $false { $NodePort.IfgrpDistributionFunction } + default { 'Unknown' } + } } } } @@ -154,7 +163,7 @@ function Get-AbrOntapNodeNetworkDiagram { } } - $NodeVlans = Get-NcNetPortVlan -Node $Node.Node -Controller $Array + $NodeVlans = Get-NcNetPortVlan -Node $Node.Node -Controller $Array | Sort-Object -Property VlanID foreach ($NodeVlan in $NodeVlans) { $NetVlanInfo += [PSCustomObject][ordered]@{ 'NodeName' = $NodeVlan.Node @@ -179,10 +188,10 @@ function Get-AbrOntapNodeNetworkDiagram { if ($NetPortInfo) { # Cluster Network Ports $ClusterPortObj = @() - foreach ($Port in ($NetPortInfo | Where-Object { $_.Nodename -eq $Node.Nodename -and $_.AdditionalInfo.'Broadcast Domain' -eq 'Cluster' })) { + foreach ($Port in ($NetPortInfo | Where-Object { $_.NodeName -eq $Node.NodeName -and $_.AdditionalInfo.'Broadcast Domain' -eq 'Cluster' })) { $PerPortLifs = @() - foreach ($Lif in ($NetLifsInfo | Where-Object { $_.NodeName -eq $Node.Nodename -and $_.CurrentPort -eq $Port.PortName })) { + foreach ($Lif in ($NetLifsInfo | Where-Object { $_.NodeName -eq $Node.NodeName -and $_.CurrentPort -eq $Port.PortName })) { $PerPortLifs += if ($Lif.AdditionalInfo.'Is Home?' -eq 'Yes') { Add-NodeIcon -Name $Lif.InterfaceName -ImagesObj $Images -Align 'Center' -IconType 'Ontap_Network_Nic' -IconDebug $IconDebug -AditionalInfo $Lif.AdditionalInfo -ImageSizePercent 50 -IconPath $IconPath -FontSize 12 } else { @@ -208,9 +217,9 @@ function Get-AbrOntapNodeNetworkDiagram { Add-NodeEdge -From "$($Port.NodeName)ClusterPorts" -To $Node.NodeName -EdgeColor $Edgecolor -EdgeStyle 'dashed' -EdgeThickness 1 -Arrowhead 'box' -Arrowtail 'box' -EdgeLabelFontColor $Fontcolor -EdgeLabelFontSize 12 -EdgeLength 1 # Non-IFGRP Ports without Vlan Interfces - foreach ($Port in ($NetPortInfo | Where-Object { $_.Nodename -eq $Node.Nodename -and $_.AdditionalInfo.'Broadcast Domain' -ne 'Cluster' -and $_.AdditionalInfo.'Ifgrp Port' -in @('None', 'Unknown') -and $_.PortName -notmatch 'a0' -and $_.PortType -ne 'vlan' -and $_.IsParentVlan -eq $false })) { + foreach ($Port in ($NetPortInfo | Where-Object { $_.NodeName -eq $Node.NodeName -and $_.AdditionalInfo.'Broadcast Domain' -ne 'Cluster' -and $_.AdditionalInfo.'Ifgrp Port' -in @('None', 'Unknown') -and $_.PortName -notmatch 'a0' -and $_.PortType -ne 'vlan' -and $_.IsParentVlan -eq $false })) { $PerPortLifs = @() - foreach ($Lif in ($NetLifsInfo | Where-Object { $_.CurrentNode -eq $Node.Nodename -and $_.CurrentPort -eq $Port.PortName })) { + foreach ($Lif in ($NetLifsInfo | Where-Object { $_.CurrentNode -eq $Node.NodeName -and $_.CurrentPort -eq $Port.PortName })) { $PerPortLifs += if ($Lif.AdditionalInfo.'Is Home?' -eq 'Yes') { Add-NodeIcon -Name $Lif.InterfaceName -ImagesObj $Images -Align 'Center' -IconType 'Ontap_Network_Nic' -IconDebug $IconDebug -AditionalInfo $Lif.AdditionalInfo -ImageSizePercent 50 -IconPath $IconPath -FontSize 12 } else { @@ -230,9 +239,9 @@ function Get-AbrOntapNodeNetworkDiagram { } # IFGRP Ports (Link Aggregation Groups) with member ports and LIFs - foreach ($IfgrpPort in ($NetPortInfo | Where-Object { $_.Nodename -eq $Node.Nodename -and $_.PortType -eq 'ifgrp' })) { + foreach ($IfgrpPort in ($NetPortInfo | Where-Object { $_.NodeName -eq $Node.NodeName -and $_.PortType -eq 'if_group' })) { $IfgrpPortLifs = @() - foreach ($Lif in ($NetLifsInfo | Where-Object { $_.CurrentNode -eq $Node.Nodename -and $_.CurrentPort -eq $IfgrpPort.PortName })) { + foreach ($Lif in ($NetLifsInfo | Where-Object { $_.CurrentNode -eq $Node.NodeName -and $_.CurrentPort -eq $IfgrpPort.PortName })) { $IfgrpPortLifs += if ($Lif.AdditionalInfo.'Is Home?' -eq 'Yes') { Add-NodeIcon -Name $Lif.InterfaceName -ImagesObj $Images -Align 'Center' -IconType 'Ontap_Network_Nic' -IconDebug $IconDebug -AditionalInfo $Lif.AdditionalInfo -ImageSizePercent 50 -IconPath $IconPath -FontSize 12 } else { @@ -244,27 +253,26 @@ function Get-AbrOntapNodeNetworkDiagram { $IfgrpPortLifs = Add-NodeIcon -Name "$($IfgrpPort.NodeName)_$($IfgrpPort.PortName)_NoLifs" -LabelName 'No LIFs Assigned' -ImagesObj $Images -IconType 'Ontap_Network_Nic' -IconDebug $IconDebug -FontSize 12 -ImageSizePercent 50 -AditionalInfo @() -IconPath $IconPath } - if ($IfgrpPortLifs.Count -eq 1) { $IfgrpPortLifsColumnSize = 1 } elseif ($Options.DiagramColumnSize) { $IfgrpPortLifsColumnSize = $Options.DiagramColumnSize } else { $IfgrpPortLifsColumnSize = $IfgrpPortLifs.Count } - # Member physical ports of this IFGRP $MemberPortItems = @() - foreach ($MemberPort in ($NetPortInfo | Where-Object { $_.Nodename -eq $Node.Nodename -and $_.AdditionalInfo.'Ifgrp Port' -eq $IfgrpPort.PortName })) { - $MemberPortItems += Add-NodeText -Name "$($MemberPort.NodeName)_$($MemberPort.PortName)_MemberPort" -Text $MemberPort.PortName -IconDebug $IconDebug -FontSize 12 + foreach ($MemberPort in ($NetPortInfo | Where-Object { $_.NodeName -eq $Node.NodeName -and $_.AdditionalInfo.'Ifgrp Port' -eq $IfgrpPort.PortName })) { + $MemberPortItems += Add-NodeIcon -Name "$($MemberPort.NodeName)_$($MemberPort.PortName)_MemberPort" -LabelName $MemberPort.PortName -ImagesObj $Images -IconType 'Ontap_Network_Port' -IconDebug $IconDebug -FontSize 12 -ImageSizePercent 50 -AditionalInfo ($MemberPort.AdditionalInfo | Select-Object -Property Health, 'Link Status', Mac, Mtu) -IconPath $IconPath } $IfgrpPortObj = @() + + $IfgrpMemberPortObj = @() if ($MemberPortItems) { - if ($MemberPortItems.Count -eq 1) { $MemberPortColumnSize = 1 } elseif ($Options.DiagramColumnSize) { $MemberPortColumnSize = $Options.DiagramColumnSize } else { $MemberPortColumnSize = $MemberPortItems.Count } - $IfgrpPortObj += Add-HtmlSubGraph -Name "$($IfgrpPort.NodeName)$($IfgrpPort.PortName)_Members" -TableArray $MemberPortItems -ImagesObj $Images -IconDebug $IconDebug -TableBorder 1 -Label 'Member Ports' -LabelPos top -TableStyle 'rounded,dashed' -TableBorderColor '#71797E' -FontName 'Segoe Ui Bold' -ColumnSize $MemberPortColumnSize + if ($MemberPortItems.Count -eq 1) { $MemberPortColumnSize = 1 } elseif ($MemberPortItems.Count -lt $Options.DiagramColumnSize) { $MemberPortColumnSize = $MemberPortItems.Count }elseif ($Options.DiagramColumnSize) { $MemberPortColumnSize = $Options.DiagramColumnSize } else { $MemberPortColumnSize = $MemberPortItems.Count } + $IfgrpMemberPortObj += Add-HtmlSubGraph -Name "$($IfgrpPort.NodeName)$($IfgrpPort.PortName)_Members" -TableArray $MemberPortItems -ImagesObj $Images -IconDebug $IconDebug -TableBorder 1 -Label 'Interface Group Member Ports' -LabelPos top -TableStyle 'rounded,dashed' -TableBorderColor '#71797E' -FontName 'Segoe Ui Bold' -ColumnSize $MemberPortColumnSize } - $IfgrpPortObj += Add-HtmlSubGraph -Name "$($IfgrpPort.NodeName)$($IfgrpPort.PortName)_IfgrpLifs" -TableArray $IfgrpPortLifs -ImagesObj $Images -IconDebug $IconDebug -TableBorder 1 -Label $IfgrpPort.PortName -LabelPos top -TableStyle 'rounded,dashed' -TableBorderColor '#71797E' -FontName 'Segoe Ui Bold' -ColumnSize $IfgrpPortLifsColumnSize - # Child VLAN interfaces grouped under this IFGRP port + $IfgrpVlanPortObj = @() if ($NetVlanInfo) { - foreach ($VlanPort in ($NetVlanInfo | Where-Object { $_.NodeName -eq $Node.Nodename -and $_.ParentInterface -eq $IfgrpPort.PortName })) { + foreach ($VlanPort in ($NetVlanInfo | Where-Object { $_.NodeName -eq $Node.NodeName -and $_.ParentInterface -eq $IfgrpPort.PortName })) { $IfgrpVlanLifs = @() - foreach ($Lif in ($NetLifsInfo | Where-Object { $_.CurrentNode -eq $Node.Nodename -and $_.CurrentPort -eq $VlanPort.InterfaceName })) { + foreach ($Lif in ($NetLifsInfo | Where-Object { $_.CurrentNode -eq $Node.NodeName -and $_.CurrentPort -eq $VlanPort.InterfaceName })) { $IfgrpVlanLifs += if ($Lif.AdditionalInfo.'Is Home?' -eq 'Yes') { Add-NodeIcon -Name $Lif.InterfaceName -ImagesObj $Images -Align 'Center' -IconType 'Ontap_Network_Nic' -IconDebug $IconDebug -AditionalInfo $Lif.AdditionalInfo -ImageSizePercent 50 -IconPath $IconPath -FontSize 12 } else { @@ -278,8 +286,22 @@ function Get-AbrOntapNodeNetworkDiagram { if ($IfgrpVlanLifs.Count -eq 1) { $IfgrpVlanLifsColumnSize = 1 } elseif ($Options.DiagramColumnSize) { $IfgrpVlanLifsColumnSize = $Options.DiagramColumnSize } else { $IfgrpVlanLifsColumnSize = $IfgrpVlanLifs.Count } - $IfgrpPortObj += Add-HtmlSubGraph -Name "$($VlanPort.NodeName)$($VlanPort.InterfaceName)_VlanLifs" -TableArray $IfgrpVlanLifs -ImagesObj $Images -IconDebug $IconDebug -TableBorder 1 -Label "$($VlanPort.InterfaceName) (VLAN $($VlanPort.VlanID))" -LabelPos top -TableStyle 'rounded,dashed' -TableBorderColor '#71797E' -FontName 'Segoe Ui Bold' -ColumnSize $IfgrpVlanLifsColumnSize + $IfgrpVlanPortObj += Add-HtmlSubGraph -Name "$($VlanPort.NodeName)$($VlanPort.InterfaceName)_VlanLifs" -TableArray $IfgrpVlanLifs -ImagesObj $Images -IconDebug $IconDebug -TableBorder 1 -Label "$($VlanPort.InterfaceName) (VLAN $($VlanPort.VlanID))" -LabelPos top -TableStyle 'rounded,dashed' -TableBorderColor '#71797E' -FontName 'Segoe Ui Bold' -ColumnSize $IfgrpVlanLifsColumnSize } + if ($IfgrpVlanPortObj.Count -eq 1) { $IfgrpVlanPortObjColumnSize = 1 } elseif ($Options.DiagramColumnSize) { $IfgrpVlanPortObjColumnSize = $Options.DiagramColumnSize } else { $IfgrpVlanPortObjColumnSize = $IfgrpVlanPortObj.Count } + + $IfgrpMemberPortObj += Add-HtmlSubGraph -Name "$($VlanPort.ParentInterface)_Vlans" -TableArray $IfgrpVlanPortObj -ImagesObj $Images -IconDebug $IconDebug -TableBorder 1 -Label 'Port VLANs' -LabelPos top -TableStyle 'rounded,dashed' -TableBorderColor '#71797E' -FontName 'Segoe Ui Bold' -ColumnSize $IfgrpVlanPortObjColumnSize + + } + + if ($IfgrpPortLifs.Count -eq 1) { $IfgrpPortLifsColumnSize = 1 } elseif ($Options.DiagramColumnSize) { $IfgrpPortLifsColumnSize = $Options.DiagramColumnSize } else { $IfgrpPortLifsColumnSize = $IfgrpPortLifs.Count } + + if ($IfgrpPortLifs -and (-not $IfgrpVlanPortObj)) { + $IfgrpMemberPortObj += Add-HtmlSubGraph -Name "$($IfgrpPort.NodeName)$($IfgrpPort.PortName)_IfgrpLifs" -TableArray $IfgrpPortLifs -ImagesObj $Images -IconDebug $IconDebug -TableBorder 1 -Label $IfgrpPort.PortName -LabelPos top -TableStyle 'rounded,dashed' -TableBorderColor '#71797E' -FontName 'Segoe Ui Bold' -ColumnSize $IfgrpPortLifsColumnSize + } + + if ($IfgrpMemberPortObj) { + $IfgrpPortObj += Add-HtmlSubGraph -Name "$($IfgrpPort.NodeName)$($IfgrpPort.PortName)_IfgrpLifs" -TableArray $IfgrpMemberPortObj -ImagesObj $Images -IconDebug $IconDebug -TableBorder 1 -Label "Mode: $($IfgrpPort.AdditionalInfo.Mode.toUpper()) Distribution: $($IfgrpPort.AdditionalInfo.DistributionFunction.toUpper())" -LabelPos top -TableStyle 'rounded,dashed' -TableBorderColor '#71797E' -FontName 'Segoe Ui Bold' -ColumnSize 1 } if ($IfgrpPortObj.Count -eq 1) { $IfgrpPortObjColumnSize = 1 } elseif ($Options.DiagramColumnSize) { $IfgrpPortObjColumnSize = $Options.DiagramColumnSize } else { $IfgrpPortObjColumnSize = $IfgrpPortObj.Count } @@ -291,11 +313,11 @@ function Get-AbrOntapNodeNetworkDiagram { # Physical Parent Ports with grouped Child VLAN Interfaces if ($NetVlanInfo) { - foreach ($ParentPort in ($NetPortInfo | Where-Object { $_.Nodename -eq $Node.Nodename -and $_.IsParentVlan -eq $true -and $_.PortType -ne 'ifgrp' -and $_.AdditionalInfo.'Broadcast Domain' -ne 'Cluster' -and $_.AdditionalInfo.'Ifgrp Port' -in @('None', 'Unknown') })) { + foreach ($ParentPort in ($NetPortInfo | Where-Object { $_.NodeName -eq $Node.NodeName -and $_.IsParentVlan -eq $true -and $_.PortType -ne 'if_group' -and $_.AdditionalInfo.'Broadcast Domain' -ne 'Cluster' -and $_.AdditionalInfo.'Ifgrp Port' -in @('None', 'Unknown') })) { $ChildVlanObjs = @() - foreach ($VlanPort in ($NetVlanInfo | Where-Object { $_.NodeName -eq $Node.Nodename -and $_.ParentInterface -eq $ParentPort.PortName })) { + foreach ($VlanPort in ($NetVlanInfo | Where-Object { $_.NodeName -eq $Node.NodeName -and $_.ParentInterface -eq $ParentPort.PortName })) { $VlanPortLifs = @() - foreach ($Lif in ($NetLifsInfo | Where-Object { $_.CurrentNode -eq $Node.Nodename -and $_.CurrentPort -eq $VlanPort.InterfaceName })) { + foreach ($Lif in ($NetLifsInfo | Where-Object { $_.CurrentNode -eq $Node.NodeName -and $_.CurrentPort -eq $VlanPort.InterfaceName })) { $VlanPortLifs += if ($Lif.AdditionalInfo.'Is Home?' -eq 'Yes') { Add-NodeIcon -Name $Lif.InterfaceName -ImagesObj $Images -Align 'Center' -IconType 'Ontap_Network_Nic' -IconDebug $IconDebug -AditionalInfo $Lif.AdditionalInfo -ImageSizePercent 50 -IconPath $IconPath -FontSize 12 } else { diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 index 50284d2..b3d2ca3 100755 --- a/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Public/Invoke-AsBuiltReport.NetApp.ONTAP.ps1 @@ -75,7 +75,6 @@ function Invoke-AsBuiltReport.NetApp.ONTAP { $script:TextInfo = (Get-Culture).TextInfo $script:RootPath = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent - $FontPath = Join-Path -Path $RootPath -ChildPath 'Tools/Fonts/ARIAL.TTF' #Connect to Ontap Storage Array using supplied credentials foreach ($OntapArray in $Target) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e3a2a6..eb97d4b 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated health check section titles for better readability - Refined iSCSI and NVMe service section titles for consistency - Improved Consistency Group section +- Improved Network diagram to better display Ifgrp ports and their associated Vlan\LIFs ### Fixed From bc914f4e19b0273bdfab3d16a25e91920ba790e5 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sun, 24 May 2026 19:29:34 -0400 Subject: [PATCH 4/5] chore: Update version number to 0.6.14 in Get-AbrOntapNodeNetworkDiagram.ps1 --- .../Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 index 69f3100..83df5b2 100644 --- a/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 +++ b/AsBuiltReport.NetApp.ONTAP/Src/Private/Get-AbrOntapNodeNetworkDiagram.ps1 @@ -5,7 +5,7 @@ function Get-AbrOntapNodeNetworkDiagram { .DESCRIPTION .NOTES - Version: 0.6.12 + Version: 0.6.14 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux From 7b731b01fcd0574b956b161fcc600a58ab241707 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Wed, 27 May 2026 16:24:19 -0400 Subject: [PATCH 5/5] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb97d4b..4cb1fd6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.6.14] - 2026-03-?? +## [0.6.14] - Unreleased ### Added