From acdeb0540ec9b7f928d1955f851aa194d9312662 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 14:21:50 +1100 Subject: [PATCH 01/24] [WIP] v2.0.0 refactor ## [2.0.0] - Unreleased ### Added - Modular architecture: each report section is now a dedicated private function (`Get-AbrVSphere*`) - Internationalization (i18n) support via `Language/` `.psd1` files (`en-US`, `en-GB`, `es-ES`, `fr-FR`, `de-DE`) - Pester test suite (`AsBuiltReport.VMware.vSphere.Tests.ps1`, `LocalizationData.Tests.ps1`, `Invoke-Tests.ps1`) - GitHub Actions Pester workflow (`.github/workflows/Pester.yml`) ### Changed - Complete module rewrite for improved maintainability and extensibility - Module source now uses nested folder structure (`AsBuiltReport.VMware.vSphere/`) - Requires `AsBuiltReport.Core` >= 1.6.2 - `CompatiblePSEditions` now explicitly declares `Desktop` and `Core` support --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/change_request.yml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/workflows/Dependabot.yml | 32 + .github/workflows/PSScriptAnalyzer.yml | 4 +- .github/workflows/Pester.yml | 44 + .github/workflows/Release.yml | 10 +- .github/workflows/Stale.yml | 19 + .gitignore | 1 + .../AsBuiltReport.VMware.vSphere.json | Bin 5254 -> 5314 bytes .../AsBuiltReport.VMware.vSphere.psd1 | 16 +- .../AsBuiltReport.VMware.vSphere.psm1 | 3 +- .../AsBuiltReport.png | Bin 0 -> 11900 bytes .../Language/de-DE/VMwarevSphere.psd1 | 1417 +++++ .../Language/en-GB/VMwarevSphere.psd1 | 1417 +++++ .../Language/en-US/VMwarevSphere.psd1 | 1417 +++++ .../Language/es-ES/VMwarevSphere.psd1 | 1417 +++++ .../Language/fr-FR/VMwarevSphere.psd1 | 1417 +++++ .../Src}/Private/Convert-DataSize.ps1 | 0 .../Src/Private/Get-AbrVSphereCluster.ps1 | 153 + .../Src/Private/Get-AbrVSphereClusterDRS.ps1 | 619 ++ .../Src/Private/Get-AbrVSphereClusterHA.ps1 | 250 + .../Get-AbrVSphereClusterProactiveHA.ps1 | 88 + .../Src/Private/Get-AbrVSphereDSCluster.ps1 | 192 + .../Src/Private/Get-AbrVSphereDatastore.ps1 | 202 + .../Src/Private/Get-AbrVSphereNetwork.ps1 | 408 ++ .../Private/Get-AbrVSphereResourcePool.ps1 | 138 + .../Src/Private/Get-AbrVSphereVM.ps1 | 504 ++ .../Src/Private/Get-AbrVSphereVMHost.ps1 | 97 + .../Private/Get-AbrVSphereVMHostHardware.ps1 | 287 + .../Private/Get-AbrVSphereVMHostNetwork.ps1 | 683 +++ .../Private/Get-AbrVSphereVMHostSecurity.ps1 | 259 + .../Private/Get-AbrVSphereVMHostStorage.ps1 | 177 + .../Private/Get-AbrVSphereVMHostSystem.ps1 | 266 + .../Src/Private/Get-AbrVSphereVUM.ps1 | 92 + .../Src/Private/Get-AbrVSpherevCenter.ps1 | 447 ++ .../Src/Private/Get-AbrVSpherevSAN.ps1 | 538 ++ .../Src}/Private/Get-ESXiBootDevice.ps1 | 0 .../Src}/Private/Get-InstallDate.ps1 | 0 .../Src}/Private/Get-License.ps1 | 0 .../Src}/Private/Get-PciDeviceDetail.ps1 | 0 .../Src}/Private/Get-ScsiDeviceDetail.ps1 | 0 .../Src}/Private/Get-Uptime.ps1 | 0 .../Private/Get-VMHostNetworkAdapterDP.ps1 | 0 .../Src}/Private/Get-vCenterStats.ps1 | 0 .../Invoke-AsBuiltReport.VMware.vSphere.ps1 | 190 + CHANGELOG.md | 314 +- CLAUDE.md | 240 + LICENSE | 2 +- README.md | 157 +- Samples/Sample vSphere As-Built Report.html | 686 --- Samples/Sample_vSphere_Report_1.png | Bin 58176 -> 0 bytes Samples/Sample_vSphere_Report_2.png | Bin 118775 -> 0 bytes .../Invoke-AsBuiltReport.VMware.vSphere.ps1 | 5100 ----------------- Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 | 189 + Tests/Invoke-Tests.ps1 | 203 + Tests/LocalizationData.Tests.ps1 | 74 + 57 files changed, 13798 insertions(+), 5979 deletions(-) create mode 100644 .github/workflows/Dependabot.yml create mode 100644 .github/workflows/Pester.yml create mode 100644 .github/workflows/Stale.yml create mode 100644 .gitignore rename AsBuiltReport.VMware.vSphere.json => AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json (98%) rename AsBuiltReport.VMware.vSphere.psd1 => AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 (92%) rename AsBuiltReport.VMware.vSphere.psm1 => AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psm1 (82%) create mode 100644 AsBuiltReport.VMware.vSphere/AsBuiltReport.png create mode 100644 AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 create mode 100644 AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 create mode 100644 AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 create mode 100644 AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 create mode 100644 AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Convert-DataSize.ps1 (100%) create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterHA.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostStorage.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Get-ESXiBootDevice.ps1 (100%) rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Get-InstallDate.ps1 (100%) rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Get-License.ps1 (100%) rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Get-PciDeviceDetail.ps1 (100%) rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Get-ScsiDeviceDetail.ps1 (100%) rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Get-Uptime.ps1 (100%) rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Get-VMHostNetworkAdapterDP.ps1 (100%) rename {Src => AsBuiltReport.VMware.vSphere/Src}/Private/Get-vCenterStats.ps1 (100%) create mode 100644 AsBuiltReport.VMware.vSphere/Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 create mode 100644 CLAUDE.md delete mode 100644 Samples/Sample vSphere As-Built Report.html delete mode 100644 Samples/Sample_vSphere_Report_1.png delete mode 100644 Samples/Sample_vSphere_Report_2.png delete mode 100644 Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 create mode 100644 Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 create mode 100644 Tests/Invoke-Tests.ps1 create mode 100644 Tests/LocalizationData.Tests.ps1 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bfda8eb..3d4492b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -90,7 +90,7 @@ body: I have read and followed the [bug reporting guidelines](https://www.asbuiltreport.com/dev-guide/contributing/#reporting-issues-and-bugs). required: true - label: >- - I have read [the documentation](https://www.asbuiltreport.com/user-guide/new-asbuiltconfig), + I have read [the documentation](https://www.asbuiltreport.com/user-guide/quickstart/), and referred to the [known issues](https://www.asbuiltreport.com/support/known-issues) before submitting this bug report. required: true - label: >- diff --git a/.github/ISSUE_TEMPLATE/change_request.yml b/.github/ISSUE_TEMPLATE/change_request.yml index 003552f..bf86a4c 100644 --- a/.github/ISSUE_TEMPLATE/change_request.yml +++ b/.github/ISSUE_TEMPLATE/change_request.yml @@ -26,7 +26,7 @@ body: If you are unsure of what a specific requirement means, please follow the links to learn about it and understand why it is necessary before submitting. options: - label: >- - I have read [the documentation](https://www.asbuiltreport.com/user-guide/new-asbuiltconfig), + I have read [the documentation](https://www.asbuiltreport.com/user-guide/quickstart/), and referred to the [known issues](https://www.asbuiltreport.com/support/known-issues) before submitting this change request. required: true - label: >- diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e237f46..6622e9b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -28,7 +28,7 @@ ## Checklist: -- [ ] My code follows the code style of this project. +- [ ] My code follows the [code style](https://www.asbuiltreport.com/dev-guide/contributing/#coding-style-guidelines) of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. -- [ ] I have read the [**CONTRIBUTING**](https://www.asbuiltreport.com/about/contributing/) document. +- [ ] I have read the [contributing](https://www.asbuiltreport.com/dev-guide/contributing/) documentation. diff --git a/.github/workflows/Dependabot.yml b/.github/workflows/Dependabot.yml new file mode 100644 index 0000000..959a9f3 --- /dev/null +++ b/.github/workflows/Dependabot.yml @@ -0,0 +1,32 @@ +# Dependabot configuration for AsBuiltReport.VMware.vSphere +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + # Monitor GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci" + include: "scope" + open-pull-requests-limit: 5 + + # Monitor PowerShell modules (via manifest) + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + labels: + - "dependencies" + - "powershell" + commit-message: + prefix: "deps" + include: "scope" + open-pull-requests-limit: 5 diff --git a/.github/workflows/PSScriptAnalyzer.yml b/.github/workflows/PSScriptAnalyzer.yml index a73694f..0cbba0f 100644 --- a/.github/workflows/PSScriptAnalyzer.yml +++ b/.github/workflows/PSScriptAnalyzer.yml @@ -5,9 +5,9 @@ jobs: name: Run PSScriptAnalyzer runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: lint - uses: devblackops/github-action-psscriptanalyzer@master + uses: alagoutte/github-action-psscriptanalyzer@master with: sendComment: true failOnErrors: true diff --git a/.github/workflows/Pester.yml b/.github/workflows/Pester.yml new file mode 100644 index 0000000..471c6f9 --- /dev/null +++ b/.github/workflows/Pester.yml @@ -0,0 +1,44 @@ +name: Pester Tests + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - master + - dev + +jobs: + pester-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set PSRepository to Trusted for PowerShell Gallery + shell: pwsh + run: | + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + + - name: Install required modules + shell: pwsh + run: | + Install-Module -Name AsBuiltReport.Core -Repository PSGallery -Force -AllowClobber -Scope CurrentUser + Install-Module -Name PScribo -Repository PSGallery -Force -AllowClobber -Scope CurrentUser + Install-Module -Name Pester -MinimumVersion 5.0.0 -Repository PSGallery -Force -AllowClobber -Scope CurrentUser + Install-Module -Name PSScriptAnalyzer -Repository PSGallery -Force -AllowClobber -Scope CurrentUser + + - name: Run Pester tests + shell: pwsh + run: | + .\Tests\Invoke-Tests.ps1 -OutputFormat NUnitXml + + - name: Publish test results + uses: dorny/test-reporter@v1 + if: always() + with: + name: Pester Tests + path: Tests/testResults.xml + reporter: java-junit + fail-on-error: true diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 4adaf43..333dfa8 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -5,7 +5,7 @@ on: types: [published] jobs: - publish-to-gallery: + publish-to-psgallery: runs-on: windows-latest steps: - uses: actions/checkout@v4 @@ -20,13 +20,13 @@ jobs: - name: Test Module Manifest shell: pwsh run: | - Test-ModuleManifest .\AsBuiltReport.VMware.vSphere.psd1 + Test-ModuleManifest .\AsBuiltReport.VMware.vSphere\AsBuiltReport.VMware.vSphere.psd1 - name: Publish module to PowerShell Gallery shell: pwsh run: | - Publish-Module -Path ./ -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose + Publish-Module -Path .\AsBuiltReport.VMware.vSphere -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose tweet: - needs: publish-to-gallery + needs: publish-to-psgallery runs-on: ubuntu-latest steps: - uses: Eomm/why-don-t-you-tweet@v2 @@ -42,7 +42,7 @@ jobs: TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} bsky-post: - needs: publish-to-gallery + needs: publish-to-psgallery runs-on: ubuntu-latest steps: - uses: zentered/bluesky-post-action@v0.2.0 diff --git a/.github/workflows/Stale.yml b/.github/workflows/Stale.yml new file mode 100644 index 0000000..c351e4d --- /dev/null +++ b/.github/workflows/Stale.yml @@ -0,0 +1,19 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' + days-before-stale: 90 + days-before-close: 7 + exempt-pr-labels: 'help wanted,enhancement,security,pinned' + stale-pr-label: 'wontfix' + stale-issue-label: 'wontfix' + exempt-issue-labels: 'help wanted,enhancement,security,pinned' + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c5f206 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.claude/ diff --git a/AsBuiltReport.VMware.vSphere.json b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json similarity index 98% rename from AsBuiltReport.VMware.vSphere.json rename to AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json index 8a0bc7bfed6e251a422cb84701ac19acdd93d496..90d57ba2b526b37a4accfbb7e14bc9fa7a7c5d6c 100644 GIT binary patch delta 56 zcmZqEJfyinj8WN#A(0`EA)TQVNT)NTGAJ=vF(?4ZRG_FXLnuQqgVN+e7Rkv8j2xTg H7&C+cSA+|D delta 16 XcmX@4*`~QcjB&Ca6UXK##tdNqE#d^W diff --git a/AsBuiltReport.VMware.vSphere.psd1 b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 similarity index 92% rename from AsBuiltReport.VMware.vSphere.psd1 rename to AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 index 2375c12..c9c58ea 100644 --- a/AsBuiltReport.VMware.vSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 @@ -12,10 +12,10 @@ RootModule = 'AsBuiltReport.VMware.vSphere.psm1' # Version number of this module. - ModuleVersion = '1.3.6' + ModuleVersion = '2.0.0' # Supported PSEditions - # CompatiblePSEditions = 'Desktop' + CompatiblePSEditions = @('Desktop', 'Core') # ID used to uniquely identify this module GUID = 'e1cbf1ce-cf01-4b6e-9cc2-56323da3c351' @@ -27,7 +27,7 @@ # CompanyName = '' # Copyright statement for this module - Copyright = '(c) 2025 Tim Carman. All rights reserved.' + Copyright = '(c) 2026 Tim Carman. All rights reserved.' # Description of the functionality provided by this module Description = 'A PowerShell module to generate an as built report on the configuration of VMware vSphere.' @@ -51,7 +51,7 @@ RequiredModules = @( @{ ModuleName = 'AsBuiltReport.Core'; - ModuleVersion = '1.4.3' + ModuleVersion = '1.6.2' } ) @@ -99,19 +99,19 @@ Tags = 'AsBuiltReport', 'Report', 'VMware', 'vSphere', 'vCenter', 'Documentation', 'PScribo', 'PSEdition_Desktop', 'PSEdition_Core', 'Windows', 'MacOS', 'Linux' # A URL to the license for this module. - LicenseUri = 'https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/master/LICENSE' + LicenseUri = 'https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/main/LICENSE' # A URL to the main website for this project. ProjectUri = 'https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere' # A URL to an icon representing this module. - IconUri = ' https://github.com/AsBuiltReport.png' + IconUri = 'AsBuiltReport.png' # ReleaseNotes of this module - ReleaseNotes = 'https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/master/CHANGELOG.md' + ReleaseNotes = 'https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/main/CHANGELOG.md' # Prerelease string of this module - # Prerelease = '' + Prerelease = 'alpha' # Flag to indicate whether the module requires explicit user acceptance for install/update/save # RequireLicenseAcceptance = $false diff --git a/AsBuiltReport.VMware.vSphere.psm1 b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psm1 similarity index 82% rename from AsBuiltReport.VMware.vSphere.psm1 rename to AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psm1 index e22b0dc..880ef3c 100644 --- a/AsBuiltReport.VMware.vSphere.psm1 +++ b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psm1 @@ -10,5 +10,4 @@ foreach ($Module in @($Public + $Private)) { } } -Export-ModuleMember -Function $Public.BaseName -Export-ModuleMember -Function $Private.BaseName \ No newline at end of file +Export-ModuleMember -Function $Public.BaseName \ No newline at end of file diff --git a/AsBuiltReport.VMware.vSphere/AsBuiltReport.png b/AsBuiltReport.VMware.vSphere/AsBuiltReport.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4ee29dadfe3f80468c31d925b42d0bcf2d4d9f GIT binary patch literal 11900 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 BZ`uF= literal 0 HcmV?d00001 diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 new file mode 100644 index 0000000..f971cda --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -0,0 +1,1417 @@ +# culture = 'de-DE' +@{ + +# Module-wide strings +InvokeAsBuiltReportVMwarevSphere = ConvertFrom-StringData @' + Connecting = Verbindung zum vCenter Server '{0}' wird hergestellt. + CheckPrivileges = vCenter-Benutzerberechtigungen werden überprüft. + UnablePrivileges = vCenter-Benutzerberechtigungen konnten nicht abgerufen werden. + VMHashtable = VM-Nachschlagetabelle wird erstellt. + VMHostHashtable = VMHost-Nachschlagetabelle wird erstellt. + DatastoreHashtable = Datenspeicher-Nachschlagetabelle wird erstellt. + VDPortGrpHashtable = VDPortGroup-Nachschlagetabelle wird erstellt. + EVCHashtable = EVC-Nachschlagetabelle wird erstellt. + CheckVUM = Suche nach VMware Update Manager Server. + CheckVxRail = Suche nach VxRail Manager Server. + CheckSRM = Suche nach VMware Site Recovery Manager Server. + CheckNSXT = Suche nach VMware NSX-T Manager Server. + CollectingTags = Tag-Informationen werden gesammelt. + TagError = Fehler beim Sammeln von Tag-Informationen. + CollectingAdvSettings = Erweiterte Einstellungen von {0} werden gesammelt. +'@ + +# Get-AbrVSpherevCenter +GetAbrVSpherevCenter = ConvertFrom-StringData @' + InfoLevel = vCenter-Informationsebene auf {0} gesetzt. + Collecting = vCenter Server-Informationen werden gesammelt. + SectionHeading = vCenter Server + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration des vCenter Servers {0}. + InsufficientPrivLicense = Unzureichende Benutzerberechtigungen für den Bericht über vCenter Server-Lizenzierung. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'Global > Lizenzen' zugewiesen ist. + InsufficientPrivStoragePolicy = Unzureichende Benutzerberechtigungen für den Bericht über VM-Speicherrichtlinien. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'Speicherprofil > Anzeigen' zugewiesen ist. + vCenterServer = vCenter Server + IPAddress = IP-Adresse + Version = Version + Build = Build + Product = Produkt + LicenseKey = Lizenzschlüssel + LicenseExpiration = Lizenzablauf + InstanceID = Instanz-ID + HTTPPort = HTTP-Port + HTTPSPort = HTTPS-Port + PSC = Plattformdienstcontroller + UpdateManagerServer = Update Manager Server + SRMServer = Site Recovery Manager Server + NSXTServer = NSX-T Manager Server + VxRailServer = VxRail Manager Server + DatabaseSettings = Datenbankeinstellungen + DatabaseType = Datenbanktyp + DataSourceName = Datenquellenname + MaxDBConnection = Maximale Datenbankverbindungen + MailSettings = E-Mail-Einstellungen + SMTPServer = SMTP-Server + SMTPPort = SMTP-Port + MailSender = Absender + HistoricalStatistics = Verlaufsstatistiken + IntervalDuration = Intervalldauer + IntervalEnabled = Intervall aktiviert + SaveDuration = Speicherdauer + StatisticsLevel = Statistikebene + Licensing = Lizenzierung + Total = Gesamt + Used = Verwendet + Available = Verfügbar + Expiration = Ablauf + Certificate = Zertifikat + Country = Land + Email = E-Mail + Locality = Ort + State = Bundesland + Organization = Organisation + OrganizationUnit = Organisationseinheit + Validity = Gültigkeit + Mode = Modus + SoftThreshold = Weicher Schwellenwert + HardThreshold = Harter Schwellenwert + MinutesBefore = Minuten vorher + PollInterval = Abfrageintervall + Roles = Rollen + Role = Rolle + SystemRole = Systemrolle + PrivilegeList = Berechtigungsliste + Tags = Tags + TagName = Name + TagCategory = Kategorie + TagDescription = Beschreibung + TagCategories = Tag-Kategorien + TagCardinality = Kardinalität + TagAssignments = Tag-Zuweisungen + TagEntity = Entität + TagEntityType = Entitätstyp + VMStoragePolicies = VM-Speicherrichtlinien + StoragePolicy = Speicherrichtlinie + Description = Beschreibung + ReplicationEnabled = Replikation aktiviert + CommonRulesName = Name der gemeinsamen Regeln + CommonRulesDescription = Beschreibung der gemeinsamen Regeln + Alarms = Alarme + Alarm = Alarm + AlarmDescription = Beschreibung + AlarmEnabled = Aktiviert + AlarmTriggered = Ausgelöst + AlarmAction = Aktion + AdvancedSystemSettings = Erweiterte Systemeinstellungen + Key = Schlüssel + Value = Wert + Enabled = Aktiviert + Disabled = Deaktiviert + Yes = Ja + No = Nein + None = Keine + TablevCenterSummary = vCenter Server Summary - {0} + TablevCenterConfig = vCenter Server Configuration - {0} + TableDatabaseSettings = Database Settings - {0} + TableMailSettings = Mail Settings - {0} + TableHistoricalStatistics = Historical Statistics - {0} + TableLicensing = Licensing - {0} + TableCertificate = Certificate - {0} + TableRole = Role {0} - {1} + TableRoles = Roles - {0} + TableTags = Tags - {0} + TableTagCategories = Tag Categories - {0} + TableTagAssignments = Tag Assignments - {0} + TableVMStoragePolicies = VM Storage Policies - {0} + TableAlarm = {0} - {1} + TableAlarms = Alarms - {0} + TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} +'@ + +# Get-AbrVSphereCluster +GetAbrVSphereCluster = ConvertFrom-StringData @' + InfoLevel = Cluster-Informationsebene auf {0} gesetzt. + Collecting = Cluster-Informationen werden gesammelt. + Processing = Cluster '{0}' wird verarbeitet ({1}/{2}). + SectionHeading = Cluster + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration der vSphere HA/DRS-Cluster, die vom vCenter Server {0} verwaltet werden. + ParagraphDetail = Die folgende Tabelle beschreibt die Konfiguration des Clusters {0}. + Cluster = Cluster + ID = ID + Datacenter = Rechenzentrum + NumHosts = Anzahl Hosts + NumVMs = Anzahl VMs + HAEnabled = vSphere HA + DRSEnabled = vSphere DRS + VSANEnabled = Virtual SAN + EVCMode = EVC-Modus + VMSwapFilePolicy = VM-Auslagerungsdateirichtlinie + NumberOfHosts = Anzahl der Hosts + NumberOfVMs = Anzahl der VMs + Hosts = Hosts + VirtualMachines = Virtuelle Maschinen + Permissions = Berechtigungen + Principal = Prinzipal + Role = Rolle + Propagate = Weitergeben + IsGroup = Ist Gruppe + Enabled = Aktiviert + Disabled = Deaktiviert + SwapWithVM = Mit VM + SwapInHostDatastore = Im Host-Datenspeicher + SwapVMDirectory = Verzeichnis der virtuellen Maschine + SwapHostDatastore = Vom Host angegebener Datenspeicher + TableClusterSummary = Cluster Summary - {0} + TableClusterConfig = Cluster Configuration - {0} +'@ + +# Get-AbrVSphereClusterHA +GetAbrVSphereClusterHA = ConvertFrom-StringData @' + InfoLevel = Cluster-HA-Informationsebene auf {0} gesetzt. + Collecting = Cluster-HA-Informationen werden gesammelt. + SectionHeading = vSphere HA-Konfiguration + ParagraphSummary = Der folgende Abschnitt beschreibt die vSphere HA-Konfiguration für Cluster {0}. + FailuresAndResponses = Fehler und Antworten + HostMonitoring = Host-Überwachung + HostFailureResponse = Antwort auf Host-Ausfall + HostIsolationResponse = Antwort auf Host-Isolierung + VMRestartPriority = VM-Neustartreihenfolge + PDLProtection = Datenspeicher mit permanentem Geräteverlust + APDProtection = Datenspeicher mit allen inaktiven Pfaden + VMMonitoring = VM-Überwachung + VMMonitoringSensitivity = VM-Überwachungsempfindlichkeit + AdmissionControl = Zugangskontrolle + FailoverLevel = Tolerierte Host-Ausfälle im Cluster + ACPolicy = Richtlinie + ACHostPercentage = CPU % + ACMemPercentage = Arbeitsspeicher % + PerformanceDegradation = VM-Leistungsverschlechterung + HeartbeatDatastores = Heartbeat-Datenspeicher + Datastore = Datenspeicher + HAAdvancedOptions = Erweiterte vSphere HA-Optionen + Key = Schlüssel + Value = Wert + APDRecovery = APD-Wiederherstellung nach APD-Timeout + Disabled = Deaktiviert + Enabled = Aktiviert + RestartVMs = VMs neu starten + ShutdownAndRestart = VMs herunterfahren und neu starten + PowerOffAndRestart = VMs ausschalten und neu starten + IssueEvents = Ereignisse ausgeben + PowerOffRestartConservative = VMs ausschalten und neu starten (konservativ) + PowerOffRestartAggressive = VMs ausschalten und neu starten (aggressiv) + ResetVMs = VMs zurücksetzen + VMMonitoringOnly = Nur VM-Überwachung + VMAndAppMonitoring = VM- und Anwendungsüberwachung + DedicatedFailoverHosts = Dedizierte Failover-Hosts + ClusterResourcePercentage = Clusterressourcen-Prozentsatz + SlotPolicy = Slot-Richtlinie + Yes = Ja + No = Nein + FixedSlotSize = Feste Slot-Größe + CoverAllPoweredOnVMs = Alle eingeschalteten VMs abdecken + NoneSpecified = Keine angegeben + OverrideFailoverCapacity = Override Calculated Failover Capacity + CPUSlotSize = CPU Slot Size (MHz) + MemorySlotSize = Memory Slot Size (MB) + PerfDegradationTolerate = Performance Degradation VMs Tolerate + HeartbeatSelectionPolicy = Heartbeat Selection Policy + HBPolicyAllFeasibleDsWithUserPreference = Use datastores from the specified list and complement automatically if needed + HBPolicyAllFeasibleDs = Automatically select datastores accessible from the host + HBPolicyUserSelectedDs = Use datastores only from the specified list + TableHAFailures = vSphere HA Failures and Responses - {0} + TableHAAdmissionControl = vSphere HA Admission Control - {0} + TableHAHeartbeat = vSphere HA Heartbeat Datastores - {0} + TableHAAdvanced = vSphere HA Advanced Options - {0} +'@ + +# Get-AbrVSphereClusterProactiveHA +GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' + InfoLevel = Cluster Proaktive HA-Informationsebene auf {0} gesetzt. + Collecting = Proaktive HA-Informationen des Clusters werden gesammelt. + SectionHeading = Proaktive HA + ParagraphSummary = Der folgende Abschnitt beschreibt die Konfiguration der proaktiven HA für Cluster {0}. + FailuresAndResponses = Fehler und Antworten + Provider = Anbieter + Remediation = Korrekturmaßnahme + HealthUpdates = Zustandsaktualisierungen + ProactiveHA = Proaktive HA + AutomationLevel = Automatisierungsstufe + ModerateRemediation = Moderate Korrektur + SevereRemediation = Schwerwiegende Korrektur + Enabled = Aktiviert + Disabled = Deaktiviert + MaintenanceMode = Wartungsmodus + QuarantineMode = Quarantänemodus + MixedMode = Gemischter Modus + TableProactiveHA = Proactive HA - {0} +'@ + +# Get-AbrVSphereClusterDRS +GetAbrVSphereClusterDRS = ConvertFrom-StringData @' + InfoLevel = Cluster-DRS-Informationsebene auf {0} gesetzt. + Collecting = Cluster-DRS-Informationen werden gesammelt. + SectionHeading = vSphere DRS-Konfiguration + ParagraphSummary = Die folgende Tabelle beschreibt die vSphere DRS-Konfiguration für Cluster {0}. + AutomationLevel = DRS-Automatisierungsebene + MigrationThreshold = Migrationsschwellenwert + PredictiveDRS = Prädiktiver DRS + VirtualMachineAutomation = Individuelle Maschinenautomatisierung + AdditionalOptions = Zusätzliche Optionen + VMDistribution = VM-Verteilung + MemoryMetricForLB = Arbeitsspeichermetrik für Lastenausgleich + CPUOverCommitment = CPU-Überbelegung + PowerManagement = Energieverwaltung + DPMAutomationLevel = DPM-Automatisierungsebene + DPMThreshold = DPM-Schwellenwert + AdvancedOptions = Erweiterte Optionen + Key = Schlüssel + Value = Wert + DRSClusterGroups = DRS-Clustergruppen + GroupName = Gruppenname + GroupType = Gruppentyp + GroupMembers = Mitglieder + DRSVMHostRules = DRS VM/Host-Regeln + RuleName = Regelname + RuleType = Regeltyp + RuleEnabled = Aktiviert + VMGroup = VM-Gruppe + HostGroup = Host-Gruppe + DRSRules = DRS-Regeln + RuleVMs = Virtuelle Maschinen + VMOverrides = VM-Überschreibungen + VirtualMachine = Virtuelle Maschine + DRSAutomationLevel = DRS-Automatisierungsebene + DRSBehavior = DRS-Verhalten + HARestartPriority = HA-Neustartreihenfolge + HAIsolationResponse = HA-Isolierungsantwort + PDLProtection = Datenspeicher mit PDL + APDProtection = Datenspeicher mit APD + VMMonitoring = VM-Überwachung + VMMonitoringFailureInterval = Fehlerintervall + VMMonitoringMinUpTime = Mindestbetriebszeit + VMMonitoringMaxFailures = Maximale Fehleranzahl + VMMonitoringMaxFailureWindow = Maximales Fehlerzeitfenster + UpdateManagerBaselines = Update Manager-Baselines + Baseline = Baseline + Description = Beschreibung + Type = Typ + TargetType = Zieltyp + LastUpdate = Letzte Aktualisierungszeit + NumPatches = Anzahl Patches + UpdateManagerCompliance = Update Manager-Compliance + Entity = Entität + Status = Compliance-Status + Version = Version + BaselineInfo = Baseline + VUMPrivilegeMsgBaselines = Unzureichende Benutzerberechtigungen für den Bericht über Cluster-Baselines. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'VMware Update Manager / VMware vSphere Lifecycle Manager > Patches und Upgrades verwalten > Compliance-Status anzeigen' zugewiesen ist. + VUMPrivilegeMsgCompliance = Unzureichende Benutzerberechtigungen für den Bericht über Cluster-Compliance. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'VMware Update Manager / VMware vSphere Lifecycle Manager > Patches und Upgrades verwalten > Compliance-Status anzeigen' zugewiesen ist. + VUMBaselineNotAvailable = Cluster-VUM-Baseline-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. + VUMComplianceNotAvailable = Cluster-VUM-Compliance-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. + Enabled = Aktiviert + Disabled = Deaktiviert + Yes = Ja + No = Nein + FullyAutomated = Vollautomatisch + PartiallyAutomated = Teilweise automatisiert + Manual = Manuell + Off = Aus + NotCompliant = Nicht konform + Unknown = Unbekannt + Incompatible = Inkompatibel + DRS = vSphere DRS + DPM = DPM + Automated = Automated + None = None + VMGroupType = VM Group + VMHostGroupType = Host Group + MustRunOn = Must run on hosts in group + ShouldRunOn = Should run on hosts in group + MustNotRunOn = Must not run on hosts in group + ShouldNotRunOn = Should not run on hosts in group + VMAffinity = Keep Virtual Machines Together + VMAntiAffinity = Separate Virtual Machines + Mandatory = Mandatory + OverCommitmentRatio = Over-Commitment Ratio + OverCommitmentRatioCluster = Over-Commitment Ratio (% of cluster capacity) + Lowest = Lowest + Low = Low + Medium = Medium + High = High + Highest = Highest + ClusterDefault = Cluster default + VMDependencyTimeout = VM Dependency Restart Condition Timeout + Seconds = {0} seconds + SectionVSphereHA = vSphere HA + IssueEvents = Issue events + PowerOffAndRestart = Power off and restart VMs + ShutdownAndRestartVMs = Shutdown and restart VMs + PowerOffRestartConservative = Power off and restart VMs - Conservative restart policy + PowerOffRestartAggressive = Power off and restart VMs - Aggressive restart policy + PDLFailureResponse = PDL Failure Response + APDFailureResponse = APD Failure Response + VMFailoverDelay = VM Failover Delay + Minutes = {0} minutes + ResponseRecovery = Response Recovery + ResetVMs = Reset VMs + SectionPDLAPD = PDL/APD Protection Settings + NoWindow = No window + WithinHours = Within {0} hrs + VMMonitoringOnly = VM Monitoring Only + VMAndAppMonitoring = VM and Application Monitoring + UserGroup = User/Group + IsGroup = Is Group? + Role = Role + DefinedIn = Defined In + Propagate = Propagate + Permissions = Permissions + ParagraphPermissions = The following table details the permissions for {0}. + TableDRSConfig = vSphere DRS Configuration - {0} + TableDRSAdditional = DRS Additional Options - {0} + TableDPM = vSphere DPM - {0} + TableDRSAdvanced = vSphere DRS Advanced Options - {0} + TableDRSGroups = DRS Cluster Groups - {0} + TableDRSVMHostRules = DRS VM/Host Rules - {0} + TableDRSRules = DRS Rules - {0} + TableDRSVMOverrides = DRS VM Overrides - {0} + TableHAVMOverrides = HA VM Overrides - {0} + TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} + TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TablePermissions = Permissions - {0} +'@ + +# Get-AbrVSphereResourcePool +GetAbrVSphereResourcePool = ConvertFrom-StringData @' + InfoLevel = Ressourcenpool-Informationsebene auf {0} gesetzt. + Collecting = Ressourcenpool-Informationen werden gesammelt. + Processing = Ressourcenpool '{0}' wird verarbeitet ({1}/{2}). + SectionHeading = Ressourcenpools + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration der Ressourcenpools, die vom vCenter Server {0} verwaltet werden. + ResourcePool = Ressourcenpool + Parent = Übergeordnet + CPUSharesLevel = CPU-Freigabeebene + CPUReservationMHz = CPU-Reservierung (MHz) + CPULimitMHz = CPU-Limit (MHz) + MemSharesLevel = Arbeitsspeicher-Freigabeebene + MemReservation = Arbeitsspeicherreservierung + MemLimit = Arbeitsspeicherlimit + ID = ID + NumCPUShares = Anzahl der CPU-Freigaben + CPUReservation = CPU-Reservierung + CPUExpandable = CPU-Reservierung erweiterbar + NumMemShares = Anzahl der Arbeitsspeicherfreigaben + MemExpandable = Arbeitsspeicherreservierung erweiterbar + NumVMs = Anzahl VMs + VirtualMachines = Virtuelle Maschinen + Enabled = Aktiviert + Disabled = Deaktiviert + Unlimited = Unbegrenzt + TableResourcePoolSummary = Resource Pool Summary - {0} + TableResourcePoolConfig = Resource Pool Configuration - {0} +'@ + +# Get-AbrVSphereVMHost +GetAbrVSphereVMHost = ConvertFrom-StringData @' + InfoLevel = VMHost-Informationsebene auf {0} gesetzt. + Collecting = VMHost-Informationen werden gesammelt. + Processing = VMHost '{0}' wird verarbeitet ({1}/{2}). + SectionHeading = Hosts + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration der VMware ESXi-Hosts, die vom vCenter Server {0} verwaltet werden. + VMHost = Host + Manufacturer = Hersteller + Model = Modell + ProcessorType = Prozessortyp + NumCPUSockets = CPU-Sockel + NumCPUCores = CPU-Kerne + NumCPUThreads = CPU-Threads + MemoryGB = Arbeitsspeicher (GB) + NumNICs = Anzahl NICs + NumHBAs = Anzahl HBAs + NumDatastores = Anzahl Datenspeicher + NumVMs = Anzahl VMs + ConnectionState = Verbindungsstatus + PowerState = Energiezustand + ErrorCollecting = Fehler beim Sammeln von VMHost-Informationen mit VMHost-Informationsebene {0}. + NotResponding = Antwortet nicht + Maintenance = Wartung + Disconnected = Getrennt + Version = Version + Build = Build + Parent = Parent + TableHostSummary = Host Summary - {0} +'@ + +# Get-AbrVSphereVMHostHardware +GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' + InfoLevel = VMHost-Hardware-Informationsebene auf {0} gesetzt. + Collecting = VMHost-Hardware-Informationen werden gesammelt. + SectionHeading = Hardware + ParagraphSummary = Der folgende Abschnitt beschreibt die Hardware-Konfiguration des Hosts {0}. + InsufficientPrivLicense = Unzureichende Benutzerberechtigungen für den Bericht über ESXi-Host-Lizenzierung. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'Global > Lizenzen' zugewiesen ist. + Specifications = Spezifikationen + Manufacturer = Hersteller + Model = Modell + SerialNumber = Seriennummer + AssetTag = Asset-Tag + ProcessorType = Prozessortyp + CPUSockets = CPU-Sockel + CPUCores = CPU-Kerne + CPUThreads = CPU-Threads + HyperthreadingActive = Hyperthreading aktiv + MemoryGB = Arbeitsspeicher (GB) + NUMANodes = NUMA-Knoten + NICs = NICs + HBAs = HBAs + Version = Version + Build = Build + LicenseKey = Lizenzschlüssel + Product = Produkt + LicenseExpiration = Lizenzablauf + IPMIBMC = IPMI / BMC + IPMIBMCError = Fehler beim Sammeln von IPMI/BMC-Informationen für {0}. + IPMIBMCManufacturer = Hersteller + IPMIBMCType = Typ + IPMIBMCIPAddress = IP-Adresse + IPMIBMCMACAddress = MAC-Adresse + BootDevice = Startgerät + BootDeviceError = Fehler beim Sammeln von Startgerätinformationen für {0}. + BootDeviceDescription = Beschreibung + BootDeviceType = Typ + BootDeviceSize = Größe (GB) + BootDeviceIsSAS = Ist SAS + BootDeviceIsUSB = Ist USB + BootDeviceIsSSD = Ist SSD + BootDeviceFromSAN = Von SAN + PCIDevices = PCI-Geräte + PCIDeviceId = PCI-ID + PCIVendorName = Herstellername + PCIDeviceName = Gerätename + PCIDriverName = Treibername + PCIDriverVersion = Treiberversion + PCIFirmware = Firmware-Version + PCIDriversFirmware = PCI-Gerätetreiber und Firmware + Enabled = Aktiviert + Disabled = Deaktiviert + NotApplicable = Nicht zutreffend + Unknown = Unbekannt + Maintenance = Wartung + NotResponding = Not Responding + Host = Host + ConnectionState = Connection State + ID = ID + Parent = Parent + HyperThreading = HyperThreading + NumberOfCPUSockets = Number of CPU Sockets + NumberOfCPUCores = Number of CPU Cores + NumberOfCPUThreads = Number of CPU Threads + CPUTotalUsedFree = CPU Total / Used / Free + MemoryTotalUsedFree = Memory Total / Used / Free + NumberOfNICs = Number of NICs + NumberOfHBAs = Number of HBAs + NumberOfDatastores = Number of Datastores + NumberOfVMs = Number of VMs + MaximumEVCMode = Maximum EVC Mode + EVCGraphicsMode = EVC Graphics Mode + PowerManagementPolicy = Power Management Policy + ScratchLocation = Scratch Location + BiosVersion = BIOS Version + BiosReleaseDate = BIOS Release Date + BootTime = Boot Time + UptimeDays = Uptime Days + MACAddress = MAC Address + SubnetMask = Subnet Mask + Gateway = Gateway + FirmwareVersion = Firmware Version + Device = Device + BootType = Boot Type + Vendor = Vendor + Size = Size + PCIAddress = PCI Address + DeviceClass = Device Class + TableHardwareConfig = Hardware Configuration - {0} + TableIPMIBMC = IPMI / BMC - {0} + TableBootDevice = Boot Device - {0} + TablePCIDevices = PCI Devices - {0} + TablePCIDriversFirmware = PCI Devices Drivers & Firmware - {0} + PCIDeviceError = Error collecting PCI device information for {0}. {1} + PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} + HardwareError = Error collecting host hardware information for {0}. {1} +'@ + +# Get-AbrVSphereVMHostSystem +GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' + InfoLevel = VMHost-System-Informationsebene auf {0} gesetzt. + Collecting = VMHost-System-Informationen werden gesammelt. + HostProfile = Host-Profil + ProfileName = Host-Profil + ProfileCompliance = Compliance + ProfileRemediating = Korrekturmaßnahme + ImageProfile = Image-Profil + ImageProfileName = Name + ImageProfileVendor = Hersteller + ImageProfileAcceptance = Akzeptanzebene + TimeConfiguration = Zeitkonfiguration + NTPServer = NTP-Server + TimeZone = Zeitzone + Syslog = Syslog + SyslogHost = Syslog-Host + VUMBaseline = Update Manager-Baselines + VUMBaselineNotAvailable = VMHost-VUM-Baseline-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. + VUMBaselineName = Baseline + VUMBaselineType = Typ + VUMBaselinePatches = Anzahl Patches + VUMCompliance = Update Manager-Compliance + VUMComplianceNotAvailable = VMHost-VUM-Compliance-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. + VUMStatus = Status + AdvancedSettings = Erweiterte Systemeinstellungen + Key = Schlüssel + Value = Wert + VIBs = VMware-Installationspakete (VIBs) + VIBName = Name + VIBVersion = Version + VIBVendor = Hersteller + VIBInstallDate = Installationsdatum + VIBAcceptanceLevel = Akzeptanzebene + SectionHeading = System + ParagraphSummary = Der folgende Abschnitt enthält Details zur Systemkonfiguration des Hosts {0}. + NTPService = NTP-Dienst + NTPServers = NTP-Server + SyslogPort = Port + VUMDescription = Beschreibung + VUMTargetType = Zieltyp + VUMLastUpdate = Letzte Aktualisierungszeit + InstallDate = Installationsdatum + ImageName = Image-Profil + ImageVendor = Anbieter + Running = Wird ausgeführt + Stopped = Gestoppt + NotCompliant = Nicht konform + Unknown = Unbekannt + Incompatible = Inkompatibel + ProfileDescription = Description + VIBID = ID + VIBCreationDate = Creation Date + TableHostProfile = Host Profile - {0} + TableImageProfile = Image Profile - {0} + TableTimeConfig = Time Configuration - {0} + TableSyslog = Syslog Configuration - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TableAdvancedSettings = Advanced System Settings - {0} + TableVIBs = Software VIBs - {0} + HostProfileError = Error collecting host profile information for {0}. {1} + ImageProfileError = Error collecting image profile information for {0}. {1} + TimeConfigError = Error collecting time configuration for {0}. {1} + SyslogError = Error collecting syslog configuration for {0}. {1} + VUMBaselineError = Error collecting Update Manager baseline information for {0}. {1} + VUMComplianceError = Error collecting Update Manager compliance information for {0}. {1} + AdvancedSettingsError = Error collecting host advanced settings information for {0}. {1} + VIBsError = Error collecting software VIB information for {0}. {1} + InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. + InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. +'@ + +# Get-AbrVSphereVMHostStorage +GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' + InfoLevel = VMHost-Speicher-Informationsebene auf {0} gesetzt. + Collecting = VMHost-Speicher-Informationen werden gesammelt. + SectionHeading = Speicher + DatastoreSpecs = Datenspeicher-Spezifikationen + Datastore = Datenspeicher + DatastoreType = Typ + DatastoreCapacity = Kapazität (GB) + DatastoreFreeSpace = Freier Speicherplatz (GB) + DatastoreState = Status + DatastoreShared = Freigegeben + StorageAdapters = Speicheradapter + AdapterName = Adapter + AdapterType = Typ + AdapterDriver = Treiber + AdapterUID = UID + AdapterModel = Modell + AdapterStatus = Status + AdapterSASAddress = SAS-Adresse + AdapterWWN = WWN + AdapterDevices = Geräte + AdapterTargets = Ziele + ParagraphSummary = Der folgende Abschnitt enthält Details zur Speicherkonfiguration des Hosts {0}. + FC = Fibre Channel + iSCSI = iSCSI + ParallelSCSI = Paralleles SCSI + ChapNone = Keine + ChapUniPreferred = Unidirektionales CHAP verwenden, sofern vom Ziel nicht untersagt + ChapUniRequired = Unidirektionales CHAP verwenden, wenn vom Ziel erforderlich + ChapUnidirectional = Unidirektionales CHAP verwenden + ChapBidirectional = Bidirektionales CHAP verwenden + Online = Online + Offline = Offline + Type = Type + Version = Version + NumberOfVMs = # of VMs + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + TableDatastores = Datastores - {0} + ParagraphStorageAdapters = The following section details the storage adapter configuration for {0}. + AdapterPaths = Paths + iSCSIName = iSCSI Name + iSCSIAlias = iSCSI Alias + AdapterSpeed = Speed + DynamicDiscovery = Dynamic Discovery + StaticDiscovery = Static Discovery + AuthMethod = Authentication Method + CHAPOutgoing = Outgoing CHAP Name + CHAPIncoming = Incoming CHAP Name + AdvancedOptions = Advanced Options + NodeWWN = Node WWN + PortWWN = Port WWN + TableStorageAdapter = Storage Adapter {0} - {1} +'@ + +# Get-AbrVSphereVMHostNetwork +GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' + InfoLevel = VMHost-Netzwerk-Informationsebene auf {0} gesetzt. + Collecting = VMHost-Netzwerk-Informationen werden gesammelt. + SectionHeading = Netzwerk + NetworkConfig = Netzwerkkonfiguration + VMKernelAdapters = VMkernel-Adapter + PhysicalAdapters = Physische Adapter + CDP = Cisco Discovery Protocol + LLDP = Link Layer Discovery Protocol + StandardvSwitches = Standardmäßige virtuelle Switches + AdapterName = Adapter + AdapterMAC = MAC-Adresse + AdapterLinkSpeed = Verbindungsgeschwindigkeit + AdapterDriver = Treiber + AdapterPCIID = PCI-Gerät + AdapterStatus = Status + VMKName = VMkernel-Adapter + VMKIP = IP-Adresse + VMKSubnet = Subnetzmaske + VMKGateway = Standardgateway + VMKMTU = MTU + VMKPortGroup = Portgruppe + VMKVirtualSwitch = Virtueller Switch + VMKDHCPEnabled = DHCP aktiviert + VMKServicesEnabled = Aktivierte Dienste + vSwitch = Virtueller Switch + vSwitchPorts = Gesamtports + vSwitchUsedPorts = Verwendete Ports + vSwitchAvailablePorts = Verfügbare Ports + vSwitchMTU = MTU + vSwitchCDPStatus = CDP-Status + vSwitchSecurity = Sicherheitsrichtlinie + vSwitchFailover = Failover-Richtlinie + vSwitchLoadBalancing = Lastenausgleich + vSwitchPortGroups = Portgruppen + PortGroup = Portgruppe + VLAN = VLAN-ID + ActiveAdapters = Aktive Adapter + StandbyAdapters = Standby-Adapter + UnusedAdapters = Nicht verwendete Adapter + ParagraphSummary = Der folgende Abschnitt enthält Details zur Netzwerkkonfiguration des Hosts {0}. + Enabled = Aktiviert + Disabled = Deaktiviert + Connected = Verbunden + Disconnected = Getrennt + Yes = Ja + No = Nein + Accept = Akzeptieren + Reject = Ablehnen + Supported = Unterstützt + NotSupported = Nicht unterstützt + Inherited = Vererbt + TCPDefault = Standard + TCPProvisioning = Bereitstellung + TCPvMotion = vMotion + TCPNsxOverlay = nsx-overlay + TCPNsxHyperbus = nsx-hyperbus + TCPNotApplicable = Nicht zutreffend + LBSrcId = Route basierend auf der ursprünglichen Port-ID + LBSrcMac = Route basierend auf Source-MAC-Hash + LBIP = Route basierend auf IP-Hash + LBExplicitFailover = Explizites Failover + NFDLinkStatus = Nur Link-Status + NFDBeaconProbing = Beacon-Probing + FullDuplex = Vollduplex + AutoNegotiate = Automatisch verhandeln + Down = Deaktiviert + Host = Host + VirtualSwitches = Virtual Switches + VMkernelGateway = VMkernel Gateway + IPv6 = IPv6 + VMkernelIPv6Gateway = VMkernel IPv6 Gateway + DNSServers = DNS Servers + HostName = Host Name + DomainName = Domain Name + SearchDomain = Search Domain + TableNetworkConfig = Network Configuration - {0} + ParagraphPhysicalAdapters = The following section details the physical network adapter configuration for {0}. + VirtualSwitch = Virtual Switch + ActualSpeedDuplex = Actual Speed, Duplex + ConfiguredSpeedDuplex = Configured Speed, Duplex + WakeOnLAN = Wake on LAN + TablePhysicalAdapter = Physical Adapter {0} - {1} + TablePhysicalAdapters = Physical Adapters - {0} + ParagraphCDP = The following section details the CDP information for {0}. + Status = Status + SystemName = System Name + HardwarePlatform = Hardware Platform + SwitchID = Switch ID + SoftwareVersion = Software Version + ManagementAddress = Management Address + Address = Address + PortID = Port ID + MTU = MTU + TableAdapterCDP = Network Adapter {0} CDP Information - {1} + TableAdaptersCDP = Network Adapter CDP Information - {0} + ParagraphLLDP = The following section details the LLDP information for {0}. + ChassisID = Chassis ID + TimeToLive = Time to live + TimeOut = TimeOut + Samples = Samples + PortDescription = Port Description + SystemDescription = System Description + TableAdapterLLDP = Network Adapter {0} LLDP Information - {1} + TableAdaptersLLDP = Network Adapter LLDP Information - {0} + ParagraphVMKernelAdapters = The following section details the VMkernel adapter configuration for {0}. + NetworkLabel = Network Label + TCPIPStack = TCP/IP Stack + DHCP = DHCP + IPAddress = IP Address + SubnetMask = Subnet Mask + DefaultGateway = Default Gateway + vMotion = vMotion + Provisioning = Provisioning + FTLogging = FT Logging + Management = Management + vSphereReplication = vSphere Replication + vSphereReplicationNFC = vSphere Replication NFC + vSAN = vSAN + vSANWitness = vSAN Witness + vSphereBackupNFC = vSphere Backup NFC + TableVMKernelAdapter = VMkernel Adapter {0} - {1} + ParagraphStandardvSwitches = The following section details the standard virtual switch configuration for {0}. + NumberOfPorts = Number of Ports + NumberOfPortsAvailable = Number of Ports Available + TableStandardvSwitches = Standard Virtual Switches - {0} + VSSecurity = Virtual Switch Security + PromiscuousMode = Promiscuous Mode + MACAddressChanges = MAC Address Changes + ForgedTransmits = Forged Transmits + TableVSSecurity = Virtual Switch Security Policy - {0} + VSTrafficShaping = Virtual Switch Traffic Shaping + AverageBandwidth = Average Bandwidth (kbit/s) + PeakBandwidth = Peak Bandwidth (kbit/s) + BurstSize = Burst Size (KB) + TableVSTrafficShaping = Virtual Switch Traffic Shaping Policy - {0} + VSTeamingFailover = Virtual Switch Teaming & Failover + LoadBalancing = Load Balancing + NetworkFailureDetection = Network Failure Detection + NotifySwitches = Notify Switches + Failback = Failback + ActiveNICs = Active NICs + StandbyNICs = Standby NICs + UnusedNICs = Unused NICs + TableVSTeamingFailover = Virtual Switch Teaming & Failover - {0} + VSPortGroups = Virtual Switch Port Groups + NumberOfVMs = # of VMs + TableVSPortGroups = Virtual Switch Port Groups - {0} + VSPGSecurity = Virtual Switch Port Group Security + MACChanges = MAC Changes + TableVSPGSecurity = Virtual Switch Port Group Security Policy - {0} + VSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping + TableVSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping Policy - {0} + VSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover + TableVSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover - {0} +'@ + +# Get-AbrVSphereVMHostSecurity +GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' + InfoLevel = VMHost-Sicherheits-Informationsebene auf {0} gesetzt. + Collecting = VMHost-Sicherheits-Informationen werden gesammelt. + SectionHeading = Sicherheit + LockdownMode = Sperrmodus + Services = Dienste + ServiceName = Dienst + ServiceRunning = Wird ausgeführt + ServicePolicy = Startrichtlinie + ServiceRequired = Erforderlich + Firewall = Firewall + FirewallRule = Regel + FirewallAllowed = Erlaubte Hosts + Authentication = Authentifizierung + Domain = Domäne + Trust = Vertrauenswürdige Domänen + Membership = Domänenmitgliedschaft + VMInfoSection = Virtuelle Maschinen + VMName = Virtuelle Maschine + VMPowerState = Energiezustand + VMIPAddress = IP-Adresse + VMOS = Betriebssystem + VMGuestID = Gast-ID + VMCPUs = CPUs + VMMemoryGB = Arbeitsspeicher (GB) + VMDisks = Festplatten (GB) + VMNICs = NICs + StartupShutdown = VM-Start/Herunterfahren + StartupEnabled = Start aktiviert + StartupOrder = Startreihenfolge + StartupDelay = Startverzögerung + StopAction = Stoppaktion + StopDelay = Stoppverzögerung + WaitForHeartbeat = Auf Heartbeat warten + ParagraphSummary = Der folgende Abschnitt enthält Details zur Sicherheitskonfiguration des Hosts {0}. + LockdownDisabled = Deaktiviert + LockdownNormal = Aktiviert (Normal) + LockdownStrict = Aktiviert (Streng) + Running = Wird ausgeführt + Stopped = Gestoppt + SvcPortUsage = Mit Portnutzung starten und stoppen + SvcWithHost = Mit Host starten und stoppen + SvcManually = Manuell starten und stoppen + On = Ein + Off = Aus + Enabled = Aktiviert + Disabled = Deaktiviert + WaitHeartbeat = Fortfahren wenn VMware Tools gestartet + WaitDelay = Auf Startverzögerung warten + PowerOff = Ausschalten + GuestShutdown = Gast herunterfahren + ToolsOld = Veraltet + ToolsOK = OK + ToolsNotRunning = Wird nicht ausgeführt + ToolsNotInstalled = Nicht installiert + TableLockdownMode = Lockdown Mode - {0} + TableServices = Services - {0} + FirewallService = Service + Status = Status + IncomingPorts = Incoming Ports + OutgoingPorts = Outgoing Ports + Protocols = Protocols + Daemon = Daemon + TableFirewall = Firewall Configuration - {0} + DomainMembership = Domain Membership + TrustedDomains = Trusted Domains + TableAuthentication = Authentication Services - {0} + ParagraphVMInfo = The following section details the virtual machine configuration for {0}. + VMProvisioned = Provisioned + VMUsed = Used + VMHWVersion = HW Version + VMToolsStatus = VM Tools Status + TableVMs = Virtual Machines - {0} + TableStartupShutdown = VM Startup/Shutdown Policy - {0} +'@ + +# Get-AbrVSphereNetwork +GetAbrVSphereNetwork = ConvertFrom-StringData @' + InfoLevel = Netzwerk-Informationsebene auf {0} gesetzt. + Collecting = Informationen über verteilte Switches werden gesammelt. + Processing = Verteilter Switch '{0}' wird verarbeitet ({1}/{2}). + SectionHeading = Verteilte Switches + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration der verteilten Switches, die vom vCenter Server {0} verwaltet werden. + VDSwitch = Verteilter Switch + Datacenter = Rechenzentrum + Manufacturer = Hersteller + NumUplinks = Anzahl Uplinks + NumPorts = Anzahl Ports + NumHosts = Anzahl Hosts + NumVMs = Anzahl VMs + Version = Version + MTU = MTU + ID = ID + NumberOfUplinks = Anzahl der Uplinks + NumberOfPorts = Anzahl der Ports + NumberOfPortGroups = Anzahl der Portgruppen + NumberOfHosts = Anzahl der Hosts + NumberOfVMs = Anzahl der VMs + Hosts = Hosts + VirtualMachines = Virtuelle Maschinen + NIOC = Netzwerk-E/A-Steuerung + DiscoveryProtocol = Erkennungsprotokoll + DiscoveryOperation = Erkennungsvorgang + NumPortGroups = Anzahl Portgruppen + MaxPorts = Maximale Ports + Contact = Kontakt + Location = Standort + VDPortGroups = Verteilte Portgruppen + PortGroup = Portgruppe + PortGroupType = Portgruppentyp + VLANID = VLAN-ID + VLANConfiguration = VLAN-Konfiguration + PortBinding = Portbindung + Host = Host + UplinkName = Uplink-Name + UplinkPortGroup = Uplink-Portgruppe + PhysicalNetworkAdapter = Physischer Netzwerkadapter + ActiveUplinks = Aktive Uplinks + StandbyUplinks = Standby-Uplinks + UnusedUplinks = Nicht verwendete Uplinks + AllowPromiscuous = Promiskuitätsmodus erlauben + ForgedTransmits = Gefälschte Übertragungen + MACAddressChanges = MAC-Adressänderungen + Accept = Akzeptieren + Reject = Ablehnen + Direction = Richtung + Status = Status + AverageBandwidth = Durchschnittliche Bandbreite (kbit/s) + PeakBandwidth = Spitzenbandbreite (kbit/s) + BurstSize = Burst-Größe (KB) + LoadBalancing = Lastausgleich + LoadBalanceSrcId = Route basierend auf der ursprünglichen Port-ID + LoadBalanceSrcMac = Route basierend auf Source-MAC-Hash + LoadBalanceIP = Route basierend auf IP-Hash + ExplicitFailover = Explizites Failover + NetworkFailureDetection = Netzwerkfehler-Erkennung + LinkStatus = Nur Link-Status + BeaconProbing = Beacon-Probing + NotifySwitches = Switches benachrichtigen + FailbackEnabled = Failback aktiviert + Yes = Ja + No = Nein + PrimaryVLANID = Primäre VLAN-ID + PrivateVLANType = Privater VLAN-Typ + SecondaryVLANID = Sekundäre VLAN-ID + UplinkPorts = Uplink-Ports des verteilten Switches + VDSSecurity = Sicherheit des verteilten Switches + VDSTrafficShaping = Datenverkehrsformung des verteilten Switches + VDSPortGroups = Portgruppen des verteilten Switches + VDSPortGroupSecurity = Sicherheit der Portgruppe des verteilten Switches + VDSPortGroupTrafficShaping = Datenverkehrsformung der Portgruppe des verteilten Switches + VDSPortGroupTeaming = Teaming und Failover der Portgruppe des verteilten Switches + VDSPrivateVLANs = Private VLANs des verteilten Switches + Enabled = Aktiviert + Disabled = Deaktiviert + Listen = Lauschen + Advertise = Ankündigen + Both = Beides + TableVDSSummary = Übersicht des verteilten Switches - {0} + TableVDSGeneral = Allgemeine Eigenschaften des verteilten Switches - {0} + TableVDSUplinkPorts = Uplink-Ports des verteilten Switches - {0} + TableVDSSecurity = Sicherheit des verteilten Switches - {0} + TableVDSTrafficShaping = Datenverkehrsformung des verteilten Switches - {0} + TableVDSPortGroups = Portgruppen des verteilten Switches - {0} + TableVDSPortGroupSecurity = Sicherheit der Portgruppe des verteilten Switches - {0} + TableVDSPortGroupTrafficShaping = Datenverkehrsformung der Portgruppe des verteilten Switches - {0} + TableVDSPortGroupTeaming = Teaming und Failover der Portgruppe des verteilten Switches - {0} + TableVDSPrivateVLANs = Private VLANs des verteilten Switches - {0} +'@ + +# Get-AbrVSpherevSAN +GetAbrVSpherevSAN = ConvertFrom-StringData @' + InfoLevel = vSAN-Informationsebene auf {0} gesetzt. + Collecting = vSAN-Informationen werden gesammelt. + CollectingESA = vSAN ESA-Informationen für '{0}' werden gesammelt. + CollectingOSA = vSAN OSA-Informationen für '{0}' werden gesammelt. + CollectingDisks = vSAN-Festplatteninformationen für '{0}' werden gesammelt. + CollectingDiskGroups = vSAN-Festplattengruppeninformationen für '{0}' werden gesammelt. + CollectingiSCSITargets = vSAN-iSCSI-Zielinformationen für '{0}' werden gesammelt. + CollectingiSCSILUNs = vSAN-iSCSI-LUN-Informationen für '{0}' werden gesammelt. + Processing = vSAN-Cluster '{0}' wird verarbeitet ({1}/{2}). + SectionHeading = vSAN + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration von VMware vSAN, das vom vCenter Server {0} verwaltet wird. + ParagraphDetail = Die folgende Tabelle beschreibt die vSAN-Konfiguration für Cluster {0}. + DisksSection = Festplatten + DiskGroupsSection = Festplattengruppen + iSCSITargetsSection = iSCSI-Ziele + iSCSILUNsSection = iSCSI-LUNs + Cluster = Cluster + StorageType = Speichertyp + ClusterType = Clustertyp + NumHosts = Anzahl Hosts + ID = ID + NumberOfHosts = Anzahl der Hosts + NumberOfDisks = Anzahl der Festplatten + NumberOfDiskGroups = Anzahl der Festplattengruppen + DiskClaimMode = Festplattenanforderungsmodus + PerformanceService = Leistungsdienst + FileService = Dateidienst + iSCSITargetService = iSCSI-Zieldienst + HistoricalHealthService = Verlaufsstatusdienst + HealthCheck = Statusprüfung + TotalCapacity = Gesamtkapazität + UsedCapacity = Verwendete Kapazität + FreeCapacity = Freie Kapazität + PercentUsed = % verwendet + HCLLastUpdated = HCL zuletzt aktualisiert + Hosts = Hosts + Version = Version + Stretched = Gestreckter Cluster + VSANEnabled = vSAN aktiviert + VSANESAEnabled = vSAN ESA aktiviert + DisksFormat = Festplattenformatversion + Deduplication = Deduplizierung und Komprimierung + AllFlash = All-Flash + FaultDomains = Fehlerdomänen + PFTT = Zu tolerierende primäre Fehler + SFTT = Zu tolerierende sekundäre Fehler + NetworkDiagnosticMode = Netzwerkdiagnosemodus + HybridMode = Hybridmodus + AutoRebalance = Automatisches Neuausgleichen + ProactiveDisk = Proaktives Neuausgleichen von Festplatten + ResyncThrottle = Resynchronisierungsdrosselung + SpaceEfficiency = Speichereffizienz + Encryption = Verschlüsselung + FileServiceEnabled = Dateidienst aktiviert + iSCSITargetEnabled = iSCSI-Ziel aktiviert + VMHostSpecs = VMHost vSAN-Spezifikationen + VMHost = VMHost + DiskGroup = Festplattengruppe + CacheDisks = Cache-Festplatten + DataDisks = Datenfestplatten + DiskGroups = Festplattengruppen + CacheTier = Cache-Ebene + DataTier = Daten-Ebene + Status = Status + FaultDomainName = Fehlerdomäne + VSANAdvancedOptions = Erweiterte vSAN-Konfigurationsoptionen + Key = Schlüssel + Value = Wert + IDs = Identifikatoren + VSAN_UUID = vSAN-UUID + CapacityTier = Kapazitätsebene (GB) + BufferTier = Pufferebene (GB) + DiskName = Festplatte + Name = Name + DriveType = Laufwerkstyp + Host = Host + State = Status + Encrypted = Verschlüsselt + Capacity = Kapazität + SerialNumber = Seriennummer + Vendor = Hersteller + Model = Modell + DiskType = Festplattentyp + DiskFormatVersion = Festplattenformatversion + ClaimedAs = Beansprucht als + NumDisks = Anzahl Festplatten + Type = Typ + IQN = IQN + Alias = Alias + LUNsCount = LUNs + NetworkInterface = Netzwerkschnittstelle + IOOwnerHost = E/A-Eigentümer-Host + TCPPort = TCP-Port + Health = Zustand + StoragePolicy = Speicherrichtlinie + ComplianceStatus = Compliance-Status + Authentication = Authentifizierung + LUNName = LUN + LUNID = LUN-ID + Yes = Ja + No = Nein + Enabled = Aktiviert + Disabled = Deaktiviert + Online = Online + Offline = Offline + Mounted = Eingehängt + Unmounted = Ausgehängt + Flash = Flash + HDD = HDD + Cache = Cache + TableVSANClusterSummary = vSAN-Cluster-Zusammenfassung - {0} + TableVSANConfiguration = vSAN-Konfiguration - {0} + TableDisk = Festplatte {0} - {1} + TableVSANDisks = vSAN-Festplatten - {0} + TableVSANDiskGroups = vSAN-Festplattengruppen - {0} + TableVSANiSCSITargets = vSAN-iSCSI-Ziele - {0} + TableVSANiSCSILUNs = vSAN-iSCSI-LUNs - {0} + ESAError = Fehler beim Sammeln von vSAN ESA-Informationen für '{0}'. {1} + OSAError = Fehler beim Sammeln von vSAN OSA-Informationen für '{0}'. {1} + DiskError = Fehler beim Sammeln von vSAN-Festplatteninformationen für '{0}'. {1} + DiskGroupError = Fehler beim Sammeln von vSAN-Festplattengruppeninformationen für '{0}'. {1} + iSCSITargetError = Fehler beim Sammeln von vSAN-iSCSI-Zielinformationen für '{0}'. {1} + iSCSILUNError = Fehler beim Sammeln von vSAN-iSCSI-LUN-Informationen für '{0}'. {1} +'@ + +# Get-AbrVSphereDatastore +GetAbrVSphereDatastore = ConvertFrom-StringData @' + InfoLevel = Datenspeicher-Informationsebene auf {0} gesetzt. + Collecting = Datenspeicher-Informationen werden gesammelt. + Processing = Datenspeicher '{0}' wird verarbeitet ({1}/{2}). + SectionHeading = Datenspeicher + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration der Datenspeicher, die vom vCenter Server {0} verwaltet werden. + Datastore = Datenspeicher + Type = Typ + DatastoreURL = URL + FileSystem = Dateisystemversion + CapacityGB = Gesamtkapazität (GB) + FreeSpaceGB = Freier Speicherplatz (GB) + UsedSpaceGB = Verwendeter Speicherplatz (GB) + State = Status + Accessible = Zugänglich + NumHosts = Anzahl Hosts + NumVMs = Anzahl VMs + SIOC = Speicher-E/A-Steuerung + IOLatencyThreshold = E/A-Latenzschwellenwert (ms) + IOLoadBalancing = E/A-Lastenausgleich + Enabled = Aktiviert + Disabled = Deaktiviert + Normal = Normal + Maintenance = Wartung + Unmounted = Ausgehängt + ID = ID + Datacenter = Rechenzentrum + Version = Version + NumberOfHosts = Anzahl der Hosts + NumberOfVMs = Anzahl der VMs + CongestionThreshold = Überlastungsschwelle + TotalCapacity = Gesamtkapazität + UsedCapacity = Verwendete Kapazität + FreeCapacity = Freie Kapazität + PercentUsed = % verwendet + Hosts = Hosts + VirtualMachines = Virtuelle Maschinen + SCSILUNInfo = SCSI-LUN-Informationen + Host = Host + CanonicalName = Kanonischer Name + Capacity = Kapazität + Vendor = Hersteller + Model = Modell + IsSSD = Ist SSD + MultipathPolicy = Multipath-Richtlinie + Paths = Pfade + TableDatastoreSummary = Datenspeicher-Übersicht - {0} + TableDatastoreConfig = Datenspeicher-Konfiguration - {0} + TableSCSILUN = SCSI-LUN-Informationen - {0} +'@ + +# Get-AbrVSphereDSCluster +GetAbrVSphereDSCluster = ConvertFrom-StringData @' + InfoLevel = Datenspeicher-Cluster-Informationsebene auf {0} gesetzt. + Collecting = Datenspeicher-Cluster-Informationen werden gesammelt. + Processing = Datenspeicher-Cluster '{0}' wird verarbeitet ({1}/{2}). + SectionHeading = Datenspeicher-Cluster + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration der Datenspeicher-Cluster, die vom vCenter Server {0} verwaltet werden. + DSCluster = Datenspeicher-Cluster + Datacenter = Rechenzentrum + CapacityGB = Gesamtkapazität (GB) + FreeSpaceGB = Freier Speicherplatz (GB) + SDRSEnabled = SDRS + SDRSAutomationLevel = SDRS-Automatisierungsebene + IOLoadBalancing = E/A-Lastenausgleich + SpaceThreshold = Speicherschwellenwert (%) + IOLatencyThreshold = E/A-Latenzschwellenwert (ms) + SDRSRules = SDRS-Regeln + RuleName = Regel + RuleEnabled = Aktiviert + RuleVMs = Virtuelle Maschinen + FullyAutomated = Vollautomatisch + NoAutomation = Keine Automatisierung (Manueller Modus) + Manual = Manuell + Enabled = Aktiviert + Disabled = Deaktiviert + Yes = Ja + No = Nein + ID = ID + TotalCapacity = Gesamtkapazität + UsedCapacity = Verwendete Kapazität + FreeCapacity = Freie Kapazität + PercentUsed = % verwendet + ParagraphDSClusterDetail = Die folgende Tabelle enthält Details zur Konfiguration des Datenspeicher-Clusters {0}. + TableDSClusterConfig = Datenspeicher-Cluster-Konfiguration - {0} + TableSDRSVMOverrides = SDRS-VM-Überschreibungen - {0} + SDRSVMOverrides = SDRS-VM-Überschreibungen + VirtualMachine = Virtuelle Maschine + KeepVMDKsTogether = VMDKs zusammenhalten + DefaultBehavior = Standard ({0}) +'@ + +# Get-AbrVSphereVM +GetAbrVSphereVM = ConvertFrom-StringData @' + InfoLevel = VM-Informationsebene auf {0} gesetzt. + Collecting = Informationen über virtuelle Maschinen werden gesammelt. + Processing = Virtuelle Maschine '{0}' wird verarbeitet ({1}/{2}). + SectionHeading = Virtuelle Maschinen + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration der virtuellen Maschinen, die vom vCenter Server {0} verwaltet werden. + VirtualMachine = Virtuelle Maschine + PowerState = Energiezustand + Template = Vorlage + OS = Betriebssystem + Version = VM-Hardwareversion + GuestID = Gast-ID + Cluster = Cluster + ResourcePool = Ressourcenpool + VMHost = Host + Folder = Ordner + IPAddress = IP-Adresse + CPUs = CPUs + MemoryGB = Arbeitsspeicher (GB) + ProvisionedGB = Bereitgestellt (GB) + UsedGB = Verwendet (GB) + NumDisks = Anzahl Festplatten + NumNICs = Anzahl NICs + NumSnapshots = Anzahl Snapshots + VMwareTools = VMware Tools + ToolsVersion = Tools-Version + ToolsStatus = Tools-Status + ToolsRunningStatus = Tools-Ausführungsstatus + VMAdvancedDetail = Erweiterte Konfiguration + BootOptions = Startoptionen + BootDelay = Startverzögerung (ms) + BootRetryEnabled = Startwiederholung aktiviert + BootRetryDelay = Wiederholungsverzögerung (ms) + EFISecureBoot = EFI Secure Boot + EnterBIOSSetup = BIOS-Setup beim nächsten Start aufrufen + HardDisks = Festplatten + DiskName = Festplatte + DiskCapacityGB = Kapazität (GB) + DiskFormat = Format + DiskStoragePolicy = Speicherrichtlinie + DiskDatastore = Datenspeicher + DiskController = Controller + NetworkAdapters = Netzwerkadapter + NICName = Netzwerkadapter + NICType = Adaptertyp + NICPortGroup = Portgruppe + NICMAC = MAC-Adresse + NICConnected = Verbunden + NICConnectionPolicy = Bei Einschalten verbinden + SnapshotHeading = Snapshots + SnapshotName = Snapshot + SnapshotDescription = Beschreibung + SnapshotSize = Größe (GB) + SnapshotDate = Erstellt + SnapshotParent = Übergeordnet + On = Ein + Off = Aus + ToolsOld = Veraltet + ToolsOK = OK + ToolsNotRunning = Wird nicht ausgeführt + ToolsNotInstalled = Nicht installiert + Yes = Ja + No = Nein + Connected = Verbunden + NotConnected = Nicht verbunden + Thin = Thin + Thick = Thick + Enabled = Aktiviert + Disabled = Deaktiviert + TotalVMs = Gesamt VMs + TotalvCPUs = Gesamt vCPUs + TotalMemory = Gesamtspeicher + TotalProvisionedSpace = Gesamt bereitgestellter Speicherplatz + TotalUsedSpace = Gesamt verwendeter Speicherplatz + VMsPoweredOn = Eingeschaltete VMs + VMsPoweredOff = Ausgeschaltete VMs + VMsOrphaned = Verwaiste VMs + VMsInaccessible = Nicht zugängliche VMs + VMsSuspended = Angehaltene VMs + VMsWithSnapshots = VMs mit Snapshots + GuestOSTypes = Gastbetriebssystemtypen + VMToolsOKCount = VM Tools OK + VMToolsOldCount = VM Tools veraltet + VMToolsNotRunningCount = VM Tools wird nicht ausgeführt + VMToolsNotInstalledCount = VM Tools nicht installiert + vCPUs = vCPUs + Memory = Arbeitsspeicher + Provisioned = Bereitgestellt + Used = Verwendet + HWVersion = HW-Version + VMToolsStatus = VM Tools Status + ID = ID + OperatingSystem = Betriebssystem + HardwareVersion = Hardwareversion + ConnectionState = Verbindungsstatus + FaultToleranceState = Fehlertoleranzstatus + FTNotConfigured = Nicht konfiguriert + FTNeedsSecondary = Sekundäres erforderlich + FTRunning = Wird ausgeführt + FTDisabled = Deaktiviert + FTStarting = Wird gestartet + FTEnabled = Aktiviert + Parent = Übergeordnet + ParentFolder = Übergeordneter Ordner + ParentResourcePool = Übergeordneter Ressourcenpool + CoresPerSocket = Kerne pro Socket + CPUShares = CPU-Anteile + CPUReservation = CPU-Reservierung + CPULimit = CPU-Limit + CPUHotAdd = CPU-Hot-Add + CPUHotRemove = CPU-Hot-Remove + MemoryAllocation = Speicherzuweisung + MemoryShares = Speicheranteile + MemoryHotAdd = Speicher-Hot-Add + vNICs = vNICs + DNSName = DNS-Name + Networks = Netzwerke + MACAddress = MAC-Adresse + vDisks = Virtuelle Festplatten + ProvisionedSpace = Bereitgestellter Speicherplatz + UsedSpace = Verwendeter Speicherplatz + ChangedBlockTracking = Geändertes Block-Tracking + StorageBasedPolicy = Speicherbasierte Richtlinie + StorageBasedPolicyCompliance = Konformität der speicherbasierten Richtlinie + Compliant = Konform + NonCompliant = Nicht konform + Unknown = Unbekannt + Notes = Notizen + BootTime = Startzeit + UptimeDays = Betriebstage + NetworkName = Netzwerkname + SCSIControllers = SCSI-Controller + Device = Gerät + ControllerType = Controller-Typ + BusSharing = Bus-Sharing + None = Keine + GuestVolumes = Gast-Volumes + Capacity = Kapazität + DiskProvisioning = Festplatten-Bereitstellung + ThickEagerZeroed = Thick Eager Zeroed + ThickLazyZeroed = Thick Lazy Zeroed + DiskType = Festplattentyp + PhysicalRDM = Physisches RDM + VirtualRDM = Virtuelles RDM + VMDK = VMDK + DiskMode = Festplattenmodus + IndependentPersistent = Unabhängig - Persistent + IndependentNonpersistent = Unabhängig - Nicht persistent + Dependent = Abhängig + DiskPath = Festplattenpfad + DiskShares = Festplatten-Anteile + DiskLimitIOPs = Festplatten-IOPS-Limit + Unlimited = Unbegrenzt + SCSIController = SCSI-Controller + SCSIAddress = SCSI-Adresse + Path = Pfad + FreeSpace = Freier Speicherplatz + DaysOld = Alter in Tagen + TableVMSummary = VM-Übersicht - {0} + TableVMAdvancedSummary = Erweiterte VM-Übersicht - {0} + TableVMSnapshotSummary = Snapshot-Übersicht der VMs - {0} + TableVMConfig = VM-Konfiguration - {0} + TableVMNetworkAdapters = Netzwerkadapter - {0} + TableVMSCSIControllers = SCSI-Controller - {0} + TableVMHardDiskConfig = Festplattenkonfiguration - {0} + TableVMHardDisk = {0} - {1} + TableVMGuestVolumes = Gast-Volumes - {0} + TableVMSnapshots = VM-Snapshots - {0} +'@ + +# Get-AbrVSphereVUM +GetAbrVSphereVUM = ConvertFrom-StringData @' + InfoLevel = VUM-Informationsebene auf {0} gesetzt. + Collecting = VMware Update Manager-Informationen werden gesammelt. + NotAvailable = VUM-Patch-Baseline-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. + PatchNotAvailable = VUM-Patch-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. + SectionHeading = VMware Update Manager + ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration von VMware Update Manager, der vom vCenter Server {0} verwaltet wird. + Baselines = Baselines + BaselineName = Baseline + Description = Beschreibung + Type = Typ + TargetType = Zieltyp + LastUpdate = Letzte Aktualisierungszeit + NumPatches = Anzahl Patches + Patches = Patches + PatchName = Patch + PatchProduct = Produkt + PatchDescription = Beschreibung + PatchReleaseDate = Veröffentlichungsdatum + PatchVendorID = Hersteller-ID + TableVUMBaselines = VMware Update Manager Baseline-Zusammenfassung - {0} + TableVUMPatches = VMware Update Manager Patch-Informationen - {0} +'@ + +} \ No newline at end of file diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 new file mode 100644 index 0000000..2b43686 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -0,0 +1,1417 @@ +# culture = 'en-GB' +@{ + +# Module-wide strings +InvokeAsBuiltReportVMwarevSphere = ConvertFrom-StringData @' + Connecting = Connecting to vCenter Server '{0}'. + CheckPrivileges = Checking vCenter user privileges. + UnablePrivileges = Unable to obtain vCenter user privileges. + VMHashtable = Creating VM lookup hashtable. + VMHostHashtable = Creating VMHost lookup hashtable. + DatastoreHashtable = Creating Datastore lookup hashtable. + VDPortGrpHashtable = Creating VDPortGroup lookup hashtable. + EVCHashtable = Creating EVC lookup hashtable. + CheckVUM = Checking for VMware Update Manager Server. + CheckVxRail = Checking for VxRail Manager Server. + CheckSRM = Checking for VMware Site Recovery Manager Server. + CheckNSXT = Checking for VMware NSX-T Manager Server. + CollectingTags = Collecting tag information. + TagError = Error collecting tag information. + CollectingAdvSettings = Collecting {0} advanced settings. +'@ + +# Get-AbrVSpherevCenter +GetAbrVSpherevCenter = ConvertFrom-StringData @' + InfoLevel = vCenter InfoLevel set to {0}. + Collecting = Collecting vCenter Server information. + SectionHeading = vCenter Server + ParagraphSummary = The following sections detail the configuration of vCenter Server {0}. + InsufficientPrivLicense = Insufficient user privileges to report vCenter Server licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned. + InsufficientPrivStoragePolicy = Insufficient user privileges to report VM storage policies. Please ensure the user account has the 'Storage Profile > View' privilege assigned. + vCenterServer = vCenter Server + IPAddress = IP Address + Version = Version + Build = Build + Product = Product + LicenseKey = License Key + LicenseExpiration = License Expiration + InstanceID = Instance ID + HTTPPort = HTTP Port + HTTPSPort = HTTPS Port + PSC = Platform Services Controller + UpdateManagerServer = Update Manager Server + SRMServer = Site Recovery Manager Server + NSXTServer = NSX-T Manager Server + VxRailServer = VxRail Manager Server + DatabaseSettings = Database Settings + DatabaseType = Database Type + DataSourceName = Data Source Name + MaxDBConnection = Maximum Database Connection + MailSettings = Mail Settings + SMTPServer = SMTP Server + SMTPPort = SMTP Port + MailSender = Mail Sender + HistoricalStatistics = Historical Statistics + IntervalDuration = Interval Duration + IntervalEnabled = Interval Enabled + SaveDuration = Save Duration + StatisticsLevel = Statistics Level + Licensing = Licensing + Total = Total + Used = Used + Available = Available + Expiration = Expiration + Certificate = Certificate + Country = Country + Email = Email + Locality = Locality + State = State + Organization = Organization + OrganizationUnit = Organization Unit + Validity = Validity + Mode = Mode + SoftThreshold = Soft Threshold + HardThreshold = Hard Threshold + MinutesBefore = Minutes Before + PollInterval = Poll Interval + Roles = Roles + Role = Role + SystemRole = System Role + PrivilegeList = Privilege List + Tags = Tags + TagName = Name + TagCategory = Category + TagDescription = Description + TagCategories = Tag Categories + TagCardinality = Cardinality + TagAssignments = Tag Assignments + TagEntity = Entity + TagEntityType = Entity Type + VMStoragePolicies = VM Storage Policies + StoragePolicy = Storage Policy + Description = Description + ReplicationEnabled = Replication Enabled + CommonRulesName = Common Rules Name + CommonRulesDescription = Common Rules Description + Alarms = Alarms + Alarm = Alarm + AlarmDescription = Description + AlarmEnabled = Enabled + AlarmTriggered = Triggered + AlarmAction = Action + AdvancedSystemSettings = Advanced System Settings + Key = Key + Value = Value + Enabled = Enabled + Disabled = Disabled + Yes = Yes + No = No + None = None + TablevCenterSummary = vCenter Server Summary - {0} + TablevCenterConfig = vCenter Server Configuration - {0} + TableDatabaseSettings = Database Settings - {0} + TableMailSettings = Mail Settings - {0} + TableHistoricalStatistics = Historical Statistics - {0} + TableLicensing = Licensing - {0} + TableCertificate = Certificate - {0} + TableRole = Role {0} - {1} + TableRoles = Roles - {0} + TableTags = Tags - {0} + TableTagCategories = Tag Categories - {0} + TableTagAssignments = Tag Assignments - {0} + TableVMStoragePolicies = VM Storage Policies - {0} + TableAlarm = {0} - {1} + TableAlarms = Alarms - {0} + TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} +'@ + +# Get-AbrVSphereCluster +GetAbrVSphereCluster = ConvertFrom-StringData @' + InfoLevel = Cluster InfoLevel set to {0}. + Collecting = Collecting Cluster information. + Processing = Processing Cluster '{0}' ({1}/{2}). + SectionHeading = Clusters + ParagraphSummary = The following sections detail the configuration of vSphere HA/DRS clusters managed by vCenter Server {0}. + ParagraphDetail = The following table details the configuration for cluster {0}. + Cluster = Cluster + ID = ID + Datacenter = Datacenter + NumHosts = # of Hosts + NumVMs = # of VMs + HAEnabled = vSphere HA + DRSEnabled = vSphere DRS + VSANEnabled = Virtual SAN + EVCMode = EVC Mode + VMSwapFilePolicy = VM Swap File Policy + NumberOfHosts = Number of Hosts + NumberOfVMs = Number of VMs + Hosts = Hosts + VirtualMachines = Virtual Machines + Permissions = Permissions + Principal = Principal + Role = Role + Propagate = Propagate + IsGroup = Is Group + Enabled = Enabled + Disabled = Disabled + SwapWithVM = With VM + SwapInHostDatastore = In Host Datastore + SwapVMDirectory = Virtual machine directory + SwapHostDatastore = Datastore specified by host + TableClusterSummary = Cluster Summary - {0} + TableClusterConfig = Cluster Configuration - {0} +'@ + +# Get-AbrVSphereClusterHA +GetAbrVSphereClusterHA = ConvertFrom-StringData @' + InfoLevel = Cluster HA InfoLevel set to {0}. + Collecting = Collecting Cluster HA information. + SectionHeading = vSphere HA Configuration + ParagraphSummary = The following section details the vSphere HA configuration for {0} cluster. + FailuresAndResponses = Failures and Responses + HostMonitoring = Host Monitoring + HostFailureResponse = Host Failure Response + HostIsolationResponse = Host Isolation Response + VMRestartPriority = VM Restart Priority + PDLProtection = Datastore with Permanent Device Loss + APDProtection = Datastore with All Paths Down + VMMonitoring = VM Monitoring + VMMonitoringSensitivity = VM Monitoring Sensitivity + AdmissionControl = Admission Control + FailoverLevel = Host Failures Cluster Tolerates + ACPolicy = Policy + ACHostPercentage = CPU % + ACMemPercentage = Memory % + PerformanceDegradation = VM Performance Degradation + HeartbeatDatastores = Heartbeat Datastores + Datastore = Datastore + HAAdvancedOptions = vSphere HA Advanced Options + Key = Key + Value = Value + APDRecovery = APD recovery after APD timeout + Disabled = Disabled + Enabled = Enabled + RestartVMs = Restart VMs + ShutdownAndRestart = Shutdown and restart VMs + PowerOffAndRestart = Power off and restart VMs + IssueEvents = Issue events + PowerOffRestartConservative = Power off and restart VMs (conservative) + PowerOffRestartAggressive = Power off and restart VMs (aggressive) + ResetVMs = Reset VMs + VMMonitoringOnly = VM monitoring only + VMAndAppMonitoring = VM and application monitoring + DedicatedFailoverHosts = Dedicated failover hosts + ClusterResourcePercentage = Cluster resource percentage + SlotPolicy = Slot policy + Yes = Yes + No = No + FixedSlotSize = Fixed slot size + CoverAllPoweredOnVMs = Cover all powered-on virtual machines + NoneSpecified = None specified + OverrideFailoverCapacity = Override Calculated Failover Capacity + CPUSlotSize = CPU Slot Size (MHz) + MemorySlotSize = Memory Slot Size (MB) + PerfDegradationTolerate = Performance Degradation VMs Tolerate + HeartbeatSelectionPolicy = Heartbeat Selection Policy + HBPolicyAllFeasibleDsWithUserPreference = Use datastores from the specified list and complement automatically if needed + HBPolicyAllFeasibleDs = Automatically select datastores accessible from the host + HBPolicyUserSelectedDs = Use datastores only from the specified list + TableHAFailures = vSphere HA Failures and Responses - {0} + TableHAAdmissionControl = vSphere HA Admission Control - {0} + TableHAHeartbeat = vSphere HA Heartbeat Datastores - {0} + TableHAAdvanced = vSphere HA Advanced Options - {0} +'@ + +# Get-AbrVSphereClusterProactiveHA +GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' + InfoLevel = Cluster Proactive HA InfoLevel set to {0}. + Collecting = Collecting Cluster Proactive HA information. + SectionHeading = Proactive HA + ParagraphSummary = The following section details the Proactive HA configuration for {0} cluster. + FailuresAndResponses = Failures and Responses + Provider = Provider + Remediation = Remediation + HealthUpdates = Health Updates + ProactiveHA = Proactive HA + AutomationLevel = Automation Level + ModerateRemediation = Moderate Remediation + SevereRemediation = Severe Remediation + Enabled = Enabled + Disabled = Disabled + MaintenanceMode = Maintenance Mode + QuarantineMode = Quarantine Mode + MixedMode = Mixed Mode + TableProactiveHA = Proactive HA - {0} +'@ + +# Get-AbrVSphereClusterDRS +GetAbrVSphereClusterDRS = ConvertFrom-StringData @' + InfoLevel = Cluster DRS InfoLevel set to {0}. + Collecting = Collecting Cluster DRS information. + SectionHeading = vSphere DRS Configuration + ParagraphSummary = The following table details the vSphere DRS configuration for {0} cluster. + AutomationLevel = DRS Automation Level + MigrationThreshold = Migration Threshold + PredictiveDRS = Predictive DRS + VirtualMachineAutomation = Individual Machine Automation + AdditionalOptions = Additional Options + VMDistribution = VM Distribution + MemoryMetricForLB = Memory Metric for Load Balancing + CPUOverCommitment = CPU Over-Commitment + PowerManagement = Power Management + DPMAutomationLevel = DPM Automation Level + DPMThreshold = DPM Threshold + AdvancedOptions = Advanced Options + Key = Key + Value = Value + DRSClusterGroups = DRS Cluster Groups + GroupName = Group Name + GroupType = Group Type + GroupMembers = Members + DRSVMHostRules = DRS VM/Host Rules + RuleName = Rule Name + RuleType = Rule Type + RuleEnabled = Enabled + VMGroup = VM Group + HostGroup = Host Group + DRSRules = DRS Rules + RuleVMs = Virtual Machines + VMOverrides = VM Overrides + VirtualMachine = Virtual Machine + DRSAutomationLevel = DRS Automation Level + DRSBehavior = DRS Behavior + HARestartPriority = HA Restart Priority + HAIsolationResponse = HA Isolation Response + PDLProtection = Datastore with PDL + APDProtection = Datastore with APD + VMMonitoring = VM Monitoring + VMMonitoringFailureInterval = Failure Interval + VMMonitoringMinUpTime = Minimum Uptime + VMMonitoringMaxFailures = Maximum Failures + VMMonitoringMaxFailureWindow = Maximum Failure Window + UpdateManagerBaselines = Update Manager Baselines + Baseline = Baseline + Description = Description + Type = Type + TargetType = Target Type + LastUpdate = Last Update Time + NumPatches = # of Patches + UpdateManagerCompliance = Update Manager Compliance + Entity = Entity + Status = Compliance Status + Version = Version + BaselineInfo = Baseline + VUMPrivilegeMsgBaselines = Insufficient user privileges to report Cluster baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + VUMPrivilegeMsgCompliance = Insufficient user privileges to report Cluster compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + VUMBaselineNotAvailable = Cluster VUM baseline information is not currently available with your version of PowerShell. + VUMComplianceNotAvailable = Cluster VUM compliance information is not currently available with your version of PowerShell. + Enabled = Enabled + Disabled = Disabled + Yes = Yes + No = No + FullyAutomated = Fully Automated + PartiallyAutomated = Partially Automated + Manual = Manual + Off = Off + NotCompliant = Not Compliant + Unknown = Unknown + Incompatible = Incompatible + DRS = vSphere DRS + DPM = DPM + Automated = Automated + None = None + VMGroupType = VM Group + VMHostGroupType = Host Group + MustRunOn = Must run on hosts in group + ShouldRunOn = Should run on hosts in group + MustNotRunOn = Must not run on hosts in group + ShouldNotRunOn = Should not run on hosts in group + VMAffinity = Keep Virtual Machines Together + VMAntiAffinity = Separate Virtual Machines + Mandatory = Mandatory + OverCommitmentRatio = Over-Commitment Ratio + OverCommitmentRatioCluster = Over-Commitment Ratio (% of cluster capacity) + Lowest = Lowest + Low = Low + Medium = Medium + High = High + Highest = Highest + ClusterDefault = Cluster default + VMDependencyTimeout = VM Dependency Restart Condition Timeout + Seconds = {0} seconds + SectionVSphereHA = vSphere HA + IssueEvents = Issue events + PowerOffAndRestart = Power off and restart VMs + ShutdownAndRestartVMs = Shutdown and restart VMs + PowerOffRestartConservative = Power off and restart VMs - Conservative restart policy + PowerOffRestartAggressive = Power off and restart VMs - Aggressive restart policy + PDLFailureResponse = PDL Failure Response + APDFailureResponse = APD Failure Response + VMFailoverDelay = VM Failover Delay + Minutes = {0} minutes + ResponseRecovery = Response Recovery + ResetVMs = Reset VMs + SectionPDLAPD = PDL/APD Protection Settings + NoWindow = No window + WithinHours = Within {0} hrs + VMMonitoringOnly = VM Monitoring Only + VMAndAppMonitoring = VM and Application Monitoring + UserGroup = User/Group + IsGroup = Is Group? + Role = Role + DefinedIn = Defined In + Propagate = Propagate + Permissions = Permissions + ParagraphPermissions = The following table details the permissions for {0}. + TableDRSConfig = vSphere DRS Configuration - {0} + TableDRSAdditional = DRS Additional Options - {0} + TableDPM = vSphere DPM - {0} + TableDRSAdvanced = vSphere DRS Advanced Options - {0} + TableDRSGroups = DRS Cluster Groups - {0} + TableDRSVMHostRules = DRS VM/Host Rules - {0} + TableDRSRules = DRS Rules - {0} + TableDRSVMOverrides = DRS VM Overrides - {0} + TableHAVMOverrides = HA VM Overrides - {0} + TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} + TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TablePermissions = Permissions - {0} +'@ + +# Get-AbrVSphereResourcePool +GetAbrVSphereResourcePool = ConvertFrom-StringData @' + InfoLevel = ResourcePool InfoLevel set to {0}. + Collecting = Collecting Resource Pool information. + Processing = Processing Resource Pool '{0}' ({1}/{2}). + SectionHeading = Resource Pools + ParagraphSummary = The following sections detail the configuration of resource pools managed by vCenter Server {0}. + ResourcePool = Resource Pool + Parent = Parent + CPUSharesLevel = CPU Shares Level + CPUReservationMHz = CPU Reservation MHz + CPULimitMHz = CPU Limit MHz + MemSharesLevel = Memory Shares Level + MemReservation = Memory Reservation + MemLimit = Memory Limit + ID = ID + NumCPUShares = Number of CPU Shares + CPUReservation = CPU Reservation + CPUExpandable = CPU Expandable Reservation + NumMemShares = Number of Memory Shares + MemExpandable = Memory Expandable Reservation + NumVMs = Number of VMs + VirtualMachines = Virtual Machines + Enabled = Enabled + Disabled = Disabled + Unlimited = Unlimited + TableResourcePoolSummary = Resource Pool Summary - {0} + TableResourcePoolConfig = Resource Pool Configuration - {0} +'@ + +# Get-AbrVSphereVMHost +GetAbrVSphereVMHost = ConvertFrom-StringData @' + InfoLevel = VMHost InfoLevel set to {0}. + Collecting = Collecting VMHost information. + Processing = Processing VMHost '{0}' ({1}/{2}). + SectionHeading = Hosts + ParagraphSummary = The following sections detail the configuration of VMware ESXi hosts managed by vCenter Server {0}. + VMHost = Host + Manufacturer = Manufacturer + Model = Model + ProcessorType = Processor Type + NumCPUSockets = CPU Sockets + NumCPUCores = CPU Cores + NumCPUThreads = CPU Threads + MemoryGB = Memory GB + NumNICs = # NICs + NumHBAs = # HBAs + NumDatastores = # Datastores + NumVMs = # VMs + ConnectionState = Connection State + PowerState = Power State + ErrorCollecting = Error collecting VMHost information using VMHost InfoLevel {0}. + NotResponding = Not Responding + Maintenance = Maintenance + Disconnected = Disconnected + Version = Version + Build = Build + Parent = Parent + TableHostSummary = Host Summary - {0} +'@ + +# Get-AbrVSphereVMHostHardware +GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' + InfoLevel = VMHost Hardware InfoLevel set to {0}. + Collecting = Collecting VMHost Hardware information. + SectionHeading = Hardware + ParagraphSummary = The following section details the host hardware configuration for {0}. + InsufficientPrivLicense = Insufficient user privileges to report ESXi host licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned. + Specifications = Specifications + Manufacturer = Manufacturer + Model = Model + SerialNumber = Serial Number + AssetTag = Asset Tag + ProcessorType = Processor Type + CPUSockets = CPU Sockets + CPUCores = CPU Cores + CPUThreads = CPU Threads + HyperthreadingActive = Hyperthreading Active + MemoryGB = Memory GB + NUMANodes = NUMA Nodes + NICs = NICs + HBAs = HBAs + Version = Version + Build = Build + LicenseKey = License Key + Product = Product + LicenseExpiration = License Expiration + IPMIBMC = IPMI / BMC + IPMIBMCError = Error collecting IPMI/BMC information for {0}. + IPMIBMCManufacturer = Manufacturer + IPMIBMCType = Type + IPMIBMCIPAddress = IP Address + IPMIBMCMACAddress = MAC Address + BootDevice = Boot Device + BootDeviceError = Error collecting boot device information for {0}. + BootDeviceDescription = Description + BootDeviceType = Type + BootDeviceSize = Size (GB) + BootDeviceIsSAS = Is SAS + BootDeviceIsUSB = Is USB + BootDeviceIsSSD = Is SSD + BootDeviceFromSAN = From SAN + PCIDevices = PCI Devices + PCIDeviceId = PCI ID + PCIVendorName = Vendor Name + PCIDeviceName = Device Name + PCIDriverName = Driver Name + PCIDriverVersion = Driver Version + PCIFirmware = Firmware Version + PCIDriversFirmware = PCI Devices Drivers & Firmware + Enabled = Enabled + Disabled = Disabled + NotApplicable = Not applicable + Unknown = Unknown + Maintenance = Maintenance + NotResponding = Not Responding + Host = Host + ConnectionState = Connection State + ID = ID + Parent = Parent + HyperThreading = HyperThreading + NumberOfCPUSockets = Number of CPU Sockets + NumberOfCPUCores = Number of CPU Cores + NumberOfCPUThreads = Number of CPU Threads + CPUTotalUsedFree = CPU Total / Used / Free + MemoryTotalUsedFree = Memory Total / Used / Free + NumberOfNICs = Number of NICs + NumberOfHBAs = Number of HBAs + NumberOfDatastores = Number of Datastores + NumberOfVMs = Number of VMs + MaximumEVCMode = Maximum EVC Mode + EVCGraphicsMode = EVC Graphics Mode + PowerManagementPolicy = Power Management Policy + ScratchLocation = Scratch Location + BiosVersion = BIOS Version + BiosReleaseDate = BIOS Release Date + BootTime = Boot Time + UptimeDays = Uptime Days + MACAddress = MAC Address + SubnetMask = Subnet Mask + Gateway = Gateway + FirmwareVersion = Firmware Version + Device = Device + BootType = Boot Type + Vendor = Vendor + Size = Size + PCIAddress = PCI Address + DeviceClass = Device Class + TableHardwareConfig = Hardware Configuration - {0} + TableIPMIBMC = IPMI / BMC - {0} + TableBootDevice = Boot Device - {0} + TablePCIDevices = PCI Devices - {0} + TablePCIDriversFirmware = PCI Devices Drivers & Firmware - {0} + PCIDeviceError = Error collecting PCI device information for {0}. {1} + PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} + HardwareError = Error collecting host hardware information for {0}. {1} +'@ + +# Get-AbrVSphereVMHostSystem +GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' + InfoLevel = VMHost System InfoLevel set to {0}. + Collecting = Collecting VMHost System information. + HostProfile = Host Profile + ProfileName = Host Profile + ProfileCompliance = Compliance + ProfileRemediating = Remediating + ImageProfile = Image Profile + ImageProfileName = Name + ImageProfileVendor = Vendor + ImageProfileAcceptance = Acceptance Level + TimeConfiguration = Time Configuration + NTPServer = NTP Server + TimeZone = Time Zone + Syslog = Syslog + SyslogHost = Syslog Host + VUMBaseline = Update Manager Baselines + VUMBaselineNotAvailable = VMHost VUM baseline information is not currently available with your version of PowerShell. + VUMBaselineName = Baseline + VUMBaselineType = Type + VUMBaselinePatches = # of Patches + VUMCompliance = Update Manager Compliance + VUMComplianceNotAvailable = VMHost VUM compliance information is not currently available with your version of PowerShell. + VUMStatus = Status + AdvancedSettings = Advanced System Settings + Key = Key + Value = Value + VIBs = VMware Installation Bundles (VIBs) + VIBName = Name + VIBVersion = Version + VIBVendor = Vendor + VIBInstallDate = Install Date + VIBAcceptanceLevel = Acceptance Level + SectionHeading = System + ParagraphSummary = The following section details the host system configuration for {0}. + NTPService = NTP Service + NTPServers = NTP Server(s) + SyslogPort = Port + VUMDescription = Description + VUMTargetType = Target Type + VUMLastUpdate = Last Update Time + InstallDate = Installation Date + ImageName = Image Profile + ImageVendor = Vendor + Running = Running + Stopped = Stopped + NotCompliant = Not Compliant + Unknown = Unknown + Incompatible = Incompatible + ProfileDescription = Description + VIBID = ID + VIBCreationDate = Creation Date + TableHostProfile = Host Profile - {0} + TableImageProfile = Image Profile - {0} + TableTimeConfig = Time Configuration - {0} + TableSyslog = Syslog Configuration - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TableAdvancedSettings = Advanced System Settings - {0} + TableVIBs = Software VIBs - {0} + HostProfileError = Error collecting host profile information for {0}. {1} + ImageProfileError = Error collecting image profile information for {0}. {1} + TimeConfigError = Error collecting time configuration for {0}. {1} + SyslogError = Error collecting syslog configuration for {0}. {1} + VUMBaselineError = Error collecting Update Manager baseline information for {0}. {1} + VUMComplianceError = Error collecting Update Manager compliance information for {0}. {1} + AdvancedSettingsError = Error collecting host advanced settings information for {0}. {1} + VIBsError = Error collecting software VIB information for {0}. {1} + InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. + InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. +'@ + +# Get-AbrVSphereVMHostStorage +GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' + InfoLevel = VMHost Storage InfoLevel set to {0}. + Collecting = Collecting VMHost Storage information. + SectionHeading = Storage + DatastoreSpecs = Datastore Specifications + Datastore = Datastore + DatastoreType = Type + DatastoreCapacity = Capacity (GB) + DatastoreFreeSpace = Free Space (GB) + DatastoreState = State + DatastoreShared = Shared + StorageAdapters = Storage Adapters + AdapterName = Adapter + AdapterType = Type + AdapterDriver = Driver + AdapterUID = UID + AdapterModel = Model + AdapterStatus = Status + AdapterSASAddress = SAS Address + AdapterWWN = WWN + AdapterDevices = Devices + AdapterTargets = Targets + ParagraphSummary = The following section details the host storage configuration for {0}. + FC = Fibre Channel + iSCSI = iSCSI + ParallelSCSI = Parallel SCSI + ChapNone = None + ChapUniPreferred = Use unidirectional CHAP unless prohibited by target + ChapUniRequired = Use unidirectional CHAP if required by target + ChapUnidirectional = Use unidirectional CHAP + ChapBidirectional = Use bidirectional CHAP + Online = Online + Offline = Offline + Type = Type + Version = Version + NumberOfVMs = # of VMs + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + TableDatastores = Datastores - {0} + ParagraphStorageAdapters = The following section details the storage adapter configuration for {0}. + AdapterPaths = Paths + iSCSIName = iSCSI Name + iSCSIAlias = iSCSI Alias + AdapterSpeed = Speed + DynamicDiscovery = Dynamic Discovery + StaticDiscovery = Static Discovery + AuthMethod = Authentication Method + CHAPOutgoing = Outgoing CHAP Name + CHAPIncoming = Incoming CHAP Name + AdvancedOptions = Advanced Options + NodeWWN = Node WWN + PortWWN = Port WWN + TableStorageAdapter = Storage Adapter {0} - {1} +'@ + +# Get-AbrVSphereVMHostNetwork +GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' + InfoLevel = VMHost Network InfoLevel set to {0}. + Collecting = Collecting VMHost Network information. + SectionHeading = Network + NetworkConfig = Network Configuration + VMKernelAdapters = VMkernel Adapters + PhysicalAdapters = Physical Adapters + CDP = Cisco Discovery Protocol + LLDP = Link Layer Discovery Protocol + StandardvSwitches = Standard Virtual Switches + AdapterName = Adapter + AdapterMAC = MAC Address + AdapterLinkSpeed = Link Speed + AdapterDriver = Driver + AdapterPCIID = PCI Device + AdapterStatus = Status + VMKName = VMkernel Adapter + VMKIP = IP Address + VMKSubnet = Subnet Mask + VMKGateway = Default Gateway + VMKMTU = MTU + VMKPortGroup = Port Group + VMKVirtualSwitch = Virtual Switch + VMKDHCPEnabled = DHCP Enabled + VMKServicesEnabled = Enabled Services + vSwitch = Virtual Switch + vSwitchPorts = Total Ports + vSwitchUsedPorts = Used Ports + vSwitchAvailablePorts = Available Ports + vSwitchMTU = MTU + vSwitchCDPStatus = CDP Status + vSwitchSecurity = Security Policy + vSwitchFailover = Failover Policy + vSwitchLoadBalancing = Load Balancing + vSwitchPortGroups = Port Groups + PortGroup = Port Group + VLAN = VLAN ID + ActiveAdapters = Active Adapters + StandbyAdapters = Standby Adapters + UnusedAdapters = Unused Adapters + ParagraphSummary = The following section details the host network configuration for {0}. + Enabled = Enabled + Disabled = Disabled + Connected = Connected + Disconnected = Disconnected + Yes = Yes + No = No + Accept = Accept + Reject = Reject + Supported = Supported + NotSupported = Not Supported + Inherited = Inherited + TCPDefault = Default + TCPProvisioning = Provisioning + TCPvMotion = vMotion + TCPNsxOverlay = nsx-overlay + TCPNsxHyperbus = nsx-hyperbus + TCPNotApplicable = Not Applicable + LBSrcId = Route based on the originating port ID + LBSrcMac = Route based on source MAC hash + LBIP = Route based on IP hash + LBExplicitFailover = Explicit Failover + NFDLinkStatus = Link status only + NFDBeaconProbing = Beacon probing + FullDuplex = Full Duplex + AutoNegotiate = Auto negotiate + Down = Down + Host = Host + VirtualSwitches = Virtual Switches + VMkernelGateway = VMkernel Gateway + IPv6 = IPv6 + VMkernelIPv6Gateway = VMkernel IPv6 Gateway + DNSServers = DNS Servers + HostName = Host Name + DomainName = Domain Name + SearchDomain = Search Domain + TableNetworkConfig = Network Configuration - {0} + ParagraphPhysicalAdapters = The following section details the physical network adapter configuration for {0}. + VirtualSwitch = Virtual Switch + ActualSpeedDuplex = Actual Speed, Duplex + ConfiguredSpeedDuplex = Configured Speed, Duplex + WakeOnLAN = Wake on LAN + TablePhysicalAdapter = Physical Adapter {0} - {1} + TablePhysicalAdapters = Physical Adapters - {0} + ParagraphCDP = The following section details the CDP information for {0}. + Status = Status + SystemName = System Name + HardwarePlatform = Hardware Platform + SwitchID = Switch ID + SoftwareVersion = Software Version + ManagementAddress = Management Address + Address = Address + PortID = Port ID + MTU = MTU + TableAdapterCDP = Network Adapter {0} CDP Information - {1} + TableAdaptersCDP = Network Adapter CDP Information - {0} + ParagraphLLDP = The following section details the LLDP information for {0}. + ChassisID = Chassis ID + TimeToLive = Time to live + TimeOut = TimeOut + Samples = Samples + PortDescription = Port Description + SystemDescription = System Description + TableAdapterLLDP = Network Adapter {0} LLDP Information - {1} + TableAdaptersLLDP = Network Adapter LLDP Information - {0} + ParagraphVMKernelAdapters = The following section details the VMkernel adapter configuration for {0}. + NetworkLabel = Network Label + TCPIPStack = TCP/IP Stack + DHCP = DHCP + IPAddress = IP Address + SubnetMask = Subnet Mask + DefaultGateway = Default Gateway + vMotion = vMotion + Provisioning = Provisioning + FTLogging = FT Logging + Management = Management + vSphereReplication = vSphere Replication + vSphereReplicationNFC = vSphere Replication NFC + vSAN = vSAN + vSANWitness = vSAN Witness + vSphereBackupNFC = vSphere Backup NFC + TableVMKernelAdapter = VMkernel Adapter {0} - {1} + ParagraphStandardvSwitches = The following section details the standard virtual switch configuration for {0}. + NumberOfPorts = Number of Ports + NumberOfPortsAvailable = Number of Ports Available + TableStandardvSwitches = Standard Virtual Switches - {0} + VSSecurity = Virtual Switch Security + PromiscuousMode = Promiscuous Mode + MACAddressChanges = MAC Address Changes + ForgedTransmits = Forged Transmits + TableVSSecurity = Virtual Switch Security Policy - {0} + VSTrafficShaping = Virtual Switch Traffic Shaping + AverageBandwidth = Average Bandwidth (kbit/s) + PeakBandwidth = Peak Bandwidth (kbit/s) + BurstSize = Burst Size (KB) + TableVSTrafficShaping = Virtual Switch Traffic Shaping Policy - {0} + VSTeamingFailover = Virtual Switch Teaming & Failover + LoadBalancing = Load Balancing + NetworkFailureDetection = Network Failure Detection + NotifySwitches = Notify Switches + Failback = Failback + ActiveNICs = Active NICs + StandbyNICs = Standby NICs + UnusedNICs = Unused NICs + TableVSTeamingFailover = Virtual Switch Teaming & Failover - {0} + VSPortGroups = Virtual Switch Port Groups + NumberOfVMs = # of VMs + TableVSPortGroups = Virtual Switch Port Groups - {0} + VSPGSecurity = Virtual Switch Port Group Security + MACChanges = MAC Changes + TableVSPGSecurity = Virtual Switch Port Group Security Policy - {0} + VSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping + TableVSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping Policy - {0} + VSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover + TableVSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover - {0} +'@ + +# Get-AbrVSphereVMHostSecurity +GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' + InfoLevel = VMHost Security InfoLevel set to {0}. + Collecting = Collecting VMHost Security information. + SectionHeading = Security + LockdownMode = Lockdown Mode + Services = Services + ServiceName = Service + ServiceRunning = Running + ServicePolicy = Startup Policy + ServiceRequired = Required + Firewall = Firewall + FirewallRule = Rule + FirewallAllowed = Allowed Hosts + Authentication = Authentication + Domain = Domain + Trust = Trusted Domains + Membership = Domain Membership + VMInfoSection = Virtual Machines + VMName = Virtual Machine + VMPowerState = Power State + VMIPAddress = IP Address + VMOS = OS + VMGuestID = Guest ID + VMCPUs = CPUs + VMMemoryGB = Memory GB + VMDisks = Disks (GB) + VMNICs = NICs + StartupShutdown = VM Startup/Shutdown + StartupEnabled = Startup Enabled + StartupOrder = Startup Order + StartupDelay = Startup Delay + StopAction = Stop Action + StopDelay = Stop Delay + WaitForHeartbeat = Wait For Heartbeat + ParagraphSummary = The following section details the host security configuration for {0}. + LockdownDisabled = Disabled + LockdownNormal = Enabled (Normal) + LockdownStrict = Enabled (Strict) + Running = Running + Stopped = Stopped + SvcPortUsage = Start and stop with port usage + SvcWithHost = Start and stop with host + SvcManually = Start and stop manually + On = On + Off = Off + Enabled = Enabled + Disabled = Disabled + WaitHeartbeat = Continue if VMware Tools is started + WaitDelay = Wait for startup delay + PowerOff = Power Off + GuestShutdown = Guest Shutdown + ToolsOld = Old + ToolsOK = OK + ToolsNotRunning = Not Running + ToolsNotInstalled = Not Installed + TableLockdownMode = Lockdown Mode - {0} + TableServices = Services - {0} + FirewallService = Service + Status = Status + IncomingPorts = Incoming Ports + OutgoingPorts = Outgoing Ports + Protocols = Protocols + Daemon = Daemon + TableFirewall = Firewall Configuration - {0} + DomainMembership = Domain Membership + TrustedDomains = Trusted Domains + TableAuthentication = Authentication Services - {0} + ParagraphVMInfo = The following section details the virtual machine configuration for {0}. + VMProvisioned = Provisioned + VMUsed = Used + VMHWVersion = HW Version + VMToolsStatus = VM Tools Status + TableVMs = Virtual Machines - {0} + TableStartupShutdown = VM Startup/Shutdown Policy - {0} +'@ + +# Get-AbrVSphereNetwork +GetAbrVSphereNetwork = ConvertFrom-StringData @' + InfoLevel = Network InfoLevel set to {0}. + Collecting = Collecting Distributed Switch information. + Processing = Processing Distributed Switch '{0}' ({1}/{2}). + SectionHeading = Distributed Switches + ParagraphSummary = The following sections detail the configuration of distributed switches managed by vCenter Server {0}. + VDSwitch = Distributed Switch + Datacenter = Datacenter + Manufacturer = Manufacturer + NumUplinks = # Uplinks + NumPorts = # Ports + NumHosts = # Hosts + NumVMs = # VMs + Version = Version + MTU = MTU + ID = ID + NumberOfUplinks = Number of Uplinks + NumberOfPorts = Number of Ports + NumberOfPortGroups = Number of Port Groups + NumberOfHosts = Number of Hosts + NumberOfVMs = Number of VMs + Hosts = Hosts + VirtualMachines = Virtual Machines + NIOC = Network I/O Control + DiscoveryProtocol = Discovery Protocol + DiscoveryOperation = Discovery Operation + NumPortGroups = # Port Groups + MaxPorts = Max Ports + Contact = Contact + Location = Location + VDPortGroups = Distributed Port Groups + PortGroup = Port Group + PortGroupType = Port Group Type + VLANID = VLAN ID + VLANConfiguration = VLAN Configuration + PortBinding = Port Binding + Host = Host + UplinkName = Uplink Name + UplinkPortGroup = Uplink Port Group + PhysicalNetworkAdapter = Physical Network Adapter + ActiveUplinks = Active Uplinks + StandbyUplinks = Standby Uplinks + UnusedUplinks = Unused Uplinks + AllowPromiscuous = Allow Promiscuous + ForgedTransmits = Forged Transmits + MACAddressChanges = MAC Address Changes + Accept = Accept + Reject = Reject + Direction = Direction + Status = Status + AverageBandwidth = Average Bandwidth (kbit/s) + PeakBandwidth = Peak Bandwidth (kbit/s) + BurstSize = Burst Size (KB) + LoadBalancing = Load Balancing + LoadBalanceSrcId = Route based on the originating port ID + LoadBalanceSrcMac = Route based on source MAC hash + LoadBalanceIP = Route based on IP hash + ExplicitFailover = Explicit Failover + NetworkFailureDetection = Network Failure Detection + LinkStatus = Link status only + BeaconProbing = Beacon probing + NotifySwitches = Notify Switches + FailbackEnabled = Failback Enabled + Yes = Yes + No = No + PrimaryVLANID = Primary VLAN ID + PrivateVLANType = Private VLAN Type + SecondaryVLANID = Secondary VLAN ID + UplinkPorts = Distributed Switch Uplink Ports + VDSSecurity = Distributed Switch Security + VDSTrafficShaping = Distributed Switch Traffic Shaping + VDSPortGroups = Distributed Switch Port Groups + VDSPortGroupSecurity = Distributed Switch Port Group Security + VDSPortGroupTrafficShaping = Distributed Switch Port Group Traffic Shaping + VDSPortGroupTeaming = Distributed Switch Port Group Teaming & Failover + VDSPrivateVLANs = Distributed Switch Private VLANs + Enabled = Enabled + Disabled = Disabled + Listen = Listen + Advertise = Advertise + Both = Both + TableVDSSummary = Distributed Switch Summary - {0} + TableVDSGeneral = Distributed Switch General Properties - {0} + TableVDSUplinkPorts = Distributed Switch Uplink Ports - {0} + TableVDSSecurity = Distributed Switch Security - {0} + TableVDSTrafficShaping = Distributed Switch Traffic Shaping - {0} + TableVDSPortGroups = Distributed Switch Port Groups - {0} + TableVDSPortGroupSecurity = Distributed Switch Port Group Security - {0} + TableVDSPortGroupTrafficShaping = Distributed Switch Port Group Traffic Shaping - {0} + TableVDSPortGroupTeaming = Distributed Switch Port Group Teaming & Failover - {0} + TableVDSPrivateVLANs = Distributed Switch Private VLANs - {0} +'@ + +# Get-AbrVSpherevSAN +GetAbrVSpherevSAN = ConvertFrom-StringData @' + InfoLevel = vSAN InfoLevel set to {0}. + Collecting = Collecting vSAN information. + CollectingESA = Collecting vSAN ESA information for '{0}'. + CollectingOSA = Collecting vSAN OSA information for '{0}'. + CollectingDisks = Collecting vSAN disk information for '{0}'. + CollectingDiskGroups = Collecting vSAN disk group information for '{0}'. + CollectingiSCSITargets = Collecting vSAN iSCSI target information for '{0}'. + CollectingiSCSILUNs = Collecting vSAN iSCSI LUN information for '{0}'. + Processing = Processing vSAN Cluster '{0}' ({1}/{2}). + SectionHeading = vSAN + ParagraphSummary = The following sections detail the configuration of VMware vSAN managed by vCenter Server {0}. + ParagraphDetail = The following table details the vSAN configuration for cluster {0}. + DisksSection = Disks + DiskGroupsSection = Disk Groups + iSCSITargetsSection = iSCSI Targets + iSCSILUNsSection = iSCSI LUNs + Cluster = Cluster + StorageType = Storage Type + ClusterType = Cluster Type + NumHosts = # of Hosts + ID = ID + NumberOfHosts = Number of Hosts + NumberOfDisks = Number of Disks + NumberOfDiskGroups = Number of Disk Groups + DiskClaimMode = Disk Claim Mode + PerformanceService = Performance Service + FileService = File Service + iSCSITargetService = iSCSI Target Service + HistoricalHealthService = Historical Health Service + HealthCheck = Health Check + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + HCLLastUpdated = HCL Last Updated + Hosts = Hosts + Version = Version + Stretched = Stretched Cluster + VSANEnabled = vSAN Enabled + VSANESAEnabled = vSAN ESA Enabled + DisksFormat = Disk Format Version + Deduplication = Deduplication and Compression + AllFlash = All Flash + FaultDomains = Fault Domains + PFTT = Primary Failures to Tolerate + SFTT = Secondary Failures to Tolerate + NetworkDiagnosticMode = Network Diagnostic Mode + HybridMode = Hybrid Mode + AutoRebalance = Automatic Rebalance + ProactiveDisk = Proactive Disk Rebalance + ResyncThrottle = Resync Throttle + SpaceEfficiency = Space Efficiency + Encryption = Encryption + FileServiceEnabled = File Service Enabled + iSCSITargetEnabled = iSCSI Target Enabled + VMHostSpecs = VMHost vSAN Specifications + VMHost = VMHost + DiskGroup = Disk Group + CacheDisks = Cache Disks + DataDisks = Data Disks + DiskGroups = Disk Groups + CacheTier = Cache Tier + DataTier = Data Tier + Status = Status + FaultDomainName = Fault Domain + VSANAdvancedOptions = vSAN Advanced Configuration Options + Key = Key + Value = Value + IDs = Identifiers + VSAN_UUID = vSAN UUID + CapacityTier = Capacity Tier (GB) + BufferTier = Buffer Tier (GB) + DiskName = Disk + Name = Name + DriveType = Drive Type + Host = Host + State = State + Encrypted = Encrypted + Capacity = Capacity + SerialNumber = Serial Number + Vendor = Vendor + Model = Model + DiskType = Disk Type + DiskFormatVersion = Disk Format Version + ClaimedAs = Claimed As + NumDisks = # of Disks + Type = Type + IQN = IQN + Alias = Alias + LUNsCount = LUNs + NetworkInterface = Network Interface + IOOwnerHost = I/O Owner Host + TCPPort = TCP Port + Health = Health + StoragePolicy = Storage Policy + ComplianceStatus = Compliance Status + Authentication = Authentication + LUNName = LUN + LUNID = LUN ID + Yes = Yes + No = No + Enabled = Enabled + Disabled = Disabled + Online = Online + Offline = Offline + Mounted = Mounted + Unmounted = Unmounted + Flash = Flash + HDD = HDD + Cache = Cache + TableVSANClusterSummary = vSAN Cluster Summary - {0} + TableVSANConfiguration = vSAN Configuration - {0} + TableDisk = Disk {0} - {1} + TableVSANDisks = vSAN Disks - {0} + TableVSANDiskGroups = vSAN Disk Groups - {0} + TableVSANiSCSITargets = vSAN iSCSI Targets - {0} + TableVSANiSCSILUNs = vSAN iSCSI LUNs - {0} + ESAError = Error collecting vSAN ESA information for '{0}'. {1} + OSAError = Error collecting vSAN OSA information for '{0}'. {1} + DiskError = Error collecting vSAN disk information for '{0}'. {1} + DiskGroupError = Error collecting vSAN disk group information for '{0}'. {1} + iSCSITargetError = Error collecting vSAN iSCSI target information for '{0}'. {1} + iSCSILUNError = Error collecting vSAN iSCSI LUN information for '{0}'. {1} +'@ + +# Get-AbrVSphereDatastore +GetAbrVSphereDatastore = ConvertFrom-StringData @' + InfoLevel = Datastore InfoLevel set to {0}. + Collecting = Collecting Datastore information. + Processing = Processing Datastore '{0}' ({1}/{2}). + SectionHeading = Datastores + ParagraphSummary = The following sections detail the configuration of datastores managed by vCenter Server {0}. + Datastore = Datastore + Type = Type + DatastoreURL = URL + FileSystem = File System Version + CapacityGB = Total Capacity (GB) + FreeSpaceGB = Free Space (GB) + UsedSpaceGB = Used Space (GB) + State = State + Accessible = Accessible + NumHosts = # Hosts + NumVMs = # VMs + SIOC = Storage I/O Control + IOLatencyThreshold = I/O Latency Threshold (ms) + IOLoadBalancing = I/O Load Balancing + Enabled = Enabled + Disabled = Disabled + Normal = Normal + Maintenance = Maintenance + Unmounted = Unmounted + ID = ID + Datacenter = Datacenter + Version = Version + NumberOfHosts = Number of Hosts + NumberOfVMs = Number of VMs + CongestionThreshold = Congestion Threshold + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + Hosts = Hosts + VirtualMachines = Virtual Machines + SCSILUNInfo = SCSI LUN Information + Host = Host + CanonicalName = Canonical Name + Capacity = Capacity + Vendor = Vendor + Model = Model + IsSSD = Is SSD + MultipathPolicy = Multipath Policy + Paths = Paths + TableDatastoreSummary = Datastore Summary - {0} + TableDatastoreConfig = Datastore Configuration - {0} + TableSCSILUN = SCSI LUN Information - {0} +'@ + +# Get-AbrVSphereDSCluster +GetAbrVSphereDSCluster = ConvertFrom-StringData @' + InfoLevel = DSCluster InfoLevel set to {0}. + Collecting = Collecting Datastore Cluster information. + Processing = Processing Datastore Cluster '{0}' ({1}/{2}). + SectionHeading = Datastore Clusters + ParagraphSummary = The following sections detail the configuration of datastore clusters managed by vCenter Server {0}. + DSCluster = Datastore Cluster + Datacenter = Datacenter + CapacityGB = Total Capacity (GB) + FreeSpaceGB = Free Space (GB) + SDRSEnabled = SDRS + SDRSAutomationLevel = SDRS Automation Level + IOLoadBalancing = I/O Load Balancing + SpaceThreshold = Space Threshold (%) + IOLatencyThreshold = I/O Latency Threshold (ms) + SDRSRules = SDRS Rules + RuleName = Rule + RuleEnabled = Enabled + RuleVMs = Virtual Machines + FullyAutomated = Fully Automated + NoAutomation = No Automation (Manual Mode) + Manual = Manual + Enabled = Enabled + Disabled = Disabled + Yes = Yes + No = No + ID = ID + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + ParagraphDSClusterDetail = The following table details the configuration for datastore cluster {0}. + TableDSClusterConfig = Datastore Cluster Configuration - {0} + TableSDRSVMOverrides = SDRS VM Overrides - {0} + SDRSVMOverrides = SDRS VM Overrides + VirtualMachine = Virtual Machine + KeepVMDKsTogether = Keep VMDKs Together + DefaultBehavior = Default ({0}) +'@ + +# Get-AbrVSphereVM +GetAbrVSphereVM = ConvertFrom-StringData @' + InfoLevel = VM InfoLevel set to {0}. + Collecting = Collecting Virtual Machine information. + Processing = Processing Virtual Machine '{0}' ({1}/{2}). + SectionHeading = Virtual Machines + ParagraphSummary = The following sections detail the configuration of virtual machines managed by vCenter Server {0}. + VirtualMachine = Virtual Machine + PowerState = Power State + Template = Template + OS = OS + Version = VM Hardware Version + GuestID = Guest ID + Cluster = Cluster + ResourcePool = Resource Pool + VMHost = Host + Folder = Folder + IPAddress = IP Address + CPUs = CPUs + MemoryGB = Memory GB + ProvisionedGB = Provisioned (GB) + UsedGB = Used (GB) + NumDisks = # Disks + NumNICs = # NICs + NumSnapshots = # Snapshots + VMwareTools = VMware Tools + ToolsVersion = Tools Version + ToolsStatus = Tools Status + ToolsRunningStatus = Tools Running Status + VMAdvancedDetail = Advanced Configuration + BootOptions = Boot Options + BootDelay = Boot Delay (ms) + BootRetryEnabled = Boot Retry Enabled + BootRetryDelay = Boot Retry Delay (ms) + EFISecureBoot = EFI Secure Boot + EnterBIOSSetup = Enter BIOS Setup on Next Boot + HardDisks = Hard Disks + DiskName = Disk + DiskCapacityGB = Capacity (GB) + DiskFormat = Format + DiskStoragePolicy = Storage Policy + DiskDatastore = Datastore + DiskController = Controller + NetworkAdapters = Network Adapters + NICName = Network Adapter + NICType = Adapter Type + NICPortGroup = Port Group + NICMAC = MAC Address + NICConnected = Connected + NICConnectionPolicy = Connect at Power On + SnapshotHeading = Snapshots + SnapshotName = Snapshot + SnapshotDescription = Description + SnapshotSize = Size (GB) + SnapshotDate = Created + SnapshotParent = Parent + On = On + Off = Off + ToolsOld = Old + ToolsOK = OK + ToolsNotRunning = Not Running + ToolsNotInstalled = Not Installed + Yes = Yes + No = No + Connected = Connected + NotConnected = Not Connected + Thin = Thin + Thick = Thick + Enabled = Enabled + Disabled = Disabled + TotalVMs = Total VMs + TotalvCPUs = Total vCPUs + TotalMemory = Total Memory + TotalProvisionedSpace = Total Provisioned Space + TotalUsedSpace = Total Used Space + VMsPoweredOn = VMs Powered On + VMsPoweredOff = VMs Powered Off + VMsOrphaned = VMs Orphaned + VMsInaccessible = VMs Inaccessible + VMsSuspended = VMs Suspended + VMsWithSnapshots = VMs with Snapshots + GuestOSTypes = Guest Operating System Types + VMToolsOKCount = VM Tools OK + VMToolsOldCount = VM Tools Old + VMToolsNotRunningCount = VM Tools Not Running + VMToolsNotInstalledCount = VM Tools Not Installed + vCPUs = vCPUs + Memory = Memory + Provisioned = Provisioned + Used = Used + HWVersion = HW Version + VMToolsStatus = VM Tools Status + ID = ID + OperatingSystem = Operating System + HardwareVersion = Hardware Version + ConnectionState = Connection State + FaultToleranceState = Fault Tolerance State + FTNotConfigured = Not Configured + FTNeedsSecondary = Needs Secondary + FTRunning = Running + FTDisabled = Disabled + FTStarting = Starting + FTEnabled = Enabled + Parent = Parent + ParentFolder = Parent Folder + ParentResourcePool = Parent Resource Pool + CoresPerSocket = Cores per Socket + CPUShares = CPU Shares + CPUReservation = CPU Reservation + CPULimit = CPU Limit + CPUHotAdd = CPU Hot Add + CPUHotRemove = CPU Hot Remove + MemoryAllocation = Memory Allocation + MemoryShares = Memory Shares + MemoryHotAdd = Memory Hot Add + vNICs = vNICs + DNSName = DNS Name + Networks = Networks + MACAddress = MAC Address + vDisks = vDisks + ProvisionedSpace = Provisioned Space + UsedSpace = Used Space + ChangedBlockTracking = Changed Block Tracking + StorageBasedPolicy = Storage Based Policy + StorageBasedPolicyCompliance = Storage Based Policy Compliance + Compliant = Compliant + NonCompliant = Non Compliant + Unknown = Unknown + Notes = Notes + BootTime = Boot Time + UptimeDays = Uptime Days + NetworkName = Network Name + SCSIControllers = SCSI Controllers + Device = Device + ControllerType = Controller Type + BusSharing = Bus Sharing + None = None + GuestVolumes = Guest Volumes + Capacity = Capacity + DiskProvisioning = Disk Provisioning + ThickEagerZeroed = Thick Eager Zeroed + ThickLazyZeroed = Thick Lazy Zeroed + DiskType = Disk Type + PhysicalRDM = Physical RDM + VirtualRDM = Virtual RDM + VMDK = VMDK + DiskMode = Disk Mode + IndependentPersistent = Independent - Persistent + IndependentNonpersistent = Independent - Nonpersistent + Dependent = Dependent + DiskPath = Disk Path + DiskShares = Disk Shares + DiskLimitIOPs = Disk Limit IOPs + Unlimited = Unlimited + SCSIController = SCSI Controller + SCSIAddress = SCSI Address + Path = Path + FreeSpace = Free Space + DaysOld = Days Old + TableVMSummary = VM Summary - {0} + TableVMAdvancedSummary = VM Advanced Summary - {0} + TableVMSnapshotSummary = VM Snapshot Summary - {0} + TableVMConfig = VM Configuration - {0} + TableVMNetworkAdapters = Network Adapters - {0} + TableVMSCSIControllers = SCSI Controllers - {0} + TableVMHardDiskConfig = Hard Disk Configuration - {0} + TableVMHardDisk = {0} - {1} + TableVMGuestVolumes = Guest Volumes - {0} + TableVMSnapshots = VM Snapshots - {0} +'@ + +# Get-AbrVSphereVUM +GetAbrVSphereVUM = ConvertFrom-StringData @' + InfoLevel = VUM InfoLevel set to {0}. + Collecting = Collecting VMware Update Manager information. + NotAvailable = VUM patch baseline information is not currently available with your version of PowerShell. + PatchNotAvailable = VUM patch information is not currently available with your version of PowerShell. + SectionHeading = VMware Update Manager + ParagraphSummary = The following sections detail the configuration of VMware Update Manager managed by vCenter Server {0}. + Baselines = Baselines + BaselineName = Baseline + Description = Description + Type = Type + TargetType = Target Type + LastUpdate = Last Update Time + NumPatches = # of Patches + Patches = Patches + PatchName = Patch + PatchProduct = Product + PatchDescription = Description + PatchReleaseDate = Release Date + PatchVendorID = Vendor ID + TableVUMBaselines = VMware Update Manager Baseline Summary - {0} + TableVUMPatches = VMware Update Manager Patch Information - {0} +'@ + +} \ No newline at end of file diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 new file mode 100644 index 0000000..d4264ea --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -0,0 +1,1417 @@ +# culture = 'en-US' +@{ + +# Module-wide strings +InvokeAsBuiltReportVMwarevSphere = ConvertFrom-StringData @' + Connecting = Connecting to vCenter Server '{0}'. + CheckPrivileges = Checking vCenter user privileges. + UnablePrivileges = Unable to obtain vCenter user privileges. + VMHashtable = Creating VM lookup hashtable. + VMHostHashtable = Creating VMHost lookup hashtable. + DatastoreHashtable = Creating Datastore lookup hashtable. + VDPortGrpHashtable = Creating VDPortGroup lookup hashtable. + EVCHashtable = Creating EVC lookup hashtable. + CheckVUM = Checking for VMware Update Manager Server. + CheckVxRail = Checking for VxRail Manager Server. + CheckSRM = Checking for VMware Site Recovery Manager Server. + CheckNSXT = Checking for VMware NSX-T Manager Server. + CollectingTags = Collecting tag information. + TagError = Error collecting tag information. + CollectingAdvSettings = Collecting {0} advanced settings. +'@ + +# Get-AbrVSpherevCenter +GetAbrVSpherevCenter = ConvertFrom-StringData @' + InfoLevel = vCenter InfoLevel set to {0}. + Collecting = Collecting vCenter Server information. + SectionHeading = vCenter Server + ParagraphSummary = The following sections detail the configuration of vCenter Server {0}. + InsufficientPrivLicense = Insufficient user privileges to report vCenter Server licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned. + InsufficientPrivStoragePolicy = Insufficient user privileges to report VM storage policies. Please ensure the user account has the 'Storage Profile > View' privilege assigned. + vCenterServer = vCenter Server + IPAddress = IP Address + Version = Version + Build = Build + Product = Product + LicenseKey = License Key + LicenseExpiration = License Expiration + InstanceID = Instance ID + HTTPPort = HTTP Port + HTTPSPort = HTTPS Port + PSC = Platform Services Controller + UpdateManagerServer = Update Manager Server + SRMServer = Site Recovery Manager Server + NSXTServer = NSX-T Manager Server + VxRailServer = VxRail Manager Server + DatabaseSettings = Database Settings + DatabaseType = Database Type + DataSourceName = Data Source Name + MaxDBConnection = Maximum Database Connection + MailSettings = Mail Settings + SMTPServer = SMTP Server + SMTPPort = SMTP Port + MailSender = Mail Sender + HistoricalStatistics = Historical Statistics + IntervalDuration = Interval Duration + IntervalEnabled = Interval Enabled + SaveDuration = Save Duration + StatisticsLevel = Statistics Level + Licensing = Licensing + Total = Total + Used = Used + Available = Available + Expiration = Expiration + Certificate = Certificate + Country = Country + Email = Email + Locality = Locality + State = State + Organization = Organization + OrganizationUnit = Organization Unit + Validity = Validity + Mode = Mode + SoftThreshold = Soft Threshold + HardThreshold = Hard Threshold + MinutesBefore = Minutes Before + PollInterval = Poll Interval + Roles = Roles + Role = Role + SystemRole = System Role + PrivilegeList = Privilege List + Tags = Tags + TagName = Name + TagCategory = Category + TagDescription = Description + TagCategories = Tag Categories + TagCardinality = Cardinality + TagAssignments = Tag Assignments + TagEntity = Entity + TagEntityType = Entity Type + VMStoragePolicies = VM Storage Policies + StoragePolicy = Storage Policy + Description = Description + ReplicationEnabled = Replication Enabled + CommonRulesName = Common Rules Name + CommonRulesDescription = Common Rules Description + Alarms = Alarms + Alarm = Alarm + AlarmDescription = Description + AlarmEnabled = Enabled + AlarmTriggered = Triggered + AlarmAction = Action + AdvancedSystemSettings = Advanced System Settings + Key = Key + Value = Value + Enabled = Enabled + Disabled = Disabled + Yes = Yes + No = No + None = None + TablevCenterSummary = vCenter Server Summary - {0} + TablevCenterConfig = vCenter Server Configuration - {0} + TableDatabaseSettings = Database Settings - {0} + TableMailSettings = Mail Settings - {0} + TableHistoricalStatistics = Historical Statistics - {0} + TableLicensing = Licensing - {0} + TableCertificate = Certificate - {0} + TableRole = Role {0} - {1} + TableRoles = Roles - {0} + TableTags = Tags - {0} + TableTagCategories = Tag Categories - {0} + TableTagAssignments = Tag Assignments - {0} + TableVMStoragePolicies = VM Storage Policies - {0} + TableAlarm = {0} - {1} + TableAlarms = Alarms - {0} + TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} +'@ + +# Get-AbrVSphereCluster +GetAbrVSphereCluster = ConvertFrom-StringData @' + InfoLevel = Cluster InfoLevel set to {0}. + Collecting = Collecting Cluster information. + Processing = Processing Cluster '{0}' ({1}/{2}). + SectionHeading = Clusters + ParagraphSummary = The following sections detail the configuration of vSphere HA/DRS clusters managed by vCenter Server {0}. + ParagraphDetail = The following table details the configuration for cluster {0}. + Cluster = Cluster + ID = ID + Datacenter = Datacenter + NumHosts = # of Hosts + NumVMs = # of VMs + HAEnabled = vSphere HA + DRSEnabled = vSphere DRS + VSANEnabled = Virtual SAN + EVCMode = EVC Mode + VMSwapFilePolicy = VM Swap File Policy + NumberOfHosts = Number of Hosts + NumberOfVMs = Number of VMs + Hosts = Hosts + VirtualMachines = Virtual Machines + Permissions = Permissions + Principal = Principal + Role = Role + Propagate = Propagate + IsGroup = Is Group + Enabled = Enabled + Disabled = Disabled + SwapWithVM = With VM + SwapInHostDatastore = In Host Datastore + SwapVMDirectory = Virtual machine directory + SwapHostDatastore = Datastore specified by host + TableClusterSummary = Cluster Summary - {0} + TableClusterConfig = Cluster Configuration - {0} +'@ + +# Get-AbrVSphereClusterHA +GetAbrVSphereClusterHA = ConvertFrom-StringData @' + InfoLevel = Cluster HA InfoLevel set to {0}. + Collecting = Collecting Cluster HA information. + SectionHeading = vSphere HA Configuration + ParagraphSummary = The following section details the vSphere HA configuration for {0} cluster. + FailuresAndResponses = Failures and Responses + HostMonitoring = Host Monitoring + HostFailureResponse = Host Failure Response + HostIsolationResponse = Host Isolation Response + VMRestartPriority = VM Restart Priority + PDLProtection = Datastore with Permanent Device Loss + APDProtection = Datastore with All Paths Down + VMMonitoring = VM Monitoring + VMMonitoringSensitivity = VM Monitoring Sensitivity + AdmissionControl = Admission Control + FailoverLevel = Host Failures Cluster Tolerates + ACPolicy = Policy + ACHostPercentage = CPU % + ACMemPercentage = Memory % + PerformanceDegradation = VM Performance Degradation + HeartbeatDatastores = Heartbeat Datastores + Datastore = Datastore + HAAdvancedOptions = vSphere HA Advanced Options + Key = Key + Value = Value + APDRecovery = APD recovery after APD timeout + Disabled = Disabled + Enabled = Enabled + RestartVMs = Restart VMs + ShutdownAndRestart = Shutdown and restart VMs + PowerOffAndRestart = Power off and restart VMs + IssueEvents = Issue events + PowerOffRestartConservative = Power off and restart VMs (conservative) + PowerOffRestartAggressive = Power off and restart VMs (aggressive) + ResetVMs = Reset VMs + VMMonitoringOnly = VM monitoring only + VMAndAppMonitoring = VM and application monitoring + DedicatedFailoverHosts = Dedicated failover hosts + ClusterResourcePercentage = Cluster resource percentage + SlotPolicy = Slot policy + Yes = Yes + No = No + FixedSlotSize = Fixed slot size + CoverAllPoweredOnVMs = Cover all powered-on virtual machines + NoneSpecified = None specified + OverrideFailoverCapacity = Override Calculated Failover Capacity + CPUSlotSize = CPU Slot Size (MHz) + MemorySlotSize = Memory Slot Size (MB) + PerfDegradationTolerate = Performance Degradation VMs Tolerate + HeartbeatSelectionPolicy = Heartbeat Selection Policy + HBPolicyAllFeasibleDsWithUserPreference = Use datastores from the specified list and complement automatically if needed + HBPolicyAllFeasibleDs = Automatically select datastores accessible from the host + HBPolicyUserSelectedDs = Use datastores only from the specified list + TableHAFailures = vSphere HA Failures and Responses - {0} + TableHAAdmissionControl = vSphere HA Admission Control - {0} + TableHAHeartbeat = vSphere HA Heartbeat Datastores - {0} + TableHAAdvanced = vSphere HA Advanced Options - {0} +'@ + +# Get-AbrVSphereClusterProactiveHA +GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' + InfoLevel = Cluster Proactive HA InfoLevel set to {0}. + Collecting = Collecting Cluster Proactive HA information. + SectionHeading = Proactive HA + ParagraphSummary = The following section details the Proactive HA configuration for {0} cluster. + FailuresAndResponses = Failures and Responses + Provider = Provider + Remediation = Remediation + HealthUpdates = Health Updates + ProactiveHA = Proactive HA + AutomationLevel = Automation Level + ModerateRemediation = Moderate Remediation + SevereRemediation = Severe Remediation + Enabled = Enabled + Disabled = Disabled + MaintenanceMode = Maintenance Mode + QuarantineMode = Quarantine Mode + MixedMode = Mixed Mode + TableProactiveHA = Proactive HA - {0} +'@ + +# Get-AbrVSphereClusterDRS +GetAbrVSphereClusterDRS = ConvertFrom-StringData @' + InfoLevel = Cluster DRS InfoLevel set to {0}. + Collecting = Collecting Cluster DRS information. + SectionHeading = vSphere DRS Configuration + ParagraphSummary = The following table details the vSphere DRS configuration for {0} cluster. + AutomationLevel = Automation Level + MigrationThreshold = Migration Threshold + PredictiveDRS = Predictive DRS + VirtualMachineAutomation = Individual Machine Automation + AdditionalOptions = Additional Options + VMDistribution = VM Distribution + MemoryMetricForLB = Memory Metric for Load Balancing + CPUOverCommitment = CPU Over-Commitment + PowerManagement = Power Management + DPMAutomationLevel = DPM Automation Level + DPMThreshold = DPM Threshold + AdvancedOptions = Advanced Options + Key = Key + Value = Value + DRSClusterGroups = DRS Cluster Groups + GroupName = Group Name + GroupType = Group Type + GroupMembers = Members + DRSVMHostRules = DRS VM/Host Rules + RuleName = Rule Name + RuleType = Rule Type + RuleEnabled = Enabled + VMGroup = VM Group + HostGroup = Host Group + DRSRules = DRS Rules + RuleVMs = Virtual Machines + VMOverrides = VM Overrides + VirtualMachine = Virtual Machine + DRSAutomationLevel = DRS Automation Level + DRSBehavior = DRS Behavior + HARestartPriority = HA Restart Priority + HAIsolationResponse = HA Isolation Response + PDLProtection = Datastore with PDL + APDProtection = Datastore with APD + VMMonitoring = VM Monitoring + VMMonitoringFailureInterval = Failure Interval + VMMonitoringMinUpTime = Minimum Uptime + VMMonitoringMaxFailures = Maximum Failures + VMMonitoringMaxFailureWindow = Maximum Failure Window + UpdateManagerBaselines = Update Manager Baselines + Baseline = Baseline + Description = Description + Type = Type + TargetType = Target Type + LastUpdate = Last Update Time + NumPatches = # of Patches + UpdateManagerCompliance = Update Manager Compliance + Entity = Entity + Status = Compliance Status + Version = Version + BaselineInfo = Baseline + VUMPrivilegeMsgBaselines = Insufficient user privileges to report Cluster baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + VUMPrivilegeMsgCompliance = Insufficient user privileges to report Cluster compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + VUMBaselineNotAvailable = Cluster VUM baseline information is not currently available with your version of PowerShell. + VUMComplianceNotAvailable = Cluster VUM compliance information is not currently available with your version of PowerShell. + Enabled = Enabled + Disabled = Disabled + Yes = Yes + No = No + FullyAutomated = Fully Automated + PartiallyAutomated = Partially Automated + Manual = Manual + Off = Off + NotCompliant = Not Compliant + Unknown = Unknown + Incompatible = Incompatible + DRS = vSphere DRS + DPM = DPM + Automated = Automated + None = None + VMGroupType = VM Group + VMHostGroupType = Host Group + MustRunOn = Must run on hosts in group + ShouldRunOn = Should run on hosts in group + MustNotRunOn = Must not run on hosts in group + ShouldNotRunOn = Should not run on hosts in group + VMAffinity = Keep Virtual Machines Together + VMAntiAffinity = Separate Virtual Machines + Mandatory = Mandatory + OverCommitmentRatio = Over-Commitment Ratio + OverCommitmentRatioCluster = Over-Commitment Ratio (% of cluster capacity) + Lowest = Lowest + Low = Low + Medium = Medium + High = High + Highest = Highest + ClusterDefault = Cluster default + VMDependencyTimeout = VM Dependency Restart Condition Timeout + Seconds = {0} seconds + SectionVSphereHA = vSphere HA + IssueEvents = Issue events + PowerOffAndRestart = Power off and restart VMs + ShutdownAndRestartVMs = Shutdown and restart VMs + PowerOffRestartConservative = Power off and restart VMs - Conservative restart policy + PowerOffRestartAggressive = Power off and restart VMs - Aggressive restart policy + PDLFailureResponse = PDL Failure Response + APDFailureResponse = APD Failure Response + VMFailoverDelay = VM Failover Delay + Minutes = {0} minutes + ResponseRecovery = Response Recovery + ResetVMs = Reset VMs + SectionPDLAPD = PDL/APD Protection Settings + NoWindow = No window + WithinHours = Within {0} hrs + VMMonitoringOnly = VM Monitoring Only + VMAndAppMonitoring = VM and Application Monitoring + UserGroup = User/Group + IsGroup = Is Group? + Role = Role + DefinedIn = Defined In + Propagate = Propagate + Permissions = Permissions + ParagraphPermissions = The following table details the permissions for {0}. + TableDRSConfig = vSphere DRS Configuration - {0} + TableDRSAdditional = DRS Additional Options - {0} + TableDPM = vSphere DPM - {0} + TableDRSAdvanced = vSphere DRS Advanced Options - {0} + TableDRSGroups = DRS Cluster Groups - {0} + TableDRSVMHostRules = DRS VM/Host Rules - {0} + TableDRSRules = DRS Rules - {0} + TableDRSVMOverrides = DRS VM Overrides - {0} + TableHAVMOverrides = HA VM Overrides - {0} + TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} + TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TablePermissions = Permissions - {0} +'@ + +# Get-AbrVSphereResourcePool +GetAbrVSphereResourcePool = ConvertFrom-StringData @' + InfoLevel = ResourcePool InfoLevel set to {0}. + Collecting = Collecting Resource Pool information. + Processing = Processing Resource Pool '{0}' ({1}/{2}). + SectionHeading = Resource Pools + ParagraphSummary = The following sections detail the configuration of resource pools managed by vCenter Server {0}. + ResourcePool = Resource Pool + Parent = Parent + CPUSharesLevel = CPU Shares Level + CPUReservationMHz = CPU Reservation MHz + CPULimitMHz = CPU Limit MHz + MemSharesLevel = Memory Shares Level + MemReservation = Memory Reservation + MemLimit = Memory Limit + ID = ID + NumCPUShares = Number of CPU Shares + CPUReservation = CPU Reservation + CPUExpandable = CPU Expandable Reservation + NumMemShares = Number of Memory Shares + MemExpandable = Memory Expandable Reservation + NumVMs = Number of VMs + VirtualMachines = Virtual Machines + Enabled = Enabled + Disabled = Disabled + Unlimited = Unlimited + TableResourcePoolSummary = Resource Pool Summary - {0} + TableResourcePoolConfig = Resource Pool Configuration - {0} +'@ + +# Get-AbrVSphereVMHost +GetAbrVSphereVMHost = ConvertFrom-StringData @' + InfoLevel = VMHost InfoLevel set to {0}. + Collecting = Collecting VMHost information. + Processing = Processing VMHost '{0}' ({1}/{2}). + SectionHeading = Hosts + ParagraphSummary = The following sections detail the configuration of VMware ESXi hosts managed by vCenter Server {0}. + VMHost = Host + Manufacturer = Manufacturer + Model = Model + ProcessorType = Processor Type + NumCPUSockets = CPU Sockets + NumCPUCores = CPU Cores + NumCPUThreads = CPU Threads + MemoryGB = Memory GB + NumNICs = # NICs + NumHBAs = # HBAs + NumDatastores = # Datastores + NumVMs = # VMs + ConnectionState = Connection State + PowerState = Power State + ErrorCollecting = Error collecting VMHost information using VMHost InfoLevel {0}. + NotResponding = Not Responding + Maintenance = Maintenance + Disconnected = Disconnected + Version = Version + Build = Build + Parent = Parent + TableHostSummary = Host Summary - {0} +'@ + +# Get-AbrVSphereVMHostHardware +GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' + InfoLevel = VMHost Hardware InfoLevel set to {0}. + Collecting = Collecting VMHost Hardware information. + SectionHeading = Hardware + ParagraphSummary = The following section details the host hardware configuration for {0}. + InsufficientPrivLicense = Insufficient user privileges to report ESXi host licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned. + Specifications = Specifications + Manufacturer = Manufacturer + Model = Model + SerialNumber = Serial Number + AssetTag = Asset Tag + ProcessorType = Processor Type + CPUSockets = CPU Sockets + CPUCores = CPU Cores + CPUThreads = CPU Threads + HyperthreadingActive = Hyperthreading Active + MemoryGB = Memory GB + NUMANodes = NUMA Nodes + NICs = NICs + HBAs = HBAs + Version = Version + Build = Build + LicenseKey = License Key + Product = Product + LicenseExpiration = License Expiration + IPMIBMC = IPMI / BMC + IPMIBMCError = Error collecting IPMI/BMC information for {0}. {1} + IPMIBMCManufacturer = Manufacturer + IPMIBMCType = Type + IPMIBMCIPAddress = IP Address + IPMIBMCMACAddress = MAC Address + BootDevice = Boot Device + BootDeviceError = Error collecting boot device information for {0}. {1} + BootDeviceDescription = Description + BootDeviceType = Type + BootDeviceSize = Size (GB) + BootDeviceIsSAS = Is SAS + BootDeviceIsUSB = Is USB + BootDeviceIsSSD = Is SSD + BootDeviceFromSAN = From SAN + PCIDevices = PCI Devices + PCIDeviceId = PCI ID + PCIVendorName = Vendor Name + PCIDeviceName = Device Name + PCIDriverName = Driver Name + PCIDriverVersion = Driver Version + PCIFirmware = Firmware Version + PCIDriversFirmware = PCI Devices Drivers & Firmware + Enabled = Enabled + Disabled = Disabled + NotApplicable = Not applicable + Unknown = Unknown + Maintenance = Maintenance + NotResponding = Not Responding + Host = Host + ConnectionState = Connection State + ID = ID + Parent = Parent + HyperThreading = HyperThreading + NumberOfCPUSockets = Number of CPU Sockets + NumberOfCPUCores = Number of CPU Cores + NumberOfCPUThreads = Number of CPU Threads + CPUTotalUsedFree = CPU Total / Used / Free + MemoryTotalUsedFree = Memory Total / Used / Free + NumberOfNICs = Number of NICs + NumberOfHBAs = Number of HBAs + NumberOfDatastores = Number of Datastores + NumberOfVMs = Number of VMs + MaximumEVCMode = Maximum EVC Mode + EVCGraphicsMode = EVC Graphics Mode + PowerManagementPolicy = Power Management Policy + ScratchLocation = Scratch Location + BiosVersion = BIOS Version + BiosReleaseDate = BIOS Release Date + BootTime = Boot Time + UptimeDays = Uptime Days + MACAddress = MAC Address + SubnetMask = Subnet Mask + Gateway = Gateway + FirmwareVersion = Firmware Version + Device = Device + BootType = Boot Type + Vendor = Vendor + Size = Size + PCIAddress = PCI Address + DeviceClass = Device Class + TableHardwareConfig = Hardware Configuration - {0} + TableIPMIBMC = IPMI / BMC - {0} + TableBootDevice = Boot Device - {0} + TablePCIDevices = PCI Devices - {0} + TablePCIDriversFirmware = PCI Devices Drivers & Firmware - {0} + PCIDeviceError = Error collecting PCI device information for {0}. {1} + PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} + HardwareError = Error collecting host hardware information for {0}. {1} +'@ + +# Get-AbrVSphereVMHostSystem +GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' + InfoLevel = VMHost System InfoLevel set to {0}. + Collecting = Collecting VMHost System information. + HostProfile = Host Profile + ProfileName = Host Profile + ProfileCompliance = Compliance + ProfileRemediating = Remediating + ImageProfile = Image Profile + ImageProfileName = Name + ImageProfileVendor = Vendor + ImageProfileAcceptance = Acceptance Level + TimeConfiguration = Time Configuration + NTPServer = NTP Server + TimeZone = Time Zone + Syslog = Syslog + SyslogHost = Syslog Host + VUMBaseline = Update Manager Baselines + VUMBaselineNotAvailable = VMHost VUM baseline information is not currently available with your version of PowerShell. + VUMBaselineName = Baseline + VUMBaselineType = Type + VUMBaselinePatches = # of Patches + VUMCompliance = Update Manager Compliance + VUMComplianceNotAvailable = VMHost VUM compliance information is not currently available with your version of PowerShell. + VUMStatus = Status + AdvancedSettings = Advanced System Settings + Key = Key + Value = Value + VIBs = VMware Installation Bundles (VIBs) + VIBName = Name + VIBVersion = Version + VIBVendor = Vendor + VIBInstallDate = Install Date + VIBAcceptanceLevel = Acceptance Level + SectionHeading = System + ParagraphSummary = The following section details the host system configuration for {0}. + NTPService = NTP Service + NTPServers = NTP Server(s) + SyslogPort = Port + VUMDescription = Description + VUMTargetType = Target Type + VUMLastUpdate = Last Update Time + InstallDate = Installation Date + ImageName = Image Profile + ImageVendor = Vendor + Running = Running + Stopped = Stopped + NotCompliant = Not Compliant + Unknown = Unknown + Incompatible = Incompatible + ProfileDescription = Description + VIBID = ID + VIBCreationDate = Creation Date + TableHostProfile = Host Profile - {0} + TableImageProfile = Image Profile - {0} + TableTimeConfig = Time Configuration - {0} + TableSyslog = Syslog Configuration - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TableAdvancedSettings = Advanced System Settings - {0} + TableVIBs = Software VIBs - {0} + HostProfileError = Error collecting host profile information for {0}. {1} + ImageProfileError = Error collecting image profile information for {0}. {1} + TimeConfigError = Error collecting time configuration for {0}. {1} + SyslogError = Error collecting syslog configuration for {0}. {1} + VUMBaselineError = Error collecting Update Manager baseline information for {0}. {1} + VUMComplianceError = Error collecting Update Manager compliance information for {0}. {1} + AdvancedSettingsError = Error collecting host advanced settings information for {0}. {1} + VIBsError = Error collecting software VIB information for {0}. {1} + InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. + InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. +'@ + +# Get-AbrVSphereVMHostStorage +GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' + InfoLevel = VMHost Storage InfoLevel set to {0}. + Collecting = Collecting VMHost Storage information. + SectionHeading = Storage + DatastoreSpecs = Datastore Specifications + Datastore = Datastore + DatastoreType = Type + DatastoreCapacity = Capacity (GB) + DatastoreFreeSpace = Free Space (GB) + DatastoreState = State + DatastoreShared = Shared + StorageAdapters = Storage Adapters + AdapterName = Adapter + AdapterType = Type + AdapterDriver = Driver + AdapterUID = UID + AdapterModel = Model + AdapterStatus = Status + AdapterSASAddress = SAS Address + AdapterWWN = WWN + AdapterDevices = Devices + AdapterTargets = Targets + ParagraphSummary = The following section details the host storage configuration for {0}. + FC = Fibre Channel + iSCSI = iSCSI + ParallelSCSI = Parallel SCSI + ChapNone = None + ChapUniPreferred = Use unidirectional CHAP unless prohibited by target + ChapUniRequired = Use unidirectional CHAP if required by target + ChapUnidirectional = Use unidirectional CHAP + ChapBidirectional = Use bidirectional CHAP + Online = Online + Offline = Offline + Type = Type + Version = Version + NumberOfVMs = # of VMs + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + TableDatastores = Datastores - {0} + ParagraphStorageAdapters = The following section details the storage adapter configuration for {0}. + AdapterPaths = Paths + iSCSIName = iSCSI Name + iSCSIAlias = iSCSI Alias + AdapterSpeed = Speed + DynamicDiscovery = Dynamic Discovery + StaticDiscovery = Static Discovery + AuthMethod = Authentication Method + CHAPOutgoing = Outgoing CHAP Name + CHAPIncoming = Incoming CHAP Name + AdvancedOptions = Advanced Options + NodeWWN = Node WWN + PortWWN = Port WWN + TableStorageAdapter = Storage Adapter {0} - {1} +'@ + +# Get-AbrVSphereVMHostNetwork +GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' + InfoLevel = VMHost Network InfoLevel set to {0}. + Collecting = Collecting VMHost Network information. + SectionHeading = Network + NetworkConfig = Network Configuration + VMKernelAdapters = VMkernel Adapters + PhysicalAdapters = Physical Adapters + CDP = Cisco Discovery Protocol + LLDP = Link Layer Discovery Protocol + StandardvSwitches = Standard Virtual Switches + AdapterName = Adapter + AdapterMAC = MAC Address + AdapterLinkSpeed = Link Speed + AdapterDriver = Driver + AdapterPCIID = PCI Device + AdapterStatus = Status + VMKName = VMkernel Adapter + VMKIP = IP Address + VMKSubnet = Subnet Mask + VMKGateway = Default Gateway + VMKMTU = MTU + VMKPortGroup = Port Group + VMKVirtualSwitch = Virtual Switch + VMKDHCPEnabled = DHCP Enabled + VMKServicesEnabled = Enabled Services + vSwitch = Virtual Switch + vSwitchPorts = Total Ports + vSwitchUsedPorts = Used Ports + vSwitchAvailablePorts = Available Ports + vSwitchMTU = MTU + vSwitchCDPStatus = CDP Status + vSwitchSecurity = Security Policy + vSwitchFailover = Failover Policy + vSwitchLoadBalancing = Load Balancing + vSwitchPortGroups = Port Groups + PortGroup = Port Group + VLAN = VLAN ID + ActiveAdapters = Active Adapters + StandbyAdapters = Standby Adapters + UnusedAdapters = Unused Adapters + ParagraphSummary = The following section details the host network configuration for {0}. + Enabled = Enabled + Disabled = Disabled + Connected = Connected + Disconnected = Disconnected + Yes = Yes + No = No + Accept = Accept + Reject = Reject + Supported = Supported + NotSupported = Not Supported + Inherited = Inherited + TCPDefault = Default + TCPProvisioning = Provisioning + TCPvMotion = vMotion + TCPNsxOverlay = nsx-overlay + TCPNsxHyperbus = nsx-hyperbus + TCPNotApplicable = Not Applicable + LBSrcId = Route based on the originating port ID + LBSrcMac = Route based on source MAC hash + LBIP = Route based on IP hash + LBExplicitFailover = Explicit Failover + NFDLinkStatus = Link status only + NFDBeaconProbing = Beacon probing + FullDuplex = Full Duplex + AutoNegotiate = Auto negotiate + Down = Down + Host = Host + VirtualSwitches = Virtual Switches + VMkernelGateway = VMkernel Gateway + IPv6 = IPv6 + VMkernelIPv6Gateway = VMkernel IPv6 Gateway + DNSServers = DNS Servers + HostName = Host Name + DomainName = Domain Name + SearchDomain = Search Domain + TableNetworkConfig = Network Configuration - {0} + ParagraphPhysicalAdapters = The following section details the physical network adapter configuration for {0}. + VirtualSwitch = Virtual Switch + ActualSpeedDuplex = Actual Speed, Duplex + ConfiguredSpeedDuplex = Configured Speed, Duplex + WakeOnLAN = Wake on LAN + TablePhysicalAdapter = Physical Adapter {0} - {1} + TablePhysicalAdapters = Physical Adapters - {0} + ParagraphCDP = The following section details the CDP information for {0}. + Status = Status + SystemName = System Name + HardwarePlatform = Hardware Platform + SwitchID = Switch ID + SoftwareVersion = Software Version + ManagementAddress = Management Address + Address = Address + PortID = Port ID + MTU = MTU + TableAdapterCDP = Network Adapter {0} CDP Information - {1} + TableAdaptersCDP = Network Adapter CDP Information - {0} + ParagraphLLDP = The following section details the LLDP information for {0}. + ChassisID = Chassis ID + TimeToLive = Time to live + TimeOut = TimeOut + Samples = Samples + PortDescription = Port Description + SystemDescription = System Description + TableAdapterLLDP = Network Adapter {0} LLDP Information - {1} + TableAdaptersLLDP = Network Adapter LLDP Information - {0} + ParagraphVMKernelAdapters = The following section details the VMkernel adapter configuration for {0}. + NetworkLabel = Network Label + TCPIPStack = TCP/IP Stack + DHCP = DHCP + IPAddress = IP Address + SubnetMask = Subnet Mask + DefaultGateway = Default Gateway + vMotion = vMotion + Provisioning = Provisioning + FTLogging = FT Logging + Management = Management + vSphereReplication = vSphere Replication + vSphereReplicationNFC = vSphere Replication NFC + vSAN = vSAN + vSANWitness = vSAN Witness + vSphereBackupNFC = vSphere Backup NFC + TableVMKernelAdapter = VMkernel Adapter {0} - {1} + ParagraphStandardvSwitches = The following section details the standard virtual switch configuration for {0}. + NumberOfPorts = Number of Ports + NumberOfPortsAvailable = Number of Ports Available + TableStandardvSwitches = Standard Virtual Switches - {0} + VSSecurity = Virtual Switch Security + PromiscuousMode = Promiscuous Mode + MACAddressChanges = MAC Address Changes + ForgedTransmits = Forged Transmits + TableVSSecurity = Virtual Switch Security Policy - {0} + VSTrafficShaping = Virtual Switch Traffic Shaping + AverageBandwidth = Average Bandwidth (kbit/s) + PeakBandwidth = Peak Bandwidth (kbit/s) + BurstSize = Burst Size (KB) + TableVSTrafficShaping = Virtual Switch Traffic Shaping Policy - {0} + VSTeamingFailover = Virtual Switch Teaming & Failover + LoadBalancing = Load Balancing + NetworkFailureDetection = Network Failure Detection + NotifySwitches = Notify Switches + Failback = Failback + ActiveNICs = Active NICs + StandbyNICs = Standby NICs + UnusedNICs = Unused NICs + TableVSTeamingFailover = Virtual Switch Teaming & Failover - {0} + VSPortGroups = Virtual Switch Port Groups + NumberOfVMs = # of VMs + TableVSPortGroups = Virtual Switch Port Groups - {0} + VSPGSecurity = Virtual Switch Port Group Security + MACChanges = MAC Changes + TableVSPGSecurity = Virtual Switch Port Group Security Policy - {0} + VSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping + TableVSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping Policy - {0} + VSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover + TableVSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover - {0} +'@ + +# Get-AbrVSphereVMHostSecurity +GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' + InfoLevel = VMHost Security InfoLevel set to {0}. + Collecting = Collecting VMHost Security information. + SectionHeading = Security + LockdownMode = Lockdown Mode + Services = Services + ServiceName = Service + ServiceRunning = Running + ServicePolicy = Startup Policy + ServiceRequired = Required + Firewall = Firewall + FirewallRule = Rule + FirewallAllowed = Allowed Hosts + Authentication = Authentication + Domain = Domain + Trust = Trusted Domains + Membership = Domain Membership + VMInfoSection = Virtual Machines + VMName = Virtual Machine + VMPowerState = Power State + VMIPAddress = IP Address + VMOS = OS + VMGuestID = Guest ID + VMCPUs = CPUs + VMMemoryGB = Memory GB + VMDisks = Disks (GB) + VMNICs = NICs + StartupShutdown = VM Startup/Shutdown + StartupEnabled = Startup Enabled + StartupOrder = Startup Order + StartupDelay = Startup Delay + StopAction = Stop Action + StopDelay = Stop Delay + WaitForHeartbeat = Wait For Heartbeat + ParagraphSummary = The following section details the host security configuration for {0}. + LockdownDisabled = Disabled + LockdownNormal = Enabled (Normal) + LockdownStrict = Enabled (Strict) + Running = Running + Stopped = Stopped + SvcPortUsage = Start and stop with port usage + SvcWithHost = Start and stop with host + SvcManually = Start and stop manually + On = On + Off = Off + Enabled = Enabled + Disabled = Disabled + WaitHeartbeat = Continue if VMware Tools is started + WaitDelay = Wait for startup delay + PowerOff = Power Off + GuestShutdown = Guest Shutdown + ToolsOld = Old + ToolsOK = OK + ToolsNotRunning = Not Running + ToolsNotInstalled = Not Installed + TableLockdownMode = Lockdown Mode - {0} + TableServices = Services - {0} + FirewallService = Service + Status = Status + IncomingPorts = Incoming Ports + OutgoingPorts = Outgoing Ports + Protocols = Protocols + Daemon = Daemon + TableFirewall = Firewall Configuration - {0} + DomainMembership = Domain Membership + TrustedDomains = Trusted Domains + TableAuthentication = Authentication Services - {0} + ParagraphVMInfo = The following section details the virtual machine configuration for {0}. + VMProvisioned = Provisioned + VMUsed = Used + VMHWVersion = HW Version + VMToolsStatus = VM Tools Status + TableVMs = Virtual Machines - {0} + TableStartupShutdown = VM Startup/Shutdown Policy - {0} +'@ + +# Get-AbrVSphereNetwork +GetAbrVSphereNetwork = ConvertFrom-StringData @' + InfoLevel = Network InfoLevel set to {0}. + Collecting = Collecting Distributed Switch information. + Processing = Processing Distributed Switch '{0}' ({1}/{2}). + SectionHeading = Distributed Switches + ParagraphSummary = The following sections detail the configuration of distributed switches managed by vCenter Server {0}. + VDSwitch = Distributed Switch + Datacenter = Datacenter + Manufacturer = Manufacturer + NumUplinks = # Uplinks + NumPorts = # Ports + NumHosts = # Hosts + NumVMs = # VMs + Version = Version + MTU = MTU + ID = ID + NumberOfUplinks = Number of Uplinks + NumberOfPorts = Number of Ports + NumberOfPortGroups = Number of Port Groups + NumberOfHosts = Number of Hosts + NumberOfVMs = Number of VMs + Hosts = Hosts + VirtualMachines = Virtual Machines + NIOC = Network I/O Control + DiscoveryProtocol = Discovery Protocol + DiscoveryOperation = Discovery Operation + NumPortGroups = # Port Groups + MaxPorts = Max Ports + Contact = Contact + Location = Location + VDPortGroups = Distributed Port Groups + PortGroup = Port Group + PortGroupType = Port Group Type + VLANID = VLAN ID + VLANConfiguration = VLAN Configuration + PortBinding = Port Binding + Host = Host + UplinkName = Uplink Name + UplinkPortGroup = Uplink Port Group + PhysicalNetworkAdapter = Physical Network Adapter + ActiveUplinks = Active Uplinks + StandbyUplinks = Standby Uplinks + UnusedUplinks = Unused Uplinks + AllowPromiscuous = Allow Promiscuous + ForgedTransmits = Forged Transmits + MACAddressChanges = MAC Address Changes + Accept = Accept + Reject = Reject + Direction = Direction + Status = Status + AverageBandwidth = Average Bandwidth (kbit/s) + PeakBandwidth = Peak Bandwidth (kbit/s) + BurstSize = Burst Size (KB) + LoadBalancing = Load Balancing + LoadBalanceSrcId = Route based on the originating port ID + LoadBalanceSrcMac = Route based on source MAC hash + LoadBalanceIP = Route based on IP hash + ExplicitFailover = Explicit Failover + NetworkFailureDetection = Network Failure Detection + LinkStatus = Link status only + BeaconProbing = Beacon probing + NotifySwitches = Notify Switches + FailbackEnabled = Failback Enabled + Yes = Yes + No = No + PrimaryVLANID = Primary VLAN ID + PrivateVLANType = Private VLAN Type + SecondaryVLANID = Secondary VLAN ID + UplinkPorts = Distributed Switch Uplink Ports + VDSSecurity = Distributed Switch Security + VDSTrafficShaping = Distributed Switch Traffic Shaping + VDSPortGroups = Distributed Switch Port Groups + VDSPortGroupSecurity = Distributed Switch Port Group Security + VDSPortGroupTrafficShaping = Distributed Switch Port Group Traffic Shaping + VDSPortGroupTeaming = Distributed Switch Port Group Teaming & Failover + VDSPrivateVLANs = Distributed Switch Private VLANs + Enabled = Enabled + Disabled = Disabled + Listen = Listen + Advertise = Advertise + Both = Both + TableVDSSummary = Distributed Switch Summary - {0} + TableVDSGeneral = Distributed Switch General Properties - {0} + TableVDSUplinkPorts = Distributed Switch Uplink Ports - {0} + TableVDSSecurity = Distributed Switch Security - {0} + TableVDSTrafficShaping = Distributed Switch Traffic Shaping - {0} + TableVDSPortGroups = Distributed Switch Port Groups - {0} + TableVDSPortGroupSecurity = Distributed Switch Port Group Security - {0} + TableVDSPortGroupTrafficShaping = Distributed Switch Port Group Traffic Shaping - {0} + TableVDSPortGroupTeaming = Distributed Switch Port Group Teaming & Failover - {0} + TableVDSPrivateVLANs = Distributed Switch Private VLANs - {0} +'@ + +# Get-AbrVSpherevSAN +GetAbrVSpherevSAN = ConvertFrom-StringData @' + InfoLevel = vSAN InfoLevel set to {0}. + Collecting = Collecting vSAN information. + CollectingESA = Collecting vSAN ESA information for '{0}'. + CollectingOSA = Collecting vSAN OSA information for '{0}'. + CollectingDisks = Collecting vSAN disk information for '{0}'. + CollectingDiskGroups = Collecting vSAN disk group information for '{0}'. + CollectingiSCSITargets = Collecting vSAN iSCSI target information for '{0}'. + CollectingiSCSILUNs = Collecting vSAN iSCSI LUN information for '{0}'. + Processing = Processing vSAN Cluster '{0}' ({1}/{2}). + SectionHeading = vSAN + ParagraphSummary = The following sections detail the configuration of VMware vSAN managed by vCenter Server {0}. + ParagraphDetail = The following table details the vSAN configuration for cluster {0}. + DisksSection = Disks + DiskGroupsSection = Disk Groups + iSCSITargetsSection = iSCSI Targets + iSCSILUNsSection = iSCSI LUNs + Cluster = Cluster + StorageType = Storage Type + ClusterType = Cluster Type + NumHosts = # of Hosts + ID = ID + NumberOfHosts = Number of Hosts + NumberOfDisks = Number of Disks + NumberOfDiskGroups = Number of Disk Groups + DiskClaimMode = Disk Claim Mode + PerformanceService = Performance Service + FileService = File Service + iSCSITargetService = iSCSI Target Service + HistoricalHealthService = Historical Health Service + HealthCheck = Health Check + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + HCLLastUpdated = HCL Last Updated + Hosts = Hosts + Version = Version + Stretched = Stretched Cluster + VSANEnabled = vSAN Enabled + VSANESAEnabled = vSAN ESA Enabled + DisksFormat = Disk Format Version + Deduplication = Deduplication and Compression + AllFlash = All Flash + FaultDomains = Fault Domains + PFTT = Primary Failures to Tolerate + SFTT = Secondary Failures to Tolerate + NetworkDiagnosticMode = Network Diagnostic Mode + HybridMode = Hybrid Mode + AutoRebalance = Automatic Rebalance + ProactiveDisk = Proactive Disk Rebalance + ResyncThrottle = Resync Throttle + SpaceEfficiency = Space Efficiency + Encryption = Encryption + FileServiceEnabled = File Service Enabled + iSCSITargetEnabled = iSCSI Target Enabled + VMHostSpecs = VMHost vSAN Specifications + VMHost = VMHost + DiskGroup = Disk Group + CacheDisks = Cache Disks + DataDisks = Data Disks + DiskGroups = Disk Groups + CacheTier = Cache Tier + DataTier = Data Tier + Status = Status + FaultDomainName = Fault Domain + VSANAdvancedOptions = vSAN Advanced Configuration Options + Key = Key + Value = Value + IDs = Identifiers + VSAN_UUID = vSAN UUID + CapacityTier = Capacity Tier (GB) + BufferTier = Buffer Tier (GB) + DiskName = Disk + Name = Name + DriveType = Drive Type + Host = Host + State = State + Encrypted = Encrypted + Capacity = Capacity + SerialNumber = Serial Number + Vendor = Vendor + Model = Model + DiskType = Disk Type + DiskFormatVersion = Disk Format Version + ClaimedAs = Claimed As + NumDisks = # of Disks + Type = Type + IQN = IQN + Alias = Alias + LUNsCount = LUNs + NetworkInterface = Network Interface + IOOwnerHost = I/O Owner Host + TCPPort = TCP Port + Health = Health + StoragePolicy = Storage Policy + ComplianceStatus = Compliance Status + Authentication = Authentication + LUNName = LUN + LUNID = LUN ID + Yes = Yes + No = No + Enabled = Enabled + Disabled = Disabled + Online = Online + Offline = Offline + Mounted = Mounted + Unmounted = Unmounted + Flash = Flash + HDD = HDD + Cache = Cache + TableVSANClusterSummary = vSAN Cluster Summary - {0} + TableVSANConfiguration = vSAN Configuration - {0} + TableDisk = Disk {0} - {1} + TableVSANDisks = vSAN Disks - {0} + TableVSANDiskGroups = vSAN Disk Groups - {0} + TableVSANiSCSITargets = vSAN iSCSI Targets - {0} + TableVSANiSCSILUNs = vSAN iSCSI LUNs - {0} + ESAError = Error collecting vSAN ESA information for '{0}'. {1} + OSAError = Error collecting vSAN OSA information for '{0}'. {1} + DiskError = Error collecting vSAN disk information for '{0}'. {1} + DiskGroupError = Error collecting vSAN disk group information for '{0}'. {1} + iSCSITargetError = Error collecting vSAN iSCSI target information for '{0}'. {1} + iSCSILUNError = Error collecting vSAN iSCSI LUN information for '{0}'. {1} +'@ + +# Get-AbrVSphereDatastore +GetAbrVSphereDatastore = ConvertFrom-StringData @' + InfoLevel = Datastore InfoLevel set to {0}. + Collecting = Collecting Datastore information. + Processing = Processing Datastore '{0}' ({1}/{2}). + SectionHeading = Datastores + ParagraphSummary = The following sections detail the configuration of datastores managed by vCenter Server {0}. + Datastore = Datastore + Type = Type + DatastoreURL = URL + FileSystem = File System Version + CapacityGB = Total Capacity (GB) + FreeSpaceGB = Free Space (GB) + UsedSpaceGB = Used Space (GB) + State = State + Accessible = Accessible + NumHosts = # Hosts + NumVMs = # VMs + SIOC = Storage I/O Control + IOLatencyThreshold = I/O Latency Threshold (ms) + IOLoadBalancing = I/O Load Balancing + Enabled = Enabled + Disabled = Disabled + Normal = Normal + Maintenance = Maintenance + Unmounted = Unmounted + ID = ID + Datacenter = Datacenter + Version = Version + NumberOfHosts = Number of Hosts + NumberOfVMs = Number of VMs + CongestionThreshold = Congestion Threshold + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + Hosts = Hosts + VirtualMachines = Virtual Machines + SCSILUNInfo = SCSI LUN Information + Host = Host + CanonicalName = Canonical Name + Capacity = Capacity + Vendor = Vendor + Model = Model + IsSSD = Is SSD + MultipathPolicy = Multipath Policy + Paths = Paths + TableDatastoreSummary = Datastore Summary - {0} + TableDatastoreConfig = Datastore Configuration - {0} + TableSCSILUN = SCSI LUN Information - {0} +'@ + +# Get-AbrVSphereDSCluster +GetAbrVSphereDSCluster = ConvertFrom-StringData @' + InfoLevel = DSCluster InfoLevel set to {0}. + Collecting = Collecting Datastore Cluster information. + Processing = Processing Datastore Cluster '{0}' ({1}/{2}). + SectionHeading = Datastore Clusters + ParagraphSummary = The following sections detail the configuration of datastore clusters managed by vCenter Server {0}. + DSCluster = Datastore Cluster + Datacenter = Datacenter + CapacityGB = Total Capacity (GB) + FreeSpaceGB = Free Space (GB) + SDRSEnabled = SDRS + SDRSAutomationLevel = SDRS Automation Level + IOLoadBalancing = I/O Load Balancing + SpaceThreshold = Space Threshold (%) + IOLatencyThreshold = I/O Latency Threshold (ms) + SDRSRules = SDRS Rules + RuleName = Rule + RuleEnabled = Enabled + RuleVMs = Virtual Machines + FullyAutomated = Fully Automated + NoAutomation = No Automation (Manual Mode) + Manual = Manual + Enabled = Enabled + Disabled = Disabled + Yes = Yes + No = No + ID = ID + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + ParagraphDSClusterDetail = The following table details the configuration for datastore cluster {0}. + TableDSClusterConfig = Datastore Cluster Configuration - {0} + TableSDRSVMOverrides = SDRS VM Overrides - {0} + SDRSVMOverrides = SDRS VM Overrides + VirtualMachine = Virtual Machine + KeepVMDKsTogether = Keep VMDKs Together + DefaultBehavior = Default ({0}) +'@ + +# Get-AbrVSphereVM +GetAbrVSphereVM = ConvertFrom-StringData @' + InfoLevel = VM InfoLevel set to {0}. + Collecting = Collecting Virtual Machine information. + Processing = Processing Virtual Machine '{0}' ({1}/{2}). + SectionHeading = Virtual Machines + ParagraphSummary = The following sections detail the configuration of virtual machines managed by vCenter Server {0}. + VirtualMachine = Virtual Machine + PowerState = Power State + Template = Template + OS = OS + Version = VM Hardware Version + GuestID = Guest ID + Cluster = Cluster + ResourcePool = Resource Pool + VMHost = Host + Folder = Folder + IPAddress = IP Address + CPUs = CPUs + MemoryGB = Memory GB + ProvisionedGB = Provisioned (GB) + UsedGB = Used (GB) + NumDisks = # Disks + NumNICs = # NICs + NumSnapshots = # Snapshots + VMwareTools = VMware Tools + ToolsVersion = Tools Version + ToolsStatus = Tools Status + ToolsRunningStatus = Tools Running Status + VMAdvancedDetail = Advanced Configuration + BootOptions = Boot Options + BootDelay = Boot Delay (ms) + BootRetryEnabled = Boot Retry Enabled + BootRetryDelay = Boot Retry Delay (ms) + EFISecureBoot = EFI Secure Boot + EnterBIOSSetup = Enter BIOS Setup on Next Boot + HardDisks = Hard Disks + DiskName = Disk + DiskCapacityGB = Capacity (GB) + DiskFormat = Format + DiskStoragePolicy = Storage Policy + DiskDatastore = Datastore + DiskController = Controller + NetworkAdapters = Network Adapters + NICName = Network Adapter + NICType = Adapter Type + NICPortGroup = Port Group + NICMAC = MAC Address + NICConnected = Connected + NICConnectionPolicy = Connect at Power On + SnapshotHeading = Snapshots + SnapshotName = Snapshot + SnapshotDescription = Description + SnapshotSize = Size (GB) + SnapshotDate = Created + SnapshotParent = Parent + On = On + Off = Off + ToolsOld = Old + ToolsOK = OK + ToolsNotRunning = Not Running + ToolsNotInstalled = Not Installed + Yes = Yes + No = No + Connected = Connected + NotConnected = Not Connected + Thin = Thin + Thick = Thick + Enabled = Enabled + Disabled = Disabled + TotalVMs = Total VMs + TotalvCPUs = Total vCPUs + TotalMemory = Total Memory + TotalProvisionedSpace = Total Provisioned Space + TotalUsedSpace = Total Used Space + VMsPoweredOn = VMs Powered On + VMsPoweredOff = VMs Powered Off + VMsOrphaned = VMs Orphaned + VMsInaccessible = VMs Inaccessible + VMsSuspended = VMs Suspended + VMsWithSnapshots = VMs with Snapshots + GuestOSTypes = Guest Operating System Types + VMToolsOKCount = VM Tools OK + VMToolsOldCount = VM Tools Old + VMToolsNotRunningCount = VM Tools Not Running + VMToolsNotInstalledCount = VM Tools Not Installed + vCPUs = vCPUs + Memory = Memory + Provisioned = Provisioned + Used = Used + HWVersion = HW Version + VMToolsStatus = VM Tools Status + ID = ID + OperatingSystem = Operating System + HardwareVersion = Hardware Version + ConnectionState = Connection State + FaultToleranceState = Fault Tolerance State + FTNotConfigured = Not Configured + FTNeedsSecondary = Needs Secondary + FTRunning = Running + FTDisabled = Disabled + FTStarting = Starting + FTEnabled = Enabled + Parent = Parent + ParentFolder = Parent Folder + ParentResourcePool = Parent Resource Pool + CoresPerSocket = Cores per Socket + CPUShares = CPU Shares + CPUReservation = CPU Reservation + CPULimit = CPU Limit + CPUHotAdd = CPU Hot Add + CPUHotRemove = CPU Hot Remove + MemoryAllocation = Memory Allocation + MemoryShares = Memory Shares + MemoryHotAdd = Memory Hot Add + vNICs = vNICs + DNSName = DNS Name + Networks = Networks + MACAddress = MAC Address + vDisks = vDisks + ProvisionedSpace = Provisioned Space + UsedSpace = Used Space + ChangedBlockTracking = Changed Block Tracking + StorageBasedPolicy = Storage Based Policy + StorageBasedPolicyCompliance = Storage Based Policy Compliance + Compliant = Compliant + NonCompliant = Non Compliant + Unknown = Unknown + Notes = Notes + BootTime = Boot Time + UptimeDays = Uptime Days + NetworkName = Network Name + SCSIControllers = SCSI Controllers + Device = Device + ControllerType = Controller Type + BusSharing = Bus Sharing + None = None + GuestVolumes = Guest Volumes + Capacity = Capacity + DiskProvisioning = Disk Provisioning + ThickEagerZeroed = Thick Eager Zeroed + ThickLazyZeroed = Thick Lazy Zeroed + DiskType = Disk Type + PhysicalRDM = Physical RDM + VirtualRDM = Virtual RDM + VMDK = VMDK + DiskMode = Disk Mode + IndependentPersistent = Independent - Persistent + IndependentNonpersistent = Independent - Nonpersistent + Dependent = Dependent + DiskPath = Disk Path + DiskShares = Disk Shares + DiskLimitIOPs = Disk Limit IOPs + Unlimited = Unlimited + SCSIController = SCSI Controller + SCSIAddress = SCSI Address + Path = Path + FreeSpace = Free Space + DaysOld = Days Old + TableVMSummary = VM Summary - {0} + TableVMAdvancedSummary = VM Advanced Summary - {0} + TableVMSnapshotSummary = VM Snapshot Summary - {0} + TableVMConfig = VM Configuration - {0} + TableVMNetworkAdapters = Network Adapters - {0} + TableVMSCSIControllers = SCSI Controllers - {0} + TableVMHardDiskConfig = Hard Disk Configuration - {0} + TableVMHardDisk = {0} - {1} + TableVMGuestVolumes = Guest Volumes - {0} + TableVMSnapshots = VM Snapshots - {0} +'@ + +# Get-AbrVSphereVUM +GetAbrVSphereVUM = ConvertFrom-StringData @' + InfoLevel = VUM InfoLevel set to {0}. + Collecting = Collecting VMware Update Manager information. + NotAvailable = VUM patch baseline information is not currently available with your version of PowerShell. + PatchNotAvailable = VUM patch information is not currently available with your version of PowerShell. + SectionHeading = VMware Update Manager + ParagraphSummary = The following sections detail the configuration of VMware Update Manager managed by vCenter Server {0}. + Baselines = Baselines + BaselineName = Baseline + Description = Description + Type = Type + TargetType = Target Type + LastUpdate = Last Update Time + NumPatches = # of Patches + Patches = Patches + PatchName = Patch + PatchProduct = Product + PatchDescription = Description + PatchReleaseDate = Release Date + PatchVendorID = Vendor ID + TableVUMBaselines = VMware Update Manager Baseline Summary - {0} + TableVUMPatches = VMware Update Manager Patch Information - {0} +'@ + +} diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 new file mode 100644 index 0000000..93fbfe2 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -0,0 +1,1417 @@ +# culture = 'es-ES' +@{ + +# Module-wide strings +InvokeAsBuiltReportVMwarevSphere = ConvertFrom-StringData @' + Connecting = Conectando al servidor vCenter '{0}'. + CheckPrivileges = Comprobando los privilegios del usuario de vCenter. + UnablePrivileges = No se han podido obtener los privilegios del usuario de vCenter. + VMHashtable = Creando tabla de búsqueda de máquinas virtuales. + VMHostHashtable = Creando tabla de búsqueda de hosts VMware. + DatastoreHashtable = Creando tabla de búsqueda de almacenes de datos. + VDPortGrpHashtable = Creando tabla de búsqueda de grupos de puertos distribuidos. + EVCHashtable = Creando tabla de búsqueda de modos EVC. + CheckVUM = Comprobando el servidor VMware Update Manager. + CheckVxRail = Comprobando el servidor VxRail Manager. + CheckSRM = Comprobando el servidor VMware Site Recovery Manager. + CheckNSXT = Comprobando el servidor VMware NSX-T Manager. + CollectingTags = Recopilando información de etiquetas. + TagError = Error al recopilar información de etiquetas. + CollectingAdvSettings = Recopilando configuración avanzada de {0}. +'@ + +# Get-AbrVSpherevCenter +GetAbrVSpherevCenter = ConvertFrom-StringData @' + InfoLevel = Nivel de información de vCenter establecido en {0}. + Collecting = Recopilando información del servidor vCenter. + SectionHeading = Servidor vCenter + ParagraphSummary = Las siguientes secciones detallan la configuración del servidor vCenter {0}. + InsufficientPrivLicense = Privilegios de usuario insuficientes para informar sobre las licencias del servidor vCenter. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'Global > Licencias'. + InsufficientPrivStoragePolicy = Privilegios de usuario insuficientes para informar sobre las políticas de almacenamiento de máquinas virtuales. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'Perfil de almacenamiento > Ver'. + vCenterServer = Servidor vCenter + IPAddress = Dirección IP + Version = Versión + Build = Compilación + Product = Producto + LicenseKey = Clave de licencia + LicenseExpiration = Vencimiento de licencia + InstanceID = ID de instancia + HTTPPort = Puerto HTTP + HTTPSPort = Puerto HTTPS + PSC = Platform Services Controller + UpdateManagerServer = Servidor Update Manager + SRMServer = Servidor Site Recovery Manager + NSXTServer = Servidor NSX-T Manager + VxRailServer = Servidor VxRail Manager + DatabaseSettings = Configuración de base de datos + DatabaseType = Tipo de base de datos + DataSourceName = Nombre del origen de datos + MaxDBConnection = Conexiones máximas a la base de datos + MailSettings = Configuración de correo + SMTPServer = Servidor SMTP + SMTPPort = Puerto SMTP + MailSender = Remitente de correo + HistoricalStatistics = Estadísticas históricas + IntervalDuration = Duración del intervalo + IntervalEnabled = Intervalo habilitado + SaveDuration = Duración de retención + StatisticsLevel = Nivel de estadísticas + Licensing = Licencias + Total = Total + Used = Usado + Available = Disponible + Expiration = Vencimiento + Certificate = Certificado + Country = País + Email = Correo electrónico + Locality = Localidad + State = Estado + Organization = Organización + OrganizationUnit = Unidad organizativa + Validity = Validez + Mode = Modo + SoftThreshold = Umbral suave + HardThreshold = Umbral estricto + MinutesBefore = Minutos antes + PollInterval = Intervalo de sondeo + Roles = Roles + Role = Rol + SystemRole = Rol del sistema + PrivilegeList = Lista de privilegios + Tags = Etiquetas + TagName = Nombre + TagCategory = Categoría + TagDescription = Descripción + TagCategories = Categorías de etiquetas + TagCardinality = Cardinalidad + TagAssignments = Asignaciones de etiquetas + TagEntity = Entidad + TagEntityType = Tipo de entidad + VMStoragePolicies = Políticas de almacenamiento de máquinas virtuales + StoragePolicy = Política de almacenamiento + Description = Descripción + ReplicationEnabled = Replicación habilitada + CommonRulesName = Nombre de reglas comunes + CommonRulesDescription = Descripción de reglas comunes + Alarms = Alarmas + Alarm = Alarma + AlarmDescription = Descripción + AlarmEnabled = Habilitado + AlarmTriggered = Disparado + AlarmAction = Acción + AdvancedSystemSettings = Configuración avanzada del sistema + Key = Clave + Value = Valor + Enabled = Habilitado + Disabled = Deshabilitado + Yes = Sí + No = No + None = Ninguno + TablevCenterSummary = vCenter Server Summary - {0} + TablevCenterConfig = vCenter Server Configuration - {0} + TableDatabaseSettings = Database Settings - {0} + TableMailSettings = Mail Settings - {0} + TableHistoricalStatistics = Historical Statistics - {0} + TableLicensing = Licensing - {0} + TableCertificate = Certificate - {0} + TableRole = Role {0} - {1} + TableRoles = Roles - {0} + TableTags = Tags - {0} + TableTagCategories = Tag Categories - {0} + TableTagAssignments = Tag Assignments - {0} + TableVMStoragePolicies = VM Storage Policies - {0} + TableAlarm = {0} - {1} + TableAlarms = Alarms - {0} + TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} +'@ + +# Get-AbrVSphereCluster +GetAbrVSphereCluster = ConvertFrom-StringData @' + InfoLevel = Nivel de información de Cluster establecido en {0}. + Collecting = Recopilando información de clústeres. + Processing = Procesando clúster '{0}' ({1}/{2}). + SectionHeading = Clústeres + ParagraphSummary = Las siguientes secciones detallan la configuración de los clústeres HA/DRS de vSphere administrados por el servidor vCenter {0}. + ParagraphDetail = La siguiente tabla detalla la configuración del clúster {0}. + Cluster = Clúster + ID = ID + Datacenter = Centro de datos + NumHosts = N.º de hosts + NumVMs = N.º de máquinas virtuales + HAEnabled = vSphere HA + DRSEnabled = vSphere DRS + VSANEnabled = SAN virtual + EVCMode = Modo EVC + VMSwapFilePolicy = Política de archivo de intercambio de máquina virtual + NumberOfHosts = Número de hosts + NumberOfVMs = Número de máquinas virtuales + Hosts = Hosts + VirtualMachines = Máquinas virtuales + Permissions = Permisos + Principal = Principal + Role = Rol + Propagate = Propagar + IsGroup = Es grupo + Enabled = Habilitado + Disabled = Deshabilitado + SwapWithVM = Con VM + SwapInHostDatastore = En el almacén de datos del host + SwapVMDirectory = Directorio de la máquina virtual + SwapHostDatastore = Almacén de datos especificado por el host + TableClusterSummary = Cluster Summary - {0} + TableClusterConfig = Cluster Configuration - {0} +'@ + +# Get-AbrVSphereClusterHA +GetAbrVSphereClusterHA = ConvertFrom-StringData @' + InfoLevel = Nivel de información de HA del clúster establecido en {0}. + Collecting = Recopilando información de HA del clúster. + SectionHeading = Configuración de vSphere HA + ParagraphSummary = La siguiente sección detalla la configuración de vSphere HA para el clúster {0}. + FailuresAndResponses = Errores y respuestas + HostMonitoring = Supervisión de hosts + HostFailureResponse = Respuesta a fallos de host + HostIsolationResponse = Respuesta al aislamiento de host + VMRestartPriority = Prioridad de reinicio de máquina virtual + PDLProtection = Almacén de datos con pérdida permanente de dispositivo + APDProtection = Almacén de datos con todas las rutas inactivas + VMMonitoring = Supervisión de máquinas virtuales + VMMonitoringSensitivity = Sensibilidad de supervisión de máquinas virtuales + AdmissionControl = Control de admisión + FailoverLevel = Fallos de host tolerados por el clúster + ACPolicy = Política + ACHostPercentage = CPU % + ACMemPercentage = Memoria % + PerformanceDegradation = Degradación del rendimiento de máquinas virtuales + HeartbeatDatastores = Almacenes de datos de latido + Datastore = Almacén de datos + HAAdvancedOptions = Opciones avanzadas de vSphere HA + Key = Clave + Value = Valor + APDRecovery = Recuperación de APD tras tiempo de espera de APD + Disabled = Deshabilitado + Enabled = Habilitado + RestartVMs = Reiniciar VM + ShutdownAndRestart = Apagar y reiniciar VM + PowerOffAndRestart = Apagar y reiniciar VM + IssueEvents = Emitir eventos + PowerOffRestartConservative = Apagar y reiniciar VM (conservador) + PowerOffRestartAggressive = Apagar y reiniciar VM (agresivo) + ResetVMs = Restablecer VM + VMMonitoringOnly = Solo supervisión de VM + VMAndAppMonitoring = Supervisión de VM y aplicaciones + DedicatedFailoverHosts = Hosts de conmutación por error dedicados + ClusterResourcePercentage = Porcentaje de recursos de clúster + SlotPolicy = Política de ranuras + Yes = Sí + No = No + FixedSlotSize = Tamaño de ranura fijo + CoverAllPoweredOnVMs = Cubrir todas las VM encendidas + NoneSpecified = Ninguno especificado + OverrideFailoverCapacity = Override Calculated Failover Capacity + CPUSlotSize = CPU Slot Size (MHz) + MemorySlotSize = Memory Slot Size (MB) + PerfDegradationTolerate = Performance Degradation VMs Tolerate + HeartbeatSelectionPolicy = Heartbeat Selection Policy + HBPolicyAllFeasibleDsWithUserPreference = Use datastores from the specified list and complement automatically if needed + HBPolicyAllFeasibleDs = Automatically select datastores accessible from the host + HBPolicyUserSelectedDs = Use datastores only from the specified list + TableHAFailures = vSphere HA Failures and Responses - {0} + TableHAAdmissionControl = vSphere HA Admission Control - {0} + TableHAHeartbeat = vSphere HA Heartbeat Datastores - {0} + TableHAAdvanced = vSphere HA Advanced Options - {0} +'@ + +# Get-AbrVSphereClusterProactiveHA +GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' + InfoLevel = Nivel de información de HA proactivo del clúster establecido en {0}. + Collecting = Recopilando información de HA proactivo del clúster. + SectionHeading = HA proactivo + ParagraphSummary = La siguiente sección detalla la configuración de HA proactivo para el clúster {0}. + FailuresAndResponses = Errores y respuestas + Provider = Proveedor + Remediation = Corrección + HealthUpdates = Actualizaciones de estado + ProactiveHA = HA proactivo + AutomationLevel = Nivel de automatización + ModerateRemediation = Corrección moderada + SevereRemediation = Corrección grave + Enabled = Habilitado + Disabled = Deshabilitado + MaintenanceMode = Modo de Mantenimiento + QuarantineMode = Modo de Cuarentena + MixedMode = Modo Mixto + TableProactiveHA = Proactive HA - {0} +'@ + +# Get-AbrVSphereClusterDRS +GetAbrVSphereClusterDRS = ConvertFrom-StringData @' + InfoLevel = Nivel de información de DRS del clúster establecido en {0}. + Collecting = Recopilando información de DRS del clúster. + SectionHeading = Configuración de vSphere DRS + ParagraphSummary = La siguiente tabla detalla la configuración de vSphere DRS para el clúster {0}. + AutomationLevel = Nivel de automatización de DRS + MigrationThreshold = Umbral de migración + PredictiveDRS = DRS predictivo + VirtualMachineAutomation = Automatización de máquinas individuales + AdditionalOptions = Opciones adicionales + VMDistribution = Distribución de máquinas virtuales + MemoryMetricForLB = Métrica de memoria para equilibrio de carga + CPUOverCommitment = Sobrecompromiso de CPU + PowerManagement = Gestión de energía + DPMAutomationLevel = Nivel de automatización de DPM + DPMThreshold = Umbral de DPM + AdvancedOptions = Opciones avanzadas + Key = Clave + Value = Valor + DRSClusterGroups = Grupos del clúster DRS + GroupName = Nombre del grupo + GroupType = Tipo de grupo + GroupMembers = Miembros + DRSVMHostRules = Reglas de máquina virtual/host de DRS + RuleName = Nombre de regla + RuleType = Tipo de regla + RuleEnabled = Habilitado + VMGroup = Grupo de máquinas virtuales + HostGroup = Grupo de hosts + DRSRules = Reglas de DRS + RuleVMs = Máquinas virtuales + VMOverrides = Reemplazos de máquinas virtuales + VirtualMachine = Máquina virtual + DRSAutomationLevel = Nivel de automatización de DRS + DRSBehavior = Comportamiento de DRS + HARestartPriority = Prioridad de reinicio de HA + HAIsolationResponse = Respuesta al aislamiento de HA + PDLProtection = Almacén de datos con PDL + APDProtection = Almacén de datos con APD + VMMonitoring = Supervisión de máquinas virtuales + VMMonitoringFailureInterval = Intervalo de fallos + VMMonitoringMinUpTime = Tiempo mínimo de actividad + VMMonitoringMaxFailures = Número máximo de fallos + VMMonitoringMaxFailureWindow = Ventana máxima de fallos + UpdateManagerBaselines = Líneas base de Update Manager + Baseline = Línea base + Description = Descripción + Type = Tipo + TargetType = Tipo de destino + LastUpdate = Última actualización + NumPatches = N.º de parches + UpdateManagerCompliance = Cumplimiento de Update Manager + Entity = Entidad + Status = Estado de cumplimiento + Version = Versión + BaselineInfo = Línea base + VUMPrivilegeMsgBaselines = Privilegios de usuario insuficientes para informar sobre las líneas base del clúster. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gestionar parches y actualizaciones > Ver estado de cumplimiento'. + VUMPrivilegeMsgCompliance = Privilegios de usuario insuficientes para informar sobre el cumplimiento del clúster. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gestionar parches y actualizaciones > Ver estado de cumplimiento'. + VUMBaselineNotAvailable = La información de líneas base de VUM del clúster no está disponible actualmente con su versión de PowerShell. + VUMComplianceNotAvailable = La información de cumplimiento de VUM del clúster no está disponible actualmente con su versión de PowerShell. + Enabled = Habilitado + Disabled = Deshabilitado + Yes = Sí + No = No + FullyAutomated = Totalmente automatizado + PartiallyAutomated = Parcialmente automatizado + Manual = Manual + Off = Desactivado + NotCompliant = No conforme + Unknown = Desconocido + Incompatible = Incompatible + DRS = vSphere DRS + DPM = DPM + Automated = Automated + None = None + VMGroupType = VM Group + VMHostGroupType = Host Group + MustRunOn = Must run on hosts in group + ShouldRunOn = Should run on hosts in group + MustNotRunOn = Must not run on hosts in group + ShouldNotRunOn = Should not run on hosts in group + VMAffinity = Keep Virtual Machines Together + VMAntiAffinity = Separate Virtual Machines + Mandatory = Mandatory + OverCommitmentRatio = Over-Commitment Ratio + OverCommitmentRatioCluster = Over-Commitment Ratio (% of cluster capacity) + Lowest = Lowest + Low = Low + Medium = Medium + High = High + Highest = Highest + ClusterDefault = Cluster default + VMDependencyTimeout = VM Dependency Restart Condition Timeout + Seconds = {0} seconds + SectionVSphereHA = vSphere HA + IssueEvents = Issue events + PowerOffAndRestart = Power off and restart VMs + ShutdownAndRestartVMs = Shutdown and restart VMs + PowerOffRestartConservative = Power off and restart VMs - Conservative restart policy + PowerOffRestartAggressive = Power off and restart VMs - Aggressive restart policy + PDLFailureResponse = PDL Failure Response + APDFailureResponse = APD Failure Response + VMFailoverDelay = VM Failover Delay + Minutes = {0} minutes + ResponseRecovery = Response Recovery + ResetVMs = Reset VMs + SectionPDLAPD = PDL/APD Protection Settings + NoWindow = No window + WithinHours = Within {0} hrs + VMMonitoringOnly = VM Monitoring Only + VMAndAppMonitoring = VM and Application Monitoring + UserGroup = User/Group + IsGroup = Is Group? + Role = Role + DefinedIn = Defined In + Propagate = Propagate + Permissions = Permissions + ParagraphPermissions = The following table details the permissions for {0}. + TableDRSConfig = vSphere DRS Configuration - {0} + TableDRSAdditional = DRS Additional Options - {0} + TableDPM = vSphere DPM - {0} + TableDRSAdvanced = vSphere DRS Advanced Options - {0} + TableDRSGroups = DRS Cluster Groups - {0} + TableDRSVMHostRules = DRS VM/Host Rules - {0} + TableDRSRules = DRS Rules - {0} + TableDRSVMOverrides = DRS VM Overrides - {0} + TableHAVMOverrides = HA VM Overrides - {0} + TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} + TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TablePermissions = Permissions - {0} +'@ + +# Get-AbrVSphereResourcePool +GetAbrVSphereResourcePool = ConvertFrom-StringData @' + InfoLevel = Nivel de información de grupo de recursos establecido en {0}. + Collecting = Recopilando información de grupos de recursos. + Processing = Procesando grupo de recursos '{0}' ({1}/{2}). + SectionHeading = Grupos de recursos + ParagraphSummary = Las siguientes secciones detallan la configuración de los grupos de recursos administrados por el servidor vCenter {0}. + ResourcePool = Grupo de recursos + Parent = Principal + CPUSharesLevel = Nivel de recursos compartidos de CPU + CPUReservationMHz = Reserva de CPU (MHz) + CPULimitMHz = Límite de CPU (MHz) + MemSharesLevel = Nivel de recursos compartidos de memoria + MemReservation = Reserva de memoria + MemLimit = Límite de memoria + ID = ID + NumCPUShares = Número de recursos compartidos de CPU + CPUReservation = Reserva de CPU + CPUExpandable = Reserva ampliable de CPU + NumMemShares = Número de recursos compartidos de memoria + MemExpandable = Reserva ampliable de memoria + NumVMs = Número de máquinas virtuales + VirtualMachines = Máquinas virtuales + Enabled = Habilitado + Disabled = Deshabilitado + Unlimited = Ilimitado + TableResourcePoolSummary = Resource Pool Summary - {0} + TableResourcePoolConfig = Resource Pool Configuration - {0} +'@ + +# Get-AbrVSphereVMHost +GetAbrVSphereVMHost = ConvertFrom-StringData @' + InfoLevel = Nivel de información de host VMware establecido en {0}. + Collecting = Recopilando información de hosts VMware. + Processing = Procesando host VMware '{0}' ({1}/{2}). + SectionHeading = Hosts + ParagraphSummary = Las siguientes secciones detallan la configuración de los hosts VMware ESXi administrados por el servidor vCenter {0}. + VMHost = Host + Manufacturer = Fabricante + Model = Modelo + ProcessorType = Tipo de procesador + NumCPUSockets = Zócalos de CPU + NumCPUCores = Núcleos de CPU + NumCPUThreads = Hilos de CPU + MemoryGB = Memoria (GB) + NumNICs = N.º de NIC + NumHBAs = N.º de HBA + NumDatastores = N.º de almacenes de datos + NumVMs = N.º de máquinas virtuales + ConnectionState = Estado de conexión + PowerState = Estado de energía + ErrorCollecting = Error al recopilar información del host VMware con el nivel de información {0}. + NotResponding = No responde + Maintenance = Mantenimiento + Disconnected = Desconectado + Version = Version + Build = Build + Parent = Parent + TableHostSummary = Host Summary - {0} +'@ + +# Get-AbrVSphereVMHostHardware +GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' + InfoLevel = Nivel de información de hardware del host VMware establecido en {0}. + Collecting = Recopilando información de hardware del host VMware. + SectionHeading = Hardware + ParagraphSummary = La siguiente sección detalla la configuración de hardware del host {0}. + InsufficientPrivLicense = Privilegios de usuario insuficientes para informar sobre las licencias del host ESXi. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'Global > Licencias'. + Specifications = Especificaciones + Manufacturer = Fabricante + Model = Modelo + SerialNumber = Número de serie + AssetTag = Etiqueta de activo + ProcessorType = Tipo de procesador + CPUSockets = Zócalos de CPU + CPUCores = Núcleos de CPU + CPUThreads = Hilos de CPU + HyperthreadingActive = Hyperthreading activo + MemoryGB = Memoria (GB) + NUMANodes = Nodos NUMA + NICs = NIC + HBAs = HBA + Version = Versión + Build = Compilación + LicenseKey = Clave de licencia + Product = Producto + LicenseExpiration = Vencimiento de licencia + IPMIBMC = IPMI / BMC + IPMIBMCError = Error al recopilar información de IPMI/BMC para {0}. + IPMIBMCManufacturer = Fabricante + IPMIBMCType = Tipo + IPMIBMCIPAddress = Dirección IP + IPMIBMCMACAddress = Dirección MAC + BootDevice = Dispositivo de arranque + BootDeviceError = Error al recopilar información del dispositivo de arranque para {0}. + BootDeviceDescription = Descripción + BootDeviceType = Tipo + BootDeviceSize = Tamaño (GB) + BootDeviceIsSAS = Es SAS + BootDeviceIsUSB = Es USB + BootDeviceIsSSD = Es SSD + BootDeviceFromSAN = Desde SAN + PCIDevices = Dispositivos PCI + PCIDeviceId = ID de PCI + PCIVendorName = Nombre del fabricante + PCIDeviceName = Nombre del dispositivo + PCIDriverName = Nombre del controlador + PCIDriverVersion = Versión del controlador + PCIFirmware = Versión de firmware + PCIDriversFirmware = Controladores y firmware de dispositivos PCI + Enabled = Habilitado + Disabled = Deshabilitado + NotApplicable = No aplicable + Unknown = Desconocido + Maintenance = Mantenimiento + NotResponding = Not Responding + Host = Host + ConnectionState = Connection State + ID = ID + Parent = Parent + HyperThreading = HyperThreading + NumberOfCPUSockets = Number of CPU Sockets + NumberOfCPUCores = Number of CPU Cores + NumberOfCPUThreads = Number of CPU Threads + CPUTotalUsedFree = CPU Total / Used / Free + MemoryTotalUsedFree = Memory Total / Used / Free + NumberOfNICs = Number of NICs + NumberOfHBAs = Number of HBAs + NumberOfDatastores = Number of Datastores + NumberOfVMs = Number of VMs + MaximumEVCMode = Maximum EVC Mode + EVCGraphicsMode = EVC Graphics Mode + PowerManagementPolicy = Power Management Policy + ScratchLocation = Scratch Location + BiosVersion = BIOS Version + BiosReleaseDate = BIOS Release Date + BootTime = Boot Time + UptimeDays = Uptime Days + MACAddress = MAC Address + SubnetMask = Subnet Mask + Gateway = Gateway + FirmwareVersion = Firmware Version + Device = Device + BootType = Boot Type + Vendor = Vendor + Size = Size + PCIAddress = PCI Address + DeviceClass = Device Class + TableHardwareConfig = Hardware Configuration - {0} + TableIPMIBMC = IPMI / BMC - {0} + TableBootDevice = Boot Device - {0} + TablePCIDevices = PCI Devices - {0} + TablePCIDriversFirmware = PCI Devices Drivers & Firmware - {0} + PCIDeviceError = Error collecting PCI device information for {0}. {1} + PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} + HardwareError = Error collecting host hardware information for {0}. {1} +'@ + +# Get-AbrVSphereVMHostSystem +GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' + InfoLevel = Nivel de información del sistema del host VMware establecido en {0}. + Collecting = Recopilando información del sistema del host VMware. + HostProfile = Perfil de host + ProfileName = Perfil de host + ProfileCompliance = Cumplimiento + ProfileRemediating = Corrigiendo + ImageProfile = Perfil de imagen + ImageProfileName = Nombre + ImageProfileVendor = Fabricante + ImageProfileAcceptance = Nivel de aceptación + TimeConfiguration = Configuración de hora + NTPServer = Servidor NTP + TimeZone = Zona horaria + Syslog = Syslog + SyslogHost = Host de Syslog + VUMBaseline = Líneas base de Update Manager + VUMBaselineNotAvailable = La información de líneas base de VUM del host VMware no está disponible actualmente con su versión de PowerShell. + VUMBaselineName = Línea base + VUMBaselineType = Tipo + VUMBaselinePatches = N.º de parches + VUMCompliance = Cumplimiento de Update Manager + VUMComplianceNotAvailable = La información de cumplimiento de VUM del host VMware no está disponible actualmente con su versión de PowerShell. + VUMStatus = Estado + AdvancedSettings = Configuración avanzada del sistema + Key = Clave + Value = Valor + VIBs = Paquetes de instalación de VMware (VIB) + VIBName = Nombre + VIBVersion = Versión + VIBVendor = Fabricante + VIBInstallDate = Fecha de instalación + VIBAcceptanceLevel = Nivel de aceptación + SectionHeading = Sistema + ParagraphSummary = La siguiente sección detalla la configuración del sistema del host {0}. + NTPService = Servicio NTP + NTPServers = Servidor(es) NTP + SyslogPort = Puerto + VUMDescription = Descripción + VUMTargetType = Tipo de destino + VUMLastUpdate = Hora de la última actualización + InstallDate = Fecha de instalación + ImageName = Perfil de imagen + ImageVendor = Proveedor + Running = En ejecución + Stopped = Detenido + NotCompliant = No conforme + Unknown = Desconocido + Incompatible = Incompatible + ProfileDescription = Description + VIBID = ID + VIBCreationDate = Creation Date + TableHostProfile = Host Profile - {0} + TableImageProfile = Image Profile - {0} + TableTimeConfig = Time Configuration - {0} + TableSyslog = Syslog Configuration - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TableAdvancedSettings = Advanced System Settings - {0} + TableVIBs = Software VIBs - {0} + HostProfileError = Error collecting host profile information for {0}. {1} + ImageProfileError = Error collecting image profile information for {0}. {1} + TimeConfigError = Error collecting time configuration for {0}. {1} + SyslogError = Error collecting syslog configuration for {0}. {1} + VUMBaselineError = Error collecting Update Manager baseline information for {0}. {1} + VUMComplianceError = Error collecting Update Manager compliance information for {0}. {1} + AdvancedSettingsError = Error collecting host advanced settings information for {0}. {1} + VIBsError = Error collecting software VIB information for {0}. {1} + InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. + InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. +'@ + +# Get-AbrVSphereVMHostStorage +GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' + InfoLevel = Nivel de información de almacenamiento del host VMware establecido en {0}. + Collecting = Recopilando información de almacenamiento del host VMware. + SectionHeading = Almacenamiento + DatastoreSpecs = Especificaciones del almacén de datos + Datastore = Almacén de datos + DatastoreType = Tipo + DatastoreCapacity = Capacidad (GB) + DatastoreFreeSpace = Espacio libre (GB) + DatastoreState = Estado + DatastoreShared = Compartido + StorageAdapters = Adaptadores de almacenamiento + AdapterName = Adaptador + AdapterType = Tipo + AdapterDriver = Controlador + AdapterUID = UID + AdapterModel = Modelo + AdapterStatus = Estado + AdapterSASAddress = Dirección SAS + AdapterWWN = WWN + AdapterDevices = Dispositivos + AdapterTargets = Destinos + ParagraphSummary = La siguiente sección detalla la configuración de almacenamiento del host {0}. + FC = Fibre Channel + iSCSI = iSCSI + ParallelSCSI = SCSI paralelo + ChapNone = Ninguno + ChapUniPreferred = Usar CHAP unidireccional a menos que el destino lo prohíba + ChapUniRequired = Usar CHAP unidireccional si lo requiere el destino + ChapUnidirectional = Usar CHAP unidireccional + ChapBidirectional = Usar CHAP bidireccional + Online = En línea + Offline = Sin conexión + Type = Type + Version = Version + NumberOfVMs = # of VMs + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + TableDatastores = Datastores - {0} + ParagraphStorageAdapters = The following section details the storage adapter configuration for {0}. + AdapterPaths = Paths + iSCSIName = iSCSI Name + iSCSIAlias = iSCSI Alias + AdapterSpeed = Speed + DynamicDiscovery = Dynamic Discovery + StaticDiscovery = Static Discovery + AuthMethod = Authentication Method + CHAPOutgoing = Outgoing CHAP Name + CHAPIncoming = Incoming CHAP Name + AdvancedOptions = Advanced Options + NodeWWN = Node WWN + PortWWN = Port WWN + TableStorageAdapter = Storage Adapter {0} - {1} +'@ + +# Get-AbrVSphereVMHostNetwork +GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' + InfoLevel = Nivel de información de red del host VMware establecido en {0}. + Collecting = Recopilando información de red del host VMware. + SectionHeading = Red + NetworkConfig = Configuración de red + VMKernelAdapters = Adaptadores VMkernel + PhysicalAdapters = Adaptadores físicos + CDP = Protocolo de descubrimiento de Cisco + LLDP = Protocolo de descubrimiento de capa de enlace + StandardvSwitches = Conmutadores virtuales estándar + AdapterName = Adaptador + AdapterMAC = Dirección MAC + AdapterLinkSpeed = Velocidad de enlace + AdapterDriver = Controlador + AdapterPCIID = Dispositivo PCI + AdapterStatus = Estado + VMKName = Adaptador VMkernel + VMKIP = Dirección IP + VMKSubnet = Máscara de subred + VMKGateway = Puerta de enlace predeterminada + VMKMTU = MTU + VMKPortGroup = Grupo de puertos + VMKVirtualSwitch = Conmutador virtual + VMKDHCPEnabled = DHCP habilitado + VMKServicesEnabled = Servicios habilitados + vSwitch = Conmutador virtual + vSwitchPorts = Puertos totales + vSwitchUsedPorts = Puertos usados + vSwitchAvailablePorts = Puertos disponibles + vSwitchMTU = MTU + vSwitchCDPStatus = Estado de CDP + vSwitchSecurity = Política de seguridad + vSwitchFailover = Política de conmutación por error + vSwitchLoadBalancing = Equilibrio de carga + vSwitchPortGroups = Grupos de puertos + PortGroup = Grupo de puertos + VLAN = ID de VLAN + ActiveAdapters = Adaptadores activos + StandbyAdapters = Adaptadores en espera + UnusedAdapters = Adaptadores sin usar + ParagraphSummary = La siguiente sección detalla la configuración de red del host {0}. + Enabled = Habilitado + Disabled = Deshabilitado + Connected = Conectado + Disconnected = Desconectado + Yes = Sí + No = No + Accept = Aceptar + Reject = Rechazar + Supported = Compatible + NotSupported = No compatible + Inherited = Heredado + TCPDefault = Predeterminado + TCPProvisioning = Aprovisionamiento + TCPvMotion = vMotion + TCPNsxOverlay = nsx-overlay + TCPNsxHyperbus = nsx-hyperbus + TCPNotApplicable = No aplicable + LBSrcId = Enrutar basado en el ID del puerto de origen + LBSrcMac = Enrutar basado en el hash MAC de origen + LBIP = Enrutar basado en el hash IP + LBExplicitFailover = Conmutación por error explícita + NFDLinkStatus = Solo estado del enlace + NFDBeaconProbing = Sondeo de baliza + FullDuplex = Dúplex completo + AutoNegotiate = Negociación automática + Down = Inactivo + Host = Host + VirtualSwitches = Virtual Switches + VMkernelGateway = VMkernel Gateway + IPv6 = IPv6 + VMkernelIPv6Gateway = VMkernel IPv6 Gateway + DNSServers = DNS Servers + HostName = Host Name + DomainName = Domain Name + SearchDomain = Search Domain + TableNetworkConfig = Network Configuration - {0} + ParagraphPhysicalAdapters = The following section details the physical network adapter configuration for {0}. + VirtualSwitch = Virtual Switch + ActualSpeedDuplex = Actual Speed, Duplex + ConfiguredSpeedDuplex = Configured Speed, Duplex + WakeOnLAN = Wake on LAN + TablePhysicalAdapter = Physical Adapter {0} - {1} + TablePhysicalAdapters = Physical Adapters - {0} + ParagraphCDP = The following section details the CDP information for {0}. + Status = Status + SystemName = System Name + HardwarePlatform = Hardware Platform + SwitchID = Switch ID + SoftwareVersion = Software Version + ManagementAddress = Management Address + Address = Address + PortID = Port ID + MTU = MTU + TableAdapterCDP = Network Adapter {0} CDP Information - {1} + TableAdaptersCDP = Network Adapter CDP Information - {0} + ParagraphLLDP = The following section details the LLDP information for {0}. + ChassisID = Chassis ID + TimeToLive = Time to live + TimeOut = TimeOut + Samples = Samples + PortDescription = Port Description + SystemDescription = System Description + TableAdapterLLDP = Network Adapter {0} LLDP Information - {1} + TableAdaptersLLDP = Network Adapter LLDP Information - {0} + ParagraphVMKernelAdapters = The following section details the VMkernel adapter configuration for {0}. + NetworkLabel = Network Label + TCPIPStack = TCP/IP Stack + DHCP = DHCP + IPAddress = IP Address + SubnetMask = Subnet Mask + DefaultGateway = Default Gateway + vMotion = vMotion + Provisioning = Provisioning + FTLogging = FT Logging + Management = Management + vSphereReplication = vSphere Replication + vSphereReplicationNFC = vSphere Replication NFC + vSAN = vSAN + vSANWitness = vSAN Witness + vSphereBackupNFC = vSphere Backup NFC + TableVMKernelAdapter = VMkernel Adapter {0} - {1} + ParagraphStandardvSwitches = The following section details the standard virtual switch configuration for {0}. + NumberOfPorts = Number of Ports + NumberOfPortsAvailable = Number of Ports Available + TableStandardvSwitches = Standard Virtual Switches - {0} + VSSecurity = Virtual Switch Security + PromiscuousMode = Promiscuous Mode + MACAddressChanges = MAC Address Changes + ForgedTransmits = Forged Transmits + TableVSSecurity = Virtual Switch Security Policy - {0} + VSTrafficShaping = Virtual Switch Traffic Shaping + AverageBandwidth = Average Bandwidth (kbit/s) + PeakBandwidth = Peak Bandwidth (kbit/s) + BurstSize = Burst Size (KB) + TableVSTrafficShaping = Virtual Switch Traffic Shaping Policy - {0} + VSTeamingFailover = Virtual Switch Teaming & Failover + LoadBalancing = Load Balancing + NetworkFailureDetection = Network Failure Detection + NotifySwitches = Notify Switches + Failback = Failback + ActiveNICs = Active NICs + StandbyNICs = Standby NICs + UnusedNICs = Unused NICs + TableVSTeamingFailover = Virtual Switch Teaming & Failover - {0} + VSPortGroups = Virtual Switch Port Groups + NumberOfVMs = # of VMs + TableVSPortGroups = Virtual Switch Port Groups - {0} + VSPGSecurity = Virtual Switch Port Group Security + MACChanges = MAC Changes + TableVSPGSecurity = Virtual Switch Port Group Security Policy - {0} + VSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping + TableVSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping Policy - {0} + VSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover + TableVSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover - {0} +'@ + +# Get-AbrVSphereVMHostSecurity +GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' + InfoLevel = Nivel de información de seguridad del host VMware establecido en {0}. + Collecting = Recopilando información de seguridad del host VMware. + SectionHeading = Seguridad + LockdownMode = Modo de bloqueo + Services = Servicios + ServiceName = Servicio + ServiceRunning = En ejecución + ServicePolicy = Política de inicio + ServiceRequired = Obligatorio + Firewall = Cortafuegos + FirewallRule = Regla + FirewallAllowed = Hosts permitidos + Authentication = Autenticación + Domain = Dominio + Trust = Dominios de confianza + Membership = Pertenencia al dominio + VMInfoSection = Máquinas virtuales + VMName = Máquina virtual + VMPowerState = Estado de energía + VMIPAddress = Dirección IP + VMOS = Sistema operativo + VMGuestID = ID de invitado + VMCPUs = CPU + VMMemoryGB = Memoria (GB) + VMDisks = Discos (GB) + VMNICs = NIC + StartupShutdown = Inicio/apagado de máquina virtual + StartupEnabled = Inicio habilitado + StartupOrder = Orden de inicio + StartupDelay = Retraso de inicio + StopAction = Acción de detención + StopDelay = Retraso de detención + WaitForHeartbeat = Esperar latido + ParagraphSummary = La siguiente sección detalla la configuración de seguridad del host {0}. + LockdownDisabled = Deshabilitado + LockdownNormal = Habilitado (Normal) + LockdownStrict = Habilitado (Estricto) + Running = En ejecución + Stopped = Detenido + SvcPortUsage = Iniciar y detener con el uso del puerto + SvcWithHost = Iniciar y detener con el host + SvcManually = Iniciar y detener manualmente + On = Encendido + Off = Apagado + Enabled = Habilitado + Disabled = Deshabilitado + WaitHeartbeat = Continuar si VMware Tools está iniciado + WaitDelay = Esperar el retraso de inicio + PowerOff = Apagar + GuestShutdown = Apagado del SO + ToolsOld = Antiguo + ToolsOK = Correcto + ToolsNotRunning = No está en ejecución + ToolsNotInstalled = No instalado + TableLockdownMode = Lockdown Mode - {0} + TableServices = Services - {0} + FirewallService = Service + Status = Status + IncomingPorts = Incoming Ports + OutgoingPorts = Outgoing Ports + Protocols = Protocols + Daemon = Daemon + TableFirewall = Firewall Configuration - {0} + DomainMembership = Domain Membership + TrustedDomains = Trusted Domains + TableAuthentication = Authentication Services - {0} + ParagraphVMInfo = The following section details the virtual machine configuration for {0}. + VMProvisioned = Provisioned + VMUsed = Used + VMHWVersion = HW Version + VMToolsStatus = VM Tools Status + TableVMs = Virtual Machines - {0} + TableStartupShutdown = VM Startup/Shutdown Policy - {0} +'@ + +# Get-AbrVSphereNetwork +GetAbrVSphereNetwork = ConvertFrom-StringData @' + InfoLevel = Nivel de información de red establecido en {0}. + Collecting = Recopilando información de conmutadores distribuidos. + Processing = Procesando conmutador distribuido '{0}' ({1}/{2}). + SectionHeading = Conmutadores distribuidos + ParagraphSummary = Las siguientes secciones detallan la configuración de los conmutadores distribuidos administrados por el servidor vCenter {0}. + VDSwitch = Conmutador distribuido + Datacenter = Centro de datos + Manufacturer = Fabricante + NumUplinks = N.º de uplinks + NumPorts = N.º de puertos + NumHosts = N.º de hosts + NumVMs = N.º de máquinas virtuales + Version = Versión + MTU = MTU + ID = ID + NumberOfUplinks = Número de uplinks + NumberOfPorts = Número de puertos + NumberOfPortGroups = Número de grupos de puertos + NumberOfHosts = Número de hosts + NumberOfVMs = Número de máquinas virtuales + Hosts = Hosts + VirtualMachines = Máquinas virtuales + NIOC = Control de E/S de red + DiscoveryProtocol = Protocolo de descubrimiento + DiscoveryOperation = Operación de descubrimiento + NumPortGroups = N.º de grupos de puertos + MaxPorts = Puertos máximos + Contact = Contacto + Location = Ubicación + VDPortGroups = Grupos de puertos distribuidos + PortGroup = Grupo de puertos + PortGroupType = Tipo de grupo de puertos + VLANID = ID de VLAN + VLANConfiguration = Configuración de VLAN + PortBinding = Vinculación de puertos + Host = Host + UplinkName = Nombre del uplink + UplinkPortGroup = Grupo de puertos de uplink + PhysicalNetworkAdapter = Adaptador de red físico + ActiveUplinks = Uplinks activos + StandbyUplinks = Uplinks en espera + UnusedUplinks = Uplinks sin usar + AllowPromiscuous = Permitir modo promiscuo + ForgedTransmits = Transmisiones falsificadas + MACAddressChanges = Cambios de dirección MAC + Accept = Aceptar + Reject = Rechazar + Direction = Dirección + Status = Estado + AverageBandwidth = Ancho de banda promedio (kbit/s) + PeakBandwidth = Ancho de banda pico (kbit/s) + BurstSize = Tamaño de ráfaga (KB) + LoadBalancing = Equilibrio de carga + LoadBalanceSrcId = Enrutar basado en el ID del puerto de origen + LoadBalanceSrcMac = Enrutar basado en el hash MAC de origen + LoadBalanceIP = Enrutar basado en el hash IP + ExplicitFailover = Conmutación por error explícita + NetworkFailureDetection = Detección de fallo de red + LinkStatus = Solo estado del enlace + BeaconProbing = Sondeo de baliza + NotifySwitches = Notificar conmutadores + FailbackEnabled = Conmutación de retorno habilitada + Yes = Sí + No = No + PrimaryVLANID = ID de VLAN primaria + PrivateVLANType = Tipo de VLAN privada + SecondaryVLANID = ID de VLAN secundaria + UplinkPorts = Puertos de uplink del conmutador distribuido + VDSSecurity = Seguridad del conmutador distribuido + VDSTrafficShaping = Modelado de tráfico del conmutador distribuido + VDSPortGroups = Grupos de puertos del conmutador distribuido + VDSPortGroupSecurity = Seguridad del grupo de puertos del conmutador distribuido + VDSPortGroupTrafficShaping = Modelado de tráfico del grupo de puertos del conmutador distribuido + VDSPortGroupTeaming = Equipos y conmutación por error del grupo de puertos del conmutador distribuido + VDSPrivateVLANs = VLAN privadas del conmutador distribuido + Enabled = Habilitado + Disabled = Deshabilitado + Listen = Escuchar + Advertise = Anunciar + Both = Ambos + TableVDSSummary = Resumen del conmutador distribuido - {0} + TableVDSGeneral = Propiedades generales del conmutador distribuido - {0} + TableVDSUplinkPorts = Puertos de uplink del conmutador distribuido - {0} + TableVDSSecurity = Seguridad del conmutador distribuido - {0} + TableVDSTrafficShaping = Modelado de tráfico del conmutador distribuido - {0} + TableVDSPortGroups = Grupos de puertos del conmutador distribuido - {0} + TableVDSPortGroupSecurity = Seguridad del grupo de puertos del conmutador distribuido - {0} + TableVDSPortGroupTrafficShaping = Modelado de tráfico del grupo de puertos del conmutador distribuido - {0} + TableVDSPortGroupTeaming = Equipos y conmutación por error del grupo de puertos del conmutador distribuido - {0} + TableVDSPrivateVLANs = VLAN privadas del conmutador distribuido - {0} +'@ + +# Get-AbrVSpherevSAN +GetAbrVSpherevSAN = ConvertFrom-StringData @' + InfoLevel = Nivel de información de vSAN establecido en {0}. + Collecting = Recopilando información de vSAN. + CollectingESA = Recopilando información vSAN ESA para '{0}'. + CollectingOSA = Recopilando información vSAN OSA para '{0}'. + CollectingDisks = Recopilando información de discos vSAN para '{0}'. + CollectingDiskGroups = Recopilando información de grupos de discos vSAN para '{0}'. + CollectingiSCSITargets = Recopilando información de destinos iSCSI vSAN para '{0}'. + CollectingiSCSILUNs = Recopilando información de LUN iSCSI vSAN para '{0}'. + Processing = Procesando clúster vSAN '{0}' ({1}/{2}). + SectionHeading = vSAN + ParagraphSummary = Las siguientes secciones detallan la configuración de VMware vSAN administrado por el servidor vCenter {0}. + ParagraphDetail = La siguiente tabla detalla la configuración de vSAN para el clúster {0}. + DisksSection = Discos + DiskGroupsSection = Grupos de discos + iSCSITargetsSection = Destinos iSCSI + iSCSILUNsSection = LUN iSCSI + Cluster = Clúster + StorageType = Tipo de almacenamiento + ClusterType = Tipo de clúster + NumHosts = N.º de hosts + ID = ID + NumberOfHosts = Número de hosts + NumberOfDisks = Número de discos + NumberOfDiskGroups = Número de grupos de discos + DiskClaimMode = Modo de reclamación de disco + PerformanceService = Servicio de rendimiento + FileService = Servicio de archivos + iSCSITargetService = Servicio de destino iSCSI + HistoricalHealthService = Servicio de historial de salud + HealthCheck = Comprobación de estado + TotalCapacity = Capacidad total + UsedCapacity = Capacidad usada + FreeCapacity = Capacidad libre + PercentUsed = % usado + HCLLastUpdated = Última actualización de HCL + Hosts = Hosts + Version = Versión + Stretched = Clúster extendido + VSANEnabled = vSAN habilitado + VSANESAEnabled = vSAN ESA habilitado + DisksFormat = Versión del formato de disco + Deduplication = Deduplicación y compresión + AllFlash = All Flash + FaultDomains = Dominios de fallo + PFTT = Fallos primarios a tolerar + SFTT = Fallos secundarios a tolerar + NetworkDiagnosticMode = Modo de diagnóstico de red + HybridMode = Modo híbrido + AutoRebalance = Reequilibrio automático + ProactiveDisk = Reequilibrio proactivo de disco + ResyncThrottle = Limitación de resincronización + SpaceEfficiency = Eficiencia de espacio + Encryption = Cifrado + FileServiceEnabled = Servicio de archivos habilitado + iSCSITargetEnabled = Destino iSCSI habilitado + VMHostSpecs = Especificaciones de vSAN del host VMware + VMHost = Host VMware + DiskGroup = Grupo de discos + CacheDisks = Discos de caché + DataDisks = Discos de datos + DiskGroups = Grupos de discos + CacheTier = Nivel de caché + DataTier = Nivel de datos + Status = Estado + FaultDomainName = Dominio de fallo + VSANAdvancedOptions = Opciones de configuración avanzada de vSAN + Key = Clave + Value = Valor + IDs = Identificadores + VSAN_UUID = UUID de vSAN + CapacityTier = Nivel de capacidad (GB) + BufferTier = Nivel de búfer (GB) + DiskName = Disco + Name = Nombre + DriveType = Tipo de unidad + Host = Host + State = Estado + Encrypted = Cifrado + Capacity = Capacidad + SerialNumber = Número de serie + Vendor = Fabricante + Model = Modelo + DiskType = Tipo de disco + DiskFormatVersion = Versión del formato de disco + ClaimedAs = Reclamado como + NumDisks = N.º de discos + Type = Tipo + IQN = IQN + Alias = Alias + LUNsCount = LUN + NetworkInterface = Interfaz de red + IOOwnerHost = Host propietario de E/S + TCPPort = Puerto TCP + Health = Estado de salud + StoragePolicy = Política de almacenamiento + ComplianceStatus = Estado de cumplimiento + Authentication = Autenticación + LUNName = LUN + LUNID = ID de LUN + Yes = Sí + No = No + Enabled = Habilitado + Disabled = Deshabilitado + Online = En línea + Offline = Sin conexión + Mounted = Montado + Unmounted = Desmontado + Flash = Flash + HDD = HDD + Cache = Caché + TableVSANClusterSummary = Resumen del clúster vSAN - {0} + TableVSANConfiguration = Configuración de vSAN - {0} + TableDisk = Disco {0} - {1} + TableVSANDisks = Discos vSAN - {0} + TableVSANDiskGroups = Grupos de discos vSAN - {0} + TableVSANiSCSITargets = Destinos iSCSI vSAN - {0} + TableVSANiSCSILUNs = LUN iSCSI vSAN - {0} + ESAError = Error al recopilar información vSAN ESA para '{0}'. {1} + OSAError = Error al recopilar información vSAN OSA para '{0}'. {1} + DiskError = Error al recopilar información de discos vSAN para '{0}'. {1} + DiskGroupError = Error al recopilar información de grupos de discos vSAN para '{0}'. {1} + iSCSITargetError = Error al recopilar información de destinos iSCSI vSAN para '{0}'. {1} + iSCSILUNError = Error al recopilar información de LUN iSCSI vSAN para '{0}'. {1} +'@ + +# Get-AbrVSphereDatastore +GetAbrVSphereDatastore = ConvertFrom-StringData @' + InfoLevel = Nivel de información de almacén de datos establecido en {0}. + Collecting = Recopilando información de almacenes de datos. + Processing = Procesando almacén de datos '{0}' ({1}/{2}). + SectionHeading = Almacenes de datos + ParagraphSummary = Las siguientes secciones detallan la configuración de los almacenes de datos administrados por el servidor vCenter {0}. + Datastore = Almacén de datos + Type = Tipo + DatastoreURL = URL + FileSystem = Versión del sistema de archivos + CapacityGB = Capacidad total (GB) + FreeSpaceGB = Espacio libre (GB) + UsedSpaceGB = Espacio usado (GB) + State = Estado + Accessible = Accesible + NumHosts = N.º de hosts + NumVMs = N.º de máquinas virtuales + SIOC = Control de E/S de almacenamiento + IOLatencyThreshold = Umbral de latencia de E/S (ms) + IOLoadBalancing = Equilibrio de carga de E/S + Enabled = Habilitado + Disabled = Deshabilitado + Normal = Normal + Maintenance = Mantenimiento + Unmounted = Desmontado + ID = ID + Datacenter = Centro de datos + Version = Versión + NumberOfHosts = Número de hosts + NumberOfVMs = Número de máquinas virtuales + CongestionThreshold = Umbral de congestión + TotalCapacity = Capacidad total + UsedCapacity = Capacidad usada + FreeCapacity = Capacidad libre + PercentUsed = % usado + Hosts = Hosts + VirtualMachines = Máquinas virtuales + SCSILUNInfo = Información de LUN SCSI + Host = Host + CanonicalName = Nombre canónico + Capacity = Capacidad + Vendor = Fabricante + Model = Modelo + IsSSD = Es SSD + MultipathPolicy = Política multiruta + Paths = Rutas + TableDatastoreSummary = Resumen del almacén de datos - {0} + TableDatastoreConfig = Configuración del almacén de datos - {0} + TableSCSILUN = Información de LUN SCSI - {0} +'@ + +# Get-AbrVSphereDSCluster +GetAbrVSphereDSCluster = ConvertFrom-StringData @' + InfoLevel = Nivel de información de clúster de almacenes de datos establecido en {0}. + Collecting = Recopilando información de clústeres de almacenes de datos. + Processing = Procesando clúster de almacenes de datos '{0}' ({1}/{2}). + SectionHeading = Clústeres de almacenes de datos + ParagraphSummary = Las siguientes secciones detallan la configuración de los clústeres de almacenes de datos administrados por el servidor vCenter {0}. + DSCluster = Clúster de almacenes de datos + Datacenter = Centro de datos + CapacityGB = Capacidad total (GB) + FreeSpaceGB = Espacio libre (GB) + SDRSEnabled = SDRS + SDRSAutomationLevel = Nivel de automatización de SDRS + IOLoadBalancing = Equilibrio de carga de E/S + SpaceThreshold = Umbral de espacio (%) + IOLatencyThreshold = Umbral de latencia de E/S (ms) + SDRSRules = Reglas de SDRS + RuleName = Regla + RuleEnabled = Habilitado + RuleVMs = Máquinas virtuales + FullyAutomated = Totalmente automatizado + NoAutomation = Sin automatización (Modo manual) + Manual = Manual + Enabled = Habilitado + Disabled = Deshabilitado + Yes = Sí + No = No + ID = ID + TotalCapacity = Capacidad total + UsedCapacity = Capacidad usada + FreeCapacity = Capacidad libre + PercentUsed = % usado + ParagraphDSClusterDetail = La siguiente tabla detalla la configuración del clúster de almacenes de datos {0}. + TableDSClusterConfig = Configuración del clúster de almacenes de datos - {0} + TableSDRSVMOverrides = Excepciones de máquinas virtuales de SDRS - {0} + SDRSVMOverrides = Excepciones de máquinas virtuales de SDRS + VirtualMachine = Máquina virtual + KeepVMDKsTogether = Mantener VMDK juntos + DefaultBehavior = Predeterminado ({0}) +'@ + +# Get-AbrVSphereVM +GetAbrVSphereVM = ConvertFrom-StringData @' + InfoLevel = Nivel de información de máquina virtual establecido en {0}. + Collecting = Recopilando información de máquinas virtuales. + Processing = Procesando máquina virtual '{0}' ({1}/{2}). + SectionHeading = Máquinas virtuales + ParagraphSummary = Las siguientes secciones detallan la configuración de las máquinas virtuales administradas por el servidor vCenter {0}. + VirtualMachine = Máquina virtual + PowerState = Estado de energía + Template = Plantilla + OS = Sistema operativo + Version = Versión de hardware de máquina virtual + GuestID = ID de invitado + Cluster = Clúster + ResourcePool = Grupo de recursos + VMHost = Host + Folder = Carpeta + IPAddress = Dirección IP + CPUs = CPU + MemoryGB = Memoria (GB) + ProvisionedGB = Aprovisionado (GB) + UsedGB = Usado (GB) + NumDisks = N.º de discos + NumNICs = N.º de NIC + NumSnapshots = N.º de instantáneas + VMwareTools = VMware Tools + ToolsVersion = Versión de Tools + ToolsStatus = Estado de Tools + ToolsRunningStatus = Estado de ejecución de Tools + VMAdvancedDetail = Configuración avanzada + BootOptions = Opciones de arranque + BootDelay = Retraso de arranque (ms) + BootRetryEnabled = Reintento de arranque habilitado + BootRetryDelay = Retraso de reintento de arranque (ms) + EFISecureBoot = Arranque seguro EFI + EnterBIOSSetup = Acceder a la configuración del BIOS en el próximo arranque + HardDisks = Discos duros + DiskName = Disco + DiskCapacityGB = Capacidad (GB) + DiskFormat = Formato + DiskStoragePolicy = Política de almacenamiento + DiskDatastore = Almacén de datos + DiskController = Controlador + NetworkAdapters = Adaptadores de red + NICName = Adaptador de red + NICType = Tipo de adaptador + NICPortGroup = Grupo de puertos + NICMAC = Dirección MAC + NICConnected = Conectado + NICConnectionPolicy = Conectar al encender + SnapshotHeading = Instantáneas + SnapshotName = Instantánea + SnapshotDescription = Descripción + SnapshotSize = Tamaño (GB) + SnapshotDate = Creado + SnapshotParent = Principal + On = Encendido + Off = Apagado + ToolsOld = Antiguo + ToolsOK = Correcto + ToolsNotRunning = No está en ejecución + ToolsNotInstalled = No instalado + Yes = Sí + No = No + Connected = Conectado + NotConnected = No conectado + Thin = Aprovisionamiento ligero + Thick = Aprovisionamiento grueso + Enabled = Habilitado + Disabled = Deshabilitado + TotalVMs = Total de máquinas virtuales + TotalvCPUs = Total de vCPU + TotalMemory = Memoria total + TotalProvisionedSpace = Espacio total aprovisionado + TotalUsedSpace = Espacio total usado + VMsPoweredOn = Máquinas virtuales encendidas + VMsPoweredOff = Máquinas virtuales apagadas + VMsOrphaned = Máquinas virtuales huérfanas + VMsInaccessible = Máquinas virtuales inaccesibles + VMsSuspended = Máquinas virtuales suspendidas + VMsWithSnapshots = Máquinas virtuales con instantáneas + GuestOSTypes = Tipos de sistema operativo invitado + VMToolsOKCount = Herramientas de VM correctas + VMToolsOldCount = Herramientas de VM antiguas + VMToolsNotRunningCount = Herramientas de VM no en ejecución + VMToolsNotInstalledCount = Herramientas de VM no instaladas + vCPUs = vCPU + Memory = Memoria + Provisioned = Aprovisionado + Used = Usado + HWVersion = Versión de hardware + VMToolsStatus = Estado de herramientas de VM + ID = ID + OperatingSystem = Sistema operativo + HardwareVersion = Versión de hardware + ConnectionState = Estado de conexión + FaultToleranceState = Estado de tolerancia a fallos + FTNotConfigured = No configurado + FTNeedsSecondary = Necesita secundario + FTRunning = En ejecución + FTDisabled = Deshabilitado + FTStarting = Iniciando + FTEnabled = Habilitado + Parent = Principal + ParentFolder = Carpeta principal + ParentResourcePool = Grupo de recursos principal + CoresPerSocket = Núcleos por socket + CPUShares = Recursos de CPU + CPUReservation = Reserva de CPU + CPULimit = Límite de CPU + CPUHotAdd = Adición en caliente de CPU + CPUHotRemove = Eliminación en caliente de CPU + MemoryAllocation = Asignación de memoria + MemoryShares = Recursos de memoria + MemoryHotAdd = Adición en caliente de memoria + vNICs = vNIC + DNSName = Nombre DNS + Networks = Redes + MACAddress = Dirección MAC + vDisks = Discos virtuales + ProvisionedSpace = Espacio aprovisionado + UsedSpace = Espacio usado + ChangedBlockTracking = Seguimiento de bloques modificados + StorageBasedPolicy = Política basada en almacenamiento + StorageBasedPolicyCompliance = Cumplimiento de política basada en almacenamiento + Compliant = Conforme + NonCompliant = No conforme + Unknown = Desconocido + Notes = Notas + BootTime = Hora de arranque + UptimeDays = Días de actividad + NetworkName = Nombre de red + SCSIControllers = Controladores SCSI + Device = Dispositivo + ControllerType = Tipo de controlador + BusSharing = Uso compartido del bus + None = Ninguno + GuestVolumes = Volúmenes del invitado + Capacity = Capacidad + DiskProvisioning = Aprovisionamiento de disco + ThickEagerZeroed = Grosor con cero por adelantado + ThickLazyZeroed = Grosor con cero por demanda + DiskType = Tipo de disco + PhysicalRDM = RDM físico + VirtualRDM = RDM virtual + VMDK = VMDK + DiskMode = Modo de disco + IndependentPersistent = Independiente - Persistente + IndependentNonpersistent = Independiente - No persistente + Dependent = Dependiente + DiskPath = Ruta de disco + DiskShares = Recursos de disco + DiskLimitIOPs = Límite de IOPS de disco + Unlimited = Ilimitado + SCSIController = Controlador SCSI + SCSIAddress = Dirección SCSI + Path = Ruta + FreeSpace = Espacio libre + DaysOld = Días de antigüedad + TableVMSummary = Resumen de máquinas virtuales - {0} + TableVMAdvancedSummary = Resumen avanzado de máquinas virtuales - {0} + TableVMSnapshotSummary = Resumen de instantáneas de máquinas virtuales - {0} + TableVMConfig = Configuración de máquinas virtuales - {0} + TableVMNetworkAdapters = Adaptadores de red - {0} + TableVMSCSIControllers = Controladores SCSI - {0} + TableVMHardDiskConfig = Configuración de disco duro - {0} + TableVMHardDisk = {0} - {1} + TableVMGuestVolumes = Volúmenes del invitado - {0} + TableVMSnapshots = Instantáneas de máquinas virtuales - {0} +'@ + +# Get-AbrVSphereVUM +GetAbrVSphereVUM = ConvertFrom-StringData @' + InfoLevel = Nivel de información de VUM establecido en {0}. + Collecting = Recopilando información de VMware Update Manager. + NotAvailable = La información de líneas base de parches de VUM no está disponible actualmente con su versión de PowerShell. + PatchNotAvailable = La información de parches de VUM no está disponible actualmente con su versión de PowerShell. + SectionHeading = VMware Update Manager + ParagraphSummary = Las siguientes secciones detallan la configuración de VMware Update Manager administrado por el servidor vCenter {0}. + Baselines = Líneas base + BaselineName = Línea base + Description = Descripción + Type = Tipo + TargetType = Tipo de destino + LastUpdate = Última actualización + NumPatches = N.º de parches + Patches = Parches + PatchName = Parche + PatchProduct = Producto + PatchDescription = Descripción + PatchReleaseDate = Fecha de lanzamiento + PatchVendorID = ID de fabricante + TableVUMBaselines = Resumen de líneas base de VMware Update Manager - {0} + TableVUMPatches = Información de parches de VMware Update Manager - {0} +'@ + +} \ No newline at end of file diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 new file mode 100644 index 0000000..5d1e5cd --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -0,0 +1,1417 @@ +# culture = 'fr-FR' +@{ + +# Module-wide strings +InvokeAsBuiltReportVMwarevSphere = ConvertFrom-StringData @' + Connecting = Connexion au serveur vCenter '{0}'. + CheckPrivileges = Vérification des privilèges de l'utilisateur vCenter. + UnablePrivileges = Impossible d'obtenir les privilèges de l'utilisateur vCenter. + VMHashtable = Création de la table de correspondance des machines virtuelles. + VMHostHashtable = Création de la table de correspondance des hôtes VMHost. + DatastoreHashtable = Création de la table de correspondance des magasins de données. + VDPortGrpHashtable = Création de la table de correspondance des groupes de ports distribués. + EVCHashtable = Création de la table de correspondance EVC. + CheckVUM = Vérification de la présence du serveur VMware Update Manager. + CheckVxRail = Vérification de la présence du serveur VxRail Manager. + CheckSRM = Vérification de la présence du serveur VMware Site Recovery Manager. + CheckNSXT = Vérification de la présence du serveur VMware NSX-T Manager. + CollectingTags = Collecte des informations sur les étiquettes. + TagError = Erreur lors de la collecte des informations sur les étiquettes. + CollectingAdvSettings = Collecte des paramètres avancés de {0}. +'@ + +# Get-AbrVSpherevCenter +GetAbrVSpherevCenter = ConvertFrom-StringData @' + InfoLevel = Niveau d'information vCenter défini sur {0}. + Collecting = Collecte des informations du serveur vCenter. + SectionHeading = Serveur vCenter + ParagraphSummary = Les sections suivantes détaillent la configuration du serveur vCenter {0}. + InsufficientPrivLicense = Privilèges utilisateur insuffisants pour rapporter les licences du serveur vCenter. Veuillez vous assurer que le compte utilisateur dispose du privilège 'Global > Licences'. + InsufficientPrivStoragePolicy = Privilèges utilisateur insuffisants pour rapporter les stratégies de stockage des machines virtuelles. Veuillez vous assurer que le compte utilisateur dispose du privilège 'Profil de stockage > Afficher'. + vCenterServer = Serveur vCenter + IPAddress = Adresse IP + Version = Version + Build = Build + Product = Produit + LicenseKey = Clé de licence + LicenseExpiration = Expiration de la licence + InstanceID = ID d'instance + HTTPPort = Port HTTP + HTTPSPort = Port HTTPS + PSC = Contrôleur de services de plateforme + UpdateManagerServer = Serveur Update Manager + SRMServer = Serveur Site Recovery Manager + NSXTServer = Serveur NSX-T Manager + VxRailServer = Serveur VxRail Manager + DatabaseSettings = Paramètres de base de données + DatabaseType = Type de base de données + DataSourceName = Nom de la source de données + MaxDBConnection = Connexions maximales à la base de données + MailSettings = Paramètres de messagerie + SMTPServer = Serveur SMTP + SMTPPort = Port SMTP + MailSender = Expéditeur + HistoricalStatistics = Statistiques historiques + IntervalDuration = Durée de l'intervalle + IntervalEnabled = Intervalle activé + SaveDuration = Durée de conservation + StatisticsLevel = Niveau des statistiques + Licensing = Licences + Total = Total + Used = Utilisé + Available = Disponible + Expiration = Expiration + Certificate = Certificat + Country = Pays + Email = E-mail + Locality = Localité + State = État + Organization = Organisation + OrganizationUnit = Unité organisationnelle + Validity = Validité + Mode = Mode + SoftThreshold = Seuil souple + HardThreshold = Seuil strict + MinutesBefore = Minutes avant + PollInterval = Intervalle d'interrogation + Roles = Rôles + Role = Rôle + SystemRole = Rôle système + PrivilegeList = Liste des privilèges + Tags = Étiquettes + TagName = Nom + TagCategory = Catégorie + TagDescription = Description + TagCategories = Catégories d'étiquettes + TagCardinality = Cardinalité + TagAssignments = Affectations d'étiquettes + TagEntity = Entité + TagEntityType = Type d'entité + VMStoragePolicies = Stratégies de stockage des machines virtuelles + StoragePolicy = Stratégie de stockage + Description = Description + ReplicationEnabled = Réplication activée + CommonRulesName = Nom des règles communes + CommonRulesDescription = Description des règles communes + Alarms = Alarmes + Alarm = Alarme + AlarmDescription = Description + AlarmEnabled = Activée + AlarmTriggered = Déclenchée + AlarmAction = Action + AdvancedSystemSettings = Paramètres système avancés + Key = Clé + Value = Valeur + Enabled = Activé + Disabled = Désactivé + Yes = Oui + No = Non + None = Aucun + TablevCenterSummary = vCenter Server Summary - {0} + TablevCenterConfig = vCenter Server Configuration - {0} + TableDatabaseSettings = Database Settings - {0} + TableMailSettings = Mail Settings - {0} + TableHistoricalStatistics = Historical Statistics - {0} + TableLicensing = Licensing - {0} + TableCertificate = Certificate - {0} + TableRole = Role {0} - {1} + TableRoles = Roles - {0} + TableTags = Tags - {0} + TableTagCategories = Tag Categories - {0} + TableTagAssignments = Tag Assignments - {0} + TableVMStoragePolicies = VM Storage Policies - {0} + TableAlarm = {0} - {1} + TableAlarms = Alarms - {0} + TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} +'@ + +# Get-AbrVSphereCluster +GetAbrVSphereCluster = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Cluster défini sur {0}. + Collecting = Collecte des informations sur les clusters. + Processing = Traitement du cluster '{0}' ({1}/{2}). + SectionHeading = Clusters + ParagraphSummary = Les sections suivantes détaillent la configuration des clusters vSphere HA/DRS gérés par le serveur vCenter {0}. + ParagraphDetail = Le tableau suivant détaille la configuration du cluster {0}. + Cluster = Cluster + ID = ID + Datacenter = Centre de données + NumHosts = Nb d'hôtes + NumVMs = Nb de machines virtuelles + HAEnabled = vSphere HA + DRSEnabled = vSphere DRS + VSANEnabled = SAN virtuel + EVCMode = Mode EVC + VMSwapFilePolicy = Stratégie de fichier d'échange des MV + NumberOfHosts = Nombre d'hôtes + NumberOfVMs = Nombre de machines virtuelles + Hosts = Hôtes + VirtualMachines = Machines virtuelles + Permissions = Autorisations + Principal = Principal + Role = Rôle + Propagate = Propager + IsGroup = Est un groupe + Enabled = Activé + Disabled = Désactivé + SwapWithVM = Avec la VM + SwapInHostDatastore = Dans la banque de données de l'hôte + SwapVMDirectory = Répertoire de la machine virtuelle + SwapHostDatastore = Banque de données spécifiée par l'hôte + TableClusterSummary = Cluster Summary - {0} + TableClusterConfig = Cluster Configuration - {0} +'@ + +# Get-AbrVSphereClusterHA +GetAbrVSphereClusterHA = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Cluster HA défini sur {0}. + Collecting = Collecte des informations sur le Cluster HA. + SectionHeading = Configuration de vSphere HA + ParagraphSummary = La section suivante détaille la configuration de vSphere HA pour le cluster {0}. + FailuresAndResponses = Pannes et réponses + HostMonitoring = Surveillance des hôtes + HostFailureResponse = Réponse aux pannes d'hôtes + HostIsolationResponse = Réponse à l'isolation des hôtes + VMRestartPriority = Priorité de redémarrage des MV + PDLProtection = Magasin de données avec perte permanente de périphérique + APDProtection = Magasin de données avec tous les chemins inactifs + VMMonitoring = Surveillance des MV + VMMonitoringSensitivity = Sensibilité de la surveillance des MV + AdmissionControl = Contrôle d'admission + FailoverLevel = Pannes d'hôtes tolérées par le cluster + ACPolicy = Stratégie + ACHostPercentage = % CPU + ACMemPercentage = % Mémoire + PerformanceDegradation = Dégradation des performances des MV + HeartbeatDatastores = Magasins de données de pulsation + Datastore = Magasin de données + HAAdvancedOptions = Options avancées de vSphere HA + Key = Clé + Value = Valeur + APDRecovery = Récupération APD après expiration du délai APD + Disabled = Désactivé + Enabled = Activé + RestartVMs = Redémarrer les VM + ShutdownAndRestart = Arrêter et redémarrer les VM + PowerOffAndRestart = Éteindre et redémarrer les VM + IssueEvents = Générer des événements + PowerOffRestartConservative = Éteindre et redémarrer les VM (conservateur) + PowerOffRestartAggressive = Éteindre et redémarrer les VM (agressif) + ResetVMs = Réinitialiser les VM + VMMonitoringOnly = Surveillance VM uniquement + VMAndAppMonitoring = Surveillance VM et applications + DedicatedFailoverHosts = Hôtes de basculement dédiés + ClusterResourcePercentage = Pourcentage des ressources du cluster + SlotPolicy = Politique d'emplacement + Yes = Oui + No = Non + FixedSlotSize = Taille d'emplacement fixe + CoverAllPoweredOnVMs = Couvrir toutes les machines virtuelles sous tension + NoneSpecified = Aucun spécifié + OverrideFailoverCapacity = Override Calculated Failover Capacity + CPUSlotSize = CPU Slot Size (MHz) + MemorySlotSize = Memory Slot Size (MB) + PerfDegradationTolerate = Performance Degradation VMs Tolerate + HeartbeatSelectionPolicy = Heartbeat Selection Policy + HBPolicyAllFeasibleDsWithUserPreference = Use datastores from the specified list and complement automatically if needed + HBPolicyAllFeasibleDs = Automatically select datastores accessible from the host + HBPolicyUserSelectedDs = Use datastores only from the specified list + TableHAFailures = vSphere HA Failures and Responses - {0} + TableHAAdmissionControl = vSphere HA Admission Control - {0} + TableHAHeartbeat = vSphere HA Heartbeat Datastores - {0} + TableHAAdvanced = vSphere HA Advanced Options - {0} +'@ + +# Get-AbrVSphereClusterProactiveHA +GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Cluster HA proactif défini sur {0}. + Collecting = Collecte des informations sur le Cluster HA proactif. + SectionHeading = HA proactif + ParagraphSummary = La section suivante détaille la configuration du HA proactif pour le cluster {0}. + FailuresAndResponses = Pannes et réponses + Provider = Fournisseur + Remediation = Remédiation + HealthUpdates = Mises à jour de l'état de santé + ProactiveHA = HA proactive + AutomationLevel = Niveau d'automatisation + ModerateRemediation = Correction modérée + SevereRemediation = Correction grave + Enabled = Activé + Disabled = Désactivé + MaintenanceMode = Mode Maintenance + QuarantineMode = Mode Quarantaine + MixedMode = Mode Mixte + TableProactiveHA = Proactive HA - {0} +'@ + +# Get-AbrVSphereClusterDRS +GetAbrVSphereClusterDRS = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Cluster DRS défini sur {0}. + Collecting = Collecte des informations sur le Cluster DRS. + SectionHeading = Configuration de vSphere DRS + ParagraphSummary = Le tableau suivant détaille la configuration de vSphere DRS pour le cluster {0}. + AutomationLevel = Niveau d'automatisation DRS + MigrationThreshold = Seuil de migration + PredictiveDRS = DRS prédictif + VirtualMachineAutomation = Automatisation individuelle des machines + AdditionalOptions = Options supplémentaires + VMDistribution = Distribution des MV + MemoryMetricForLB = Métrique mémoire pour l'équilibrage de charge + CPUOverCommitment = Sur-engagement CPU + PowerManagement = Gestion de l'alimentation + DPMAutomationLevel = Niveau d'automatisation DPM + DPMThreshold = Seuil DPM + AdvancedOptions = Options avancées + Key = Clé + Value = Valeur + DRSClusterGroups = Groupes de clusters DRS + GroupName = Nom du groupe + GroupType = Type de groupe + GroupMembers = Membres + DRSVMHostRules = Règles MV/Hôte DRS + RuleName = Nom de la règle + RuleType = Type de règle + RuleEnabled = Activée + VMGroup = Groupe de MV + HostGroup = Groupe d'hôtes + DRSRules = Règles DRS + RuleVMs = Machines virtuelles + VMOverrides = Remplacements de MV + VirtualMachine = Machine virtuelle + DRSAutomationLevel = Niveau d'automatisation DRS + DRSBehavior = Comportement DRS + HARestartPriority = Priorité de redémarrage HA + HAIsolationResponse = Réponse à l'isolation HA + PDLProtection = Magasin de données avec PDL + APDProtection = Magasin de données avec APD + VMMonitoring = Surveillance des MV + VMMonitoringFailureInterval = Intervalle d'échec + VMMonitoringMinUpTime = Temps de fonctionnement minimum + VMMonitoringMaxFailures = Nombre maximum d'échecs + VMMonitoringMaxFailureWindow = Fenêtre d'échec maximale + UpdateManagerBaselines = Lignes de base Update Manager + Baseline = Ligne de base + Description = Description + Type = Type + TargetType = Type de cible + LastUpdate = Heure de la dernière mise à jour + NumPatches = Nb de correctifs + UpdateManagerCompliance = Conformité Update Manager + Entity = Entité + Status = État de conformité + Version = Version + BaselineInfo = Ligne de base + VUMPrivilegeMsgBaselines = Privilèges utilisateur insuffisants pour rapporter les lignes de base du cluster. Veuillez vous assurer que le compte utilisateur dispose du privilège 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gérer les correctifs et les mises à niveau > Afficher l'état de conformité'. + VUMPrivilegeMsgCompliance = Privilèges utilisateur insuffisants pour rapporter la conformité du cluster. Veuillez vous assurer que le compte utilisateur dispose du privilège 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gérer les correctifs et les mises à niveau > Afficher l'état de conformité'. + VUMBaselineNotAvailable = Les informations de ligne de base VUM du cluster ne sont pas disponibles avec votre version de PowerShell. + VUMComplianceNotAvailable = Les informations de conformité VUM du cluster ne sont pas disponibles avec votre version de PowerShell. + Enabled = Activé + Disabled = Désactivé + Yes = Oui + No = Non + FullyAutomated = Entièrement automatisé + PartiallyAutomated = Partiellement automatisé + Manual = Manuel + Off = Désactivé + NotCompliant = Non conforme + Unknown = Inconnu + Incompatible = Incompatible + DRS = vSphere DRS + DPM = DPM + Automated = Automated + None = None + VMGroupType = VM Group + VMHostGroupType = Host Group + MustRunOn = Must run on hosts in group + ShouldRunOn = Should run on hosts in group + MustNotRunOn = Must not run on hosts in group + ShouldNotRunOn = Should not run on hosts in group + VMAffinity = Keep Virtual Machines Together + VMAntiAffinity = Separate Virtual Machines + Mandatory = Mandatory + OverCommitmentRatio = Over-Commitment Ratio + OverCommitmentRatioCluster = Over-Commitment Ratio (% of cluster capacity) + Lowest = Lowest + Low = Low + Medium = Medium + High = High + Highest = Highest + ClusterDefault = Cluster default + VMDependencyTimeout = VM Dependency Restart Condition Timeout + Seconds = {0} seconds + SectionVSphereHA = vSphere HA + IssueEvents = Issue events + PowerOffAndRestart = Power off and restart VMs + ShutdownAndRestartVMs = Shutdown and restart VMs + PowerOffRestartConservative = Power off and restart VMs - Conservative restart policy + PowerOffRestartAggressive = Power off and restart VMs - Aggressive restart policy + PDLFailureResponse = PDL Failure Response + APDFailureResponse = APD Failure Response + VMFailoverDelay = VM Failover Delay + Minutes = {0} minutes + ResponseRecovery = Response Recovery + ResetVMs = Reset VMs + SectionPDLAPD = PDL/APD Protection Settings + NoWindow = No window + WithinHours = Within {0} hrs + VMMonitoringOnly = VM Monitoring Only + VMAndAppMonitoring = VM and Application Monitoring + UserGroup = User/Group + IsGroup = Is Group? + Role = Role + DefinedIn = Defined In + Propagate = Propagate + Permissions = Permissions + ParagraphPermissions = The following table details the permissions for {0}. + TableDRSConfig = vSphere DRS Configuration - {0} + TableDRSAdditional = DRS Additional Options - {0} + TableDPM = vSphere DPM - {0} + TableDRSAdvanced = vSphere DRS Advanced Options - {0} + TableDRSGroups = DRS Cluster Groups - {0} + TableDRSVMHostRules = DRS VM/Host Rules - {0} + TableDRSRules = DRS Rules - {0} + TableDRSVMOverrides = DRS VM Overrides - {0} + TableHAVMOverrides = HA VM Overrides - {0} + TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} + TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TablePermissions = Permissions - {0} +'@ + +# Get-AbrVSphereResourcePool +GetAbrVSphereResourcePool = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Pool de ressources défini sur {0}. + Collecting = Collecte des informations sur les pools de ressources. + Processing = Traitement du pool de ressources '{0}' ({1}/{2}). + SectionHeading = Pools de ressources + ParagraphSummary = Les sections suivantes détaillent la configuration des pools de ressources gérés par le serveur vCenter {0}. + ResourcePool = Pool de ressources + Parent = Parent + CPUSharesLevel = Niveau de partage CPU + CPUReservationMHz = Réservation CPU (MHz) + CPULimitMHz = Limite CPU (MHz) + MemSharesLevel = Niveau de partage mémoire + MemReservation = Réservation mémoire + MemLimit = Limite mémoire + ID = ID + NumCPUShares = Nombre de partages CPU + CPUReservation = Réservation CPU + CPUExpandable = Réservation CPU extensible + NumMemShares = Nombre de partages mémoire + MemExpandable = Réservation mémoire extensible + NumVMs = Nombre de MV + VirtualMachines = Machines virtuelles + Enabled = Activé + Disabled = Désactivé + Unlimited = Illimité + TableResourcePoolSummary = Resource Pool Summary - {0} + TableResourcePoolConfig = Resource Pool Configuration - {0} +'@ + +# Get-AbrVSphereVMHost +GetAbrVSphereVMHost = ConvertFrom-StringData @' + InfoLevel = Niveau d'information VMHost défini sur {0}. + Collecting = Collecte des informations sur les hôtes VMHost. + Processing = Traitement de l'hôte VMHost '{0}' ({1}/{2}). + SectionHeading = Hôtes + ParagraphSummary = Les sections suivantes détaillent la configuration des hôtes VMware ESXi gérés par le serveur vCenter {0}. + VMHost = Hôte + Manufacturer = Fabricant + Model = Modèle + ProcessorType = Type de processeur + NumCPUSockets = Sockets CPU + NumCPUCores = Cœurs CPU + NumCPUThreads = Threads CPU + MemoryGB = Mémoire (Go) + NumNICs = Nb de cartes réseau + NumHBAs = Nb de HBA + NumDatastores = Nb de magasins de données + NumVMs = Nb de MV + ConnectionState = État de connexion + PowerState = État d'alimentation + ErrorCollecting = Erreur lors de la collecte des informations VMHost avec le niveau d'information VMHost {0}. + NotResponding = Ne répond pas + Maintenance = Maintenance + Disconnected = Déconnecté + Version = Version + Build = Build + Parent = Parent + TableHostSummary = Host Summary - {0} +'@ + +# Get-AbrVSphereVMHostHardware +GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Matériel VMHost défini sur {0}. + Collecting = Collecte des informations matérielles de l'hôte VMHost. + SectionHeading = Matériel + ParagraphSummary = La section suivante détaille la configuration matérielle de l'hôte {0}. + InsufficientPrivLicense = Privilèges utilisateur insuffisants pour rapporter les licences de l'hôte ESXi. Veuillez vous assurer que le compte utilisateur dispose du privilège 'Global > Licences'. + Specifications = Spécifications + Manufacturer = Fabricant + Model = Modèle + SerialNumber = Numéro de série + AssetTag = Étiquette d'inventaire + ProcessorType = Type de processeur + CPUSockets = Sockets CPU + CPUCores = Cœurs CPU + CPUThreads = Threads CPU + HyperthreadingActive = Hyperthreading actif + MemoryGB = Mémoire (Go) + NUMANodes = Nœuds NUMA + NICs = Cartes réseau + HBAs = HBA + Version = Version + Build = Build + LicenseKey = Clé de licence + Product = Produit + LicenseExpiration = Expiration de la licence + IPMIBMC = IPMI / BMC + IPMIBMCError = Erreur lors de la collecte des informations IPMI/BMC pour {0}. + IPMIBMCManufacturer = Fabricant + IPMIBMCType = Type + IPMIBMCIPAddress = Adresse IP + IPMIBMCMACAddress = Adresse MAC + BootDevice = Périphérique de démarrage + BootDeviceError = Erreur lors de la collecte des informations sur le périphérique de démarrage pour {0}. + BootDeviceDescription = Description + BootDeviceType = Type + BootDeviceSize = Taille (Go) + BootDeviceIsSAS = Est SAS + BootDeviceIsUSB = Est USB + BootDeviceIsSSD = Est SSD + BootDeviceFromSAN = Depuis SAN + PCIDevices = Périphériques PCI + PCIDeviceId = ID PCI + PCIVendorName = Nom du fabricant + PCIDeviceName = Nom du périphérique + PCIDriverName = Nom du pilote + PCIDriverVersion = Version du pilote + PCIFirmware = Version du micrologiciel + PCIDriversFirmware = Pilotes et micrologiciels des périphériques PCI + Enabled = Activé + Disabled = Désactivé + NotApplicable = Non applicable + Unknown = Inconnu + Maintenance = Maintenance + NotResponding = Not Responding + Host = Host + ConnectionState = Connection State + ID = ID + Parent = Parent + HyperThreading = HyperThreading + NumberOfCPUSockets = Number of CPU Sockets + NumberOfCPUCores = Number of CPU Cores + NumberOfCPUThreads = Number of CPU Threads + CPUTotalUsedFree = CPU Total / Used / Free + MemoryTotalUsedFree = Memory Total / Used / Free + NumberOfNICs = Number of NICs + NumberOfHBAs = Number of HBAs + NumberOfDatastores = Number of Datastores + NumberOfVMs = Number of VMs + MaximumEVCMode = Maximum EVC Mode + EVCGraphicsMode = EVC Graphics Mode + PowerManagementPolicy = Power Management Policy + ScratchLocation = Scratch Location + BiosVersion = BIOS Version + BiosReleaseDate = BIOS Release Date + BootTime = Boot Time + UptimeDays = Uptime Days + MACAddress = MAC Address + SubnetMask = Subnet Mask + Gateway = Gateway + FirmwareVersion = Firmware Version + Device = Device + BootType = Boot Type + Vendor = Vendor + Size = Size + PCIAddress = PCI Address + DeviceClass = Device Class + TableHardwareConfig = Hardware Configuration - {0} + TableIPMIBMC = IPMI / BMC - {0} + TableBootDevice = Boot Device - {0} + TablePCIDevices = PCI Devices - {0} + TablePCIDriversFirmware = PCI Devices Drivers & Firmware - {0} + PCIDeviceError = Error collecting PCI device information for {0}. {1} + PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} + HardwareError = Error collecting host hardware information for {0}. {1} +'@ + +# Get-AbrVSphereVMHostSystem +GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Système VMHost défini sur {0}. + Collecting = Collecte des informations système de l'hôte VMHost. + HostProfile = Profil d'hôte + ProfileName = Profil d'hôte + ProfileCompliance = Conformité + ProfileRemediating = Remédiation + ImageProfile = Profil d'image + ImageProfileName = Nom + ImageProfileVendor = Fabricant + ImageProfileAcceptance = Niveau d'acceptation + TimeConfiguration = Configuration de l'heure + NTPServer = Serveur NTP + TimeZone = Fuseau horaire + Syslog = Syslog + SyslogHost = Hôte Syslog + VUMBaseline = Lignes de base Update Manager + VUMBaselineNotAvailable = Les informations de ligne de base VUM de l'hôte VMHost ne sont pas disponibles avec votre version de PowerShell. + VUMBaselineName = Ligne de base + VUMBaselineType = Type + VUMBaselinePatches = Nb de correctifs + VUMCompliance = Conformité Update Manager + VUMComplianceNotAvailable = Les informations de conformité VUM de l'hôte VMHost ne sont pas disponibles avec votre version de PowerShell. + VUMStatus = État + AdvancedSettings = Paramètres système avancés + Key = Clé + Value = Valeur + VIBs = Bundles d'installation VMware (VIB) + VIBName = Nom + VIBVersion = Version + VIBVendor = Fabricant + VIBInstallDate = Date d'installation + VIBAcceptanceLevel = Niveau d'acceptation + SectionHeading = Système + ParagraphSummary = La section suivante détaille la configuration du système de l'hôte {0}. + NTPService = Service NTP + NTPServers = Serveur(s) NTP + SyslogPort = Port + VUMDescription = Description + VUMTargetType = Type de cible + VUMLastUpdate = Heure de la dernière mise à jour + InstallDate = Date d'installation + ImageName = Profil d'image + ImageVendor = Fournisseur + Running = En cours d'exécution + Stopped = Arrêté + NotCompliant = Non conforme + Unknown = Inconnu + Incompatible = Incompatible + ProfileDescription = Description + VIBID = ID + VIBCreationDate = Creation Date + TableHostProfile = Host Profile - {0} + TableImageProfile = Image Profile - {0} + TableTimeConfig = Time Configuration - {0} + TableSyslog = Syslog Configuration - {0} + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} + TableAdvancedSettings = Advanced System Settings - {0} + TableVIBs = Software VIBs - {0} + HostProfileError = Error collecting host profile information for {0}. {1} + ImageProfileError = Error collecting image profile information for {0}. {1} + TimeConfigError = Error collecting time configuration for {0}. {1} + SyslogError = Error collecting syslog configuration for {0}. {1} + VUMBaselineError = Error collecting Update Manager baseline information for {0}. {1} + VUMComplianceError = Error collecting Update Manager compliance information for {0}. {1} + AdvancedSettingsError = Error collecting host advanced settings information for {0}. {1} + VIBsError = Error collecting software VIB information for {0}. {1} + InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. + InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. +'@ + +# Get-AbrVSphereVMHostStorage +GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Stockage VMHost défini sur {0}. + Collecting = Collecte des informations de stockage de l'hôte VMHost. + SectionHeading = Stockage + DatastoreSpecs = Spécifications des magasins de données + Datastore = Magasin de données + DatastoreType = Type + DatastoreCapacity = Capacité (Go) + DatastoreFreeSpace = Espace libre (Go) + DatastoreState = État + DatastoreShared = Partagé + StorageAdapters = Adaptateurs de stockage + AdapterName = Adaptateur + AdapterType = Type + AdapterDriver = Pilote + AdapterUID = UID + AdapterModel = Modèle + AdapterStatus = État + AdapterSASAddress = Adresse SAS + AdapterWWN = WWN + AdapterDevices = Périphériques + AdapterTargets = Cibles + ParagraphSummary = La section suivante détaille la configuration de stockage de l'hôte {0}. + FC = Fibre Channel + iSCSI = iSCSI + ParallelSCSI = SCSI parallèle + ChapNone = Aucun + ChapUniPreferred = Utiliser CHAP unidirectionnel sauf si interdit par la cible + ChapUniRequired = Utiliser CHAP unidirectionnel si requis par la cible + ChapUnidirectional = Utiliser CHAP unidirectionnel + ChapBidirectional = Utiliser CHAP bidirectionnel + Online = En ligne + Offline = Hors ligne + Type = Type + Version = Version + NumberOfVMs = # of VMs + TotalCapacity = Total Capacity + UsedCapacity = Used Capacity + FreeCapacity = Free Capacity + PercentUsed = % Used + TableDatastores = Datastores - {0} + ParagraphStorageAdapters = The following section details the storage adapter configuration for {0}. + AdapterPaths = Paths + iSCSIName = iSCSI Name + iSCSIAlias = iSCSI Alias + AdapterSpeed = Speed + DynamicDiscovery = Dynamic Discovery + StaticDiscovery = Static Discovery + AuthMethod = Authentication Method + CHAPOutgoing = Outgoing CHAP Name + CHAPIncoming = Incoming CHAP Name + AdvancedOptions = Advanced Options + NodeWWN = Node WWN + PortWWN = Port WWN + TableStorageAdapter = Storage Adapter {0} - {1} +'@ + +# Get-AbrVSphereVMHostNetwork +GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Réseau VMHost défini sur {0}. + Collecting = Collecte des informations réseau de l'hôte VMHost. + SectionHeading = Réseau + NetworkConfig = Configuration réseau + VMKernelAdapters = Adaptateurs VMkernel + PhysicalAdapters = Adaptateurs physiques + CDP = Protocole de découverte Cisco + LLDP = Protocole de découverte de la couche liaison + StandardvSwitches = Commutateurs virtuels standard + AdapterName = Adaptateur + AdapterMAC = Adresse MAC + AdapterLinkSpeed = Vitesse de liaison + AdapterDriver = Pilote + AdapterPCIID = Périphérique PCI + AdapterStatus = État + VMKName = Adaptateur VMkernel + VMKIP = Adresse IP + VMKSubnet = Masque de sous-réseau + VMKGateway = Passerelle par défaut + VMKMTU = MTU + VMKPortGroup = Groupe de ports + VMKVirtualSwitch = Commutateur virtuel + VMKDHCPEnabled = DHCP activé + VMKServicesEnabled = Services activés + vSwitch = Commutateur virtuel + vSwitchPorts = Ports totaux + vSwitchUsedPorts = Ports utilisés + vSwitchAvailablePorts = Ports disponibles + vSwitchMTU = MTU + vSwitchCDPStatus = État CDP + vSwitchSecurity = Stratégie de sécurité + vSwitchFailover = Stratégie de basculement + vSwitchLoadBalancing = Équilibrage de charge + vSwitchPortGroups = Groupes de ports + PortGroup = Groupe de ports + VLAN = ID VLAN + ActiveAdapters = Adaptateurs actifs + StandbyAdapters = Adaptateurs en attente + UnusedAdapters = Adaptateurs inutilisés + ParagraphSummary = La section suivante détaille la configuration réseau de l'hôte {0}. + Enabled = Activé + Disabled = Désactivé + Connected = Connecté + Disconnected = Déconnecté + Yes = Oui + No = Non + Accept = Accepter + Reject = Rejeter + Supported = Pris en charge + NotSupported = Non pris en charge + Inherited = Hérité + TCPDefault = Défaut + TCPProvisioning = Provisionnement + TCPvMotion = vMotion + TCPNsxOverlay = nsx-overlay + TCPNsxHyperbus = nsx-hyperbus + TCPNotApplicable = Non applicable + LBSrcId = Route basée sur l'ID du port d'origine + LBSrcMac = Route basée sur le hachage MAC source + LBIP = Route basée sur le hachage IP + LBExplicitFailover = Basculement explicite + NFDLinkStatus = Statut du lien uniquement + NFDBeaconProbing = Sondage de balise + FullDuplex = Full Duplex + AutoNegotiate = Négociation automatique + Down = Indisponible + Host = Host + VirtualSwitches = Virtual Switches + VMkernelGateway = VMkernel Gateway + IPv6 = IPv6 + VMkernelIPv6Gateway = VMkernel IPv6 Gateway + DNSServers = DNS Servers + HostName = Host Name + DomainName = Domain Name + SearchDomain = Search Domain + TableNetworkConfig = Network Configuration - {0} + ParagraphPhysicalAdapters = The following section details the physical network adapter configuration for {0}. + VirtualSwitch = Virtual Switch + ActualSpeedDuplex = Actual Speed, Duplex + ConfiguredSpeedDuplex = Configured Speed, Duplex + WakeOnLAN = Wake on LAN + TablePhysicalAdapter = Physical Adapter {0} - {1} + TablePhysicalAdapters = Physical Adapters - {0} + ParagraphCDP = The following section details the CDP information for {0}. + Status = Status + SystemName = System Name + HardwarePlatform = Hardware Platform + SwitchID = Switch ID + SoftwareVersion = Software Version + ManagementAddress = Management Address + Address = Address + PortID = Port ID + MTU = MTU + TableAdapterCDP = Network Adapter {0} CDP Information - {1} + TableAdaptersCDP = Network Adapter CDP Information - {0} + ParagraphLLDP = The following section details the LLDP information for {0}. + ChassisID = Chassis ID + TimeToLive = Time to live + TimeOut = TimeOut + Samples = Samples + PortDescription = Port Description + SystemDescription = System Description + TableAdapterLLDP = Network Adapter {0} LLDP Information - {1} + TableAdaptersLLDP = Network Adapter LLDP Information - {0} + ParagraphVMKernelAdapters = The following section details the VMkernel adapter configuration for {0}. + NetworkLabel = Network Label + TCPIPStack = TCP/IP Stack + DHCP = DHCP + IPAddress = IP Address + SubnetMask = Subnet Mask + DefaultGateway = Default Gateway + vMotion = vMotion + Provisioning = Provisioning + FTLogging = FT Logging + Management = Management + vSphereReplication = vSphere Replication + vSphereReplicationNFC = vSphere Replication NFC + vSAN = vSAN + vSANWitness = vSAN Witness + vSphereBackupNFC = vSphere Backup NFC + TableVMKernelAdapter = VMkernel Adapter {0} - {1} + ParagraphStandardvSwitches = The following section details the standard virtual switch configuration for {0}. + NumberOfPorts = Number of Ports + NumberOfPortsAvailable = Number of Ports Available + TableStandardvSwitches = Standard Virtual Switches - {0} + VSSecurity = Virtual Switch Security + PromiscuousMode = Promiscuous Mode + MACAddressChanges = MAC Address Changes + ForgedTransmits = Forged Transmits + TableVSSecurity = Virtual Switch Security Policy - {0} + VSTrafficShaping = Virtual Switch Traffic Shaping + AverageBandwidth = Average Bandwidth (kbit/s) + PeakBandwidth = Peak Bandwidth (kbit/s) + BurstSize = Burst Size (KB) + TableVSTrafficShaping = Virtual Switch Traffic Shaping Policy - {0} + VSTeamingFailover = Virtual Switch Teaming & Failover + LoadBalancing = Load Balancing + NetworkFailureDetection = Network Failure Detection + NotifySwitches = Notify Switches + Failback = Failback + ActiveNICs = Active NICs + StandbyNICs = Standby NICs + UnusedNICs = Unused NICs + TableVSTeamingFailover = Virtual Switch Teaming & Failover - {0} + VSPortGroups = Virtual Switch Port Groups + NumberOfVMs = # of VMs + TableVSPortGroups = Virtual Switch Port Groups - {0} + VSPGSecurity = Virtual Switch Port Group Security + MACChanges = MAC Changes + TableVSPGSecurity = Virtual Switch Port Group Security Policy - {0} + VSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping + TableVSPGTrafficShaping = Virtual Switch Port Group Traffic Shaping Policy - {0} + VSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover + TableVSPGTeamingFailover = Virtual Switch Port Group Teaming & Failover - {0} +'@ + +# Get-AbrVSphereVMHostSecurity +GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Sécurité VMHost défini sur {0}. + Collecting = Collecte des informations de sécurité de l'hôte VMHost. + SectionHeading = Sécurité + LockdownMode = Mode de verrouillage + Services = Services + ServiceName = Service + ServiceRunning = En cours d'exécution + ServicePolicy = Stratégie de démarrage + ServiceRequired = Requis + Firewall = Pare-feu + FirewallRule = Règle + FirewallAllowed = Hôtes autorisés + Authentication = Authentification + Domain = Domaine + Trust = Domaines approuvés + Membership = Appartenance au domaine + VMInfoSection = Machines virtuelles + VMName = Machine virtuelle + VMPowerState = État d'alimentation + VMIPAddress = Adresse IP + VMOS = Système d'exploitation + VMGuestID = ID invité + VMCPUs = CPU + VMMemoryGB = Mémoire (Go) + VMDisks = Disques (Go) + VMNICs = Cartes réseau + StartupShutdown = Démarrage/Arrêt des MV + StartupEnabled = Démarrage activé + StartupOrder = Ordre de démarrage + StartupDelay = Délai de démarrage + StopAction = Action d'arrêt + StopDelay = Délai d'arrêt + WaitForHeartbeat = Attendre la pulsation + ParagraphSummary = La section suivante détaille la configuration de sécurité de l'hôte {0}. + LockdownDisabled = Désactivé + LockdownNormal = Activé (Normal) + LockdownStrict = Activé (Strict) + Running = En cours d'exécution + Stopped = Arrêté + SvcPortUsage = Démarrer et arrêter avec l'utilisation du port + SvcWithHost = Démarrer et arrêter avec l'hôte + SvcManually = Démarrer et arrêter manuellement + On = Activé + Off = Désactivé + Enabled = Activé + Disabled = Désactivé + WaitHeartbeat = Continuer si VMware Tools est démarré + WaitDelay = Attendre le délai de démarrage + PowerOff = Éteindre + GuestShutdown = Arrêt du système d'exploitation + ToolsOld = Obsolète + ToolsOK = OK + ToolsNotRunning = Pas en cours d'exécution + ToolsNotInstalled = Pas installé + TableLockdownMode = Lockdown Mode - {0} + TableServices = Services - {0} + FirewallService = Service + Status = Status + IncomingPorts = Incoming Ports + OutgoingPorts = Outgoing Ports + Protocols = Protocols + Daemon = Daemon + TableFirewall = Firewall Configuration - {0} + DomainMembership = Domain Membership + TrustedDomains = Trusted Domains + TableAuthentication = Authentication Services - {0} + ParagraphVMInfo = The following section details the virtual machine configuration for {0}. + VMProvisioned = Provisioned + VMUsed = Used + VMHWVersion = HW Version + VMToolsStatus = VM Tools Status + TableVMs = Virtual Machines - {0} + TableStartupShutdown = VM Startup/Shutdown Policy - {0} +'@ + +# Get-AbrVSphereNetwork +GetAbrVSphereNetwork = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Réseau défini sur {0}. + Collecting = Collecte des informations sur les commutateurs distribués. + Processing = Traitement du commutateur distribué '{0}' ({1}/{2}). + SectionHeading = Commutateurs distribués + ParagraphSummary = Les sections suivantes détaillent la configuration des commutateurs distribués gérés par le serveur vCenter {0}. + VDSwitch = Commutateur distribué + Datacenter = Centre de données + Manufacturer = Fabricant + NumUplinks = Nb de liaisons montantes + NumPorts = Nb de ports + NumHosts = Nb d'hôtes + NumVMs = Nb de MV + Version = Version + MTU = MTU + ID = ID + NumberOfUplinks = Nombre de liaisons montantes + NumberOfPorts = Nombre de ports + NumberOfPortGroups = Nombre de groupes de ports + NumberOfHosts = Nombre d'hôtes + NumberOfVMs = Nombre de MV + Hosts = Hôtes + VirtualMachines = Machines virtuelles + NIOC = Contrôle des E/S réseau + DiscoveryProtocol = Protocole de découverte + DiscoveryOperation = Opération de découverte + NumPortGroups = Nb de groupes de ports + MaxPorts = Ports maximum + Contact = Contact + Location = Emplacement + VDPortGroups = Groupes de ports distribués + PortGroup = Groupe de ports + PortGroupType = Type de groupe de ports + VLANID = ID VLAN + VLANConfiguration = Configuration VLAN + PortBinding = Liaison de port + Host = Hôte + UplinkName = Nom de la liaison montante + UplinkPortGroup = Groupe de ports de liaison montante + PhysicalNetworkAdapter = Adaptateur réseau physique + ActiveUplinks = Liaisons montantes actives + StandbyUplinks = Liaisons montantes en attente + UnusedUplinks = Liaisons montantes inutilisées + AllowPromiscuous = Autoriser le mode promiscuité + ForgedTransmits = Transmissions forgées + MACAddressChanges = Modifications d'adresse MAC + Accept = Accepter + Reject = Rejeter + Direction = Direction + Status = Statut + AverageBandwidth = Bande passante moyenne (kbit/s) + PeakBandwidth = Bande passante de pointe (kbit/s) + BurstSize = Taille de rafale (Ko) + LoadBalancing = Équilibrage de charge + LoadBalanceSrcId = Route basée sur l'ID du port d'origine + LoadBalanceSrcMac = Route basée sur le hachage MAC source + LoadBalanceIP = Route basée sur le hachage IP + ExplicitFailover = Basculement explicite + NetworkFailureDetection = Détection d'échec réseau + LinkStatus = Statut du lien uniquement + BeaconProbing = Sondage de balise + NotifySwitches = Notifier les commutateurs + FailbackEnabled = Retour arrière activé + Yes = Oui + No = Non + PrimaryVLANID = ID VLAN primaire + PrivateVLANType = Type de VLAN privé + SecondaryVLANID = ID VLAN secondaire + UplinkPorts = Ports de liaison montante du commutateur distribué + VDSSecurity = Sécurité du commutateur distribué + VDSTrafficShaping = Régulation du trafic du commutateur distribué + VDSPortGroups = Groupes de ports du commutateur distribué + VDSPortGroupSecurity = Sécurité du groupe de ports du commutateur distribué + VDSPortGroupTrafficShaping = Régulation du trafic du groupe de ports du commutateur distribué + VDSPortGroupTeaming = Équipe et basculement du groupe de ports du commutateur distribué + VDSPrivateVLANs = VLAN privés du commutateur distribué + Enabled = Activé + Disabled = Désactivé + Listen = Écouter + Advertise = Annonce + Both = Les deux + TableVDSSummary = Récapitulatif du commutateur distribué - {0} + TableVDSGeneral = Propriétés générales du commutateur distribué - {0} + TableVDSUplinkPorts = Ports de liaison montante du commutateur distribué - {0} + TableVDSSecurity = Sécurité du commutateur distribué - {0} + TableVDSTrafficShaping = Régulation du trafic du commutateur distribué - {0} + TableVDSPortGroups = Groupes de ports du commutateur distribué - {0} + TableVDSPortGroupSecurity = Sécurité du groupe de ports du commutateur distribué - {0} + TableVDSPortGroupTrafficShaping = Régulation du trafic du groupe de ports du commutateur distribué - {0} + TableVDSPortGroupTeaming = Équipe et basculement du groupe de ports du commutateur distribué - {0} + TableVDSPrivateVLANs = VLAN privés du commutateur distribué - {0} +'@ + +# Get-AbrVSpherevSAN +GetAbrVSpherevSAN = ConvertFrom-StringData @' + InfoLevel = Niveau d'information vSAN défini sur {0}. + Collecting = Collecte des informations vSAN. + CollectingESA = Collecte des informations vSAN ESA pour '{0}'. + CollectingOSA = Collecte des informations vSAN OSA pour '{0}'. + CollectingDisks = Collecte des informations sur les disques vSAN pour '{0}'. + CollectingDiskGroups = Collecte des informations sur les groupes de disques vSAN pour '{0}'. + CollectingiSCSITargets = Collecte des informations sur les cibles iSCSI vSAN pour '{0}'. + CollectingiSCSILUNs = Collecte des informations sur les LUN iSCSI vSAN pour '{0}'. + Processing = Traitement du cluster vSAN '{0}' ({1}/{2}). + SectionHeading = vSAN + ParagraphSummary = Les sections suivantes détaillent la configuration de VMware vSAN géré par le serveur vCenter {0}. + ParagraphDetail = Le tableau suivant détaille la configuration vSAN pour le cluster {0}. + DisksSection = Disques + DiskGroupsSection = Groupes de disques + iSCSITargetsSection = Cibles iSCSI + iSCSILUNsSection = LUN iSCSI + Cluster = Cluster + StorageType = Type de stockage + ClusterType = Type de cluster + NumHosts = Nb d'hôtes + ID = ID + NumberOfHosts = Nombre d'hôtes + NumberOfDisks = Nombre de disques + NumberOfDiskGroups = Nombre de groupes de disques + DiskClaimMode = Mode de revendication de disque + PerformanceService = Service de performances + FileService = Service de fichiers + iSCSITargetService = Service de cible iSCSI + HistoricalHealthService = Service d'historique de santé + HealthCheck = Vérification de l'état + TotalCapacity = Capacité totale + UsedCapacity = Capacité utilisée + FreeCapacity = Capacité libre + PercentUsed = % utilisé + HCLLastUpdated = Dernière mise à jour de la LCM + Hosts = Hôtes + Version = Version + Stretched = Cluster étendu + VSANEnabled = vSAN activé + VSANESAEnabled = vSAN ESA activé + DisksFormat = Version du format de disque + Deduplication = Déduplication et compression + AllFlash = Tout flash + FaultDomains = Domaines de panne + PFTT = Pannes primaires à tolérer + SFTT = Pannes secondaires à tolérer + NetworkDiagnosticMode = Mode de diagnostic réseau + HybridMode = Mode hybride + AutoRebalance = Rééquilibrage automatique + ProactiveDisk = Rééquilibrage proactif des disques + ResyncThrottle = Limitation de resynchronisation + SpaceEfficiency = Efficacité d'espace + Encryption = Chiffrement + FileServiceEnabled = Service de fichiers activé + iSCSITargetEnabled = Cible iSCSI activée + VMHostSpecs = Spécifications vSAN des hôtes VMHost + VMHost = Hôte VMHost + DiskGroup = Groupe de disques + CacheDisks = Disques de cache + DataDisks = Disques de données + DiskGroups = Groupes de disques + CacheTier = Niveau cache + DataTier = Niveau données + Status = État + FaultDomainName = Domaine de panne + VSANAdvancedOptions = Options de configuration avancées vSAN + Key = Clé + Value = Valeur + IDs = Identifiants + VSAN_UUID = UUID vSAN + CapacityTier = Niveau capacité (Go) + BufferTier = Niveau tampon (Go) + DiskName = Disque + Name = Nom + DriveType = Type de lecteur + Host = Hôte + State = État + Encrypted = Chiffré + Capacity = Capacité + SerialNumber = Numéro de série + Vendor = Fournisseur + Model = Modèle + DiskType = Type de disque + DiskFormatVersion = Version du format de disque + ClaimedAs = Revendiqué comme + NumDisks = Nb de disques + Type = Type + IQN = IQN + Alias = Alias + LUNsCount = LUN + NetworkInterface = Interface réseau + IOOwnerHost = Hôte propriétaire des E/S + TCPPort = Port TCP + Health = État de santé + StoragePolicy = Stratégie de stockage + ComplianceStatus = État de conformité + Authentication = Authentification + LUNName = LUN + LUNID = ID LUN + Yes = Oui + No = Non + Enabled = Activé + Disabled = Désactivé + Online = En ligne + Offline = Hors ligne + Mounted = Monté + Unmounted = Démonté + Flash = Flash + HDD = HDD + Cache = Cache + TableVSANClusterSummary = Résumé du cluster vSAN - {0} + TableVSANConfiguration = Configuration vSAN - {0} + TableDisk = Disque {0} - {1} + TableVSANDisks = Disques vSAN - {0} + TableVSANDiskGroups = Groupes de disques vSAN - {0} + TableVSANiSCSITargets = Cibles iSCSI vSAN - {0} + TableVSANiSCSILUNs = LUN iSCSI vSAN - {0} + ESAError = Erreur lors de la collecte des informations vSAN ESA pour '{0}'. {1} + OSAError = Erreur lors de la collecte des informations vSAN OSA pour '{0}'. {1} + DiskError = Erreur lors de la collecte des informations sur les disques vSAN pour '{0}'. {1} + DiskGroupError = Erreur lors de la collecte des informations sur les groupes de disques vSAN pour '{0}'. {1} + iSCSITargetError = Erreur lors de la collecte des informations sur les cibles iSCSI vSAN pour '{0}'. {1} + iSCSILUNError = Erreur lors de la collecte des informations sur les LUN iSCSI vSAN pour '{0}'. {1} +'@ + +# Get-AbrVSphereDatastore +GetAbrVSphereDatastore = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Magasin de données défini sur {0}. + Collecting = Collecte des informations sur les magasins de données. + Processing = Traitement du magasin de données '{0}' ({1}/{2}). + SectionHeading = Magasins de données + ParagraphSummary = Les sections suivantes détaillent la configuration des magasins de données gérés par le serveur vCenter {0}. + Datastore = Magasin de données + Type = Type + DatastoreURL = URL + FileSystem = Version du système de fichiers + CapacityGB = Capacité totale (Go) + FreeSpaceGB = Espace libre (Go) + UsedSpaceGB = Espace utilisé (Go) + State = État + Accessible = Accessible + NumHosts = Nb d'hôtes + NumVMs = Nb de MV + SIOC = Contrôle des E/S de stockage + IOLatencyThreshold = Seuil de latence E/S (ms) + IOLoadBalancing = Équilibrage de charge E/S + Enabled = Activé + Disabled = Désactivé + Normal = Normal + Maintenance = Maintenance + Unmounted = Démonté + ID = ID + Datacenter = Centre de données + Version = Version + NumberOfHosts = Nombre d'hôtes + NumberOfVMs = Nombre de MV + CongestionThreshold = Seuil de congestion + TotalCapacity = Capacité totale + UsedCapacity = Capacité utilisée + FreeCapacity = Capacité libre + PercentUsed = % utilisé + Hosts = Hôtes + VirtualMachines = Machines virtuelles + SCSILUNInfo = Informations LUN SCSI + Host = Hôte + CanonicalName = Nom canonique + Capacity = Capacité + Vendor = Fabricant + Model = Modèle + IsSSD = Est SSD + MultipathPolicy = Politique de chemins multiples + Paths = Chemins + TableDatastoreSummary = Récapitulatif du magasin de données - {0} + TableDatastoreConfig = Configuration du magasin de données - {0} + TableSCSILUN = Informations LUN SCSI - {0} +'@ + +# Get-AbrVSphereDSCluster +GetAbrVSphereDSCluster = ConvertFrom-StringData @' + InfoLevel = Niveau d'information Cluster de magasins de données défini sur {0}. + Collecting = Collecte des informations sur les clusters de magasins de données. + Processing = Traitement du cluster de magasins de données '{0}' ({1}/{2}). + SectionHeading = Clusters de magasins de données + ParagraphSummary = Les sections suivantes détaillent la configuration des clusters de magasins de données gérés par le serveur vCenter {0}. + DSCluster = Cluster de magasins de données + Datacenter = Centre de données + CapacityGB = Capacité totale (Go) + FreeSpaceGB = Espace libre (Go) + SDRSEnabled = SDRS + SDRSAutomationLevel = Niveau d'automatisation SDRS + IOLoadBalancing = Équilibrage de charge E/S + SpaceThreshold = Seuil d'espace (%) + IOLatencyThreshold = Seuil de latence E/S (ms) + SDRSRules = Règles SDRS + RuleName = Règle + RuleEnabled = Activée + RuleVMs = Machines virtuelles + FullyAutomated = Entièrement automatisé + NoAutomation = Aucune automatisation (Mode Manuel) + Manual = Manuel + Enabled = Activé + Disabled = Désactivé + Yes = Oui + No = Non + ID = ID + TotalCapacity = Capacité totale + UsedCapacity = Capacité utilisée + FreeCapacity = Capacité libre + PercentUsed = % utilisé + ParagraphDSClusterDetail = Le tableau suivant détaille la configuration du cluster de magasins de données {0}. + TableDSClusterConfig = Configuration du cluster de magasins de données - {0} + TableSDRSVMOverrides = Remplacements de MV SDRS - {0} + SDRSVMOverrides = Remplacements de MV SDRS + VirtualMachine = Machine virtuelle + KeepVMDKsTogether = Garder les VMDK ensemble + DefaultBehavior = Défaut ({0}) +'@ + +# Get-AbrVSphereVM +GetAbrVSphereVM = ConvertFrom-StringData @' + InfoLevel = Niveau d'information MV défini sur {0}. + Collecting = Collecte des informations sur les machines virtuelles. + Processing = Traitement de la machine virtuelle '{0}' ({1}/{2}). + SectionHeading = Machines virtuelles + ParagraphSummary = Les sections suivantes détaillent la configuration des machines virtuelles gérées par le serveur vCenter {0}. + VirtualMachine = Machine virtuelle + PowerState = État d'alimentation + Template = Modèle + OS = Système d'exploitation + Version = Version du matériel MV + GuestID = ID invité + Cluster = Cluster + ResourcePool = Pool de ressources + VMHost = Hôte + Folder = Dossier + IPAddress = Adresse IP + CPUs = CPU + MemoryGB = Mémoire (Go) + ProvisionedGB = Provisionné (Go) + UsedGB = Utilisé (Go) + NumDisks = Nb de disques + NumNICs = Nb de cartes réseau + NumSnapshots = Nb de snapshots + VMwareTools = VMware Tools + ToolsVersion = Version des outils + ToolsStatus = État des outils + ToolsRunningStatus = État d'exécution des outils + VMAdvancedDetail = Configuration avancée + BootOptions = Options de démarrage + BootDelay = Délai de démarrage (ms) + BootRetryEnabled = Nouvelle tentative de démarrage activée + BootRetryDelay = Délai de nouvelle tentative (ms) + EFISecureBoot = Démarrage sécurisé EFI + EnterBIOSSetup = Accéder à la configuration du BIOS au prochain démarrage + HardDisks = Disques durs + DiskName = Disque + DiskCapacityGB = Capacité (Go) + DiskFormat = Format + DiskStoragePolicy = Stratégie de stockage + DiskDatastore = Magasin de données + DiskController = Contrôleur + NetworkAdapters = Adaptateurs réseau + NICName = Adaptateur réseau + NICType = Type d'adaptateur + NICPortGroup = Groupe de ports + NICMAC = Adresse MAC + NICConnected = Connecté + NICConnectionPolicy = Se connecter à la mise sous tension + SnapshotHeading = Snapshots + SnapshotName = Snapshot + SnapshotDescription = Description + SnapshotSize = Taille (Go) + SnapshotDate = Créé + SnapshotParent = Parent + On = Activé + Off = Désactivé + ToolsOld = Obsolète + ToolsOK = OK + ToolsNotRunning = Pas en cours d'exécution + ToolsNotInstalled = Pas installé + Yes = Oui + No = Non + Connected = Connecté + NotConnected = Non connecté + Thin = Fin + Thick = Épais + Enabled = Activé + Disabled = Désactivé + TotalVMs = Total des MV + TotalvCPUs = Total des vCPU + TotalMemory = Mémoire totale + TotalProvisionedSpace = Espace total provisionné + TotalUsedSpace = Espace total utilisé + VMsPoweredOn = MV sous tension + VMsPoweredOff = MV hors tension + VMsOrphaned = MV orphelines + VMsInaccessible = MV inaccessibles + VMsSuspended = MV suspendues + VMsWithSnapshots = MV avec des snapshots + GuestOSTypes = Types de systèmes d'exploitation invités + VMToolsOKCount = Outils VM corrects + VMToolsOldCount = Outils VM obsolètes + VMToolsNotRunningCount = Outils VM non en cours d'exécution + VMToolsNotInstalledCount = Outils VM non installés + vCPUs = vCPU + Memory = Mémoire + Provisioned = Provisionné + Used = Utilisé + HWVersion = Version matérielle + VMToolsStatus = Statut des outils VM + ID = ID + OperatingSystem = Système d'exploitation + HardwareVersion = Version matérielle + ConnectionState = État de la connexion + FaultToleranceState = État de tolérance aux pannes + FTNotConfigured = Non configuré + FTNeedsSecondary = Nécessite un secondaire + FTRunning = En cours d'exécution + FTDisabled = Désactivé + FTStarting = Démarrage + FTEnabled = Activé + Parent = Parent + ParentFolder = Dossier parent + ParentResourcePool = Pool de ressources parent + CoresPerSocket = Cœurs par socket + CPUShares = Parts CPU + CPUReservation = Réservation CPU + CPULimit = Limite CPU + CPUHotAdd = Ajout à chaud CPU + CPUHotRemove = Suppression à chaud CPU + MemoryAllocation = Allocation mémoire + MemoryShares = Parts mémoire + MemoryHotAdd = Ajout à chaud mémoire + vNICs = vNIC + DNSName = Nom DNS + Networks = Réseaux + MACAddress = Adresse MAC + vDisks = Disques virtuels + ProvisionedSpace = Espace provisionné + UsedSpace = Espace utilisé + ChangedBlockTracking = Suivi des blocs modifiés + StorageBasedPolicy = Stratégie basée sur le stockage + StorageBasedPolicyCompliance = Conformité de la stratégie basée sur le stockage + Compliant = Conforme + NonCompliant = Non conforme + Unknown = Inconnu + Notes = Notes + BootTime = Heure de démarrage + UptimeDays = Jours de fonctionnement + NetworkName = Nom du réseau + SCSIControllers = Contrôleurs SCSI + Device = Périphérique + ControllerType = Type de contrôleur + BusSharing = Partage de bus + None = Aucun + GuestVolumes = Volumes invités + Capacity = Capacité + DiskProvisioning = Provisionnement de disque + ThickEagerZeroed = Épais avec zéro d'avance + ThickLazyZeroed = Épais avec zéro à la demande + DiskType = Type de disque + PhysicalRDM = RDM physique + VirtualRDM = RDM virtuel + VMDK = VMDK + DiskMode = Mode de disque + IndependentPersistent = Indépendant - Persistant + IndependentNonpersistent = Indépendant - Non persistant + Dependent = Dépendant + DiskPath = Chemin du disque + DiskShares = Parts de disque + DiskLimitIOPs = Limite IOPS du disque + Unlimited = Illimité + SCSIController = Contrôleur SCSI + SCSIAddress = Adresse SCSI + Path = Chemin + FreeSpace = Espace libre + DaysOld = Jours d'ancienneté + TableVMSummary = Récapitulatif des MV - {0} + TableVMAdvancedSummary = Récapitulatif avancé des MV - {0} + TableVMSnapshotSummary = Récapitulatif des snapshots des MV - {0} + TableVMConfig = Configuration des MV - {0} + TableVMNetworkAdapters = Adaptateurs réseau - {0} + TableVMSCSIControllers = Contrôleurs SCSI - {0} + TableVMHardDiskConfig = Configuration du disque dur - {0} + TableVMHardDisk = {0} - {1} + TableVMGuestVolumes = Volumes invités - {0} + TableVMSnapshots = Snapshots des MV - {0} +'@ + +# Get-AbrVSphereVUM +GetAbrVSphereVUM = ConvertFrom-StringData @' + InfoLevel = Niveau d'information VUM défini sur {0}. + Collecting = Collecte des informations sur VMware Update Manager. + NotAvailable = Les informations de ligne de base des correctifs VUM ne sont pas disponibles avec votre version de PowerShell. + PatchNotAvailable = Les informations sur les correctifs VUM ne sont pas disponibles avec votre version de PowerShell. + SectionHeading = VMware Update Manager + ParagraphSummary = Les sections suivantes détaillent la configuration de VMware Update Manager géré par le serveur vCenter {0}. + Baselines = Lignes de base + BaselineName = Ligne de base + Description = Description + Type = Type + TargetType = Type de cible + LastUpdate = Heure de la dernière mise à jour + NumPatches = Nb de correctifs + Patches = Correctifs + PatchName = Correctif + PatchProduct = Produit + PatchDescription = Description + PatchReleaseDate = Date de publication + PatchVendorID = ID fournisseur + TableVUMBaselines = Résumé des lignes de base VMware Update Manager - {0} + TableVUMPatches = Informations sur les correctifs VMware Update Manager - {0} +'@ + +} \ No newline at end of file diff --git a/Src/Private/Convert-DataSize.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Convert-DataSize.ps1 similarity index 100% rename from Src/Private/Convert-DataSize.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Convert-DataSize.ps1 diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 new file mode 100644 index 0000000..0765c28 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 @@ -0,0 +1,153 @@ +function Get-AbrVSphereCluster { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere Cluster information. + .DESCRIPTION + Documents the configuration of VMware vSphere Clusters in Word/HTML/Text formats using PScribo. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + .EXAMPLE + + .LINK + + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereCluster + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Cluster) + } + + process { + try { + if ($InfoLevel.Cluster -ge 1) { + $Clusters = Get-Cluster -Server $vCenter | Sort-Object Name + if ($Clusters) { + Write-PScriboMessage -Message $LocalizedData.Collecting + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region Cluster Summary + if ($InfoLevel.Cluster -le 2) { + BlankLine + $ClusterInfo = foreach ($Cluster in $Clusters) { + [PSCustomObject]@{ + ($LocalizedData.Cluster) = $Cluster.Name + ($LocalizedData.Datacenter) = $Cluster | Get-Datacenter + ($LocalizedData.NumHosts) = $Cluster.ExtensionData.Host.Count + ($LocalizedData.NumVMs) = $Cluster.ExtensionData.VM.Count + ($LocalizedData.HAEnabled) = if ($Cluster.HAEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + ($LocalizedData.DRSEnabled) = if ($Cluster.DRSEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + ($LocalizedData.VSANEnabled) = if ($Cluster.VsanEnabled -or $VsanCluster.VsanEsaEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + ($LocalizedData.EVCMode) = if ($Cluster.EVCMode) { $EvcModeLookup."$($Cluster.EVCMode)" } else { $LocalizedData.Disabled } + ($LocalizedData.VMSwapFilePolicy) = switch ($Cluster.VMSwapfilePolicy) { + 'WithVM' { $LocalizedData.SwapWithVM } + 'InHostDatastore' { $LocalizedData.SwapInHostDatastore } + default { $Cluster.VMSwapfilePolicy } + } + } + } + if ($Healthcheck.Cluster.HAEnabled) { + $ClusterInfo | Where-Object { $_.$($LocalizedData.HAEnabled) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.HAEnabled + } + if ($Healthcheck.Cluster.DRSEnabled) { + $ClusterInfo | Where-Object { $_.$($LocalizedData.DRSEnabled) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.DRSEnabled + } + if ($Healthcheck.Cluster.VsanEnabled) { + $ClusterInfo | Where-Object { $_.$($LocalizedData.VSANEnabled) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.VSANEnabled + } + if ($Healthcheck.Cluster.EvcEnabled) { + $ClusterInfo | Where-Object { $_.$($LocalizedData.EVCMode) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.EVCMode + } + $TableParams = @{ + Name = ($LocalizedData.TableClusterSummary -f $vCenterServerName) + ColumnWidths = 15, 15, 7, 7, 11, 11, 11, 15, 8 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterInfo | Table @TableParams + } + #endregion Cluster Summary + + #region Cluster Detailed Information + if ($InfoLevel.Cluster -ge 3) { + $Count = 1 + foreach ($Cluster in $Clusters) { + Write-PScriboMessage -Message ($LocalizedData.Processing -f $Cluster.Name, $Count, $Clusters.Count) + $Count++ + $ClusterDasConfig = $Cluster.ExtensionData.Configuration.DasConfig + $ClusterDrsConfig = $Cluster.ExtensionData.Configuration.DrsConfig + $ClusterConfigEx = $Cluster.ExtensionData.ConfigurationEx + Section -Style Heading3 $Cluster { + Paragraph ($LocalizedData.ParagraphDetail -f $Cluster) + BlankLine + #region Cluster Configuration + $ClusterDetail = [PSCustomObject]@{ + ($LocalizedData.Cluster) = $Cluster.Name + ($LocalizedData.ID) = $Cluster.Id + ($LocalizedData.Datacenter) = $Cluster | Get-Datacenter + ($LocalizedData.NumberOfHosts) = $Cluster.ExtensionData.Host.Count + ($LocalizedData.NumberOfVMs) = ($Cluster | Get-VM).Count + ($LocalizedData.HAEnabled) = if ($Cluster.HAEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + ($LocalizedData.DRSEnabled) = if ($Cluster.DRSEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + ($LocalizedData.VSANEnabled) = if ($Cluster.VsanEnabled -or $Cluster.VsanEsaEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + ($LocalizedData.EVCMode) = if ($Cluster.EVCMode) { $EvcModeLookup."$($Cluster.EVCMode)" } else { $LocalizedData.Disabled } + ($LocalizedData.VMSwapFilePolicy) = switch ($Cluster.VMSwapfilePolicy) { + 'WithVM' { $LocalizedData.SwapVMDirectory } + 'InHostDatastore' { $LocalizedData.SwapHostDatastore } + default { $Cluster.VMSwapfilePolicy } + } + } + if ($Healthcheck.Cluster.HAEnabled) { + $ClusterDetail | Where-Object { $_.$($LocalizedData.HAEnabled) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.HAEnabled + } + if ($Healthcheck.Cluster.DRSEnabled) { + $ClusterDetail | Where-Object { $_.$($LocalizedData.DRSEnabled) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.DRSEnabled + } + if ($Healthcheck.Cluster.VsanEnabled) { + $ClusterDetail | Where-Object { $_.$($LocalizedData.VSANEnabled) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.VSANEnabled + } + if ($Healthcheck.Cluster.EvcEnabled) { + $ClusterDetail | Where-Object { $_.$($LocalizedData.EVCMode) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.EVCMode + } + if ($InfoLevel.Cluster -ge 4) { + $ClusterDetail | ForEach-Object { + $ClusterHosts = $Cluster | Get-VMHost | Sort-Object Name + Add-Member -InputObject $_ -MemberType NoteProperty -Name $LocalizedData.Hosts -Value ($ClusterHosts.Name -join ', ') + $ClusterVMs = $Cluster | Get-VM | Sort-Object Name + Add-Member -InputObject $_ -MemberType NoteProperty -Name $LocalizedData.VirtualMachines -Value ($ClusterVMs.Name -join ', ') + } + } + $TableParams = @{ + Name = ($LocalizedData.TableClusterConfig -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterDetail | Table @TableParams + #endregion Cluster Configuration + + # Call sub-functions for HA, Proactive HA, and DRS + Get-AbrVSphereClusterHA + Get-AbrVSphereClusterProactiveHA + Get-AbrVSphereClusterDRS + } + } + } + #endregion Cluster Detailed Information + } + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 new file mode 100644 index 0000000..2ba1890 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 @@ -0,0 +1,619 @@ +<# +.SYNOPSIS + Private function to report vSphere DRS cluster configuration. +.DESCRIPTION + Generates a PScriboDocument section detailing the vSphere DRS configuration + for a given cluster, including DRS settings, Additional Options, Power Management, + Advanced Options, DRS Cluster Groups, DRS VM/Host Rules, DRS Rules, VM Overrides, + Update Manager Baselines, Update Manager Compliance, and Permissions subsections. +.NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman +.INPUTS + None. Uses variables from the parent scope: + $Cluster, $ClusterDrsConfig, $ClusterConfigEx, $VMLookup, $InfoLevel, $Report, + $Healthcheck, $UserPrivileges, $VumServer, $vCenter, $VUMConnection, $reportTranslate +.OUTPUTS + None. Writes PScriboDocument content directly. +#> +function Get-AbrVSphereClusterDRS { + [CmdletBinding()] + param () + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereClusterDRS + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Cluster) + } + process { + Try { + if ($Cluster.DrsEnabled) { + Write-PScriboMessage -Message $LocalizedData.Collecting + Section -Style Heading4 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $Cluster) + BlankLine + + #region vSphere DRS Cluster Specifications + $DrsCluster = [PSCustomObject]@{ + ($LocalizedData.DRS) = if ($Cluster.DrsEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + } + $MemberProps = @{ + 'InputObject' = $DrsCluster + 'MemberType' = 'NoteProperty' + } + Switch ($Cluster.DrsAutomationLevel) { + 'Manual' { + Add-Member @MemberProps -Name $LocalizedData.AutomationLevel -Value $LocalizedData.Manual + } + 'PartiallyAutomated' { + Add-Member @MemberProps -Name $LocalizedData.AutomationLevel -Value $LocalizedData.PartiallyAutomated + } + 'FullyAutomated' { + Add-Member @MemberProps -Name $LocalizedData.AutomationLevel -Value $LocalizedData.FullyAutomated + } + } + Add-Member @MemberProps -Name $LocalizedData.MigrationThreshold -Value $ClusterDrsConfig.VmotionRate + if ($ClusterConfigEx.ProactiveDrsConfig.Enabled) { + Add-Member @MemberProps -Name $LocalizedData.PredictiveDRS -Value $LocalizedData.Enabled + } else { + Add-Member @MemberProps -Name $LocalizedData.PredictiveDRS -Value $LocalizedData.Disabled + } + if ($ClusterDrsConfig.EnableVmBehaviorOverrides) { + Add-Member @MemberProps -Name $LocalizedData.VirtualMachineAutomation -Value $LocalizedData.Enabled + } else { + Add-Member @MemberProps -Name $LocalizedData.VirtualMachineAutomation -Value $LocalizedData.Disabled + } + if ($Healthcheck.Cluster.DrsEnabled) { + $DrsCluster | Where-Object { $_.$($LocalizedData.DRS) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.DRS + } + if ($Healthcheck.Cluster.DrsAutomationLevelFullyAuto) { + $DrsCluster | Where-Object { $_.$($LocalizedData.AutomationLevel) -ne $LocalizedData.FullyAutomated } | Set-Style -Style Warning -Property $LocalizedData.AutomationLevel + } + $TableParams = @{ + Name = ($LocalizedData.TableDRSConfig -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DrsCluster | Table @TableParams + #endregion vSphere DRS Cluster Specifications + + #region DRS Cluster Additional Options + $DrsAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterDRS' } + if ($DrsAdvancedSettings) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.AdditionalOptions { + $DrsAdditionalOptions = [PSCustomObject] @{ + ($LocalizedData.VMDistribution) = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'TryBalanceVmsPerHost' }).Value) { + '1' { $LocalizedData.Enabled } + $null { $LocalizedData.Disabled } + } + ($LocalizedData.MemoryMetricForLB) = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'PercentIdleMBInMemDemand' }).Value) { + '100' { $LocalizedData.Enabled } + $null { $LocalizedData.Disabled } + } + ($LocalizedData.CPUOverCommitment) = if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerCore' }).Value) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + } + $MemberProps = @{ + 'InputObject' = $DrsAdditionalOptions + 'MemberType' = 'NoteProperty' + } + if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerCore' }).Value) { + Add-Member @MemberProps -Name $LocalizedData.OverCommitmentRatio -Value "$(($DrsAdvancedSettings | Where-Object {$_.name -eq 'MaxVcpusPerCore'}).Value):1 (vCPU:pCPU)" + } + if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerClusterPct' }).Value) { + Add-Member @MemberProps -Name $LocalizedData.OverCommitmentRatioCluster -Value "$(($DrsAdvancedSettings | Where-Object {$_.name -eq 'MaxVcpusPerClusterPct'}).Value) %" + } + $TableParams = @{ + Name = ($LocalizedData.TableDRSAdditional -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DrsAdditionalOptions | Table @TableParams + } + } + #endregion DRS Cluster Additional Options + + #region vSphere DPM Configuration + if ($ClusterConfigEx.DpmConfigInfo.Enabled) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.PowerManagement { + $DpmConfig = [PSCustomObject]@{ + ($LocalizedData.DPM) = if ($ClusterConfigEx.DpmConfigInfo.Enabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + } + $MemberProps = @{ + 'InputObject' = $DpmConfig + 'MemberType' = 'NoteProperty' + } + Switch ($ClusterConfigEx.DpmConfigInfo.DefaultDpmBehavior) { + 'manual' { + Add-Member @MemberProps -Name $LocalizedData.DPMAutomationLevel -Value $LocalizedData.Manual + } + 'automated' { + Add-Member @MemberProps -Name $LocalizedData.DPMAutomationLevel -Value $LocalizedData.Automated + } + } + if ($ClusterConfigEx.DpmConfigInfo.DefaultDpmBehavior -eq 'automated') { + Add-Member @MemberProps -Name $LocalizedData.DPMThreshold -Value $ClusterConfigEx.DpmConfigInfo.HostPowerActionRate + } + $TableParams = @{ + Name = ($LocalizedData.TableDPM -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DpmConfig | Table @TableParams + } + } + #endregion vSphere DPM Configuration + + #region vSphere DRS Cluster Advanced Options + $DrsAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterDRS' } + if ($DrsAdvancedSettings) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.AdvancedOptions { + $DrsAdvancedOptions = @() + foreach ($DrsAdvancedSetting in $DrsAdvancedSettings) { + $DrsAdvancedOption = [PSCustomObject]@{ + ($LocalizedData.Key) = $DrsAdvancedSetting.Name + ($LocalizedData.Value) = $DrsAdvancedSetting.Value + } + $DrsAdvancedOptions += $DrsAdvancedOption + } + $TableParams = @{ + Name = ($LocalizedData.TableDRSAdvanced -f $Cluster) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DrsAdvancedOptions | Sort-Object $LocalizedData.Key | Table @TableParams + } + } + #endregion vSphere DRS Cluster Advanced Options + + #region vSphere DRS Cluster Group + $DrsClusterGroups = $Cluster | Get-DrsClusterGroup + if ($DrsClusterGroups) { + #region vSphere DRS Cluster Group Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DRSClusterGroups { + $DrsGroups = foreach ($DrsClusterGroup in $DrsClusterGroups) { + [PSCustomObject]@{ + ($LocalizedData.GroupName) = $DrsClusterGroup.Name + ($LocalizedData.GroupType) = Switch ($DrsClusterGroup.GroupType) { + 'VMGroup' { $LocalizedData.VMGroupType } + 'VMHostGroup' { $LocalizedData.VMHostGroupType } + default { $DrsClusterGroup.GroupType } + } + ($LocalizedData.GroupMembers) = if (($DrsClusterGroup.Member).Count -gt 0) { + ($DrsClusterGroup.Member | Sort-Object) -join ', ' + } else { + $LocalizedData.None + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableDRSGroups -f $Cluster) + ColumnWidths = 42, 16, 42 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DrsGroups | Sort-Object $LocalizedData.GroupName, $LocalizedData.GroupType | Table @TableParams + } + #endregion vSphere DRS Cluster Group Section + + #region vSphere DRS Cluster VM/Host Rules + $DrsVMHostRules = $Cluster | Get-DrsVMHostRule + if ($DrsVMHostRules) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DRSVMHostRules { + $DrsVMHostRuleDetail = foreach ($DrsVMHostRule in $DrsVMHostRules) { + [PSCustomObject]@{ + ($LocalizedData.RuleName) = $DrsVMHostRule.Name + ($LocalizedData.RuleType) = Switch ($DrsVMHostRule.Type) { + 'MustRunOn' { $LocalizedData.MustRunOn } + 'ShouldRunOn' { $LocalizedData.ShouldRunOn } + 'MustNotRunOn' { $LocalizedData.MustNotRunOn } + 'ShouldNotRunOn' { $LocalizedData.ShouldNotRunOn } + default { $DrsVMHostRule.Type } + } + ($LocalizedData.RuleEnabled) = if ($DrsVMHostRule.Enabled) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + ($LocalizedData.VMGroup) = $DrsVMHostRule.VMGroup + ($LocalizedData.HostGroup) = $DrsVMHostRule.VMHostGroup + } + } + if ($Healthcheck.Cluster.DrsVMHostRules) { + $DrsVMHostRuleDetail | Where-Object { $_.$($LocalizedData.RuleEnabled) -eq $LocalizedData.No } | Set-Style -Style Warning -Property $LocalizedData.RuleEnabled + } + $TableParams = @{ + Name = ($LocalizedData.TableDRSVMHostRules -f $Cluster) + ColumnWidths = 22, 22, 12, 22, 22 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DrsVMHostRuleDetail | Sort-Object $LocalizedData.RuleName | Table @TableParams + } + } + #endregion vSphere DRS Cluster VM/Host Rules + + #region vSphere DRS Cluster Rules + $DrsRules = $Cluster | Get-DrsRule + if ($DrsRules) { + #region vSphere DRS Cluster Rules Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DRSRules { + $DrsRuleDetail = foreach ($DrsRule in $DrsRules) { + [PSCustomObject]@{ + ($LocalizedData.RuleName) = $DrsRule.Name + ($LocalizedData.RuleType) = Switch ($DrsRule.Type) { + 'VMAffinity' { $LocalizedData.VMAffinity } + 'VMAntiAffinity' { $LocalizedData.VMAntiAffinity } + } + ($LocalizedData.RuleEnabled) = if ($DrsRule.Enabled) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + ($LocalizedData.Mandatory) = $DrsRule.Mandatory + ($LocalizedData.RuleVMs) = ($DrsRule.VMIds | ForEach-Object { (Get-View -Id $_).name }) -join ', ' + } + if ($Healthcheck.Cluster.DrsRules) { + $DrsRuleDetail | Where-Object { $_.$($LocalizedData.RuleEnabled) -eq $LocalizedData.No } | Set-Style -Style Warning -Property $LocalizedData.RuleEnabled + } + } + $TableParams = @{ + Name = ($LocalizedData.TableDRSRules -f $Cluster) + ColumnWidths = 26, 25, 12, 12, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DrsRuleDetail | Sort-Object $LocalizedData.RuleType | Table @TableParams + } + #endregion vSphere DRS Cluster Rules Section + } + #endregion vSphere DRS Cluster Rules + } + #endregion vSphere DRS Cluster Group + + #region Cluster VM Overrides + $DrsVmOverrides = $Cluster.ExtensionData.Configuration.DrsVmConfig + $DasVmOverrides = $Cluster.ExtensionData.Configuration.DasVmConfig + if ($DrsVmOverrides -or $DasVmOverrides) { + #region VM Overrides Section + Section -Style NOTOCHeading4 -ExcludeFromTOC $LocalizedData.VMOverrides { + #region vSphere DRS VM Overrides + if ($DrsVmOverrides) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DRS { + $DrsVmOverrideDetails = foreach ($DrsVmOverride in $DrsVmOverrides) { + [PSCustomObject]@{ + ($LocalizedData.VirtualMachine) = $VMLookup."$($DrsVmOverride.Key.Type)-$($DrsVmOverride.Key.Value)" + ($LocalizedData.DRSAutomationLevel) = if ($DrsVmOverride.Enabled -eq $false) { + $LocalizedData.Disabled + } else { + Switch ($DrsVmOverride.Behavior) { + 'manual' { $LocalizedData.Manual } + 'partiallyAutomated' { $LocalizedData.PartiallyAutomated } + 'fullyAutomated' { $LocalizedData.FullyAutomated } + default { $DrsVmOverride.Behavior } + } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableDRSVMOverrides -f $Cluster) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DrsVmOverrideDetails | Sort-Object $LocalizedData.VirtualMachine | Table @TableParams + } + } + #endregion vSphere DRS VM Overrides + + #region vSphere HA VM Overrides + if ($DasVmOverrides) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.SectionVSphereHA { + $DasVmOverrideDetails = foreach ($DasVmOverride in $DasVmOverrides) { + [PSCustomObject]@{ + ($LocalizedData.VirtualMachine) = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" + ($LocalizedData.HARestartPriority) = Switch ($DasVmOverride.DasSettings.RestartPriority) { + $null { '--' } + 'lowest' { $LocalizedData.Lowest } + 'low' { $LocalizedData.Low } + 'medium' { $LocalizedData.Medium } + 'high' { $LocalizedData.High } + 'highest' { $LocalizedData.Highest } + 'disabled' { $LocalizedData.Disabled } + 'clusterRestartPriority' { $LocalizedData.ClusterDefault } + } + ($LocalizedData.VMDependencyTimeout) = Switch ($DasVmOverride.DasSettings.RestartPriorityTimeout) { + $null { '--' } + '-1' { $LocalizedData.Disabled } + default { $LocalizedData.Seconds -f $DasVmOverride.DasSettings.RestartPriorityTimeout } + } + ($LocalizedData.HAIsolationResponse) = Switch ($DasVmOverride.DasSettings.IsolationResponse) { + $null { '--' } + 'none' { $LocalizedData.Disabled } + 'powerOff' { $LocalizedData.PowerOffAndRestart } + 'shutdown' { $LocalizedData.ShutdownAndRestartVMs } + 'clusterIsolationResponse' { $LocalizedData.ClusterDefault } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableHAVMOverrides -f $Cluster) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DasVmOverrideDetails | Sort-Object $LocalizedData.VirtualMachine | Table @TableParams + + #region PDL/APD Protection Settings Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.SectionPDLAPD { + $DasVmOverridePdlApd = foreach ($DasVmOverride in $DasVmOverrides) { + $DasVmComponentProtection = $DasVmOverride.DasSettings.VmComponentProtectionSettings + [PSCustomObject]@{ + ($LocalizedData.VirtualMachine) = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" + ($LocalizedData.PDLFailureResponse) = Switch ($DasVmComponentProtection.VmStorageProtectionForPDL) { + $null { '--' } + 'clusterDefault' { $LocalizedData.ClusterDefault } + 'warning' { $LocalizedData.IssueEvents } + 'restartAggressive' { $LocalizedData.PowerOffAndRestart } + 'disabled' { $LocalizedData.Disabled } + } + ($LocalizedData.APDFailureResponse) = Switch ($DasVmComponentProtection.VmStorageProtectionForAPD) { + $null { '--' } + 'clusterDefault' { $LocalizedData.ClusterDefault } + 'warning' { $LocalizedData.IssueEvents } + 'restartConservative' { $LocalizedData.PowerOffRestartConservative } + 'restartAggressive' { $LocalizedData.PowerOffRestartAggressive } + 'disabled' { $LocalizedData.Disabled } + } + ($LocalizedData.VMFailoverDelay) = Switch ($DasVmComponentProtection.VmTerminateDelayForAPDSec) { + $null { '--' } + '-1' { $LocalizedData.Disabled } + default { $LocalizedData.Minutes -f (($DasVmComponentProtection.VmTerminateDelayForAPDSec)/60) } + } + ($LocalizedData.ResponseRecovery) = Switch ($DasVmComponentProtection.VmReactionOnAPDCleared) { + $null { '--' } + 'reset' { $LocalizedData.ResetVMs } + 'disabled' { $LocalizedData.Disabled } + 'useClusterDefault' { $LocalizedData.ClusterDefault } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableHAPDLAPD -f $Cluster) + ColumnWidths = 20, 20, 20, 20, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DasVmOverridePdlApd | Sort-Object $LocalizedData.VirtualMachine | Table @TableParams + } + #endregion PDL/APD Protection Settings Section + + #region VM Monitoring Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VMMonitoring { + $DasVmOverrideVmMonitoring = foreach ($DasVmOverride in $DasVmOverrides) { + $DasVmMonitoring = $DasVmOverride.DasSettings.VmToolsMonitoringSettings + [PSCustomObject]@{ + ($LocalizedData.VirtualMachine) = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" + ($LocalizedData.VMMonitoring) = Switch ($DasVmMonitoring.VmMonitoring) { + $null { '--' } + 'vmMonitoringDisabled' { $LocalizedData.Disabled } + 'vmMonitoringOnly' { $LocalizedData.VMMonitoringOnly } + 'vmAndAppMonitoring' { $LocalizedData.VMAndAppMonitoring } + } + ($LocalizedData.VMMonitoringFailureInterval) = Switch ($DasVmMonitoring.FailureInterval) { + $null { '--' } + default { + if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { + '--' + } else { + $LocalizedData.Seconds -f $DasVmMonitoring.FailureInterval + } + } + } + ($LocalizedData.VMMonitoringMinUpTime) = Switch ($DasVmMonitoring.MinUptime) { + $null { '--' } + default { + if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { + '--' + } else { + $LocalizedData.Seconds -f $DasVmMonitoring.MinUptime + } + } + } + ($LocalizedData.VMMonitoringMaxFailures) = Switch ($DasVmMonitoring.MaxFailures) { + $null { '--' } + default { + if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { + '--' + } else { + $DasVmMonitoring.MaxFailures + } + } + } + ($LocalizedData.VMMonitoringMaxFailureWindow) = Switch ($DasVmMonitoring.MaxFailureWindow) { + $null { '--' } + '-1' { $LocalizedData.NoWindow } + default { + if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { + '--' + } else { + $LocalizedData.WithinHours -f (($DasVmMonitoring.MaxFailureWindow)/3600) + } + } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableHAVMMonitoring -f $Cluster) + ColumnWidths = 40, 12, 12, 12, 12, 12 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DasVmOverrideVmMonitoring | Sort-Object $LocalizedData.VirtualMachine | Table @TableParams + } + #endregion VM Monitoring Section + } + } + #endregion vSphere HA VM Overrides + } + #endregion VM Overrides Section + } + #endregion Cluster VM Overrides + + #region Cluster VUM Baselines + if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { + if ($VUMConnection) { + Try { + $ClusterPatchBaselines = $Cluster | Get-PatchBaseline + } Catch { + Write-PScriboMessage -Message $LocalizedData.VUMBaselineNotAvailable + } + if ($ClusterPatchBaselines) { + Section -Style Heading4 $LocalizedData.UpdateManagerBaselines { + $ClusterBaselines = foreach ($ClusterBaseline in $ClusterPatchBaselines) { + [PSCustomObject]@{ + ($LocalizedData.Baseline) = $ClusterBaseline.Name + ($LocalizedData.Description) = $ClusterBaseline.Description + ($LocalizedData.Type) = $ClusterBaseline.BaselineType + ($LocalizedData.TargetType) = $ClusterBaseline.TargetType + ($LocalizedData.LastUpdate) = ($ClusterBaseline.LastUpdateTime).ToLocalTime().ToString() + ($LocalizedData.NumPatches) = $ClusterBaseline.CurrentPatches.Count + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMBaselines -f $Cluster) + ColumnWidths = 25, 25, 10, 10, 20, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterBaselines | Sort-Object $LocalizedData.Baseline | Table @TableParams + } + } + if ($Healthcheck.Cluster.VUMCompliance) { + $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.Unknown } | Set-Style -Style Warning + $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.NotCompliant -or $_.$($LocalizedData.Status) -eq $LocalizedData.Incompatible } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMCompliance -f $Cluster) + ColumnWidths = 25, 50, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterComplianceInfo | Sort-Object $LocalizedData.Entity, $LocalizedData.BaselineInfo | Table @TableParams + } + } else { + Write-PScriboMessage -Message $LocalizedData.VUMPrivilegeMsgBaselines + } + #endregion Cluster VUM Baselines + + #region Cluster VUM Compliance (Advanced Detail Information) + if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { + if ($InfoLevel.Cluster -ge 4 -and $VumServer.Name) { + Try { + $ClusterCompliances = $Cluster | Get-Compliance + } Catch { + Write-PScriboMessage -Message $LocalizedData.VUMComplianceNotAvailable + } + if ($ClusterCompliances) { + Section -Style Heading4 $LocalizedData.UpdateManagerCompliance { + $ClusterComplianceInfo = foreach ($ClusterCompliance in $ClusterCompliances) { + [PSCustomObject]@{ + ($LocalizedData.Entity) = $ClusterCompliance.Entity + ($LocalizedData.BaselineInfo) = $ClusterCompliance.Baseline.Name + ($LocalizedData.Status) = Switch ($ClusterCompliance.Status) { + 'NotCompliant' { $LocalizedData.NotCompliant } + default { $ClusterCompliance.Status } + } + } + } + if ($Healthcheck.Cluster.VUMCompliance) { + $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.Unknown } | Set-Style -Style Warning + $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.NotCompliant -or $_.$($LocalizedData.Status) -eq $LocalizedData.Incompatible } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMCompliance -f $Cluster) + ColumnWidths = 25, 50, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterComplianceInfo | Sort-Object $LocalizedData.Entity, $LocalizedData.BaselineInfo | Table @TableParams + } + } + } + } else { + Write-PScriboMessage -Message $LocalizedData.VUMPrivilegeMsgCompliance + } + #endregion Cluster VUM Compliance (Advanced Detail Information) + + #region Cluster Permissions + Section -Style NOTOCHeading4 -ExcludeFromTOC $LocalizedData.Permissions { + Paragraph ($LocalizedData.ParagraphPermissions -f $Cluster) + BlankLine + $VIPermissions = $Cluster | Get-VIPermission + $ClusterVIPermissions = foreach ($VIPermission in $VIPermissions) { + [PSCustomObject]@{ + ($LocalizedData.UserGroup) = $VIPermission.Principal + ($LocalizedData.IsGroup) = if ($VIPermission.IsGroup) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + ($LocalizedData.Role) = $VIPermission.Role + ($LocalizedData.DefinedIn) = $VIPermission.Entity + ($LocalizedData.Propagate) = if ($VIPermission.Propagate) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TablePermissions -f $Cluster) + ColumnWidths = 42, 12, 20, 14, 12 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterVIPermissions | Sort-Object $LocalizedData.UserGroup | Table @TableParams + } + #endregion Cluster Permissions + } + } + } Catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterHA.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterHA.ps1 new file mode 100644 index 0000000..9bffc37 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterHA.ps1 @@ -0,0 +1,250 @@ +<# +.SYNOPSIS + Private function to report vSphere HA cluster configuration. +.DESCRIPTION + Generates a PScriboDocument section detailing the vSphere HA configuration + for a given cluster, including Failures and Responses, Admission Control, + Heartbeat Datastores, and Advanced Options subsections. +.NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman +.INPUTS + None. Uses variables from the parent scope: + $Cluster, $ClusterDasConfig, $InfoLevel, $Report, $Healthcheck, $TextInfo, $reportTranslate +.OUTPUTS + None. Writes PScriboDocument content directly. +#> +function Get-AbrVSphereClusterHA { + [CmdletBinding()] + param () + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereClusterHA + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Cluster) + } + process { + try { + if ($Cluster.HAEnabled) { + Write-PScriboMessage -Message $LocalizedData.Collecting + Section -Style Heading4 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $Cluster) + #region vSphere HA Cluster Failures and Responses + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.FailuresAndResponses { + $HAClusterResponses = [PSCustomObject]@{ + $LocalizedData.HostMonitoring = $TextInfo.ToTitleCase($ClusterDasConfig.HostMonitoring) + } + if ($ClusterDasConfig.HostMonitoring -eq 'Enabled') { + $MemberProps = @{ + 'InputObject' = $HAClusterResponses + 'MemberType' = 'NoteProperty' + } + if ($ClusterDasConfig.DefaultVmSettings.RestartPriority -eq 'Disabled') { + Add-Member @MemberProps -Name $LocalizedData.HostFailureResponse -Value $LocalizedData.Disabled + } else { + Add-Member @MemberProps -Name $LocalizedData.HostFailureResponse -Value $LocalizedData.RestartVMs + switch ($Cluster.HAIsolationResponse) { + 'DoNothing' { + Add-Member @MemberProps -Name $LocalizedData.HostIsolationResponse -Value $LocalizedData.Disabled + } + 'Shutdown' { + Add-Member @MemberProps -Name $LocalizedData.HostIsolationResponse -Value $LocalizedData.ShutdownAndRestart + } + 'PowerOff' { + Add-Member @MemberProps -Name $LocalizedData.HostIsolationResponse -Value $LocalizedData.PowerOffAndRestart + } + } + Add-Member @MemberProps -Name $LocalizedData.VMRestartPriority -Value $Cluster.HARestartPriority + switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmStorageProtectionForPDL) { + 'disabled' { + Add-Member @MemberProps -Name $LocalizedData.PDLProtection -Value $LocalizedData.Disabled + } + 'warning' { + Add-Member @MemberProps -Name $LocalizedData.PDLProtection -Value $LocalizedData.IssueEvents + } + 'restartAggressive' { + Add-Member @MemberProps -Name $LocalizedData.PDLProtection -Value $LocalizedData.PowerOffAndRestart + } + } + switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmStorageProtectionForAPD) { + 'disabled' { + Add-Member @MemberProps -Name $LocalizedData.APDProtection -Value $LocalizedData.Disabled + } + 'warning' { + Add-Member @MemberProps -Name $LocalizedData.APDProtection -Value $LocalizedData.IssueEvents + } + 'restartConservative' { + Add-Member @MemberProps -Name $LocalizedData.APDProtection -Value $LocalizedData.PowerOffRestartConservative + } + 'restartAggressive' { + Add-Member @MemberProps -Name $LocalizedData.APDProtection -Value $LocalizedData.PowerOffRestartAggressive + } + } + switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmReactionOnAPDCleared) { + 'none' { + Add-Member @MemberProps -Name $LocalizedData.APDRecovery -Value $LocalizedData.Disabled + } + 'reset' { + Add-Member @MemberProps -Name $LocalizedData.APDRecovery -Value $LocalizedData.ResetVMs + } + } + } + switch ($ClusterDasConfig.VmMonitoring) { + 'vmMonitoringDisabled' { + Add-Member @MemberProps -Name $LocalizedData.VMMonitoring -Value $LocalizedData.Disabled + } + 'vmMonitoringOnly' { + Add-Member @MemberProps -Name $LocalizedData.VMMonitoring -Value $LocalizedData.VMMonitoringOnly + } + 'vmAndAppMonitoring' { + Add-Member @MemberProps -Name $LocalizedData.VMMonitoring -Value $LocalizedData.VMAndAppMonitoring + } + } + } + if ($Healthcheck.Cluster.HostFailureResponse) { + $HAClusterResponses | Where-Object { $_.$($LocalizedData.HostFailureResponse) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.HostFailureResponse + } + if ($Healthcheck.Cluster.HostMonitoring) { + $HAClusterResponses | Where-Object { $_.$($LocalizedData.HostMonitoring) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.HostMonitoring + } + if ($Healthcheck.Cluster.DatastoreOnPDL) { + $HAClusterResponses | Where-Object { $_.$($LocalizedData.PDLProtection) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.PDLProtection + } + if ($Healthcheck.Cluster.DatastoreOnAPD) { + $HAClusterResponses | Where-Object { $_.$($LocalizedData.APDProtection) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.APDProtection + } + if ($Healthcheck.Cluster.APDTimeout) { + $HAClusterResponses | Where-Object { $_.$($LocalizedData.APDRecovery) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.APDRecovery + } + if ($Healthcheck.Cluster.vmMonitoring) { + $HAClusterResponses | Where-Object { $_.$($LocalizedData.VMMonitoring) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.VMMonitoring + } + $TableParams = @{ + Name = ($LocalizedData.TableHAFailures -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $HAClusterResponses | Table @TableParams + } + #endregion vSphere HA Cluster Failures and Responses + + #region vSphere HA Cluster Admission Control + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.AdmissionControl { + $HAAdmissionControl = [PSCustomObject]@{ + $LocalizedData.AdmissionControl = if ($Cluster.HAAdmissionControlEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + } + if ($Cluster.HAAdmissionControlEnabled) { + $MemberProps = @{ + 'InputObject' = $HAAdmissionControl + 'MemberType' = 'NoteProperty' + } + Add-Member @MemberProps -Name $LocalizedData.FailoverLevel -Value $Cluster.HAFailoverLevel + switch ($ClusterDasConfig.AdmissionControlPolicy.GetType().Name) { + 'ClusterFailoverHostAdmissionControlPolicy' { + Add-Member @MemberProps -Name $LocalizedData.ACPolicy -Value $LocalizedData.DedicatedFailoverHosts + } + 'ClusterFailoverResourcesAdmissionControlPolicy' { + Add-Member @MemberProps -Name $LocalizedData.ACPolicy -Value $LocalizedData.ClusterResourcePercentage + } + 'ClusterFailoverLevelAdmissionControlPolicy' { + Add-Member @MemberProps -Name $LocalizedData.ACPolicy -Value $LocalizedData.SlotPolicy + } + } + if ($ClusterDasConfig.AdmissionControlPolicy.AutoComputePercentages) { + Add-Member @MemberProps -Name $LocalizedData.OverrideFailoverCapacity -Value $LocalizedData.No + } else { + Add-Member @MemberProps -Name $LocalizedData.OverrideFailoverCapacity -Value $LocalizedData.Yes + Add-Member @MemberProps -Name $LocalizedData.ACHostPercentage -Value $ClusterDasConfig.AdmissionControlPolicy.CpuFailoverResourcesPercent + Add-Member @MemberProps -Name $LocalizedData.ACMemPercentage -Value $ClusterDasConfig.AdmissionControlPolicy.MemoryFailoverResourcesPercent + } + if ($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy) { + Add-Member @MemberProps -Name $LocalizedData.SlotPolicy -Value $LocalizedData.FixedSlotSize + Add-Member @MemberProps -Name $LocalizedData.CPUSlotSize -Value "$($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy.Cpu) MHz" + Add-Member @MemberProps -Name $LocalizedData.MemorySlotSize -Value "$($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy.Memory) MB" + } else { + Add-Member @MemberProps -Name $LocalizedData.SlotPolicy -Value $LocalizedData.CoverAllPoweredOnVMs + } + if ($ClusterDasConfig.AdmissionControlPolicy.ResourceReductionToToleratePercent) { + Add-Member @MemberProps -Name $LocalizedData.PerfDegradationTolerate -Value "$($ClusterDasConfig.AdmissionControlPolicy.ResourceReductionToToleratePercent)%" + } + } + if ($Healthcheck.Cluster.HAAdmissionControl) { + $HAAdmissionControl | Where-Object { $_.$($LocalizedData.AdmissionControl) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.AdmissionControl + } + $TableParams = @{ + Name = ($LocalizedData.TableHAAdmissionControl -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $HAAdmissionControl | Table @TableParams + } + #endregion vSphere HA Cluster Admission Control + + #region vSphere HA Cluster Heartbeat Datastores + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.HeartbeatDatastores { + $HeartbeatDatastores = [PSCustomObject]@{ + $LocalizedData.HeartbeatSelectionPolicy = switch ($ClusterDasConfig.HBDatastoreCandidatePolicy) { + 'allFeasibleDsWithUserPreference' { $LocalizedData.HBPolicyAllFeasibleDsWithUserPreference } + 'allFeasibleDs' { $LocalizedData.HBPolicyAllFeasibleDs } + 'userSelectedDs' { $LocalizedData.HBPolicyUserSelectedDs } + default { $ClusterDasConfig.HBDatastoreCandidatePolicy } + } + $LocalizedData.HeartbeatDatastores = try { + (((Get-View -Id $ClusterDasConfig.HeartbeatDatastore -Property Name).Name | Sort-Object) -join ', ') + } catch { + $LocalizedData.NoneSpecified + } + } + $TableParams = @{ + Name = ($LocalizedData.TableHAHeartbeat -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $HeartbeatDatastores | Table @TableParams + } + #endregion vSphere HA Cluster Heartbeat Datastores + + #region vSphere HA Cluster Advanced Options + $HAAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterHA' } + if ($HAAdvancedSettings) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.HAAdvancedOptions { + $HAAdvancedOptions = @() + foreach ($HAAdvancedSetting in $HAAdvancedSettings) { + $HAAdvancedOption = [PSCustomObject]@{ + $LocalizedData.Key = $HAAdvancedSetting.Name + $LocalizedData.Value = $HAAdvancedSetting.Value + } + $HAAdvancedOptions += $HAAdvancedOption + } + $TableParams = @{ + Name = ($LocalizedData.TableHAAdvanced -f $Cluster) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $HAAdvancedOptions | Sort-Object $LocalizedData.Key | Table @TableParams + } + } + #endregion vSphere HA Cluster Advanced Options + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 new file mode 100644 index 0000000..7bb8549 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 @@ -0,0 +1,88 @@ +<# +.SYNOPSIS + Private function to report vSphere Proactive HA cluster configuration. +.DESCRIPTION + Generates a PScriboDocument section detailing the Proactive HA configuration + for a given cluster, including Failures and Responses subsection. + Proactive HA is only available in vSphere 6.5 and above. +.NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman +.INPUTS + None. Uses variables from the parent scope: + $ClusterConfigEx, $Cluster, $vCenter, $InfoLevel, $Report, $Healthcheck, $reportTranslate +.OUTPUTS + None. Writes PScriboDocument content directly. +#> +function Get-AbrVSphereClusterProactiveHA { + [CmdletBinding()] + param () + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereClusterProactiveHA + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Cluster) + } + process { + try { + # Proactive HA is only available in vSphere 6.5 and above + if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled -and $vCenter.Version -ge 6.5) { + Write-PScriboMessage -Message $LocalizedData.Collecting + # TODO: Proactive HA Providers + Section -Style Heading4 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $Cluster) + #region Proactive HA Failures and Responses Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.FailuresAndResponses { + $ProactiveHa = [PSCustomObject]@{ + ($LocalizedData.ProactiveHA) = if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + } + if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled) { + $ProactiveHaModerateRemediation = switch ($ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation) { + 'MaintenanceMode' { $LocalizedData.MaintenanceMode } + 'QuarantineMode' { $LocalizedData.QuarantineMode } + default { $ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation } + } + $ProactiveHaSevereRemediation = switch ($ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation) { + 'MaintenanceMode' { $LocalizedData.MaintenanceMode } + 'QuarantineMode' { $LocalizedData.QuarantineMode } + default { $ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation } + } + $MemberProps = @{ + 'InputObject' = $ProactiveHa + 'MemberType' = 'NoteProperty' + } + Add-Member @MemberProps -Name $LocalizedData.AutomationLevel -Value $ClusterConfigEx.InfraUpdateHaConfig.Behavior + if ($ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation -eq $ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation) { + Add-Member @MemberProps -Name $LocalizedData.Remediation -Value $ProactiveHaModerateRemediation + } else { + Add-Member @MemberProps -Name $LocalizedData.Remediation -Value $LocalizedData.MixedMode + Add-Member @MemberProps -Name $LocalizedData.ModerateRemediation -Value $ProactiveHaModerateRemediation + Add-Member @MemberProps -Name $LocalizedData.SevereRemediation -Value $ProactiveHaSevereRemediation + } + } + if ($Healthcheck.Cluster.ProactiveHA) { + $ProactiveHa | Where-Object { $_.$($LocalizedData.ProactiveHA) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.ProactiveHA + } + $TableParams = @{ + Name = ($LocalizedData.TableProactiveHA -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ProactiveHa | Table @TableParams + } + #endregion Proactive HA Failures and Responses Section + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 new file mode 100644 index 0000000..1e8a040 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 @@ -0,0 +1,192 @@ +function Get-AbrVSphereDSCluster { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere Datastore Cluster information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereDSCluster + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.DSCluster) + } + + process { + try { + if ($InfoLevel.DSCluster -ge 1) { + Write-PScriboMessage -Message $LocalizedData.Collecting + $DSClusters = Get-DatastoreCluster -Server $vCenter + if ($DSClusters) { + #region Datastore Clusters Section + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region Datastore Cluster Advanced Summary + if ($InfoLevel.DSCluster -le 2) { + BlankLine + $DSClusterInfo = foreach ($DSCluster in $DSClusters) { + [PSCustomObject]@{ + $LocalizedData.DSCluster = $DSCluster.Name + $LocalizedData.SDRSAutomationLevel = switch ($DSCluster.SdrsAutomationLevel) { + 'FullyAutomated' { $LocalizedData.FullyAutomated } + 'Manual' { $LocalizedData.Manual } + default { $DSCluster.SdrsAutomationLevel } + } + $LocalizedData.SpaceThreshold = "$($DSCluster.SpaceUtilizationThresholdPercent)%" + $LocalizedData.IOLoadBalancing = if ($DSCluster.IOLoadBalanceEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.IOLatencyThreshold = "$($DSCluster.IOLatencyThresholdMillisecond) ms" + } + } + if ($Healthcheck.DSCluster.SDRSAutomationLevelFullyAuto) { + $DSClusterInfo | Where-Object { $_.$($LocalizedData.SDRSAutomationLevel) -ne $LocalizedData.FullyAutomated } | Set-Style -Style Warning -Property $LocalizedData.SDRSAutomationLevel + } + $TableParams = @{ + Name = ($LocalizedData.TableDSClusterConfig -f $DSCluster.Name) + ColumnWidths = 20, 20, 20, 20, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DSClusterInfo | Sort-Object $LocalizedData.DSCluster | Table @TableParams + } + #endregion Datastore Cluster Advanced Summary + + #region Datastore Cluster Detailed Information + if ($InfoLevel.DSCluster -ge 3) { + foreach ($DSCluster in $DSClusters) { + # TODO: Space Load Balance Config, IO Load Balance Config, Rules + # TODO: Test Tags + + $DSCUsedPercent = if (0 -in @($DSCluster.FreeSpaceGB, $DSCluster.CapacityGB)) { 0 } else { [math]::Round((100 - (($DSCluster.FreeSpaceGB) / ($DSCluster.CapacityGB) * 100)), 2) } + $DSCFreePercent = if (0 -in @($DSCluster.FreeSpaceGB, $DSCluster.CapacityGB)) { 0 } else { [math]::Round(($DSCluster.FreeSpaceGB / $DSCluster.CapacityGB) * 100, 2) } + $DSCUsedCapacityGB = ($DSCluster.CapacityGB - $DSCluster.FreeSpaceGB) + + Section -Style Heading3 $DSCluster.Name { + Paragraph ($LocalizedData.ParagraphDSClusterDetail -f $DSCluster) + BlankLine + + $DSClusterDetail = [PSCustomObject]@{ + $LocalizedData.DSCluster = $DSCluster.Name + $LocalizedData.ID = $DSCluster.Id + $LocalizedData.SDRSAutomationLevel = switch ($DSCluster.SdrsAutomationLevel) { + 'FullyAutomated' { $LocalizedData.FullyAutomated } + 'Manual' { $LocalizedData.Manual } + default { $DSCluster.SdrsAutomationLevel } + } + $LocalizedData.SpaceThreshold = "$($DSCluster.SpaceUtilizationThresholdPercent)%" + $LocalizedData.IOLoadBalancing = if ($DSCluster.IOLoadBalanceEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.IOLatencyThreshold = "$($DSCluster.IOLatencyThresholdMillisecond) ms" + $LocalizedData.TotalCapacity = Convert-DataSize $DSCluster.CapacityGB + $LocalizedData.UsedCapacity = "{0} ({1}%)" -f (Convert-DataSize $DSCUsedCapacityGB), $DSCUsedPercent + $LocalizedData.FreeCapacity = "{0} ({1}%)" -f (Convert-DataSize $DSCluster.FreeSpaceGB), $DSCFreePercent + $LocalizedData.PercentUsed = $DSCUsedPercent + } + <# + $MemberProps = @{ + 'InputObject' = $DSClusterDetail + 'MemberType' = 'NoteProperty' + } + + if ($TagAssignments | Where-Object {$_.entity -eq $DSCluster}) { + Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $DSCluster}).Tag -join ',') + } + #> + if ($Healthcheck.DSCluster.CapacityUtilization) { + $DSClusterDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 90 } | Set-Style -Style Critical -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + $DSClusterDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 75 -and $_.$($LocalizedData.PercentUsed) -lt 90 } | Set-Style -Style Warning -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + } + if ($Healthcheck.DSCluster.SDRSAutomationLevel) { + $DSClusterDetail | Where-Object { $_.$($LocalizedData.SDRSAutomationLevel) -ne $LocalizedData.FullyAutomated } | Set-Style -Style Warning -Property $LocalizedData.SDRSAutomationLevel + } + $TableParams = @{ + Name = ($LocalizedData.TableDSClusterConfig -f $DSCluster.Name) + List = $true + Columns = $LocalizedData.DSCluster, $LocalizedData.ID, $LocalizedData.SDRSAutomationLevel, $LocalizedData.SpaceThreshold, $LocalizedData.IOLoadBalancing, $LocalizedData.IOLatencyThreshold, $LocalizedData.TotalCapacity, $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DSClusterDetail | Table @TableParams + + #region SDRS VM Overrides + $StoragePodProps = @{ + 'ViewType' = 'StoragePod' + 'Filter' = @{'Name' = $DSCluster.Name } + } + $StoragePod = Get-View @StoragePodProps + if ($StoragePod) { + $PodConfig = $StoragePod.PodStorageDrsEntry.StorageDrsConfig.PodConfig + # Set default automation value variables + switch ($PodConfig.DefaultVmBehavior) { + "automated" { $DefaultVmBehavior = ($LocalizedData.DefaultBehavior -f $LocalizedData.FullyAutomated) } + "manual" { $DefaultVmBehavior = ($LocalizedData.DefaultBehavior -f $LocalizedData.NoAutomation) } + } + if ($PodConfig.DefaultIntraVmAffinity) { + $DefaultIntraVmAffinity = ($LocalizedData.DefaultBehavior -f $LocalizedData.Yes) + } else { + $DefaultIntraVmAffinity = ($LocalizedData.DefaultBehavior -f $LocalizedData.No) + } + $VMOverrides = $StoragePod.PodStorageDrsEntry.StorageDrsConfig.VmConfig | Where-Object { + -not ( + ($null -eq $_.Enabled) -and + ($null -eq $_.IntraVmAffinity) + ) + } + } + + if ($VMOverrides) { + $VMOverrideDetails = foreach ($Override in $VMOverrides) { + [PSCustomObject]@{ + $LocalizedData.VirtualMachine = $VMLookup."$($Override.Vm.Type)-$($Override.Vm.Value)" + $LocalizedData.SDRSAutomationLevel = switch ($Override.Enabled) { + $true { $LocalizedData.FullyAutomated } + $false { $LocalizedData.Disabled } + $null { $DefaultVmBehavior } + } + $LocalizedData.KeepVMDKsTogether = switch ($Override.IntraVmAffinity) { + $true { $LocalizedData.Yes } + $false { $LocalizedData.No } + $null { $DefaultIntraVmAffinity } + } + } + } + Section -Style Heading4 $LocalizedData.SDRSVMOverrides { + $TableParams = @{ + Name = ($LocalizedData.TableSDRSVMOverrides -f $DSCluster.Name) + ColumnWidths = 50, 30, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMOverrideDetails | Sort-Object $LocalizedData.VirtualMachine | Table @TableParams + } + } + #endregion SDRS VM Overrides + } + } + } + #endregion Datastore Cluster Detailed Information + } + #endregion Datastore Clusters Section + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 new file mode 100644 index 0000000..51fa3d1 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 @@ -0,0 +1,202 @@ +function Get-AbrVSphereDatastore { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere Datastore information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereDatastore + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Datastore) + } + + process { + try { + if ($InfoLevel.Datastore -ge 1) { + Write-PScriboMessage -Message $LocalizedData.Collecting + if ($Datastores) { + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region Datastore Infomative Information + if ($InfoLevel.Datastore -le 2) { + BlankLine + $DatastoreInfo = foreach ($Datastore in $Datastores) { + + $DsUsedPercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) { 0 } else { [math]::Round((100 - (($Datastore.FreeSpaceGB) / ($Datastore.CapacityGB) * 100)), 2) } + $DsFreePercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) { 0 } else { [math]::Round(($Datastore.FreeSpaceGB / $Datastore.CapacityGB) * 100, 2) } + $DsUsedCapacityGB = ($Datastore.CapacityGB) - ($Datastore.FreeSpaceGB) + + [PSCustomObject]@{ + $LocalizedData.Datastore = $Datastore.Name + $LocalizedData.Type = $Datastore.Type + $LocalizedData.Version = if ($Datastore.FileSystemVersion) { + $Datastore.FileSystemVersion + } else { + '--' + } + $LocalizedData.NumHosts = $Datastore.ExtensionData.Host.Count + $LocalizedData.NumVMs = $Datastore.ExtensionData.VM.Count + $LocalizedData.TotalCapacity = Convert-DataSize $Datastore.CapacityGB + $LocalizedData.UsedCapacity = "{0} ({1}%)" -f (Convert-DataSize $DsUsedCapacityGB), $DsUsedPercent + $LocalizedData.FreeCapacity = "{0} ({1}%)" -f (Convert-DataSize $Datastore.FreeSpaceGB), $DsFreePercent + $LocalizedData.PercentUsed = $DsUsedPercent + } + } + if ($Healthcheck.Datastore.CapacityUtilization) { + $DatastoreInfo | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 90 } | Set-Style -Style Critical -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + $DatastoreInfo | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 75 -and + $_.$($LocalizedData.PercentUsed) -lt 90 } | Set-Style -Style Warning -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + } + $TableParams = @{ + Name = ($LocalizedData.TableDatastoreSummary -f $vCenterServerName) + Columns = $LocalizedData.Datastore, $LocalizedData.Type, $LocalizedData.Version, $LocalizedData.NumHosts, $LocalizedData.NumVMs, $LocalizedData.TotalCapacity, $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + ColumnWidths = 21, 10, 9, 9, 9, 14, 14, 14 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DatastoreInfo | Sort-Object $LocalizedData.Datastore | Table @TableParams + } + #endregion Datastore Advanced Summary + + #region Datastore Detailed Information + if ($InfoLevel.Datastore -ge 3) { + foreach ($Datastore in $Datastores) { + # TODO: Test Tags + + $DsUsedPercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) { 0 } else { [math]::Round((100 - (($Datastore.FreeSpaceGB) / ($Datastore.CapacityGB) * 100)), 2) } + $DSFreePercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) { 0 } else { [math]::Round(($Datastore.FreeSpaceGB / $Datastore.CapacityGB) * 100, 2) } + $UsedCapacityGB = ($Datastore.CapacityGB) - ($Datastore.FreeSpaceGB) + + #region Datastore Section + Section -Style Heading3 $Datastore.Name { + $DatastoreDetail = [PSCustomObject]@{ + $LocalizedData.Datastore = $Datastore.Name + $LocalizedData.ID = $Datastore.Id + $LocalizedData.Datacenter = $Datastore.Datacenter + $LocalizedData.Type = $Datastore.Type + $LocalizedData.Version = if ($Datastore.FileSystemVersion) { + $Datastore.FileSystemVersion + } else { + '--' + } + $LocalizedData.State = switch ($Datastore.State) { + 'Normal' { $LocalizedData.Normal } + 'Maintenance' { $LocalizedData.Maintenance } + 'Unmounted' { $LocalizedData.Unmounted } + default { $Datastore.State } + } + $LocalizedData.NumberOfHosts = $Datastore.ExtensionData.Host.Count + $LocalizedData.NumberOfVMs = $Datastore.ExtensionData.VM.Count + $LocalizedData.SIOC = if ($Datastore.StorageIOControlEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.CongestionThreshold = if ($Datastore.CongestionThresholdMillisecond) { + "$($Datastore.CongestionThresholdMillisecond) ms" + } else { + '--' + } + $LocalizedData.TotalCapacity = Convert-DataSize $Datastore.CapacityGB + $LocalizedData.UsedCapacity = "{0} ({1}%)" -f (Convert-DataSize $UsedCapacityGB), $DsUsedPercent + $LocalizedData.FreeCapacity = "{0} ({1}%)" -f (Convert-DataSize $Datastore.FreeSpaceGB), $DSFreePercent + $LocalizedData.PercentUsed = $DsUsedPercent + } + if ($Healthcheck.Datastore.CapacityUtilization) { + $DatastoreDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 90 } | Set-Style -Style Critical -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + $DatastoreDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 75 -and + $_.$($LocalizedData.PercentUsed) -lt 90 } | Set-Style -Style Warning -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + } + $MemberProps = @{ + 'InputObject' = $DatastoreDetail + 'MemberType' = 'NoteProperty' + } + <# + if ($TagAssignments | Where-Object {$_.entity -eq $Datastore}) { + Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $Datastore}).Tag -join ',') + } + #> + + #region Datastore Advanced Detailed Information + if ($InfoLevel.Datastore -ge 4) { + $DatastoreHosts = foreach ($DatastoreHost in $Datastore.ExtensionData.Host.Key) { + $VMHostLookup."$($DatastoreHost.Type)-$($DatastoreHost.Value)" + } + Add-Member @MemberProps -Name $LocalizedData.Hosts -Value (($DatastoreHosts | Sort-Object) -join ', ') + $DatastoreVMs = foreach ($DatastoreVM in $Datastore.ExtensionData.VM) { + $VMLookup."$($DatastoreVM.Type)-$($DatastoreVM.Value)" + } + Add-Member @MemberProps -Name $LocalizedData.VirtualMachines -Value (($DatastoreVMs | Sort-Object) -join ', ') + } + #endregion Datastore Advanced Detailed Information + $TableParams = @{ + Name = ($LocalizedData.TableDatastoreConfig -f $Datastore.Name) + List = $true + Columns = $LocalizedData.Datastore, $LocalizedData.ID, $LocalizedData.Datacenter, $LocalizedData.Type, $LocalizedData.Version, $LocalizedData.State, $LocalizedData.NumberOfHosts, $LocalizedData.NumberOfVMs, $LocalizedData.SIOC, $LocalizedData.CongestionThreshold, $LocalizedData.TotalCapacity, $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + ColumnWidths = 40, 60 + } + if ($InfoLevel.Datastore -ge 4) { + $TableParams['Columns'] += $LocalizedData.Hosts, $LocalizedData.VirtualMachines + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $DatastoreDetail | Sort-Object $LocalizedData.Datacenter, $LocalizedData.Datastore | Table @TableParams + + # Get VMFS volumes. Ignore local SCSILuns. + if (($Datastore.Type -eq 'VMFS') -and ($Datastore.ExtensionData.Info.Vmfs.Local -eq $false)) { + #region SCSI LUN Information Section + Section -Style Heading4 $LocalizedData.SCSILUNInfo { + $ScsiLuns = foreach ($DatastoreHost in $Datastore.ExtensionData.Host.Key) { + $DiskName = $Datastore.ExtensionData.Info.Vmfs.Extent.DiskName + $ScsiDeviceDetailProps = @{ + 'VMHosts' = $VMHosts + 'VMHostMoRef' = "$($DatastoreHost.Type)-$($DatastoreHost.Value)" + 'DatastoreDiskName' = $DiskName + } + $ScsiDeviceDetail = Get-ScsiDeviceDetail @ScsiDeviceDetailProps + + [PSCustomObject]@{ + $LocalizedData.Host = $VMHostLookup."$($DatastoreHost.Type)-$($DatastoreHost.Value)" + $LocalizedData.CanonicalName = $DiskName + $LocalizedData.Capacity = Convert-DataSize $ScsiDeviceDetail.CapacityGB + $LocalizedData.Vendor = $ScsiDeviceDetail.Vendor + $LocalizedData.Model = $ScsiDeviceDetail.Model + $LocalizedData.IsSSD = $ScsiDeviceDetail.Ssd + $LocalizedData.MultipathPolicy = $ScsiDeviceDetail.MultipathPolicy + $LocalizedData.Paths = $ScsiDeviceDetail.Paths + } + } + $TableParams = @{ + Name = ($LocalizedData.TableSCSILUN -f $vCenterServerName) + ColumnWidths = 19, 19, 10, 10, 10, 10, 14, 8 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ScsiLuns | Sort-Object $LocalizedData.Host | Table @TableParams + } + #endregion SCSI LUN Information Section + } + } + #endregion Datastore Section + } + } + #endregion Datastore Detailed Information + } + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 new file mode 100644 index 0000000..c97e8f6 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 @@ -0,0 +1,408 @@ +function Get-AbrVSphereNetwork { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere Network information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereNetwork + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Network) + } + + process { + try { + if ($InfoLevel.Network -ge 1) { + Write-PScriboMessage -Message $LocalizedData.Collecting + # Create Distributed Switch Section if they exist + $VDSwitches = Get-VDSwitch -Server $vCenter | Sort-Object Name + if ($VDSwitches) { + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region Distributed Switch Advanced Summary + if ($InfoLevel.Network -le 2) { + BlankLine + $VDSInfo = foreach ($VDS in $VDSwitches) { + [PSCustomObject]@{ + $LocalizedData.VDSwitch = $VDS.Name + $LocalizedData.Datacenter = $VDS.Datacenter + $LocalizedData.Manufacturer = $VDS.Vendor + $LocalizedData.Version = $VDS.Version + $LocalizedData.NumUplinks = $VDS.NumUplinkPorts + $LocalizedData.NumPorts = $VDS.NumPorts + $LocalizedData.NumHosts = $VDS.ExtensionData.Summary.HostMember.Count + $LocalizedData.NumVMs = $VDS.ExtensionData.Summary.VM.Count + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSSummary -f $vCenterServerName) + ColumnWidths = 20, 18, 18, 10, 10, 8, 8, 8 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSInfo | Table @TableParams + } + #endregion Distributed Switch Advanced Summary + + #region Distributed Switch Detailed Information + if ($InfoLevel.Network -ge 3) { + # TODO: LACP, NetFlow, NIOC + # TODO: Test Tags + foreach ($VDS in ($VDSwitches)) { + #region VDS Section + Section -Style Heading3 $VDS { + #region Distributed Switch General Properties + $VDSwitchDetail = [PSCustomObject]@{ + $LocalizedData.VDSwitch = $VDS.Name + $LocalizedData.ID = $VDS.Id + $LocalizedData.Datacenter = $VDS.Datacenter + $LocalizedData.Manufacturer = $VDS.Vendor + $LocalizedData.Version = $VDS.Version + $LocalizedData.NumberOfUplinks = $VDS.NumUplinkPorts + $LocalizedData.NumberOfPorts = $VDS.NumPorts + $LocalizedData.NumberOfPortGroups = $VDS.ExtensionData.Summary.PortGroupName.Count + $LocalizedData.NumberOfHosts = $VDS.ExtensionData.Summary.HostMember.Count + $LocalizedData.NumberOfVMs = $VDS.ExtensionData.Summary.VM.Count + $LocalizedData.MTU = $VDS.Mtu + $LocalizedData.NIOC = if ($VDS.ExtensionData.Config.NetworkResourceManagementEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.DiscoveryProtocol = $VDS.LinkDiscoveryProtocol + $LocalizedData.DiscoveryOperation = switch ($VDS.LinkDiscoveryProtocolOperation) { + 'listen' { $LocalizedData.Listen } + 'advertise' { $LocalizedData.Advertise } + 'both' { $LocalizedData.Both } + 'none' { $LocalizedData.Disabled } + default { $VDS.LinkDiscoveryProtocolOperation } + } + } + <# + $MemberProps = @{ + 'InputObject' = $VDSwitchDetail + 'MemberType' = 'NoteProperty' + } + if ($TagAssignments | Where-Object {$_.entity -eq $VDS}) { + Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VDS}).Tag -join ',') + } + #> + #region Network Advanced Detail Information + if ($InfoLevel.Network -ge 4) { + $VDSwitchDetail | ForEach-Object { + $VDSwitchHosts = $VDS | Get-VMHost | Sort-Object Name + Add-Member -InputObject $_ -MemberType NoteProperty -Name $LocalizedData.Hosts -Value ($VDSwitchHosts.Name -join ', ') + $VDSwitchVMs = $VDS | Get-VM | Sort-Object + Add-Member -InputObject $_ -MemberType NoteProperty -Name $LocalizedData.VirtualMachines -Value ($VDSwitchVMs.Name -join ', ') + } + } + #endregion Network Advanced Detail Information + $TableParams = @{ + Name = ($LocalizedData.TableVDSGeneral -f $VDS) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSwitchDetail | Table @TableParams + #endregion Distributed Switch General Properties + + #region Distributed Switch Uplink Ports + $VdsUplinks = $VDS | Get-VDPortgroup | Where-Object { $_.IsUplink -eq $true } | Get-VDPort + if ($VdsUplinks) { + Section -Style Heading4 $LocalizedData.UplinkPorts { + $VdsUplinkDetail = foreach ($VdsUplink in $VdsUplinks) { + [PSCustomObject]@{ + $LocalizedData.VDSwitch = $VdsUplink.Switch + $LocalizedData.Host = $VdsUplink.ProxyHost + $LocalizedData.UplinkName = $VdsUplink.Name + $LocalizedData.PhysicalNetworkAdapter = $VdsUplink.ConnectedEntity + $LocalizedData.UplinkPortGroup = $VdsUplink.Portgroup + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSUplinkPorts -f $VDS) + ColumnWidths = 20, 20, 20, 20, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VdsUplinkDetail | Sort-Object $LocalizedData.VDSwitch, $LocalizedData.Host, $LocalizedData.UplinkName | Table @TableParams + } + } + #endregion Distributed Switch Uplink Ports + + #region Distributed Switch Security + $VDSecurityPolicy = $VDS | Get-VDSecurityPolicy + if ($VDSecurityPolicy) { + Section -Style Heading4 $LocalizedData.VDSSecurity { + $VDSecurityPolicyDetail = [PSCustomObject]@{ + $LocalizedData.VDSwitch = $VDSecurityPolicy.VDSwitch + $LocalizedData.AllowPromiscuous = if ($VDSecurityPolicy.AllowPromiscuous) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + $LocalizedData.ForgedTransmits = if ($VDSecurityPolicy.ForgedTransmits) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + $LocalizedData.MACAddressChanges = if ($VDSecurityPolicy.MacChanges) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSSecurity -f $VDS) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSecurityPolicyDetail | Table @TableParams + } + } + #endregion Distributed Switch Security + + #region Distributed Switch Traffic Shaping + $VDSTrafficShaping = @() + $VDSTrafficShapingIn = $VDS | Get-VDTrafficShapingPolicy -Direction In + $VDSTrafficShapingOut = $VDS | Get-VDTrafficShapingPolicy -Direction Out + $VDSTrafficShaping += $VDSTrafficShapingIn + $VDSTrafficShaping += $VDSTrafficShapingOut + if ($VDSTrafficShapingIn -or $VDSTrafficShapingOut) { + Section -Style Heading4 $LocalizedData.VDSTrafficShaping { + $VDSTrafficShapingDetail = foreach ($VDSTrafficShape in $VDSTrafficShaping) { + [PSCustomObject]@{ + $LocalizedData.VDSwitch = $VDSTrafficShape.VDSwitch + $LocalizedData.Direction = $VDSTrafficShape.Direction + $LocalizedData.Status = if ($VDSTrafficShape.Enabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.AverageBandwidth = $VDSTrafficShape.AverageBandwidth + $LocalizedData.PeakBandwidth = $VDSTrafficShape.PeakBandwidth + $LocalizedData.BurstSize = $VDSTrafficShape.BurstSize + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSTrafficShaping -f $VDS) + ColumnWidths = 25, 13, 11, 17, 17, 17 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSTrafficShapingDetail | Sort-Object $LocalizedData.Direction | Table @TableParams + } + } + #endregion Distributed Switch Traffic Shaping + + #region Distributed Switch Port Groups + # TODO: Test Tags + $VDSPortgroups = $VDS | Get-VDPortgroup + if ($VDSPortgroups) { + Section -Style Heading4 $LocalizedData.VDSPortGroups { + $VDSPortgroupDetail = foreach ($VDSPortgroup in $VDSPortgroups) { + [PSCustomObject]@{ + $LocalizedData.PortGroup = $VDSPortgroup.Name + $LocalizedData.VDSwitch = $VDSPortgroup.VDSwitch + $LocalizedData.Datacenter = $VDSPortgroup.Datacenter + $LocalizedData.VLANConfiguration = if ($VDSPortgroup.VlanConfiguration) { + $VDSPortgroup.VlanConfiguration + } else { + '--' + } + $LocalizedData.PortBinding = $VDSPortgroup.PortBinding + $LocalizedData.NumPorts = $VDSPortgroup.NumPorts + <# + # Tags on portgroups cause Get-TagAssignments to error + 'Tags' = & { + if ($TagAssignments | Where-Object {$_.entity -eq $VDSPortgroup}) { + ($TagAssignments | Where-Object {$_.entity -eq $VDSPortgroup}).Tag -join ',' + } else { + '--' + } + } + #> + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSPortGroups -f $VDS) + ColumnWidths = 20, 20, 20, 15, 15, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSPortgroupDetail | Sort-Object $LocalizedData.PortGroup | Table @TableParams + } + } + #endregion Distributed Switch Port Groups + + #region Distributed Switch Port Group Security + $VDSPortgroupSecurity = $VDS | Get-VDPortgroup | Get-VDSecurityPolicy + if ($VDSPortgroupSecurity) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VDSPortGroupSecurity { + $VDSSecurityPolicies = foreach ($VDSSecurityPolicy in $VDSPortgroupSecurity) { + [PSCustomObject]@{ + $LocalizedData.PortGroup = $VDSSecurityPolicy.VDPortgroup + $LocalizedData.VDSwitch = $VDS.Name + $LocalizedData.AllowPromiscuous = if ($VDSSecurityPolicy.AllowPromiscuous) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + $LocalizedData.ForgedTransmits = if ($VDSSecurityPolicy.ForgedTransmits) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + $LocalizedData.MACAddressChanges = if ($VDSSecurityPolicy.MacChanges) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSPortGroupSecurity -f $VDS) + ColumnWidths = 20, 20, 20, 20, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSSecurityPolicies | Sort-Object $LocalizedData.PortGroup | Table @TableParams + } + } + #endregion Distributed Switch Port Group Security + + #region Distributed Switch Port Group Traffic Shaping + $VDSPortgroupTrafficShaping = @() + $VDSPortgroupTrafficShapingIn = $VDS | Get-VDPortgroup | Get-VDTrafficShapingPolicy -Direction In + $VDSPortgroupTrafficShapingOut = $VDS | Get-VDPortgroup | Get-VDTrafficShapingPolicy -Direction Out + $VDSPortgroupTrafficShaping += $VDSPortgroupTrafficShapingIn + $VDSPortgroupTrafficShaping += $VDSPortgroupTrafficShapingOut + if ($VDSPortgroupTrafficShaping) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VDSPortGroupTrafficShaping { + $VDSPortgroupTrafficShapingDetail = foreach ($VDSPortgroupTrafficShape in $VDSPortgroupTrafficShaping) { + [PSCustomObject]@{ + $LocalizedData.PortGroup = $VDSPortgroupTrafficShape.VDPortgroup + $LocalizedData.VDSwitch = $VDS.Name + $LocalizedData.Direction = $VDSPortgroupTrafficShape.Direction + $LocalizedData.Status = if ($VDSPortgroupTrafficShape.Enabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.AverageBandwidth = $VDSPortgroupTrafficShape.AverageBandwidth + $LocalizedData.PeakBandwidth = $VDSPortgroupTrafficShape.PeakBandwidth + $LocalizedData.BurstSize = $VDSPortgroupTrafficShape.BurstSize + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSPortGroupTrafficShaping -f $VDS) + ColumnWidths = 16, 16, 10, 10, 16, 16, 16 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSPortgroupTrafficShapingDetail | Sort-Object $LocalizedData.PortGroup, $LocalizedData.Direction | Table @TableParams + + } + } + #endregion Distributed Switch Port Group Traffic Shaping + + #region Distributed Switch Port Group Teaming & Failover + $VDUplinkTeamingPolicy = $VDS | Get-VDPortgroup | Get-VDUplinkTeamingPolicy + if ($VDUplinkTeamingPolicy) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VDSPortGroupTeaming { + $VDSPortgroupNICTeaming = foreach ($VDUplink in $VDUplinkTeamingPolicy) { + [PSCustomObject]@{ + $LocalizedData.PortGroup = $VDUplink.VDPortgroup + $LocalizedData.VDSwitch = $VDS.Name + $LocalizedData.LoadBalancing = switch ($VDUplink.LoadBalancingPolicy) { + 'LoadbalanceSrcId' { $LocalizedData.LoadBalanceSrcId } + 'LoadbalanceSrcMac' { $LocalizedData.LoadBalanceSrcMac } + 'LoadbalanceIP' { $LocalizedData.LoadBalanceIP } + 'ExplicitFailover' { $LocalizedData.ExplicitFailover } + default { $VDUplink.LoadBalancingPolicy } + } + $LocalizedData.NetworkFailureDetection = switch ($VDUplink.FailoverDetectionPolicy) { + 'LinkStatus' { $LocalizedData.LinkStatus } + 'BeaconProbing' { $LocalizedData.BeaconProbing } + default { $VDUplink.FailoverDetectionPolicy } + } + $LocalizedData.NotifySwitches = if ($VDUplink.NotifySwitches) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.FailbackEnabled = if ($VDUplink.EnableFailback) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.ActiveUplinks = $VDUplink.ActiveUplinkPort -join [Environment]::NewLine + $LocalizedData.StandbyUplinks = $VDUplink.StandbyUplinkPort -join [Environment]::NewLine + $LocalizedData.UnusedUplinks = $VDUplink.UnusedUplinkPort -join [Environment]::NewLine + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSPortGroupTeaming -f $VDS) + ColumnWidths = 12, 12, 12, 11, 10, 10, 11, 11, 11 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSPortgroupNICTeaming | Sort-Object $LocalizedData.PortGroup | Table @TableParams + } + } + #endregion Distributed Switch Port Group Teaming & Failover + + #region Distributed Switch Private VLANs + $VDSwitchPrivateVLANs = $VDS | Get-VDSwitchPrivateVlan + if ($VDSwitchPrivateVLANs) { + Section -Style Heading4 $LocalizedData.VDSPrivateVLANs { + $VDSPvlan = foreach ($VDSwitchPrivateVLAN in $VDSwitchPrivateVLANs) { + [PSCustomObject]@{ + $LocalizedData.PrimaryVLANID = $VDSwitchPrivateVLAN.PrimaryVlanId + $LocalizedData.PrivateVLANType = $VDSwitchPrivateVLAN.PrivateVlanType + $LocalizedData.SecondaryVLANID = $VDSwitchPrivateVLAN.SecondaryVlanId + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSPrivateVLANs -f $VDS) + ColumnWidths = 33, 34, 33 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VDSPvlan | Sort-Object $LocalizedData.PrimaryVLANID, $LocalizedData.SecondaryVLANID | Table @TableParams + } + } + #endregion Distributed Switch Private VLANs + } + #endregion VDS Section + } + } + #endregion Distributed Switch Detailed Information + } + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 new file mode 100644 index 0000000..e62f152 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 @@ -0,0 +1,138 @@ +function Get-AbrVSphereResourcePool { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere Resource Pool information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereResourcePool + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.ResourcePool) + } + + process { + try { + if ($InfoLevel.ResourcePool -ge 1) { + $ResourcePools = Get-ResourcePool -Server $vCenter | Sort-Object Parent, Name + if ($ResourcePools) { + Write-PScriboMessage -Message $LocalizedData.Collecting + #region Resource Pools Section + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region Resource Pool Advanced Summary + if ($InfoLevel.ResourcePool -le 2) { + BlankLine + $ResourcePoolInfo = foreach ($ResourcePool in $ResourcePools) { + [PSCustomObject]@{ + $LocalizedData.ResourcePool = $ResourcePool.Name + $LocalizedData.Parent = $ResourcePool.Parent + $LocalizedData.CPUSharesLevel = $ResourcePool.CpuSharesLevel + $LocalizedData.CPUReservationMHz = $ResourcePool.CpuReservationMHz + $LocalizedData.CPULimitMHz = switch ($ResourcePool.CpuLimitMHz) { + '-1' { $LocalizedData.Unlimited } + default { $ResourcePool.CpuLimitMHz } + } + $LocalizedData.MemSharesLevel = $ResourcePool.MemSharesLevel + $LocalizedData.MemReservation = Convert-DataSize $ResourcePool.MemReservationGB -RoundUnits 0 + $LocalizedData.MemLimit = switch ($ResourcePool.MemLimitGB) { + '-1' { $LocalizedData.Unlimited } + default { Convert-DataSize $ResourcePool.MemLimitGB -RoundUnits 0 } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableResourcePoolSummary -f $vCenterServerName) + ColumnWidths = 20, 20, 10, 10, 10, 10, 10, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ResourcePoolInfo | Sort-Object $LocalizedData.ResourcePool | Table @TableParams + } + #endregion Resource Pool Advanced Summary + + #region Resource Pool Detailed Information + # TODO: Test Tags + if ($InfoLevel.ResourcePool -ge 3) { + foreach ($ResourcePool in $ResourcePools) { + Section -Style Heading3 $ResourcePool.Name { + $ResourcePoolDetail = [PSCustomObject]@{ + $LocalizedData.ResourcePool = $ResourcePool.Name + $LocalizedData.ID = $ResourcePool.Id + $LocalizedData.Parent = $ResourcePool.Parent + $LocalizedData.CPUSharesLevel = $ResourcePool.CpuSharesLevel + $LocalizedData.NumCPUShares = $ResourcePool.NumCpuShares + $LocalizedData.CPUReservation = "$($ResourcePool.CpuReservationMHz) MHz" + $LocalizedData.CPUExpandable = if ($ResourcePool.CpuExpandableReservation) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.CPULimitMHz = switch ($ResourcePool.CpuLimitMHz) { + '-1' { $LocalizedData.Unlimited } + default { "$($ResourcePool.CpuLimitMHz) MHz" } + } + $LocalizedData.MemSharesLevel = $ResourcePool.MemSharesLevel + $LocalizedData.NumMemShares = $ResourcePool.NumMemShares + $LocalizedData.MemReservation = Convert-DataSize $ResourcePool.MemReservationGB -RoundUnits 0 + $LocalizedData.MemExpandable = if ($ResourcePool.MemExpandableReservation) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.MemLimit = switch ($ResourcePool.MemLimitGB) { + '-1' { $LocalizedData.Unlimited } + default { Convert-DataSize $ResourcePool.MemLimitGB -RoundUnits 0 } + } + $LocalizedData.NumVMs = $ResourcePool.ExtensionData.VM.Count + } + <# + $MemberProps = @{ + 'InputObject' = $ResourcePoolDetail + 'MemberType' = 'NoteProperty' + } + + if ($TagAssignments | Where-Object {$_.entity -eq $ResourcePool}) { + Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $ResourcePool}).Tag -join ',') + } + #> + #region Resource Pool Advanced Detail Information + if ($InfoLevel.ResourcePool -ge 4) { + $ResourcePoolDetail | ForEach-Object { + # Query for VMs by resource pool Id + $ResourcePoolId = $_.Id + $ResourcePoolVMs = $VMs | Where-Object { $_.ResourcePoolId -eq $ResourcePoolId } | Sort-Object Name + Add-Member -InputObject $_ -MemberType NoteProperty -Name $LocalizedData.VirtualMachines -Value ($ResourcePoolVMs.Name -join ', ') + } + } + #endregion Resource Pool Advanced Detail Information + $TableParams = @{ + Name = ($LocalizedData.TableResourcePoolConfig -f $ResourcePool.Name) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ResourcePoolDetail | Table @TableParams + } + } + } + #endregion Resource Pool Detailed Information + } + #endregion Resource Pools Section + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 new file mode 100644 index 0000000..182279b --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 @@ -0,0 +1,504 @@ +function Get-AbrVSphereVM { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere Virtual Machine information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereVM + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VM) + } + + process { + try { + if ($InfoLevel.VM -ge 1) { + Write-PScriboMessage -Message $LocalizedData.Collecting + if ($VMs) { + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region Virtual Machine Summary Information + if ($InfoLevel.VM -eq 1) { + BlankLine + $VMSummary = [PSCustomObject]@{ + $LocalizedData.TotalVMs = $VMs.Count + $LocalizedData.TotalvCPUs = ($VMs | Measure-Object -Property NumCpu -Sum).Sum + $LocalizedData.TotalMemory = Convert-DataSize ($VMs | Measure-Object -Property MemoryGB -Sum).Sum + $LocalizedData.TotalProvisionedSpace = Convert-DataSize ($VMs | Measure-Object -Property ProvisionedSpaceGB -Sum).Sum + $LocalizedData.TotalUsedSpace = Convert-DataSize ($VMs | Measure-Object -Property UsedSpaceGB -Sum).Sum + $LocalizedData.VMsPoweredOn = ($VMs | Where-Object { $_.PowerState -eq 'PoweredOn' }).Count + $LocalizedData.VMsPoweredOff = ($VMs | Where-Object { $_.PowerState -eq 'PoweredOff' }).Count + $LocalizedData.VMsOrphaned = ($VMs | Where-Object { $_.ExtensionData.Runtime.ConnectionState -eq 'Orphaned' }).Count + $LocalizedData.VMsInaccessible = ($VMs | Where-Object { $_.ExtensionData.Runtime.ConnectionState -eq 'Inaccessible' }).Count + $LocalizedData.VMsSuspended = ($VMs | Where-Object { $_.PowerState -eq 'Suspended' }).Count + $LocalizedData.VMsWithSnapshots = ($VMs | Where-Object { $_.ExtensionData.Snapshot }).Count + $LocalizedData.GuestOSTypes = (($VMs | Get-View).Summary.Config.GuestFullName | Select-Object -Unique).Count + $LocalizedData.VMToolsOKCount = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsOK' }).Count + $LocalizedData.VMToolsOldCount = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsOld' }).Count + $LocalizedData.VMToolsNotRunningCount = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsNotRunning' }).Count + $LocalizedData.VMToolsNotInstalledCount = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsNotInstalled' }).Count + } + $TableParams = @{ + Name = ($LocalizedData.TableVMSummary -f $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMSummary | Table @TableParams + } + #endregion Virtual Machine Summary Information + + #region Virtual Machine Advanced Summary + if ($InfoLevel.VM -eq 2) { + BlankLine + $VMSnapshotList = $VMs.Extensiondata.Snapshot.RootSnapshotList + $VMInfo = foreach ($VM in $VMs) { + $VMView = $VM | Get-View + [PSCustomObject]@{ + $LocalizedData.VirtualMachine = $VM.Name + $LocalizedData.PowerState = switch ($VM.PowerState) { + 'PoweredOn' { $LocalizedData.On } + 'PoweredOff' { $LocalizedData.Off } + default { $VM.PowerState } + } + $LocalizedData.IPAddress = if ($VMView.Guest.IpAddress) { + $VMView.Guest.IpAddress + } else { + '--' + } + $LocalizedData.vCPUs = $VM.NumCpu + $LocalizedData.Memory = Convert-DataSize $VM.MemoryGB -RoundUnits 0 + $LocalizedData.Provisioned = Convert-DataSize $VM.ProvisionedSpaceGB + $LocalizedData.Used = Convert-DataSize $VM.UsedSpaceGB + $LocalizedData.HWVersion = ($VM.HardwareVersion).Replace('vmx-', 'v') + $LocalizedData.VMToolsStatus = switch ($VMView.Guest.ToolsStatus) { + 'toolsOld' { $LocalizedData.ToolsOld } + 'toolsOK' { $LocalizedData.ToolsOK } + 'toolsNotRunning' { $LocalizedData.ToolsNotRunning } + 'toolsNotInstalled' { $LocalizedData.ToolsNotInstalled } + default { $VMView.Guest.ToolsStatus } + } + } + } + if ($Healthcheck.VM.VMToolsStatus) { + $VMInfo | Where-Object { $_.$($LocalizedData.VMToolsStatus) -ne $LocalizedData.ToolsOK } | Set-Style -Style Warning -Property $LocalizedData.VMToolsStatus + } + if ($Healthcheck.VM.PowerState) { + $VMInfo | Where-Object { $_.$($LocalizedData.PowerState) -ne $LocalizedData.On } | Set-Style -Style Warning -Property $LocalizedData.PowerState + } + $TableParams = @{ + Name = ($LocalizedData.TableVMAdvancedSummary -f $vCenterServerName) + ColumnWidths = 21, 8, 16, 9, 9, 9, 9, 9, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMInfo | Table @TableParams + + #region VM Snapshot Information + if ($VMSnapshotList -and $Options.ShowVMSnapshots) { + Section -Style Heading3 $LocalizedData.SnapshotHeading { + $VMSnapshotInfo = foreach ($VMSnapshot in $VMSnapshotList) { + [PSCustomObject]@{ + $LocalizedData.VirtualMachine = $VMLookup."$($VMSnapshot.VM)" + $LocalizedData.SnapshotName = $VMSnapshot.Name + $LocalizedData.SnapshotDescription = $VMSnapshot.Description + $LocalizedData.DaysOld = ((Get-Date).ToUniversalTime() - $VMSnapshot.CreateTime).Days + } + } + if ($Healthcheck.VM.VMSnapshots) { + $VMSnapshotInfo | Where-Object { $_.$($LocalizedData.DaysOld) -ge 7 } | Set-Style -Style Warning + $VMSnapshotInfo | Where-Object { $_.$($LocalizedData.DaysOld) -ge 14 } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableVMSnapshotSummary -f $vCenterServerName) + ColumnWidths = 30, 30, 30, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMSnapshotInfo | Table @TableParams + } + } + #endregion VM Snapshot Information + } + #endregion Virtual Machine Advanced Summary + + #region Virtual Machine Detailed Information + # TODO: Test Tags + if ($InfoLevel.VM -ge 3) { + if ($UserPrivileges -contains 'StorageProfile.View') { + $VMSpbmConfig = Get-SpbmEntityConfiguration -VM ($VMs) | Where-Object { $null -ne $_.StoragePolicy } + } else { + Write-PScriboMessage $LocalizedData.InsufficientPrivStoragePolicy + } + if ($InfoLevel.VM -ge 4) { + $VMHardDisks = Get-HardDisk -VM ($VMs) -Server $vCenter + } + foreach ($VM in $VMs) { + Section -Style Heading3 $VM.name { + $VMUptime = Get-Uptime -VM $VM + $VMSpbmPolicy = $VMSpbmConfig | Where-Object { $_.entity -eq $vm } + $VMView = $VM | Get-View + $VMSnapshotList = $vmview.Snapshot.RootSnapshotList + $VMDetail = [PSCustomObject]@{ + $LocalizedData.VirtualMachine = $VM.Name + $LocalizedData.ID = $VM.Id + $LocalizedData.OperatingSystem = $VMView.Summary.Config.GuestFullName + $LocalizedData.HardwareVersion = ($VM.HardwareVersion).Replace('vmx-', 'v') + $LocalizedData.PowerState = switch ($VM.PowerState) { + 'PoweredOn' { $LocalizedData.On } + 'PoweredOff' { $LocalizedData.Off } + default { $TextInfo.ToTitleCase($VM.PowerState) } + } + $LocalizedData.ConnectionState = $TextInfo.ToTitleCase($VM.ExtensionData.Runtime.ConnectionState) + $LocalizedData.VMToolsStatus = switch ($VMView.Guest.ToolsStatus) { + 'toolsOld' { $LocalizedData.ToolsOld } + 'toolsOK' { $LocalizedData.ToolsOK } + 'toolsNotRunning' { $LocalizedData.ToolsNotRunning } + 'toolsNotInstalled' { $LocalizedData.ToolsNotInstalled } + default { $TextInfo.ToTitleCase($VMView.Guest.ToolsStatus) } + } + $LocalizedData.FaultToleranceState = switch ($VMView.Runtime.FaultToleranceState) { + 'notConfigured' { $LocalizedData.FTNotConfigured } + 'needsSecondary' { $LocalizedData.FTNeedsSecondary } + 'running' { $LocalizedData.FTRunning } + 'disabled' { $LocalizedData.FTDisabled } + 'starting' { $LocalizedData.FTStarting } + 'enabled' { $LocalizedData.FTEnabled } + default { $TextInfo.ToTitleCase($VMview.Runtime.FaultToleranceState) } + } + $LocalizedData.VMHost = $VM.VMHost.Name + $LocalizedData.Parent = $VM.VMHost.Parent.Name + $LocalizedData.ParentFolder = $VM.Folder.Name + $LocalizedData.ParentResourcePool = $VM.ResourcePool.Name + $LocalizedData.vCPUs = $VM.NumCpu + $LocalizedData.CoresPerSocket = $VM.CoresPerSocket + $LocalizedData.CPUShares = "$($VM.VMResourceConfiguration.CpuSharesLevel) / $($VM.VMResourceConfiguration.NumCpuShares)" + $LocalizedData.CPUReservation = $VM.VMResourceConfiguration.CpuReservationMhz + $LocalizedData.CPULimit = "$($VM.VMResourceConfiguration.CpuReservationMhz) MHz" + $LocalizedData.CPUHotAdd = if ($VMView.Config.CpuHotAddEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.CPUHotRemove = if ($VMView.Config.CpuHotRemoveEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.MemoryAllocation = Convert-DataSize $VM.memoryGB -RoundUnits 0 + $LocalizedData.MemoryShares = "$($VM.VMResourceConfiguration.MemSharesLevel) / $($VM.VMResourceConfiguration.NumMemShares)" + $LocalizedData.MemoryHotAdd = if ($VMView.Config.MemoryHotAddEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.vNICs = $VMView.Summary.Config.NumEthernetCards + $LocalizedData.DNSName = if ($VMView.Guest.HostName) { + $VMView.Guest.HostName + } else { + '--' + } + $LocalizedData.Networks = if ($VMView.Guest.Net.Network) { + (($VMView.Guest.Net | Where-Object { $null -ne $_.Network } | Select-Object Network | Sort-Object Network).Network -join ', ') + } else { + '--' + } + $LocalizedData.IPAddress = if ($VMView.Guest.Net.IpAddress) { + (($VMView.Guest.Net | Where-Object { ($null -ne $_.Network) -and ($null -ne $_.IpAddress) } | Select-Object IpAddress | Sort-Object IpAddress).IpAddress -join ', ') + } else { + '--' + } + $LocalizedData.MACAddress = if ($VMView.Guest.Net.MacAddress) { + (($VMView.Guest.Net | Where-Object { $null -ne $_.Network } | Select-Object -Property MacAddress).MacAddress -join ', ') + } else { + '--' + } + $LocalizedData.vDisks = $VMView.Summary.Config.NumVirtualDisks + $LocalizedData.ProvisionedSpace = Convert-DataSize $VM.ProvisionedSpaceGB + $LocalizedData.UsedSpace = Convert-DataSize $VM.UsedSpaceGB + $LocalizedData.ChangedBlockTracking = if ($VMView.Config.ChangeTrackingEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.StorageBasedPolicy = if ($VMSpbmPolicy.StoragePolicy.Name) { + $TextInfo.ToTitleCase($VMSpbmPolicy.StoragePolicy.Name) + } else { + '--' + } + $LocalizedData.StorageBasedPolicyCompliance = switch ($VMSpbmPolicy.ComplianceStatus) { + $null { '--' } + 'compliant' { $LocalizedData.Compliant } + 'nonCompliant' { $LocalizedData.NonCompliant } + 'unknown' { $LocalizedData.Unknown } + default { $TextInfo.ToTitleCase($VMSpbmPolicy.ComplianceStatus) } + } + } + $MemberProps = @{ + 'InputObject' = $VMDetail + 'MemberType' = 'NoteProperty' + } + #if ($VMView.Config.CreateDate) { + # Add-Member @MemberProps -Name 'Creation Date' -Value ($VMView.Config.CreateDate).ToLocalTime().ToString() + #} + <# + if ($TagAssignments | Where-Object {$_.entity -eq $VM}) { + Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VM}).Tag -join ',') + } + #> + if ($VM.Notes) { + Add-Member @MemberProps -Name $LocalizedData.Notes -Value $VM.Notes + } + if ($VMView.Runtime.BootTime) { + Add-Member @MemberProps -Name $LocalizedData.BootTime -Value ($VMView.Runtime.BootTime).ToLocalTime().ToString() + } + if ($VMUptime.UptimeDays) { + Add-Member @MemberProps -Name $LocalizedData.UptimeDays -Value $VMUptime.UptimeDays + } + + #region VM Health Checks + if ($Healthcheck.VM.VMToolsStatus) { + $VMDetail | Where-Object { $_.$($LocalizedData.VMToolsStatus) -ne $LocalizedData.ToolsOK } | Set-Style -Style Warning -Property $LocalizedData.VMToolsStatus + } + if ($Healthcheck.VM.PowerState) { + $VMDetail | Where-Object { $_.$($LocalizedData.PowerState) -ne $LocalizedData.On } | Set-Style -Style Warning -Property $LocalizedData.PowerState + } + if ($Healthcheck.VM.ConnectionState) { + $VMDetail | Where-Object { $_.$($LocalizedData.ConnectionState) -ne 'Connected' } | Set-Style -Style Critical -Property $LocalizedData.ConnectionState + } + if ($Healthcheck.VM.CpuHotAdd) { + $VMDetail | Where-Object { $_.$($LocalizedData.CPUHotAdd) -eq $LocalizedData.Enabled } | Set-Style -Style Warning -Property $LocalizedData.CPUHotAdd + } + if ($Healthcheck.VM.CpuHotRemove) { + $VMDetail | Where-Object { $_.$($LocalizedData.CPUHotRemove) -eq $LocalizedData.Enabled } | Set-Style -Style Warning -Property $LocalizedData.CPUHotRemove + } + if ($Healthcheck.VM.MemoryHotAdd) { + $VMDetail | Where-Object { $_.$($LocalizedData.MemoryHotAdd) -eq $LocalizedData.Enabled } | Set-Style -Style Warning -Property $LocalizedData.MemoryHotAdd + } + if ($Healthcheck.VM.ChangeBlockTracking) { + $VMDetail | Where-Object { $_.$($LocalizedData.ChangedBlockTracking) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.ChangedBlockTracking + } + if ($Healthcheck.VM.SpbmPolicyCompliance) { + $VMDetail | Where-Object { $_.$($LocalizedData.StorageBasedPolicyCompliance) -eq $LocalizedData.Unknown } | Set-Style -Style Warning -Property $LocalizedData.StorageBasedPolicyCompliance + $VMDetail | Where-Object { $_.$($LocalizedData.StorageBasedPolicyCompliance) -eq $LocalizedData.NonCompliant } | Set-Style -Style Critical -Property $LocalizedData.StorageBasedPolicyCompliance + } + #endregion VM Health Checks + $TableParams = @{ + Name = ($LocalizedData.TableVMConfig -f $VM.Name) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMDetail | Table @TableParams + + if ($InfoLevel.VM -ge 4) { + $VMnics = $VM.Guest.Nics | Where-Object { $null -ne $_.Device } | Sort-Object Device + $VMHdds = $VMHardDisks | Where-Object { $_.ParentId -eq $VM.Id } | Sort-Object Name + $SCSIControllers = $VMView.Config.Hardware.Device | Where-Object { $_.DeviceInfo.Label -match "SCSI Controller" } + $VMGuestVols = $VM.Guest.Disks | Sort-Object Path + if ($VMnics) { + Section -Style Heading4 $LocalizedData.NetworkAdapters { + $VMnicInfo = foreach ($VMnic in $VMnics) { + [PSCustomObject]@{ + $LocalizedData.NICName = $VMnic.Device + $LocalizedData.NICConnected = if ($VMnic.Connected) { $LocalizedData.Connected } else { $LocalizedData.NotConnected } + $LocalizedData.NetworkName = switch -wildcard ($VMnic.Device.NetworkName) { + 'dvportgroup*' { $VDPortgroupLookup."$($VMnic.Device.NetworkName)" } + default { $VMnic.Device.NetworkName } + } + $LocalizedData.NICType = $VMnic.Device.Type + $LocalizedData.IPAddress = $VMnic.IpAddress -join [Environment]::NewLine + $LocalizedData.NICMAC = $VMnic.Device.MacAddress + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVMNetworkAdapters -f $VM.Name) + ColumnWidths = 20, 12, 16, 12, 20, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMnicInfo | Table @TableParams + } + } + if ($SCSIControllers) { + Section -Style Heading4 $LocalizedData.SCSIControllers { + $VMScsiControllers = foreach ($VMSCSIController in $SCSIControllers) { + [PSCustomObject]@{ + $LocalizedData.Device = $VMSCSIController.DeviceInfo.Label + $LocalizedData.ControllerType = $VMSCSIController.DeviceInfo.Summary + $LocalizedData.BusSharing = switch ($VMSCSIController.SharedBus) { + 'noSharing' { $LocalizedData.None } + default { $VMSCSIController.SharedBus } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVMSCSIControllers -f $VM.Name) + ColumnWidths = 33, 34, 33 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMScsiControllers | Sort-Object $LocalizedData.Device | Table @TableParams + } + } + if ($VMHdds) { + Section -Style Heading4 $LocalizedData.HardDisks { + if ($InfoLevel.VM -eq 4) { + $VMHardDiskInfo = foreach ($VMHdd in $VMHdds) { + $SCSIDevice = $VMView.Config.Hardware.Device | Where-Object { $_.Key -eq $VMHdd.ExtensionData.Key -and $_.Backing.FileName -eq $VMHdd.FileName } + $SCSIController = $SCSIControllers | Where-Object { $SCSIDevice.ControllerKey -eq $_.Key } + [PSCustomObject]@{ + $LocalizedData.DiskName = $VMHdd.Name + $LocalizedData.DiskDatastore = $VMHdd.FileName.Substring($VMHdd.Filename.IndexOf("[") + 1, $VMHdd.Filename.IndexOf("]") - 1) + $LocalizedData.Capacity = Convert-DataSize $VMHdd.CapacityGB + $LocalizedData.DiskProvisioning = switch ($VMHdd.StorageFormat) { + 'EagerZeroedThick' { $LocalizedData.ThickEagerZeroed } + 'LazyZeroedThick' { $LocalizedData.ThickLazyZeroed } + $null { '--' } + default { $VMHdd.StorageFormat } + } + $LocalizedData.DiskType = switch ($VMHdd.DiskType) { + 'RawPhysical' { $LocalizedData.PhysicalRDM } + 'RawVirtual' { $LocalizedData.VirtualRDM } + 'Flat' { $LocalizedData.VMDK } + default { $VMHdd.DiskType } + } + $LocalizedData.DiskMode = switch ($VMHdd.Persistence) { + 'IndependentPersistent' { $LocalizedData.IndependentPersistent } + 'IndependentNonPersistent' { $LocalizedData.IndependentNonpersistent } + 'Persistent' { $LocalizedData.Dependent } + default { $VMHdd.Persistence } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVMHardDiskConfig -f $VM.Name) + ColumnWidths = 15, 25, 15, 15, 15, 15 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHardDiskInfo | Table @TableParams + } else { + foreach ($VMHdd in $VMHdds) { + Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHdd.Name)" { + $SCSIDevice = $VMView.Config.Hardware.Device | Where-Object { $_.Key -eq $VMHdd.ExtensionData.Key -and $_.Backing.FileName -eq $VMHdd.FileName } + $SCSIController = $SCSIControllers | Where-Object { $SCSIDevice.ControllerKey -eq $_.Key } + $VMHardDiskInfo = [PSCustomObject]@{ + $LocalizedData.DiskDatastore = $VMHdd.FileName.Substring($VMHdd.Filename.IndexOf("[") + 1, $VMHdd.Filename.IndexOf("]") - 1) + $LocalizedData.Capacity = Convert-DataSize $VMHdd.CapacityGB + $LocalizedData.DiskPath = $VMHdd.Filename.Substring($VMHdd.Filename.IndexOf("]") + 2) + $LocalizedData.DiskShares = "$($TextInfo.ToTitleCase($VMHdd.ExtensionData.Shares.Level)) / $($VMHdd.ExtensionData.Shares.Shares)" + $LocalizedData.DiskLimitIOPs = switch ($VMHdd.ExtensionData.StorageIOAllocation.Limit) { + '-1' { $LocalizedData.Unlimited } + default { $VMHdd.ExtensionData.StorageIOAllocation.Limit } + } + $LocalizedData.DiskProvisioning = switch ($VMHdd.StorageFormat) { + 'EagerZeroedThick' { $LocalizedData.ThickEagerZeroed } + 'LazyZeroedThick' { $LocalizedData.ThickLazyZeroed } + $null { '--' } + default { $VMHdd.StorageFormat } + } + $LocalizedData.DiskType = switch ($VMHdd.DiskType) { + 'RawPhysical' { $LocalizedData.PhysicalRDM } + 'RawVirtual' { $LocalizedData.VirtualRDM } + 'Flat' { $LocalizedData.VMDK } + default { $VMHdd.DiskType } + } + $LocalizedData.DiskMode = switch ($VMHdd.Persistence) { + 'IndependentPersistent' { $LocalizedData.IndependentPersistent } + 'IndependentNonPersistent' { $LocalizedData.IndependentNonpersistent } + 'Persistent' { $LocalizedData.Dependent } + default { $VMHdd.Persistence } + } + $LocalizedData.SCSIController = $SCSIController.DeviceInfo.Label + $LocalizedData.SCSIAddress = "$($SCSIController.BusNumber):$($VMHdd.ExtensionData.UnitNumber)" + } + $TableParams = @{ + Name = ($LocalizedData.TableVMHardDisk -f $VMHdd.Name, $VM.Name) + List = $true + ColumnWidths = 25, 75 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHardDiskInfo | Table @TableParams + } + } + } + } + } + if ($VMGuestVols) { + Section -Style Heading4 $LocalizedData.GuestVolumes { + $VMGuestDiskInfo = foreach ($VMGuestVol in $VMGuestVols) { + [PSCustomObject]@{ + $LocalizedData.Path = $VMGuestVol.Path + $LocalizedData.Capacity = Convert-DataSize $VMGuestVol.CapacityGB + $LocalizedData.UsedSpace = Convert-DataSize (($VMGuestVol.CapacityGB) - ($VMGuestVol.FreeSpaceGB)) + $LocalizedData.FreeSpace = Convert-DataSize $VMGuestVol.FreeSpaceGB + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVMGuestVolumes -f $VM.Name) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMGuestDiskInfo | Table @TableParams + } + } + } + + + if ($VMSnapshotList -and $Options.ShowVMSnapshots) { + Section -Style Heading4 $LocalizedData.SnapshotHeading { + $VMSnapshots = foreach ($VMSnapshot in $VMSnapshotList) { + [PSCustomObject]@{ + $LocalizedData.SnapshotName = $VMSnapshot.Name + $LocalizedData.SnapshotDescription = $VMSnapshot.Description + $LocalizedData.DaysOld = ((Get-Date).ToUniversalTime() - $VMSnapshot.CreateTime).Days + } + } + if ($Healthcheck.VM.VMSnapshots) { + $VMSnapshots | Where-Object { $_.$($LocalizedData.DaysOld) -ge 7 } | Set-Style -Style Warning + $VMSnapshots | Where-Object { $_.$($LocalizedData.DaysOld) -ge 14 } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableVMSnapshots -f $VM.Name) + ColumnWidths = 45, 45, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMSnapshots | Table @TableParams + } + } + } + } + } + #endregion Virtual Machine Detailed Information + } + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 new file mode 100644 index 0000000..cc10ad6 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 @@ -0,0 +1,97 @@ +function Get-AbrVSphereVMHost { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere ESXi VMHost information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereVMHost + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) + } + + process { + try { + if ($InfoLevel.VMHost -ge 1) { + if ($VMHosts) { + Write-PScriboMessage -Message $LocalizedData.Collecting + #region Hosts Section + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region ESXi Host Advanced Summary + if ($InfoLevel.VMHost -le 2) { + BlankLine + try { + $VMHostInfo = foreach ($VMHost in $VMHosts) { + [PSCustomObject]@{ + $LocalizedData.VMHost = $VMHost.Name + $LocalizedData.Version = $VMHost.Version + $LocalizedData.Build = $VMHost.Build + $LocalizedData.Parent = $VMHost.Parent + $LocalizedData.ConnectionState = switch ($VMHost.ConnectionState) { + 'NotResponding' { $LocalizedData.NotResponding } + 'Maintenance' { $LocalizedData.Maintenance } + 'Disconnected' { $LocalizedData.Disconnected } + default { $TextInfo.ToTitleCase($VMHost.ConnectionState) } + } + $LocalizedData.NumCPUSockets = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuPackages + $LocalizedData.NumCPUCores = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuCores + $LocalizedData.MemoryGB = Convert-DataSize $VMHost.MemoryTotalGB -RoundUnits 0 + $LocalizedData.NumVMs = $VMHost.ExtensionData.Vm.Count + } + } + if ($Healthcheck.VMHost.ConnectionState) { + $VMHostInfo | Where-Object { $_.$($LocalizedData.ConnectionState) -eq $LocalizedData.Maintenance } | Set-Style -Style Warning + $VMHostInfo | Where-Object { $_.$($LocalizedData.ConnectionState) -eq $LocalizedData.NotResponding } | Set-Style -Style Critical + $VMHostInfo | Where-Object { $_.$($LocalizedData.ConnectionState) -eq $LocalizedData.Disconnected } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableHostSummary -f $vCenterServerName) + ColumnWidths = 17, 9, 11, 15, 13, 9, 9, 9, 8 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostInfo | Table @TableParams + } catch { + Write-PScriboMessage -Message ($LocalizedData.ErrorCollecting -f $InfoLevel.VMHost) + } + } + #endregion ESXi Host Advanced Summary + + #region ESXi Host Detailed Information + if ($InfoLevel.VMHost -ge 3) { + #region foreach VMHost Detailed Information loop + foreach ($VMHost in ($VMHosts | Where-Object { $_.ConnectionState -eq 'Connected' -or $_.ConnectionState -eq 'Maintenance' })) { + #region VMHost Section + Section -Style Heading3 $VMHost { + # TODO: Host Certificate, Swap File Location + # TODO: Test Tags + Get-AbrVSphereVMHostHardware + Get-AbrVSphereVMHostSystem + Get-AbrVSphereVMHostStorage + Get-AbrVSphereVMHostNetwork + Get-AbrVSphereVMHostSecurity + } + #endregion VMHost Section + } + #endregion foreach VMHost Detailed Information loop + } + #endregion ESXi Host Detailed Information + } + #endregion Hosts Section + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 new file mode 100644 index 0000000..3dac618 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 @@ -0,0 +1,287 @@ +function Get-AbrVSphereVMHostHardware { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere ESXi VMHost Hardware information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereVMHostHardware + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) + } + + process { + try { + Write-PScriboMessage -Message $LocalizedData.Collecting + #region ESXi Host Hardware Section + Section -Style Heading4 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $VMHost) + BlankLine + + #region ESXi Host Specifications + $VMHostUptime = Get-Uptime -VMHost $VMHost + $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter + $ScratchLocation = Get-AdvancedSetting -Entity $VMHost | Where-Object { $_.Name -eq 'ScratchConfig.CurrentScratchLocation' } + $VMHostCpuTotal = [math]::Round(($VMHost.CpuTotalMhz) / 1000, 2) + $VMHostCpuUsed = [math]::Round(($VMHost.CpuUsageMhz) / 1000, 2) + $VMHostCpuFree = $VMHostCpuTotal - $VMHostCpuUsed + $VMHostDetail = [PSCustomObject]@{ + $LocalizedData.Host = $VMHost.Name + $LocalizedData.ConnectionState = switch ($VMHost.ConnectionState) { + 'NotResponding' { $LocalizedData.NotResponding } + default { $TextInfo.ToTitleCase($VMHost.ConnectionState) } + } + $LocalizedData.ID = $VMHost.Id + $LocalizedData.Parent = $VMHost.Parent + $LocalizedData.Manufacturer = $VMHost.Manufacturer + $LocalizedData.Model = $VMHost.Model + $LocalizedData.SerialNumber = if ($VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber) { + $VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber + } else { + '--' + } + $LocalizedData.AssetTag = if ($VMHost.ExtensionData.Summary.Hardware.OtherIdentifyingInfo[0].IdentifierValue) { + $VMHost.ExtensionData.Summary.Hardware.OtherIdentifyingInfo[0].IdentifierValue + } else { + $LocalizedData.Unknown + } + $LocalizedData.ProcessorType = $VMHost.Processortype + $LocalizedData.HyperThreading = if ($VMHost.HyperthreadingActive) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.NumberOfCPUSockets = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuPackages + $LocalizedData.NumberOfCPUCores = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuCores + $LocalizedData.NumberOfCPUThreads = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuThreads + $LocalizedData.CPUTotalUsedFree = "{0:N2} GHz / {1:N2} GHz / {2:N2} GHz" -f $VMHostCpuTotal, $VMHostCpuUsed, $VMHostCpuFree + $LocalizedData.MemoryTotalUsedFree = "{0} / {1} / {2}" -f (Convert-DataSize $VMHost.MemoryTotalGB), (Convert-DataSize $VMHost.MemoryUsageGB), (Convert-DataSize ($VMHost.MemoryTotalGB - $VMHost.MemoryUsageGB)) + $LocalizedData.NUMANodes = $VMHost.ExtensionData.Hardware.NumaInfo.NumNodes + $LocalizedData.NumberOfNICs = $VMHost.ExtensionData.Summary.Hardware.NumNics + $LocalizedData.NumberOfHBAs = $VMHost.ExtensionData.Summary.Hardware.NumHBAs + $LocalizedData.NumberOfDatastores = ($VMHost.ExtensionData.Datastore).Count + $LocalizedData.NumberOfVMs = $VMHost.ExtensionData.VM.Count + $LocalizedData.MaximumEVCMode = $EvcModeLookup."$($VMHost.MaxEVCMode)" + $LocalizedData.EVCGraphicsMode = if ($VMHost.ExtensionData.Summary.CurrentEVCGraphicsModeKey) { + $VMHost.ExtensionData.Summary.CurrentEVCGraphicsModeKey + } else { + $LocalizedData.NotApplicable + } + $LocalizedData.PowerManagementPolicy = $VMHost.ExtensionData.Hardware.CpuPowerManagementInfo.CurrentPolicy + $LocalizedData.ScratchLocation = $ScratchLocation.Value + $LocalizedData.BiosVersion = $VMHost.ExtensionData.Hardware.BiosInfo.BiosVersion + $LocalizedData.BiosReleaseDate = ($VMHost.ExtensionData.Hardware.BiosInfo.ReleaseDate).ToString() + $LocalizedData.Version = $VMHost.Version + $LocalizedData.Build = $VMHost.build + $LocalizedData.BootTime = ($VMHost.ExtensionData.Runtime.Boottime).ToLocalTime().ToString() + $LocalizedData.UptimeDays = $VMHostUptime.UptimeDays + } + $MemberProps = @{ + 'InputObject' = $VMHostDetail + 'MemberType' = 'NoteProperty' + } + if ($UserPrivileges -contains 'Global.Licenses') { + $VMHostLicense = Get-License -VMHost $VMHost + Add-Member @MemberProps -Name $LocalizedData.Product -Value $VMHostLicense.Product + Add-Member @MemberProps -Name $LocalizedData.LicenseKey -Value $VMHostLicense.LicenseKey + Add-Member @MemberProps -Name $LocalizedData.LicenseExpiration -Value $VMHostLicense.Expiration + } else { + Write-PScriboMessage -Message $LocalizedData.InsufficientPrivLicense + } + <# + if ($TagAssignments | Where-Object {$_.entity -eq $VMHost}) { + Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VMHost}).Tag -join ',') + } + #> + if ($Healthcheck.VMHost.ConnectionState) { + $VMHostDetail | Where-Object { $_.$($LocalizedData.ConnectionState) -eq $LocalizedData.Maintenance } | Set-Style -Style Warning -Property $LocalizedData.ConnectionState + } + if ($Healthcheck.VMHost.HyperThreading) { + $VMHostDetail | Where-Object { $_.$($LocalizedData.HyperThreading) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.HyperThreading + } + if ($Healthcheck.VMHost.Licensing) { + $VMHostDetail | Where-Object { $_.$($LocalizedData.Product) -like '*Evaluation*' } | Set-Style -Style Warning -Property $LocalizedData.Product + $VMHostDetail | Where-Object { $_.$($LocalizedData.LicenseKey) -like '*-00000-00000' } | Set-Style -Style Warning -Property $LocalizedData.LicenseKey + $VMHostDetail | Where-Object { $_.$($LocalizedData.LicenseExpiration) -eq 'Expired' } | Set-Style -Style Critical -Property $LocalizedData.LicenseExpiration + } + if ($Healthcheck.VMHost.ScratchLocation) { + $VMHostDetail | Where-Object { $_.$($LocalizedData.ScratchLocation) -eq '/tmp/scratch' } | Set-Style -Style Warning -Property $LocalizedData.ScratchLocation + } + if ($Healthcheck.VMHost.UpTimeDays) { + $VMHostDetail | Where-Object { $_.$($LocalizedData.UptimeDays) -ge 275 -and $_.$($LocalizedData.UptimeDays) -lt 365 } | Set-Style -Style Warning -Property $LocalizedData.UptimeDays + $VMHostDetail | Where-Object { $_.$($LocalizedData.UptimeDays) -ge 365 } | Set-Style -Style Critical -Property $LocalizedData.UptimeDays + } + $TableParams = @{ + Name = ($LocalizedData.TableHardwareConfig -f $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostDetail | Table @TableParams + #endregion ESXi Host Specifications + + #region ESXi IPMI/BMC Settings + try { + $VMHostIPMI = $esxcli.hardware.ipmi.bmc.get.invoke() + } catch { + Write-PScriboMessage -Message ($LocalizedData.IPMIBMCError -f $VMHost.Name, $_.Exception.Message) + } + if ($VMHostIPMI) { + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.IPMIBMC { + $VMHostIPMIInfo = [PSCustomObject]@{ + $LocalizedData.Manufacturer = $VMHostIPMI.Manufacturer + $LocalizedData.MACAddress = $VMHostIPMI.MacAddress + $LocalizedData.IPMIBMCIPAddress = $VMHostIPMI.IPv4Address + $LocalizedData.SubnetMask = $VMHostIPMI.IPv4Subnet + $LocalizedData.Gateway = $VMHostIPMI.IPv4Gateway + $LocalizedData.FirmwareVersion = $VMHostIPMI.BMCFirmwareVersion + } + + $TableParams = @{ + Name = ($LocalizedData.TableIPMIBMC -f $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostIPMIInfo | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.IPMIBMCError -f $VMHost.Name, $_.Exception.Message) + } + } + #endregion ESXi IPMI/BMC Settings + + #region ESXi Host Boot Device + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.BootDevice { + $ESXiBootDevice = Get-ESXiBootDevice -VMHost $VMHost + $VMHostBootDevice = [PSCustomObject]@{ + $LocalizedData.Host = $ESXiBootDevice.Host + $LocalizedData.Device = $ESXiBootDevice.Device + $LocalizedData.BootType = $ESXiBootDevice.BootType + $LocalizedData.Vendor = $ESXiBootDevice.Vendor + $LocalizedData.Model = $ESXiBootDevice.Model + $LocalizedData.Size = switch ($ESXiBootDevice.SizeMB) { + 'N/A' { 'N/A' } + default { Convert-DataSize $ESXiBootDevice.SizeMB -InputUnit MB -RoundUnits 0 } + } + $LocalizedData.BootDeviceIsSAS = $ESXiBootDevice.IsSAS + $LocalizedData.BootDeviceIsSSD = $ESXiBootDevice.IsSSD + $LocalizedData.BootDeviceIsUSB = $ESXiBootDevice.IsUSB + } + $TableParams = @{ + Name = ($LocalizedData.TableBootDevice -f $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostBootDevice | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.BootDeviceError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host Boot Devices + + #region ESXi Host PCI Devices + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.PCIDevices { + <# Move away from esxcli.v2 implementation to be compatible with 8.x branch. + 'Slot Description' information does not seem to be available through the API + Create an array with PCI Address and VMware Devices (vmnic,vmhba,?vmgfx?) + #> + $PciToDeviceMapping = @{} + $NetworkAdapters = Get-VMHostNetworkAdapter -VMHost $VMHost -Physical + foreach ($adapter in $NetworkAdapters) { + $PciToDeviceMapping[$adapter.PciId] = $adapter.DeviceName + } + $hbAdapters = Get-VMHostHba -VMHost $VMHost + foreach ($adapter in $hbAdapters) { + $PciToDeviceMapping[$adapter.Pci] = $adapter.Device + } + <# Data Object - HostGraphicsInfo(vim.host.GraphicsInfo) + This function has been available since version 5.5, but we can't be sure if it is still valid. + I don't have access to a vGPU-enabled system. + #> + $GpuAdapters = (Get-VMHost $VMhost | Get-View -Property Config).Config.GraphicsInfo + foreach ($adapter in $GpuAdapters) { + $PciToDeviceMapping[$adapter.pciId] = $adapter.deviceName + } + + $VMHostPciDevice = @{ + VMHost = $VMHost + DeviceClass = @('MassStorageController', 'NetworkController', 'DisplayController', 'SerialBusController') + } + $PciDevices = Get-VMHostPciDevice @VMHostPciDevice + + # Combine PciDevices and PciToDeviceMapping + + $VMHostPciDevices = $PciDevices | ForEach-Object { + $PciDevice = $_ + $device = $PCIToDeviceMapping[$pciDevice.Id] + + if ($device) { + [PSCustomObject]@{ + $LocalizedData.Device = $device + $LocalizedData.PCIAddress = $PciDevice.Id + $LocalizedData.DeviceClass = $PciDevice.DeviceClass -replace ('Controller', "") + $LocalizedData.PCIDeviceName = $PciDevice.DeviceName + $LocalizedData.PCIVendorName = $PciDevice.VendorName + } + } + } + + $TableParams = @{ + Name = ($LocalizedData.TablePCIDevices -f $VMHost) + ColumnWidths = 17, 18, 15, 30, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostPciDevices | Sort-Object $LocalizedData.Device | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.PCIDeviceError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host PCI Devices + + #region ESXi Host PCI Devices Drivers & Firmware + $VMHostPciDevicesDetails = Get-PciDeviceDetail -Server $vCenter -esxcli $esxcli | Sort-Object 'Device' + if ($VMHostPciDevicesDetails) { + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.PCIDriversFirmware { + $TableParams = @{ + Name = ($LocalizedData.TablePCIDriversFirmware -f $VMHost) + ColumnWidths = 12, 20, 11, 19, 11, 11, 16 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostPciDevicesDetails | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.PCIDriversFirmwareError -f $VMHost.Name, $_.Exception.Message) + } + } + #endregion ESXi Host PCI Devices Drivers & Firmware + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.HardwareError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host Hardware Section + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 new file mode 100644 index 0000000..f73155e --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 @@ -0,0 +1,683 @@ +function Get-AbrVSphereVMHostNetwork { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere ESXi VMHost Network information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereVMHostNetwork + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) + } + + process { + try { + Write-PScriboMessage -Message $LocalizedData.Collecting + #region ESXi Host Network Section + Section -Style Heading4 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $VMHost) + BlankLine + #region ESXi Host Network Configuration + $VMHostNetwork = $VMHost.ExtensionData.Config.Network + $VMHostVirtualSwitch = @() + $VMHostVss = foreach ($vSwitch in $VMHost.ExtensionData.Config.Network.Vswitch) { + $VMHostVirtualSwitch += $vSwitch.Name + } + $VMHostDvs = foreach ($dvSwitch in $VMHost.ExtensionData.Config.Network.ProxySwitch) { + $VMHostVirtualSwitch += $dvSwitch.DvsName + } + $VMHostNetworkDetail = [PSCustomObject]@{ + $LocalizedData.Host = $VMHost.Name + $LocalizedData.VirtualSwitches = ($VMHostVirtualSwitch | Sort-Object) -join ', ' + $LocalizedData.VMKernelAdapters = ($VMHostNetwork.Vnic.Device | Sort-Object) -join ', ' + $LocalizedData.PhysicalAdapters = ($VMHostNetwork.Pnic.Device | Sort-Object) -join ', ' + $LocalizedData.VMkernelGateway = $VMHostNetwork.IpRouteConfig.DefaultGateway + $LocalizedData.IPv6 = if ($VMHostNetwork.IPv6Enabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.VMkernelIPv6Gateway = if ($VMHostNetwork.IpRouteConfig.IpV6DefaultGateway) { + $VMHostNetwork.IpRouteConfig.IpV6DefaultGateway + } else { + '--' + } + $LocalizedData.DNSServers = ($VMHostNetwork.DnsConfig.Address | Sort-Object) -join ', ' + $LocalizedData.HostName = $VMHostNetwork.DnsConfig.HostName + $LocalizedData.DomainName = $VMHostNetwork.DnsConfig.DomainName + $LocalizedData.SearchDomain = ($VMHostNetwork.DnsConfig.SearchDomain | Sort-Object) -join ', ' + } + if ($Healthcheck.VMHost.IPv6) { + $VMHostNetworkDetail | Where-Object { $_.$($LocalizedData.IPv6) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.IPv6 + } + $TableParams = @{ + Name = ($LocalizedData.TableNetworkConfig -f $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostNetworkDetail | Table @TableParams + #endregion ESXi Host Network Configuration + + #region ESXi Host Physical Adapters + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.PhysicalAdapters { + Paragraph ($LocalizedData.ParagraphPhysicalAdapters -f $VMHost) + $PhysicalNetAdapters = $VMHost.ExtensionData.Config.Network.Pnic | Sort-Object Device + $VMHostPhysicalNetAdapters = foreach ($PhysicalNetAdapter in $PhysicalNetAdapters) { + [PSCustomObject]@{ + $LocalizedData.AdapterName = $PhysicalNetAdapter.Device + $LocalizedData.Status = if ($PhysicalNetAdapter.Linkspeed) { + $LocalizedData.Connected + } else { + $LocalizedData.Disconnected + } + $LocalizedData.VirtualSwitch = $( + if ($VMHost.ExtensionData.Config.Network.Vswitch.Pnic -contains $PhysicalNetAdapter.Key) { + ($VMHost.ExtensionData.Config.Network.Vswitch | Where-Object { $_.Pnic -eq $PhysicalNetAdapter.Key }).Name + } elseif ($VMHost.ExtensionData.Config.Network.ProxySwitch.Pnic -contains $PhysicalNetAdapter.Key) { + ($VMHost.ExtensionData.Config.Network.ProxySwitch | Where-Object { $_.Pnic -eq $PhysicalNetAdapter.Key }).DvsName + } else { + '--' + } + ) + $LocalizedData.AdapterMAC = $PhysicalNetAdapter.Mac + $LocalizedData.ActualSpeedDuplex = if ($PhysicalNetAdapter.LinkSpeed.SpeedMb) { + if ($PhysicalNetAdapter.LinkSpeed.Duplex) { + "$($PhysicalNetAdapter.LinkSpeed.SpeedMb) Mbps, $($LocalizedData.FullDuplex)" + } else { + $LocalizedData.AutoNegotiate + } + } else { + $LocalizedData.Down + } + $LocalizedData.ConfiguredSpeedDuplex = if ($PhysicalNetAdapter.Spec.LinkSpeed) { + if ($PhysicalNetAdapter.Spec.LinkSpeed.Duplex) { + "$($PhysicalNetAdapter.Spec.LinkSpeed.SpeedMb) Mbps, $($LocalizedData.FullDuplex)" + } else { + "$($PhysicalNetAdapter.Spec.LinkSpeed.SpeedMb) Mbps" + } + } else { + $LocalizedData.AutoNegotiate + } + $LocalizedData.WakeOnLAN = if ($PhysicalNetAdapter.WakeOnLanSupported) { + $LocalizedData.Supported + } else { + $LocalizedData.NotSupported + } + } + } + if ($Healthcheck.VMHost.NetworkAdapter) { + $VMHostPhysicalNetAdapters | Where-Object { $_.$($LocalizedData.Status) -ne $LocalizedData.Connected } | Set-Style -Style Critical -Property $LocalizedData.Status + $VMHostPhysicalNetAdapters | Where-Object { $_.$($LocalizedData.ActualSpeedDuplex) -eq $LocalizedData.Down } | Set-Style -Style Critical -Property $LocalizedData.ActualSpeedDuplex + } + if ($InfoLevel.VMHost -ge 4) { + foreach ($VMHostPhysicalNetAdapter in $VMHostPhysicalNetAdapters) { + Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostPhysicalNetAdapter.$($LocalizedData.AdapterName))" { + $TableParams = @{ + Name = ($LocalizedData.TablePhysicalAdapter -f $VMHostPhysicalNetAdapter.$($LocalizedData.AdapterName), $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostPhysicalNetAdapter | Table @TableParams + } + } + } else { + BlankLine + $TableParams = @{ + Name = ($LocalizedData.TablePhysicalAdapters -f $VMHost) + ColumnWidths = 11, 13, 15, 19, 14, 14, 14 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostPhysicalNetAdapters | Table @TableParams + } + } + #endregion ESXi Host Physical Adapters + + #region ESXi Host Cisco Discovery Protocol + $VMHostNetworkAdapterCDP = $VMHost | Get-VMHostNetworkAdapterDP | Where-Object { $_.Status -eq 'Connected' } | Sort-Object Device + if ($VMHostNetworkAdapterCDP) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.CDP { + Paragraph ($LocalizedData.ParagraphCDP -f $VMHost) + if ($InfoLevel.VMHost -ge 4) { + foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterCDP) { + Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostNetworkAdapter.Device)" { + $VMHostCDP = [PSCustomObject]@{ + $LocalizedData.Status = $VMHostNetworkAdapter.Status + $LocalizedData.SystemName = $VMHostNetworkAdapter.SystemName + $LocalizedData.HardwarePlatform = $VMHostNetworkAdapter.HardwarePlatform + $LocalizedData.SwitchID = $VMHostNetworkAdapter.SwitchId + $LocalizedData.SoftwareVersion = $VMHostNetworkAdapter.SoftwareVersion + $LocalizedData.ManagementAddress = $VMHostNetworkAdapter.ManagementAddress + $LocalizedData.Address = $VMHostNetworkAdapter.Address + $LocalizedData.PortID = $VMHostNetworkAdapter.PortId + $LocalizedData.VLAN = $VMHostNetworkAdapter.Vlan + $LocalizedData.MTU = $VMHostNetworkAdapter.Mtu + } + $TableParams = @{ + Name = ($LocalizedData.TableAdapterCDP -f $VMHostNetworkAdapter.Device, $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostCDP | Table @TableParams + } + } + } else { + BlankLine + $VMHostCDP = foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterCDP) { + [PSCustomObject]@{ + $LocalizedData.AdapterName = $VMHostNetworkAdapter.Device + $LocalizedData.Status = $VMHostNetworkAdapter.Status + $LocalizedData.HardwarePlatform = $VMHostNetworkAdapter.HardwarePlatform + $LocalizedData.SwitchID = $VMHostNetworkAdapter.SwitchId + $LocalizedData.Address = $VMHostNetworkAdapter.Address + $LocalizedData.PortID = $VMHostNetworkAdapter.PortId + } + } + $TableParams = @{ + Name = ($LocalizedData.TableAdaptersCDP -f $VMHost) + ColumnWidths = 11, 13, 26, 22, 17, 11 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostCDP | Table @TableParams + } + } + } + #endregion ESXi Host Cisco Discovery Protocol + + #region ESXi Host Link Layer Discovery Protocol + $VMHostNetworkAdapterLLDP = $VMHost | Get-VMHostNetworkAdapterDP | Where-Object { $null -ne $_.ChassisId } | Sort-Object Device + if ($VMHostNetworkAdapterLLDP) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.LLDP { + Paragraph ($LocalizedData.ParagraphLLDP -f $VMHost) + if ($InfoLevel.VMHost -ge 4) { + foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterLLDP) { + Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostNetworkAdapter.Device)" { + $VMHostLLDP = [PSCustomObject]@{ + $LocalizedData.ChassisID = $VMHostNetworkAdapter.ChassisId + $LocalizedData.PortID = $VMHostNetworkAdapter.PortId + $LocalizedData.TimeToLive = $VMHostNetworkAdapter.TimeToLive + $LocalizedData.TimeOut = $VMHostNetworkAdapter.TimeOut + $LocalizedData.Samples = $VMHostNetworkAdapter.Samples + $LocalizedData.ManagementAddress = $VMHostNetworkAdapter.ManagementAddress + $LocalizedData.PortDescription = $VMHostNetworkAdapter.PortDescription + $LocalizedData.SystemDescription = $VMHostNetworkAdapter.SystemDescription + $LocalizedData.SystemName = $VMHostNetworkAdapter.SystemName + } + $TableParams = @{ + Name = ($LocalizedData.TableAdapterLLDP -f $VMHostNetworkAdapter.Device, $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostLLDP | Table @TableParams + } + } + } else { + BlankLine + $VMHostLLDP = foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterLLDP) { + [PSCustomObject]@{ + $LocalizedData.AdapterName = $VMHostNetworkAdapter.Device + $LocalizedData.ChassisID = $VMHostNetworkAdapter.ChassisId + $LocalizedData.PortID = $VMHostNetworkAdapter.PortId + $LocalizedData.ManagementAddress = $VMHostNetworkAdapter.ManagementAddress + $LocalizedData.PortDescription = $VMHostNetworkAdapter.PortDescription + $LocalizedData.SystemName = $VMHostNetworkAdapter.SystemName + } + } + $TableParams = @{ + Name = ($LocalizedData.TableAdaptersLLDP -f $VMHost) + ColumnWidths = 11, 19, 16, 19, 18, 17 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostLLDP | Table @TableParams + } + } + } + #endregion ESXi Host Link Layer Discovery Protocol + + #region ESXi Host VMkernel Adapaters + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VMKernelAdapters { + Paragraph ($LocalizedData.ParagraphVMKernelAdapters -f $VMHost) + $VMkernelAdapters = $VMHost | Get-View | ForEach-Object -Process { + #$esx = $_ + $netSys = Get-View -Id $_.ConfigManager.NetworkSystem + $vnicMgr = Get-View -Id $_.ConfigManager.VirtualNicManager + $netSys.NetworkInfo.Vnic | + ForEach-Object -Process { + $device = $_.Device + [PSCustomObject]@{ + $LocalizedData.AdapterName = $_.Device + $LocalizedData.NetworkLabel = & { + if ($_.Spec.Portgroup) { + $script:pg = $_.Spec.Portgroup + $script:pg + } elseif ($_.Spec.DistributedVirtualPort.Portgroupkey) { + $script:pg = Get-View -ViewType DistributedVirtualPortgroup -Property Name, Key -Filter @{'Key' = "$($_.Spec.DistributedVirtualPort.PortgroupKey)" } | Select-Object -ExpandProperty Name + $script:pg + } else { + '--' + } + } + $LocalizedData.VirtualSwitch = & { + if ($_.Spec.Portgroup) { + (Get-VirtualPortGroup -Standard -Name $script:pg -VMHost $VMHost).VirtualSwitchName + } elseif ($_.Spec.DistributedVirtualPort.Portgroupkey) { + (Get-VDPortgroup -Name $script:pg).VDSwitch.Name | Select-Object -Unique + } else { + # Haven't figured out how to gather this yet! + '--' + } + } + $LocalizedData.TCPIPStack = switch ($_.Spec.NetstackInstanceKey) { + 'defaultTcpipStack' { $LocalizedData.TCPDefault } + 'vSphereProvisioning' { $LocalizedData.TCPProvisioning } + 'vmotion' { $LocalizedData.TCPvMotion } + 'vxlan' { $LocalizedData.TCPNsxOverlay } + 'hyperbus' { $LocalizedData.TCPNsxHyperbus } + $null { $LocalizedData.TCPNotApplicable } + default { $_.Spec.NetstackInstanceKey } + } + $LocalizedData.MTU = $_.Spec.Mtu + $LocalizedData.AdapterMAC = $_.Spec.Mac + $LocalizedData.DHCP = if ($_.Spec.Ip.Dhcp) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.IPAddress = & { + if ($_.Spec.IP.IPAddress) { + $script:ip = $_.Spec.IP.IPAddress + } else { + $script:ip = '--' + } + $script:ip + } + $LocalizedData.SubnetMask = & { + if ($_.Spec.IP.SubnetMask) { + $script:netmask = $_.Spec.IP.SubnetMask + } else { + $script:netmask = '--' + } + $script:netmask + } + $LocalizedData.DefaultGateway = if ($_.Spec.IpRouteSpec.IpRouteConfig.DefaultGateway) { + $_.Spec.IpRouteSpec.IpRouteConfig.DefaultGateway + } else { + '--' + } + $LocalizedData.vMotion = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vmotion' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.Provisioning = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereProvisioning' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.FTLogging = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'faultToleranceLogging' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.Management = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'management' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.vSphereReplication = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereReplication' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.vSphereReplicationNFC = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereReplicationNFC' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.vSAN = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vsan' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.vSANWitness = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vsanWitness' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.vSphereBackupNFC = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereBackupnNFC' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + } + } + } + foreach ($VMkernelAdapter in ($VMkernelAdapters | Sort-Object $LocalizedData.AdapterName)) { + Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMkernelAdapter.$($LocalizedData.AdapterName))" { + $TableParams = @{ + Name = ($LocalizedData.TableVMKernelAdapter -f $VMkernelAdapter.$($LocalizedData.AdapterName), $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMkernelAdapter | Table @TableParams + } + } + } + #endregion ESXi Host VMkernel Adapaters + + #region ESXi Host Standard Virtual Switches + $VSSwitches = $VMHost | Get-VirtualSwitch -Standard | Sort-Object Name + if ($VSSwitches) { + #region Section Standard Virtual Switches + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.StandardvSwitches { + Paragraph ($LocalizedData.ParagraphStandardvSwitches -f $VMHost) + BlankLine + $VSSwitchNicTeaming = $VSSwitches | Get-NicTeamingPolicy + #region ESXi Host Standard Virtual Switch Properties + $VSSProperties = foreach ($VSSwitchNicTeam in $VSSwitchNicTeaming) { + [PSCustomObject]@{ + $LocalizedData.VirtualSwitch = $VSSwitchNicTeam.VirtualSwitch + $LocalizedData.MTU = $VSSwitchNicTeam.VirtualSwitch.Mtu + $LocalizedData.NumberOfPorts = $VSSwitchNicTeam.VirtualSwitch.NumPorts + $LocalizedData.NumberOfPortsAvailable = $VSSwitchNicTeam.VirtualSwitch.NumPortsAvailable + } + } + $TableParams = @{ + Name = ($LocalizedData.TableStandardvSwitches -f $VMHost) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VSSProperties | Table @TableParams + #endregion ESXi Host Standard Virtual Switch Properties + + #region ESXi Host Virtual Switch Security Policy + $VssSecurity = $VSSwitches | Get-SecurityPolicy + if ($VssSecurity) { + #region Virtual Switch Security Policy + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSSecurity { + $VssSecurity = foreach ($VssSec in $VssSecurity) { + [PSCustomObject]@{ + $LocalizedData.VirtualSwitch = $VssSec.VirtualSwitch + $LocalizedData.PromiscuousMode = if ($VssSec.AllowPromiscuous) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + $LocalizedData.MACAddressChanges = if ($VssSec.MacChanges) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + $LocalizedData.ForgedTransmits = if ($VssSec.ForgedTransmits) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSSecurity -f $VMHost) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VssSecurity | Sort-Object $LocalizedData.VirtualSwitch | Table @TableParams + } + #endregion Virtual Switch Security Policy + } + #endregion ESXi Host Virtual Switch Security Policy + + #region ESXi Host Virtual Switch Traffic Shaping Policy + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSTrafficShaping { + $VssTrafficShapingPolicy = foreach ($VSSwitch in $VSSwitches) { + [PSCustomObject]@{ + $LocalizedData.VirtualSwitch = $VSSwitch.Name + $LocalizedData.Status = if ($VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.Enabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.AverageBandwidth = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.AverageBandwidth + $LocalizedData.PeakBandwidth = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.PeakBandwidth + $LocalizedData.BurstSize = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.BurstSize + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSTrafficShaping -f $VMHost) + ColumnWidths = 25, 15, 20, 20, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VssTrafficShapingPolicy | Sort-Object $LocalizedData.VirtualSwitch | Table @TableParams + } + #endregion ESXi Host Virtual Switch Traffic Shaping Policy + + #region ESXi Host Virtual Switch Teaming & Failover + $VssNicTeamingPolicy = $VSSwitches | Get-NicTeamingPolicy + if ($VssNicTeamingPolicy) { + #region Virtual Switch Teaming & Failover Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSTeamingFailover { + $VssNicTeaming = foreach ($VssNicTeam in $VssNicTeamingPolicy) { + [PSCustomObject]@{ + $LocalizedData.VirtualSwitch = $VssNicTeam.VirtualSwitch + $LocalizedData.LoadBalancing = switch ($VssNicTeam.LoadBalancingPolicy) { + 'LoadbalanceSrcId' { $LocalizedData.LBSrcId } + 'LoadbalanceSrcMac' { $LocalizedData.LBSrcMac } + 'LoadbalanceIP' { $LocalizedData.LBIP } + 'ExplicitFailover' { $LocalizedData.LBExplicitFailover } + default { $VssNicTeam.LoadBalancingPolicy } + } + $LocalizedData.NetworkFailureDetection = switch ($VssNicTeam.NetworkFailoverDetectionPolicy) { + 'LinkStatus' { $LocalizedData.NFDLinkStatus } + 'BeaconProbing' { $LocalizedData.NFDBeaconProbing } + default { $VssNicTeam.NetworkFailoverDetectionPolicy } + } + $LocalizedData.NotifySwitches = if ($VssNicTeam.NotifySwitches) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.Failback = if ($VssNicTeam.FailbackEnabled) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.ActiveNICs = ($VssNicTeam.ActiveNic | Sort-Object) -join [Environment]::NewLine + $LocalizedData.StandbyNICs = ($VssNicTeam.StandbyNic | Sort-Object) -join [Environment]::NewLine + $LocalizedData.UnusedNICs = ($VssNicTeam.UnusedNic | Sort-Object) -join [Environment]::NewLine + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSTeamingFailover -f $VMHost) + ColumnWidths = 20, 17, 12, 11, 10, 10, 10, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VssNicTeaming | Sort-Object $LocalizedData.VirtualSwitch | Table @TableParams + } + #endregion Virtual Switch Teaming & Failover Section + } + #endregion ESXi Host Virtual Switch Teaming & Failover + + #region ESXi Host Virtual Switch Port Groups + $VssPortgroups = $VSSwitches | Get-VirtualPortGroup -Standard + if ($VssPortgroups) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSPortGroups { + $VssPortgroups = foreach ($VssPortgroup in $VssPortgroups) { + [PSCustomObject]@{ + $LocalizedData.PortGroup = $VssPortgroup.Name + $LocalizedData.VLAN = $VssPortgroup.VLanId + $LocalizedData.VirtualSwitch = $VssPortgroup.VirtualSwitchName + $LocalizedData.NumberOfVMs = ($VssPortgroup | Get-VM).Count + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSPortGroups -f $VMHost) + ColumnWidths = 40, 10, 40, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VssPortgroups | Sort-Object $LocalizedData.PortGroup, $LocalizedData.VLAN, $LocalizedData.VirtualSwitch | Table @TableParams + } + #endregion ESXi Host Virtual Switch Port Groups + + #region ESXi Host Virtual Switch Port Group Security Policy + $VssPortgroupSecurity = $VSSwitches | Get-VirtualPortGroup | Get-SecurityPolicy + if ($VssPortgroupSecurity) { + #region Virtual Port Group Security Policy Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSPGSecurity { + $VssPortgroupSecurity = foreach ($VssPortgroupSec in $VssPortgroupSecurity) { + [PSCustomObject]@{ + $LocalizedData.PortGroup = $VssPortgroupSec.VirtualPortGroup + $LocalizedData.VirtualSwitch = $VssPortgroupSec.virtualportgroup.virtualswitchname + $LocalizedData.PromiscuousMode = if ($VssPortgroupSec.AllowPromiscuous) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + $LocalizedData.MACChanges = if ($VssPortgroupSec.MacChanges) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + $LocalizedData.ForgedTransmits = if ($VssPortgroupSec.ForgedTransmits) { + $LocalizedData.Accept + } else { + $LocalizedData.Reject + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSPGSecurity -f $VMHost) + ColumnWidths = 27, 25, 16, 16, 16 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VssPortgroupSecurity | Sort-Object $LocalizedData.PortGroup, $LocalizedData.VirtualSwitch | Table @TableParams + } + #endregion Virtual Port Group Security Policy Section + } + #endregion ESXi Host Virtual Switch Port Group Security Policy + + #region ESXi Host Virtual Switch Port Group Traffic Shaping Policy + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSPGTrafficShaping { + $VssPortgroupTrafficShapingPolicy = foreach ($VssPortgroup in $VssPortgroups) { + [PSCustomObject]@{ + $LocalizedData.PortGroup = $VssPortgroup.Name + $LocalizedData.VirtualSwitch = $VssPortgroup.VirtualSwitchName + $LocalizedData.Status = switch ($VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.Enabled) { + $True { $LocalizedData.Enabled } + $False { $LocalizedData.Disabled } + $null { $LocalizedData.Inherited } + } + $LocalizedData.AverageBandwidth = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.AverageBandwidth + $LocalizedData.PeakBandwidth = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.PeakBandwidth + $LocalizedData.BurstSize = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.BurstSize + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSPGTrafficShaping -f $VMHost) + ColumnWidths = 19, 19, 11, 17, 17, 17 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VssPortgroupTrafficShapingPolicy | Sort-Object $LocalizedData.PortGroup, $LocalizedData.VirtualSwitch | Table @TableParams + } + #endregion ESXi Host Virtual Switch Port Group Traffic Shaping Policy + + #region ESXi Host Virtual Switch Port Group Teaming & Failover + $VssPortgroupNicTeaming = $VSSwitches | Get-VirtualPortGroup | Get-NicTeamingPolicy + if ($VssPortgroupNicTeaming) { + #region Virtual Switch Port Group Teaming & Failover Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSPGTeamingFailover { + $VssPortgroupNicTeaming = foreach ($VssPortgroupNicTeam in $VssPortgroupNicTeaming) { + [PSCustomObject]@{ + $LocalizedData.PortGroup = $VssPortgroupNicTeam.VirtualPortGroup + $LocalizedData.VirtualSwitch = $VssPortgroupNicTeam.virtualportgroup.virtualswitchname + $LocalizedData.LoadBalancing = switch ($VssPortgroupNicTeam.LoadBalancingPolicy) { + 'LoadbalanceSrcId' { $LocalizedData.LBSrcId } + 'LoadbalanceSrcMac' { $LocalizedData.LBSrcMac } + 'LoadbalanceIP' { $LocalizedData.LBIP } + 'ExplicitFailover' { $LocalizedData.LBExplicitFailover } + default { $VssPortgroupNicTeam.LoadBalancingPolicy } + } + $LocalizedData.NetworkFailureDetection = switch ($VssPortgroupNicTeam.NetworkFailoverDetectionPolicy) { + 'LinkStatus' { $LocalizedData.NFDLinkStatus } + 'BeaconProbing' { $LocalizedData.NFDBeaconProbing } + default { $VssPortgroupNicTeam.NetworkFailoverDetectionPolicy } + } + $LocalizedData.NotifySwitches = if ($VssPortgroupNicTeam.NotifySwitches) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.Failback = if ($VssPortgroupNicTeam.FailbackEnabled) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.ActiveNICs = ($VssPortgroupNicTeam.ActiveNic | Sort-Object) -join [Environment]::NewLine + $LocalizedData.StandbyNICs = ($VssPortgroupNicTeam.StandbyNic | Sort-Object) -join [Environment]::NewLine + $LocalizedData.UnusedNICs = ($VssPortgroupNicTeam.UnusedNic | Sort-Object) -join [Environment]::NewLine + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSPGTeamingFailover -f $VMHost) + ColumnWidths = 12, 11, 11, 11, 11, 11, 11, 11, 11 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VssPortgroupNicTeaming | Sort-Object $LocalizedData.PortGroup, $LocalizedData.VirtualSwitch | Table @TableParams + } + #endregion Virtual Switch Port Group Teaming & Failover Section + } + #endregion ESXi Host Virtual Switch Port Group Teaming & Failover + } + } + #endregion Section Standard Virtual Switches + } + #endregion ESXi Host Standard Virtual Switches + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + #endregion ESXi Host Network Section + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 new file mode 100644 index 0000000..00b2025 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 @@ -0,0 +1,259 @@ +function Get-AbrVSphereVMHostSecurity { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere ESXi VMHost Security information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereVMHostSecurity + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) + } + + process { + try { + Write-PScriboMessage -Message $LocalizedData.Collecting + #region ESXi Host Security Section + Section -Style Heading4 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $VMHost) + #region ESXi Host Lockdown Mode + if ($null -ne $VMHost.ExtensionData.Config.LockdownMode) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.LockdownMode { + $LockdownMode = [PSCustomObject]@{ + $LocalizedData.LockdownMode = switch ($VMHost.ExtensionData.Config.LockdownMode) { + 'lockdownDisabled' { $LocalizedData.LockdownDisabled } + 'lockdownNormal' { $LocalizedData.LockdownNormal } + 'lockdownStrict' { $LocalizedData.LockdownStrict } + default { $VMHost.ExtensionData.Config.LockdownMode } + } + } + if ($Healthcheck.VMHost.LockdownMode) { + $LockdownMode | Where-Object { $_.$($LocalizedData.LockdownMode) -eq $LocalizedData.LockdownDisabled } | Set-Style -Style Warning -Property $LocalizedData.LockdownMode + } + $TableParams = @{ + Name = ($LocalizedData.TableLockdownMode -f $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $LockdownMode | Table @TableParams + } + } + #endregion ESXi Host Lockdown Mode + + #region ESXi Host Services + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.Services { + $VMHostServices = $VMHost | Get-VMHostService + $Services = foreach ($VMHostService in $VMHostServices) { + [PSCustomObject]@{ + $LocalizedData.ServiceName = $VMHostService.Label + $LocalizedData.ServiceRunning = if ($VMHostService.Running) { + $LocalizedData.Running + } else { + $LocalizedData.Stopped + } + $LocalizedData.ServicePolicy = switch ($VMHostService.Policy) { + 'automatic' { $LocalizedData.SvcPortUsage } + 'on' { $LocalizedData.SvcWithHost } + 'off' { $LocalizedData.SvcManually } + default { $VMHostService.Policy } + } + } + } + if ($Healthcheck.VMHost.NTP) { + $Services | Where-Object { ($_.$($LocalizedData.ServiceName) -eq 'NTP Daemon') -and ($_.$($LocalizedData.ServiceRunning) -eq $LocalizedData.Stopped) } | Set-Style -Style Critical -Property $LocalizedData.ServiceRunning + $Services | Where-Object { ($_.$($LocalizedData.ServiceName) -eq 'NTP Daemon') -and ($_.$($LocalizedData.ServicePolicy) -ne $LocalizedData.SvcWithHost) } | Set-Style -Style Critical -Property $LocalizedData.ServicePolicy + } + if ($Healthcheck.VMHost.SSH) { + $Services | Where-Object { ($_.$($LocalizedData.ServiceName) -eq 'SSH') -and ($_.$($LocalizedData.ServiceRunning) -eq $LocalizedData.Running) } | Set-Style -Style Warning -Property $LocalizedData.ServiceRunning + $Services | Where-Object { ($_.$($LocalizedData.ServiceName) -eq 'SSH') -and ($_.$($LocalizedData.ServicePolicy) -ne $LocalizedData.SvcManually) } | Set-Style -Style Warning -Property $LocalizedData.ServicePolicy + } + if ($Healthcheck.VMHost.ESXiShell) { + $Services | Where-Object { ($_.$($LocalizedData.ServiceName) -eq 'ESXi Shell') -and ($_.$($LocalizedData.ServiceRunning) -eq $LocalizedData.Running) } | Set-Style -Style Warning -Property $LocalizedData.ServiceRunning + $Services | Where-Object { ($_.$($LocalizedData.ServiceName) -eq 'ESXi Shell') -and ($_.$($LocalizedData.ServicePolicy) -ne $LocalizedData.SvcManually) } | Set-Style -Style Warning -Property $LocalizedData.ServicePolicy + } + $TableParams = @{ + Name = ($LocalizedData.TableServices -f $VMHost) + ColumnWidths = 40, 20, 40 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $Services | Sort-Object $LocalizedData.ServiceName | Table @TableParams + } + #endregion ESXi Host Services + + #region ESXi Host Advanced Detail Information + if ($InfoLevel.VMHost -ge 4) { + #region ESXi Host Firewall + $VMHostFirewallExceptions = $VMHost | Get-VMHostFirewallException + if ($VMHostFirewallExceptions) { + #region Friewall Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.Firewall { + $VMHostFirewall = foreach ($VMHostFirewallException in $VMHostFirewallExceptions) { + [PScustomObject]@{ + $LocalizedData.FirewallService = $VMHostFirewallException.Name + $LocalizedData.Status = if ($VMHostFirewallException.Enabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.IncomingPorts = $VMHostFirewallException.IncomingPorts + $LocalizedData.OutgoingPorts = $VMHostFirewallException.OutgoingPorts + $LocalizedData.Protocols = $VMHostFirewallException.Protocols + $LocalizedData.Daemon = switch ($VMHostFirewallException.ServiceRunning) { + $true { $LocalizedData.Running } + $false { $LocalizedData.Stopped } + $null { 'N/A' } + default { $VMHostFirewallException.ServiceRunning } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableFirewall -f $VMHost) + ColumnWidths = 22, 12, 21, 21, 12, 12 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostFirewall | Sort-Object $LocalizedData.FirewallService | Table @TableParams + } + #endregion Friewall Section + } + #endregion ESXi Host Firewall + + #region ESXi Host Authentication + $AuthServices = $VMHost | Get-VMHostAuthentication + if ($AuthServices.DomainMembershipStatus) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.Authentication { + $AuthServices = $AuthServices | Select-Object @{L = $LocalizedData.Domain; E = { $_.Domain } }, @{L = $LocalizedData.DomainMembership; E = { $_.DomainMembershipStatus } }, @{L = $LocalizedData.TrustedDomains; E = { $_.TrustedDomains } } + $TableParams = @{ + Name = ($LocalizedData.TableAuthentication -f $VMHost) + ColumnWidths = 25, 25, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $AuthServices | Table @TableParams + } + } + #endregion ESXi Host Authentication + } + #endregion ESXi Host Advanced Detail Information + } + #endregion ESXi Host Security Section + + #region ESXi Host Virtual Machines Advanced Detail Information + if ($InfoLevel.VMHost -ge 4) { + $VMHostVMs = $VMHost | Get-VM | Sort-Object Name + if ($VMHostVMs) { + #region Virtual Machines Section + Section -Style Heading4 $LocalizedData.VMInfoSection { + Paragraph ($LocalizedData.ParagraphVMInfo -f $VMHost) + BlankLine + #region ESXi Host Virtual Machine Information + $VMHostVMInfo = foreach ($VMHostVM in $VMHostVMs) { + $VMHostVMView = $VMHostVM | Get-View + [PSCustomObject]@{ + $LocalizedData.VMName = $VMHostVM.Name + $LocalizedData.VMPowerState = switch ($VMHostVM.PowerState) { + 'PoweredOn' { $LocalizedData.On } + 'PoweredOff' { $LocalizedData.Off } + default { $VMHostVM.PowerState } + } + $LocalizedData.VMIPAddress = if ($VMHostVMView.Guest.IpAddress) { + $VMHostVMView.Guest.IpAddress + } else { + '--' + } + $LocalizedData.VMCPUs = $VMHostVM.NumCpu + #'Cores per Socket' = $VMHostVM.CoresPerSocket + $LocalizedData.VMMemoryGB = Convert-DataSize $VMHostVM.memoryGB -RoundUnits 0 + $LocalizedData.VMProvisioned = Convert-DataSize $VMHostVM.ProvisionedSpaceGB + $LocalizedData.VMUsed = Convert-DataSize $VMHostVM.UsedSpaceGB + $LocalizedData.VMHWVersion = ($VMHostVM.HardwareVersion).Replace('vmx-', 'v') + $LocalizedData.VMToolsStatus = switch ($VMHostVM.ExtensionData.Guest.ToolsStatus) { + 'toolsOld' { $LocalizedData.ToolsOld } + 'toolsOK' { $LocalizedData.ToolsOK } + 'toolsNotRunning' { $LocalizedData.ToolsNotRunning } + 'toolsNotInstalled' { $LocalizedData.ToolsNotInstalled } + default { $VMHostVM.ExtensionData.Guest.ToolsStatus } + } + } + } + if ($Healthcheck.VM.VMToolsStatus) { + $VMHostVMInfo | Where-Object { $_.$($LocalizedData.VMToolsStatus) -ne $LocalizedData.ToolsOK } | Set-Style -Style Warning -Property $LocalizedData.VMToolsStatus + } + if ($Healthcheck.VM.PowerState) { + $VMHostVMInfo | Where-Object { $_.$($LocalizedData.VMPowerState) -ne $LocalizedData.On } | Set-Style -Style Warning -Property $LocalizedData.VMPowerState + } + $TableParams = @{ + Name = ($LocalizedData.TableVMs -f $VMHost) + ColumnWidths = 21, 8, 16, 9, 9, 9, 9, 9, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostVMInfo | Table @TableParams + #endregion ESXi Host Virtual Machine Information + + #region ESXi Host VM Startup/Shutdown Information + $VMStartPolicy = $VMHost | Get-VMStartPolicy | Where-Object { $_.StartAction -ne 'None' } + if ($VMStartPolicy) { + #region VM Startup/Shutdown Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.StartupShutdown { + $VMStartPolicies = foreach ($VMStartPol in $VMStartPolicy) { + [PSCustomObject]@{ + $LocalizedData.StartupOrder = $VMStartPol.StartOrder + $LocalizedData.VMName = $VMStartPol.VirtualMachineName + $LocalizedData.StartupEnabled = switch ($VMStartPol.StartAction) { + 'PowerOn' { $LocalizedData.Enabled } + 'None' { $LocalizedData.Disabled } + default { $VMStartPol.StartAction } + } + $LocalizedData.StartupDelay = "$($VMStartPol.StartDelay) seconds" + $LocalizedData.WaitForHeartbeat = if ($VMStartPol.WaitForHeartbeat) { + $LocalizedData.WaitHeartbeat + } else { + $LocalizedData.WaitDelay + } + $LocalizedData.StopAction = switch ($VMStartPol.StopAction) { + 'PowerOff' { $LocalizedData.PowerOff } + 'GuestShutdown' { $LocalizedData.GuestShutdown } + default { $VMStartPol.StopAction } + } + $LocalizedData.StopDelay = "$($VMStartPol.StopDelay) seconds" + } + } + $TableParams = @{ + Name = ($LocalizedData.TableStartupShutdown -f $VMHost) + ColumnWidths = 11, 34, 11, 11, 11, 11, 11 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMStartPolicies | Table @TableParams + } + #endregion VM Startup/Shutdown Section + } + #endregion ESXi Host VM Startup/Shutdown Information + } + #endregion Virtual Machines Section + } + } + #endregion ESXi Host Virtual Machines Advanced Detail Information + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostStorage.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostStorage.ps1 new file mode 100644 index 0000000..8fc5d50 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostStorage.ps1 @@ -0,0 +1,177 @@ +function Get-AbrVSphereVMHostStorage { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere ESXi VMHost Storage information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereVMHostStorage + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) + } + + process { + try { + Write-PScriboMessage -Message $LocalizedData.Collecting + #region ESXi Host Storage Section + Section -Style Heading4 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $VMHost) + + #region ESXi Host Datastore Specifications + $VMHostDatastores = $VMHost | Get-Datastore | Where-Object { ($_.State -eq 'Available') -and ($_.CapacityGB -gt 0) } | Sort-Object Name + if ($VMHostDatastores) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DatastoreSpecs { + $VMHostDsSpecs = foreach ($VMHostDatastore in $VMHostDatastores) { + + $VMHostDsUsedPercent = if (0 -in @($VMHostDatastore.FreeSpaceGB, $VMHostDatastore.CapacityGB)) { 0 } else { [math]::Round((100 - (($VMHostDatastore.FreeSpaceGB) / ($VMHostDatastore.CapacityGB) * 100)), 2) } + $VMHostDsFreePercent = if (0 -in @($VMHostDatastore.FreeSpaceGB, $VMHostDatastore.CapacityGB)) { 0 } else { [math]::Round(($VMHostDatastore.FreeSpaceGB / $VMHostDatastore.CapacityGB) * 100, 2) } + $VMHostDsUsedCapacityGB = ($VMHostDatastore.CapacityGB) - ($VMHostDatastore.FreeSpaceGB) + + [PSCustomObject]@{ + $LocalizedData.Datastore = $VMHostDatastore.Name + $LocalizedData.DatastoreType = $VMHostDatastore.Type + $LocalizedData.Version = if ($VMHostDatastore.FileSystemVersion) { + $VMHostDatastore.FileSystemVersion + } else { + '--' + } + $LocalizedData.NumberOfVMs = $VMHostDatastore.ExtensionData.VM.Count + $LocalizedData.TotalCapacity = Convert-DataSize $VMHostDatastore.CapacityGB + $LocalizedData.UsedCapacity = "{0} ({1}%)" -f (Convert-DataSize $VMHostDsUsedCapacityGB), $VMHostDsUsedPercent + $LocalizedData.FreeCapacity = "{0} ({1}%)" -f (Convert-DataSize $Datastore.FreeSpaceGB), $VMHostDsFreePercent + $LocalizedData.PercentUsed = $VMHostDsUsedPercent + } + } + if ($Healthcheck.Datastore.CapacityUtilization) { + $VMHostDsSpecs | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 90 } | Set-Style -Style Critical -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + $VMHostDsSpecs | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 75 -and $_.$($LocalizedData.PercentUsed) -lt 90 } | Set-Style -Style Warning -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + } + $TableParams = @{ + Name = ($LocalizedData.TableDatastores -f $VMHost) + Columns = $LocalizedData.Datastore, $LocalizedData.DatastoreType, $LocalizedData.Version, $LocalizedData.NumberOfVMs, $LocalizedData.TotalCapacity, $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + ColumnWidths = 21, 10, 9, 9, 17, 17, 17 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostDsSpecs | Sort-Object $LocalizedData.Datastore | Table @TableParams + } + } + #endregion ESXi Host Datastore Specifications + + #region ESXi Host Storage Adapter Information + $VMHostHbas = $VMHost | Get-VMHostHba | Sort-Object Device + if ($VMHostHbas) { + #region ESXi Host Storage Adapters Section + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.StorageAdapters { + Paragraph ($LocalizedData.ParagraphStorageAdapters -f $VMHost) + foreach ($VMHostHba in $VMHostHbas) { + $Target = ((Get-View $VMHostHba.VMhost).Config.StorageDevice.ScsiTopology.Adapter | Where-Object { $_.Adapter -eq $VMHostHba.Key }).Target + $LUNs = Get-ScsiLun -Hba $VMHostHba -LunType "disk" -ErrorAction SilentlyContinue + $Paths = ($Target | ForEach-Object { $_.Lun.Count } | Measure-Object -Sum) + Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostHba.Device)" { + $VMHostStorageAdapter = [PSCustomObject]@{ + $LocalizedData.AdapterName = $VMHostHba.Device + $LocalizedData.AdapterType = switch ($VMHostHba.Type) { + 'FibreChannel' { $LocalizedData.FC } + 'IScsi' { $LocalizedData.iSCSI } + 'ParallelScsi' { $LocalizedData.ParallelSCSI } + default { $TextInfo.ToTitleCase($VMHostHba.Type) } + } + $LocalizedData.AdapterModel = $VMHostHba.Model + $LocalizedData.AdapterStatus = $TextInfo.ToTitleCase($VMHostHba.Status) + $LocalizedData.AdapterTargets = $Target.Count + $LocalizedData.AdapterDevices = $LUNs.Count + $LocalizedData.AdapterPaths = $Paths.Sum + } + $MemberProps = @{ + 'InputObject' = $VMHostStorageAdapter + 'MemberType' = 'NoteProperty' + } + if ($VMHostStorageAdapter.$($LocalizedData.AdapterType) -eq $LocalizedData.iSCSI) { + $iScsiAuthenticationMethod = switch ($VMHostHba.ExtensionData.AuthenticationProperties.ChapAuthenticationType) { + 'chapProhibited' { $LocalizedData.ChapNone } + 'chapPreferred' { $LocalizedData.ChapUniPreferred } + 'chapDiscouraged' { $LocalizedData.ChapUniRequired } + 'chapRequired' { + switch ($VMHostHba.ExtensionData.AuthenticationProperties.MutualChapAuthenticationType) { + 'chapProhibited' { $LocalizedData.ChapUnidirectional } + 'chapRequired' { $LocalizedData.ChapBidirectional } + } + } + default { $VMHostHba.ExtensionData.AuthenticationProperties.ChapAuthenticationType } + } + Add-Member @MemberProps -Name $LocalizedData.iSCSIName -Value $VMHostHba.IScsiName + if ($VMHostHba.IScsiAlias) { + Add-Member @MemberProps -Name $LocalizedData.iSCSIAlias -Value $VMHostHba.IScsiAlias + } else { + Add-Member @MemberProps -Name $LocalizedData.iSCSIAlias -Value '--' + } + if ($VMHostHba.CurrentSpeedMb) { + Add-Member @MemberProps -Name $LocalizedData.AdapterSpeed -Value "$($VMHostHba.CurrentSpeedMb) Mb" + } else { + Add-Member @MemberProps -Name $LocalizedData.AdapterSpeed -Value '--' + } + if ($VMHostHba.ExtensionData.ConfiguredSendTarget) { + Add-Member @MemberProps -Name $LocalizedData.DynamicDiscovery -Value (($VMHostHba.ExtensionData.ConfiguredSendTarget | ForEach-Object { "$($_.Address)" + ":" + "$($_.Port)" }) -join [Environment]::NewLine) + } else { + Add-Member @MemberProps -Name $LocalizedData.DynamicDiscovery -Value '--' + } + if ($VMHostHba.ExtensionData.ConfiguredStaticTarget) { + Add-Member @MemberProps -Name $LocalizedData.StaticDiscovery -Value (($VMHostHba.ExtensionData.ConfiguredStaticTarget | ForEach-Object { "$($_.Address)" + ":" + "$($_.Port)" + " " + "$($_.IScsiName)" }) -join [Environment]::NewLine) + } else { + Add-Member @MemberProps -Name $LocalizedData.StaticDiscovery -Value '--' + } + if ($iScsiAuthenticationMethod -eq $LocalizedData.ChapNone) { + Add-Member @MemberProps -Name $LocalizedData.AuthMethod -Value $iScsiAuthenticationMethod + } elseif ($iScsiAuthenticationMethod -eq $LocalizedData.ChapBidirectional) { + Add-Member @MemberProps -Name $LocalizedData.AuthMethod -Value $iScsiAuthenticationMethod + Add-Member @MemberProps -Name $LocalizedData.CHAPOutgoing -Value $VMHostHba.ExtensionData.AuthenticationProperties.ChapName + Add-Member @MemberProps -Name $LocalizedData.CHAPIncoming -Value $VMHostHba.ExtensionData.AuthenticationProperties.MutualChapName + } else { + Add-Member @MemberProps -Name $LocalizedData.AuthMethod -Value $iScsiAuthenticationMethod + Add-Member @MemberProps -Name $LocalizedData.CHAPOutgoing -Value $VMHostHba.ExtensionData.AuthenticationProperties.ChapName + } + if ($InfoLevel.VMHost -ge 4) { + Add-Member @MemberProps -Name $LocalizedData.AdvancedOptions -Value (($VMHostHba.ExtensionData.AdvancedOptions | ForEach-Object { "$($_.Key) = $($_.Value)" }) -join [Environment]::NewLine) + } + } + if ($VMHostStorageAdapter.$($LocalizedData.AdapterType) -eq $LocalizedData.FC) { + Add-Member @MemberProps -Name $LocalizedData.NodeWWN -Value (([String]::Format("{0:X}", $VMHostHba.NodeWorldWideName) -split "(\w{2})" | Where-Object { $_ -ne "" }) -join ":") + Add-Member @MemberProps -Name $LocalizedData.PortWWN -Value (([String]::Format("{0:X}", $VMHostHba.PortWorldWideName) -split "(\w{2})" | Where-Object { $_ -ne "" }) -join ":") + Add-Member @MemberProps -Name $LocalizedData.AdapterSpeed -Value $VMHostHba.Speed + } + if ($Healthcheck.VMHost.StorageAdapter) { + $VMHostStorageAdapter | Where-Object { $_.$($LocalizedData.AdapterStatus) -ne $LocalizedData.Online } | Set-Style -Style Warning -Property $LocalizedData.AdapterStatus + $VMHostStorageAdapter | Where-Object { $_.$($LocalizedData.AdapterStatus) -eq $LocalizedData.Offline } | Set-Style -Style Critical -Property $LocalizedData.AdapterStatus + } + $TableParams = @{ + Name = ($LocalizedData.TableStorageAdapter -f $VMHostStorageAdapter.$($LocalizedData.AdapterName), $VMHost) + List = $true + ColumnWidths = 25, 75 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostStorageAdapter | Table @TableParams + } + } + } + #endregion ESXi Host Storage Adapters Section + } + #endregion ESXi Host Storage Adapter Information + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + #endregion ESXi Host Storage Section + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 new file mode 100644 index 0000000..c29db7f --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 @@ -0,0 +1,266 @@ +function Get-AbrVSphereVMHostSystem { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere ESXi VMHost System information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereVMHostSystem + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) + } + + process { + try { + Write-PScriboMessage -Message $LocalizedData.Collecting + #region ESXi Host System Section + Section -Style Heading4 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $VMHost) + #region ESXi Host Profile Information + if ($VMHost | Get-VMHostProfile) { + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.HostProfile { + $VMHostProfile = $VMHost | Get-VMHostProfile | Select-Object @{L = $LocalizedData.ProfileName; E = { $_.Name } }, @{L = $LocalizedData.ProfileDescription; E = { $_.Description } } + $TableParams = @{ + Name = ($LocalizedData.TableHostProfile -f $VMHost) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostProfile | Sort-Object $LocalizedData.ProfileName | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.HostProfileError -f $VMHost.Name, $_.Exception.Message) + } + } + #endregion ESXi Host Profile Information + + #region ESXi Host Image Profile Information + if ($UserPrivileges -contains 'Host.Config.Settings') { + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.ImageProfile { + $installdate = Get-InstallDate + $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter + $ImageProfile = $esxcli.software.profile.get.Invoke() + $SecurityProfile = [PSCustomObject]@{ + $LocalizedData.ImageName = $ImageProfile.Name + $LocalizedData.ImageVendor = $ImageProfile.Vendor + $LocalizedData.InstallDate = $InstallDate.InstallDate + } + $TableParams = @{ + Name = ($LocalizedData.TableImageProfile -f $VMHost) + ColumnWidths = 50, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $SecurityProfile | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.ImageProfileError -f $VMHost.Name, $_.Exception.Message) + } + } else { + Write-PScriboMessage -Message $LocalizedData.InsufficientPrivImageProfile + } + #endregion ESXi Host Image Profile Information + + #region ESXi Host Time Configuration + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.TimeConfiguration { + $VMHostTimeSettings = [PSCustomObject]@{ + $LocalizedData.TimeZone = $VMHost.timezone + $LocalizedData.NTPService = if ((Get-VMHostService -VMHost $VMHost | Where-Object { $_.key -eq 'ntpd' }).Running) { + $LocalizedData.Running + } else { + $LocalizedData.Stopped + } + $LocalizedData.NTPServers = (Get-VMHostNtpServer -VMHost $VMHost | Sort-Object) -join ', ' + } + if ($Healthcheck.VMHost.NTP) { + $VMHostTimeSettings | Where-Object { $_.$($LocalizedData.NTPService) -eq $LocalizedData.Stopped } | Set-Style -Style Critical -Property $LocalizedData.NTPService + } + $TableParams = @{ + Name = ($LocalizedData.TableTimeConfig -f $VMHost) + ColumnWidths = 30, 30, 40 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostTimeSettings | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.TimeConfigError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host Time Configuration + + #region ESXi Host Syslog Configuration + $SyslogConfig = $VMHost | Get-VMHostSysLogServer + if ($SyslogConfig) { + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.Syslog { + # TODO: Syslog Rotate & Size, Log Directory (Adv Settings) + $SyslogConfig = $SyslogConfig | Select-Object @{L = $LocalizedData.SyslogHost; E = { $_.Host } }, @{L = $LocalizedData.SyslogPort; E = { $_.Port } } + $TableParams = @{ + Name = ($LocalizedData.TableSyslog -f $VMHost) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $SyslogConfig | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.SyslogError -f $VMHost.Name, $_.Exception.Message) + } + } + #endregion ESXi Host Syslog Configuration + + #region ESXi Update Manager Baseline Information + if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { + if ($VumServer.Name) { + try { + $VMHostPatchBaselines = $VMHost | Get-PatchBaseline + } catch { + Write-PScriboMessage -Message $LocalizedData.VUMBaselineNotAvailable + } + if ($VMHostPatchBaselines) { + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VUMBaseline { + $VMHostBaselines = foreach ($VMHostBaseline in $VMHostPatchBaselines) { + [PSCustomObject]@{ + $LocalizedData.VUMBaselineName = $VMHostBaseline.Name + $LocalizedData.VUMDescription = $VMHostBaseline.Description + $LocalizedData.VUMBaselineType = $VMHostBaseline.BaselineType + $LocalizedData.VUMTargetType = $VMHostBaseline.TargetType + $LocalizedData.VUMLastUpdate = ($VMHostBaseline.LastUpdateTime).ToLocalTime().ToString() + $LocalizedData.VUMBaselinePatches = $VMHostBaseline.CurrentPatches.Count + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMBaselines -f $VMHost) + ColumnWidths = 25, 25, 10, 10, 20, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostBaselines | Sort-Object $LocalizedData.VUMBaselineName | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.VUMBaselineError -f $VMHost.Name, $_.Exception.Message) + } + } + } + } else { + Write-PScriboMessage -Message $LocalizedData.InsufficientPrivVUMBaseline + } + #endregion ESXi Update Manager Baseline Information + + #region ESXi Update Manager Compliance Information + if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { + if ($VumServer.Name) { + try { + $VMHostCompliances = $VMHost | Get-Compliance + } catch { + Write-PScriboMessage -Message $LocalizedData.VUMComplianceNotAvailable + } + if ($VMHostCompliances) { + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VUMCompliance { + $VMHostComplianceInfo = foreach ($VMHostCompliance in $VMHostCompliances) { + [PSCustomObject]@{ + $LocalizedData.VUMBaselineName = $VMHostCompliance.Baseline.Name + $LocalizedData.VUMStatus = switch ($VMHostCompliance.Status) { + 'NotCompliant' { $LocalizedData.NotCompliant } + default { $VMHostCompliance.Status } + } + } + } + if ($Healthcheck.VMHost.VUMCompliance) { + $VMHostComplianceInfo | Where-Object { $_.$($LocalizedData.VUMStatus) -eq $LocalizedData.Unknown } | Set-Style -Style Warning + $VMHostComplianceInfo | Where-Object { $_.$($LocalizedData.VUMStatus) -eq $LocalizedData.NotCompliant -or $_.$($LocalizedData.VUMStatus) -eq $LocalizedData.Incompatible } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMCompliance -f $VMHost) + ColumnWidths = 75, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostComplianceInfo | Sort-Object $LocalizedData.VUMBaselineName | Table @TableParams + } + } catch { + Write-PScriboMessage -Message -Message ($LocalizedData.VUMComplianceError -f $VMHost.Name, $_.Exception.Message) + } + } + } + } else { + Write-PScriboMessage -Message $LocalizedData.InsufficientPrivVUMCompliance + } + #endregion ESXi Update Manager Compliance Information + + #region ESXi Host Comprehensive Information Section + if ($InfoLevel.VMHost -ge 5) { + #region ESXi Host Advanced System Settings + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.AdvancedSettings { + $AdvSettings = $VMHost | Get-AdvancedSetting | Select-Object @{L = $LocalizedData.Key; E = { $_.Name } }, @{L = $LocalizedData.Value; E = { $_.Value } } + $TableParams = @{ + Name = ($LocalizedData.TableAdvancedSettings -f $VMHost) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $AdvSettings | Sort-Object $LocalizedData.Key | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.AdvancedSettingsError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host Advanced System Settings + + #region ESXi Host Software VIBs + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VIBs { + $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter + $VMHostVibs = $esxcli.software.vib.list.Invoke() + $VMHostVibs = foreach ($VMHostVib in $VMHostVibs) { + [PSCustomObject]@{ + $LocalizedData.VIBName = $VMHostVib.Name + $LocalizedData.VIBID = $VMHostVib.Id + $LocalizedData.VIBVersion = $VMHostVib.Version + $LocalizedData.VIBAcceptanceLevel = $VMHostVib.AcceptanceLevel + $LocalizedData.VIBCreationDate = $VMHostVib.CreationDate + $LocalizedData.VIBInstallDate = $VMHostVib.InstallDate + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVIBs -f $VMHost) + ColumnWidths = 15, 25, 15, 15, 15, 15 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostVibs | Sort-Object $LocalizedData.VIBInstallDate -Descending | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.VIBsError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host Software VIBs + } + #endregion ESXi Host Comprehensive Information Section + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + #endregion ESXi Host System Section + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 new file mode 100644 index 0000000..e80f10f --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 @@ -0,0 +1,92 @@ +function Get-AbrVSphereVUM { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere VMware Update Manager information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereVUM + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VUM) + } + + process { + try { + if (($InfoLevel.VUM -ge 1) -and ($VumServer.Name)) { + Write-PScriboMessage -Message $LocalizedData.Collecting + try { + $VUMBaselines = Get-PatchBaseline -Server $vCenter + } catch { + Write-PScriboMessage -Message $LocalizedData.NotAvailable + } + if ($VUMBaselines) { + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region VUM Baseline Detailed Information + Section -Style Heading3 $LocalizedData.Baselines { + $VUMBaselineInfo = foreach ($VUMBaseline in $VUMBaselines) { + [PSCustomObject]@{ + $LocalizedData.BaselineName = $VUMBaseline.Name + $LocalizedData.Description = $VUMBaseline.Description + $LocalizedData.Type = $VUMBaseline.BaselineType + $LocalizedData.TargetType = $VUMBaseline.TargetType + $LocalizedData.LastUpdate = ($VUMBaseline.LastUpdateTime).ToLocalTime().ToString() + $LocalizedData.NumPatches = $VUMBaseline.CurrentPatches.Count + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMBaselines -f $vCenterServerName) + ColumnWidths = 25, 25, 10, 10, 20, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VUMBaselineInfo | Sort-Object $LocalizedData.BaselineName | Table @TableParams + } + #endregion VUM Baseline Detailed Information + + #region VUM Comprehensive Information + try { + $VUMPatches = Get-Patch -Server $vCenter | Sort-Object -Descending ReleaseDate + } catch { + Write-PScriboMessage -Message $LocalizedData.PatchNotAvailable + } + if ($VUMPatches -and $InfoLevel.VUM -ge 5) { + BlankLine + Section -Style Heading3 $LocalizedData.Patches { + $VUMPatchInfo = foreach ($VUMPatch in $VUMPatches) { + [PSCustomObject]@{ + $LocalizedData.PatchName = $VUMPatch.Name + $LocalizedData.PatchProduct = ($VUMPatch.Product).Name + $LocalizedData.PatchDescription = $VUMPatch.Description + $LocalizedData.PatchReleaseDate = $VUMPatch.ReleaseDate + $LocalizedData.PatchVendorID = $VUMPatch.IdByVendor + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMPatches -f $vCenterServerName) + ColumnWidths = 20, 20, 20, 20, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VUMPatchInfo | Table @TableParams + } + } + #endregion VUM Comprehensive Information + } + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 new file mode 100644 index 0000000..57e1972 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 @@ -0,0 +1,447 @@ +function Get-AbrVSpherevCenter { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vCenter Server information. + .DESCRIPTION + Documents the configuration of VMware vCenter Server in Word/HTML/Text formats using PScribo. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + .EXAMPLE + + .LINK + + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSpherevCenter + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.vCenter) + } + + process { + try { + if ($InfoLevel.vCenter -ge 1) { + Write-PScriboMessage -Message $LocalizedData.Collecting + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + BlankLine + # Gather basic vCenter Server Information + $vCenterServerInfo = [PSCustomObject]@{ + $LocalizedData.vCenterServer = $vCenterServerName + $LocalizedData.IPAddress = ($vCenterAdvSettings | Where-Object { $_.name -like 'VirtualCenter.AutoManagedIPV4' }).Value + $LocalizedData.Version = $vCenter.Version + $LocalizedData.Build = $vCenter.Build + } + #region vCenter Server Summary & Advanced Summary + if ($InfoLevel.vCenter -le 2) { + $TableParams = @{ + Name = ($LocalizedData.TablevCenterSummary -f $vCenterServerName) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterServerInfo | Table @TableParams + } + #endregion vCenter Server Summary & Advanced Summary + + #region vCenter Server Detailed Information + if ($InfoLevel.vCenter -ge 3) { + $MemberProps = @{ + 'InputObject' = $vCenterServerInfo + 'MemberType' = 'NoteProperty' + } + #region vCenter Server Detail + if ($UserPrivileges -contains 'Global.Licenses') { + $vCenterLicense = Get-License -vCenter $vCenter + Add-Member @MemberProps -Name $LocalizedData.Product -Value $vCenterLicense.Product + Add-Member @MemberProps -Name $LocalizedData.LicenseKey -Value $vCenterLicense.LicenseKey + Add-Member @MemberProps -Name $LocalizedData.LicenseExpiration -Value $vCenterLicense.Expiration + } else { + Write-PScriboMessage -Message $LocalizedData.InsufficientPrivLicense + } + + Add-Member @MemberProps -Name $LocalizedData.InstanceID -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'instance.id' }).Value + + if ($vCenter.Version -ge 6) { + Add-Member @MemberProps -Name $LocalizedData.HTTPPort -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.rhttpproxy.httpport' }).Value + Add-Member @MemberProps -Name $LocalizedData.HTTPSPort -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.rhttpproxy.httpsport' }).Value + Add-Member @MemberProps -Name $LocalizedData.PSC -Value ((($vCenterAdvSettings).Where{ $_.name -eq 'config.vpxd.sso.admin.uri' }).Value).Split('/')[2] + } + if ($VumServer.Name) { + Add-Member @MemberProps -Name $LocalizedData.UpdateManagerServer -Value $VumServer.Name + } + if ($SrmServer.Name) { + Add-Member @MemberProps -Name $LocalizedData.SRMServer -Value $SrmServer.Name + } + if ($NsxtServer.Name) { + Add-Member @MemberProps -Name $LocalizedData.NSXTServer -Value $NsxtServer.Name + } + if ($VxRailMgr.Name) { + Add-Member @MemberProps -Name $LocalizedData.VxRailServer -Value $VxRailMgr.Name + } + if ($Healthcheck.vCenter.Licensing) { + $vCenterServerInfo | Where-Object { $_.$($LocalizedData.Product) -like '*Evaluation*' } | Set-Style -Style Warning -Property $LocalizedData.Product + $vCenterServerInfo | Where-Object { $null -eq $_.$($LocalizedData.Product) } | Set-Style -Style Warning -Property $LocalizedData.Product + $vCenterServerInfo | Where-Object { $_.$($LocalizedData.LicenseKey) -like '*-00000-00000' } | Set-Style -Style Warning -Property $LocalizedData.LicenseKey + $vCenterServerInfo | Where-Object { $_.$($LocalizedData.LicenseExpiration) -eq 'Expired' } | Set-Style -Style Critical -Property $LocalizedData.LicenseExpiration + } + $TableParams = @{ + Name = ($LocalizedData.TablevCenterConfig -f $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterServerInfo | Table @TableParams + #endregion vCenter Server Detail + + #region vCenter Server Database Settings + Section -Style Heading3 $LocalizedData.DatabaseSettings { + $vCenterDbInfo = [PSCustomObject]@{ + $LocalizedData.DatabaseType = $TextInfo.ToTitleCase(($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.odbc.dbtype' }).Value) + $LocalizedData.DataSourceName = ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.odbc.dsn' }).Value + $LocalizedData.MaxDBConnection = ($vCenterAdvSettings | Where-Object { $_.name -eq 'VirtualCenter.MaxDBConnection' }).Value + } + $TableParams = @{ + Name = ($LocalizedData.TableDatabaseSettings -f $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterDbInfo | Table @TableParams + } + #endregion vCenter Server Database Settings + + #region vCenter Server Mail Settings + Section -Style Heading3 $LocalizedData.MailSettings { + $vCenterMailInfo = [PSCustomObject]@{ + $LocalizedData.SMTPServer = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.smtp.server' }).Value + $LocalizedData.SMTPPort = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.smtp.port' }).Value + $LocalizedData.MailSender = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.sender' }).Value + } + if ($Healthcheck.vCenter.Mail) { + $vCenterMailInfo | Where-Object { !($_.$($LocalizedData.SMTPServer)) } | Set-Style -Style Critical -Property $LocalizedData.SMTPServer + $vCenterMailInfo | Where-Object { !($_.$($LocalizedData.SMTPPort)) } | Set-Style -Style Critical -Property $LocalizedData.SMTPPort + $vCenterMailInfo | Where-Object { !($_.$($LocalizedData.MailSender)) } | Set-Style -Style Critical -Property $LocalizedData.MailSender + } + $TableParams = @{ + Name = ($LocalizedData.TableMailSettings -f $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterMailInfo | Table @TableParams + } + #endregion vCenter Server Mail Settings + + #region vCenter Server Historical Statistics + Section -Style Heading3 $LocalizedData.HistoricalStatistics { + $vCenterHistoricalStats = Get-vCenterStats | Select-Object @{L = $LocalizedData.IntervalDuration; E = { $_.IntervalDuration } }, + @{L = $LocalizedData.IntervalEnabled; E = { $_.IntervalEnabled } }, + @{L = $LocalizedData.SaveDuration; E = { $_.SaveDuration } }, + @{L = $LocalizedData.StatisticsLevel; E = { $_.StatsLevel } } -Unique + $TableParams = @{ + Name = ($LocalizedData.TableHistoricalStatistics -f $vCenterServerName) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterHistoricalStats | Table @TableParams + } + #endregion vCenter Server Historical Statistics + + #region vCenter Server Licensing + if ($UserPrivileges -contains 'Global.Licenses') { + Section -Style Heading3 $LocalizedData.Licensing { + $Licenses = Get-License -Licenses | Select-Object @{L = $LocalizedData.Product; E = { $_.Product } }, + @{L = $LocalizedData.LicenseKey; E = { ($_.LicenseKey) } }, + @{L = $LocalizedData.Total; E = { $_.Total } }, + @{L = $LocalizedData.Used; E = { $_.Used } }, + @{L = $LocalizedData.Available; E = { ($_.total) - ($_.Used) } }, + @{L = $LocalizedData.Expiration; E = { $_.Expiration } } -Unique + if ($Healthcheck.vCenter.Licensing) { + $Licenses | Where-Object { $_.$($LocalizedData.Product) -eq 'Product Evaluation' } | Set-Style -Style Warning + $Licenses | Where-Object { $_.$($LocalizedData.Expiration) -eq 'Expired' } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableLicensing -f $vCenterServerName) + ColumnWidths = 25, 25, 12, 12, 12, 14 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $Licenses | Sort-Object $LocalizedData.Product, $LocalizedData.LicenseKey | Table @TableParams + } + } else { + Write-PScriboMessage -Message $LocalizedData.InsufficientPrivLicense + } + #endregion vCenter Server Licensing + + #region vCenter Server Certificate + if ($vCenter.Version -ge 6) { + Section -Style Heading3 $LocalizedData.Certificate { + $VcenterCertMgmt = [PSCustomObject]@{ + $LocalizedData.Country = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.country' }).Value + $LocalizedData.Email = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.email' }).Value + $LocalizedData.Locality = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.localityName' }).Value + $LocalizedData.State = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.state' }).Value + $LocalizedData.Organization = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.organizationName' }).Value + $LocalizedData.OrganizationUnit = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.organizationalUnitName' }).Value + $LocalizedData.Validity = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.daysValid'}).Value / 365) years" + $LocalizedData.Mode = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.mode' }).Value + $LocalizedData.SoftThreshold = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.softThreshold'}).Value) days" + $LocalizedData.HardThreshold = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.hardThreshold'}).Value) days" + $LocalizedData.MinutesBefore = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.minutesBefore' }).Value + $LocalizedData.PollInterval = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.pollIntervalDays'}).Value) days" + } + $TableParams = @{ + Name = ($LocalizedData.TableCertificate -f $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VcenterCertMgmt | Table @TableParams + } + } + #endregion vCenter Server Certificate + + #region vCenter Server Roles + Section -Style Heading3 $LocalizedData.Roles { + $VIRoles = Get-VIRole -Server $vCenter | Where-Object { $null -ne $_.PrivilegeList } | Sort-Object Name + $VIRoleInfo = foreach ($VIRole in $VIRoles) { + [PSCustomObject]@{ + $LocalizedData.Role = $VIRole.Name + $LocalizedData.SystemRole = if ($VIRole.IsSystem) { $LocalizedData.Yes } else { $LocalizedData.No } + $LocalizedData.PrivilegeList = ($VIRole.PrivilegeList).Replace(".", " > ") | Select-Object -Unique + } + } + if ($InfoLevel.vCenter -ge 4) { + $VIRoleInfo | ForEach-Object { + Section -Style NOTOCHeading5 -ExcludeFromTOC $_.$($LocalizedData.Role) { + $TableParams = @{ + Name = ($LocalizedData.TableRole -f $_.$($LocalizedData.Role), $vCenterServerName) + ColumnWidths = 35, 15, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $_ | Table @TableParams + } + } + } else { + $TableParams = @{ + Name = ($LocalizedData.TableRoles -f $vCenterServerName) + Columns = $LocalizedData.Role, $LocalizedData.SystemRole + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VIRoleInfo | Table @TableParams + } + } + #endregion vCenter Server Roles + + #region vCenter Server Tags + if ($Tags) { + Section -Style Heading3 $LocalizedData.Tags { + $TagInfo = foreach ($Tag in $Tags) { + [PSCustomObject]@{ + $LocalizedData.TagName = $Tag.Name + $LocalizedData.TagDescription = if ($Tag.Description) { $Tag.Description } else { $LocalizedData.None } + $LocalizedData.TagCategory = if ($Tag.Category) { $Tag.Category } else { $LocalizedData.None } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableTags -f $vCenterServerName) + ColumnWidths = 30, 40, 30 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $TagInfo | Table @TableParams + } + } + #endregion vCenter Server Tags + + #region vCenter Server Tag Categories + if ($TagCategories) { + Section -Style Heading3 $LocalizedData.TagCategories { + $TagCategoryInfo = foreach ($TagCategory in $TagCategories) { + [PSCustomObject]@{ + $LocalizedData.TagCategory = if ($TagCategory.Name) { $TagCategory.Name } else { $LocalizedData.None } + $LocalizedData.TagDescription = if ($TagCategory.Description) { $TagCategory.Description } else { $LocalizedData.None } + $LocalizedData.TagCardinality = if ($TagCategory.Cardinality) { $TagCategory.Cardinality } else { $LocalizedData.None } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableTagCategories -f $vCenterServerName) + ColumnWidths = 30, 40, 30 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $TagCategoryInfo | Table @TableParams + } + } + #endregion vCenter Server Tag Categories + + #region vCenter Server Tag Assignments + if ($TagAssignments) { + Section -Style Heading3 $LocalizedData.TagAssignments { + $TagAssignmentInfo = foreach ($TagAssignment in $TagAssignments) { + [PSCustomObject]@{ + $LocalizedData.TagEntity = $TagAssignment.Entity.Name + $LocalizedData.TagName = $TagAssignment.Tag.Name + $LocalizedData.TagCategory = $TagAssignment.Tag.Category + } + } + $TableParams = @{ + Name = ($LocalizedData.TableTagAssignments -f $vCenterServerName) + ColumnWidths = 30, 40, 30 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $TagAssignmentInfo | Sort-Object $LocalizedData.TagEntity | Table @TableParams + } + } + #endregion vCenter Server Tag Assignments + + #region VM Storage Policies + if ($UserPrivileges -contains 'StorageProfile.View') { + $SpbmStoragePolicies = Get-SpbmStoragePolicy | Sort-Object Name + if ($SpbmStoragePolicies) { + Section -Style Heading3 $LocalizedData.VMStoragePolicies { + $VmStoragePolicies = foreach ($SpbmStoragePolicy in $SpbmStoragePolicies) { + [PSCustomObject]@{ + $LocalizedData.StoragePolicy = $SpbmStoragePolicy.Name + $LocalizedData.Description = $SpbmStoragePolicy.Description + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVMStoragePolicies -f $vCenterServerName) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VmStoragePolicies | Table @TableParams + } + } + } else { + Write-PScriboMessage -Message $LocalizedData.InsufficientPrivStoragePolicy + } + #endregion VM Storage Policies + } + #endregion vCenter Server Detailed Information + + #region vCenter Server Advanced Detail Information + if ($InfoLevel.vCenter -ge 4) { + #region vCenter Alarms + Section -Style Heading3 $LocalizedData.Alarms { + $Alarms = Get-AlarmDefinition -PipelineVariable alarm | ForEach-Object -Process { + Get-AlarmAction -AlarmDefinition $_ -PipelineVariable action | ForEach-Object -Process { + Get-AlarmActionTrigger -AlarmAction $_ | + Select-Object @{N = $LocalizedData.Alarm; E = { $alarm.Name } }, + @{N = $LocalizedData.AlarmDescription; E = { $alarm.Description } }, + @{N = $LocalizedData.AlarmEnabled; E = { if ($alarm.Enabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } }, + @{N = $LocalizedData.TagEntityType; E = { $alarm.Entity.Type } }, + @{N = $LocalizedData.AlarmTriggered; E = { + "{0}:{1}->{2} (Repeat={3})" -f $action.ActionType, + $_.StartStatus, + $_.EndStatus, + $_.Repeat + } + }, + @{N = $LocalizedData.AlarmAction; E = { switch ($action.ActionType) { + 'SendEmail' { + "To: $($action.To -join ', ') ` + Cc: $($action.Cc -join ', ') ` + Subject: $($action.Subject) ` + Body: $($action.Body)" + } + 'ExecuteScript' { + "$($action.ScriptFilePath)" + } + default { '--' } + } + } + } + } + } + $Alarms = ($Alarms).Where{ $_.$($LocalizedData.Alarm) -ne "" } | Sort-Object $LocalizedData.Alarm, $LocalizedData.AlarmTriggered + if ($Healthcheck.vCenter.Alarms) { + $Alarms | Where-Object { $_.$($LocalizedData.AlarmEnabled) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.AlarmEnabled + } + if ($InfoLevel.vCenter -ge 5) { + foreach ($Alarm in $Alarms) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $Alarm.$($LocalizedData.Alarm) { + $TableParams = @{ + Name = ($LocalizedData.TableAlarm -f $Alarm.$($LocalizedData.Alarm), $vCenterServerName) + List = $true + ColumnWidths = 25, 75 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $Alarm | Table @TableParams + } + } + } else { + $TableParams = @{ + Name = ($LocalizedData.TableAlarms -f $vCenterServerName) + Columns = $LocalizedData.Alarm, $LocalizedData.AlarmDescription, $LocalizedData.AlarmEnabled, $LocalizedData.TagEntityType, $LocalizedData.AlarmTriggered + ColumnWidths = 20, 20, 20, 20, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $Alarms | Table @TableParams + } + } + #endregion vCenter Alarms + } + #endregion vCenter Server Advanced Detail Information + + #region vCenter Server Comprehensive Information + if ($InfoLevel.vCenter -ge 5) { + #region vCenter Advanced System Settings + Section -Style Heading3 $LocalizedData.AdvancedSystemSettings { + $TableParams = @{ + Name = ($LocalizedData.TableAdvancedSystemSettings -f $vCenterServerName) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterAdvSettings | + Select-Object @{L = $LocalizedData.Key; E = { $_.Name } }, + @{L = $LocalizedData.Value; E = { $_.Value } } | + Sort-Object $LocalizedData.Key | Table @TableParams + } + #endregion vCenter Advanced System Settings + } + #endregion vCenter Server Comprehensive Information + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 new file mode 100644 index 0000000..8e7c73d --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 @@ -0,0 +1,538 @@ +function Get-AbrVSpherevSAN { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere vSAN information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSpherevSAN + Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.vSAN) + } + + process { + try { + if (($InfoLevel.vSAN -ge 1) -and ($vCenter.Version -gt 6)) { + Write-PScriboMessage -Message $LocalizedData.Collecting + $VsanClusters = Get-VsanClusterConfiguration -Server $vCenter | Where-Object { $_.vsanenabled -eq $true } | Sort-Object Name + if ($VsanClusters) { + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + #region vSAN Cluster Advanced Summary + if ($InfoLevel.vSAN -le 2) { + BlankLine + $VsanClusterInfo = foreach ($VsanCluster in $VsanClusters) { + [PSCustomObject]@{ + $LocalizedData.Cluster = $VsanCluster.Name + $LocalizedData.StorageType = if ($VsanCluster.VsanEsaEnabled) { + 'ESA' + } else { + 'OSA' + } + $LocalizedData.NumHosts = $VsanCluster.Cluster.ExtensionData.Host.Count + $LocalizedData.Stretched = if ($VsanCluster.StretchedClusterEnabled) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.Deduplication = if ($VsanCluster.SpaceEfficiencyEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.Encryption = if ($VsanCluster.EncryptionEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSANClusterSummary -f $vCenterServerName) + ColumnWidths = 25, 15, 15, 15, 15, 15 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanClusterInfo | Table @TableParams + } + #endregion vSAN Cluster Advanced Summary + + #region vSAN Cluster Detailed Information + if ($InfoLevel.vSAN -ge 3) { + foreach ($VsanCluster in $VsanClusters) { + $VsanSpaceUsage = Get-VsanSpaceUsage -Cluster $VsanCluster.Name + $VsanUsedCapacity = $VsanSpaceUsage.CapacityGB - $VsanSpaceUsage.FreeSpaceGB + + # Calculate percentages + $VsanUsedPercent = if (0 -in @($VsanUsedCapacity, $VsanSpaceUsage.CapacityGB)) { 0 } else { [math]::Round(($VsanUsedCapacity / $VsanSpaceUsage.CapacityGB) * 100, 2) } + $VsanFreePercent = if (0 -in @($VsanUsedCapacity, $VsanSpaceUsage.CapacityGB)) { 0 } else { [math]::Round(($VsanSpaceUsage.FreeSpaceGB / $VsanSpaceUsage.CapacityGB) * 100, 2) } + + #region vSAN Cluster Section + Section -Style Heading3 $VsanCluster.Name { + if ($VsanCluster.VsanEsaEnabled) { + Write-PScriboMessage -Message ($LocalizedData.CollectingESA -f $VsanCluster.Name) + try { + $VsanStoragePoolDisk = Get-VsanStoragePoolDisk -Cluster $VsanCluster.Cluster + $VsanDiskFormat = $VsanStoragePoolDisk.DiskFormatVersion | Select-Object -First 1 -Unique + $VsanClusterDetail = [PSCustomObject]@{ + $LocalizedData.Cluster = $VsanCluster.Name + $LocalizedData.ID = $VsanCluster.Id + $LocalizedData.StorageType = if ($VsanCluster.VsanEsaEnabled) { + 'ESA' + } else { + 'OSA' + } + $LocalizedData.Stretched = if ($VsanCluster.StretchedClusterEnabled) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.NumberOfHosts = $VsanCluster.Cluster.ExtensionData.Host.Count + $LocalizedData.NumberOfDisks = $VsanStoragePoolDisk.Count + $LocalizedData.DiskClaimMode = $VsanCluster.VsanDiskClaimMode + $LocalizedData.DisksFormat = $VsanDiskFormat + $LocalizedData.PerformanceService = if ($VsanCluster.PerformanceServiceEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.FileService = if ($VsanCluster.FileServiceEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.iSCSITargetService = if ($VsanCluster.IscsiTargetServiceEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.Deduplication = if ($VsanCluster.SpaceEfficiencyEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.Encryption = if ($VsanCluster.EncryptionEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.HistoricalHealthService = if ($VsanCluster.HistoricalHealthEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.HealthCheck = if ($VsanCluster.HealthCheckEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.TotalCapacity = Convert-DataSize $VsanSpaceUsage.CapacityGB + $LocalizedData.UsedCapacity = "{0} ({1}%)" -f (Convert-DataSize $VsanUsedCapacity), $VsanUsedPercent + $LocalizedData.FreeCapacity = "{0} ({1}%)" -f (Convert-DataSize $VsanSpaceUsage.FreeSpaceGB), $VsanFreePercent + $LocalizedData.PercentUsed = $VsanUsedPercent + $LocalizedData.HCLLastUpdated = ($VsanCluster.TimeOfHclUpdate).ToLocalTime().ToString() + } + if ($Healthcheck.vSAN.CapacityUtilization) { + $VsanClusterDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 90 } | Set-Style -Style Critical -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + $VsanClusterDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 75 -and + $_.$($LocalizedData.PercentUsed) -lt 90 } | Set-Style -Style Warning -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + } + if ($InfoLevel.vSAN -ge 4) { + $VsanClusterDetail | Add-Member -MemberType NoteProperty -Name $LocalizedData.Hosts -Value (($VsanStoragePoolDisk.Host | Select-Object -Unique | Sort-Object Name) -join ', ') + } + $TableParams = @{ + Name = ($LocalizedData.TableVSANConfiguration -f $VsanCluster.Name) + List = $true + Columns = $LocalizedData.Cluster, $LocalizedData.ID, $LocalizedData.StorageType, $LocalizedData.Stretched, $LocalizedData.NumberOfHosts, $LocalizedData.NumberOfDisks, $LocalizedData.DiskClaimMode, $LocalizedData.DisksFormat, $LocalizedData.PerformanceService, $LocalizedData.FileService, $LocalizedData.iSCSITargetService, $LocalizedData.Deduplication, $LocalizedData.Encryption, $LocalizedData.HistoricalHealthService, $LocalizedData.HealthCheck, $LocalizedData.TotalCapacity, $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity, $LocalizedData.HCLLastUpdated + ColumnWidths = 40, 60 + } + if ($InfoLevel.vSAN -ge 4) { + $TableParams['Columns'] += $LocalizedData.Hosts + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanClusterDetail | Table @TableParams + } catch { + Write-PScriboMessage -Message ($LocalizedData.ESAError -f $VsanCluster.Name, $_.Exception.Message) + } + + if ($VsanStoragePoolDisk) { + Write-PScriboMessage -Message ($LocalizedData.CollectingDisks -f $VsanCluster.Name) + try { + Section -Style Heading4 $LocalizedData.DisksSection { + $vDisks = foreach ($Disk in $VsanStoragePoolDisk) { + [PSCustomObject]@{ + $LocalizedData.DiskName = $Disk.Name + $LocalizedData.Name = $Disk.ExtensionData.DisplayName + $LocalizedData.DriveType = if ($Disk.IsSsd) { + $LocalizedData.Flash + } else { + $LocalizedData.HDD + } + $LocalizedData.Host = $Disk.Host.Name + $LocalizedData.State = if ($Disk.IsMounted) { + $LocalizedData.Mounted + } else { + $LocalizedData.Unmounted + } + $LocalizedData.Encrypted = if ($Disk.IsEncryped) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.Capacity = Convert-DataSize $Disk.CapacityGB + $LocalizedData.SerialNumber = $Disk.ExtensionData.SerialNumber + $LocalizedData.Vendor = $Disk.ExtensionData.Vendor + $LocalizedData.Model = $Disk.ExtensionData.Model + $LocalizedData.DiskType = $Disk.DiskType + $LocalizedData.DiskFormatVersion = $Disk.DiskFormatVersion + } + } + + if ($InfoLevel.vSAN -ge 4) { + $vDisks | Sort-Object $LocalizedData.Host | ForEach-Object { + $vDisk = $_ + Section -Style NOTOCHeading5 -ExcludeFromTOC "$($vDisk.$($LocalizedData.Name)) - $($vDisk.$($LocalizedData.Host))" { + $TableParams = @{ + Name = ($LocalizedData.TableDisk -f $vDisk.$($LocalizedData.Name), $vDisk.$($LocalizedData.Host)) + List = $true + Columns = $LocalizedData.Name, $LocalizedData.State, $LocalizedData.DriveType, $LocalizedData.Encrypted, $LocalizedData.Capacity, $LocalizedData.Host, $LocalizedData.SerialNumber, $LocalizedData.Vendor, $LocalizedData.Model, $LocalizedData.DiskFormatVersion, $LocalizedData.DiskType + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vDisk | Table @TableParams + } + } + } else { + $TableParams = @{ + Name = ($LocalizedData.TableVSANDisks -f $VsanCluster.Name) + Columns = $LocalizedData.DiskName, $LocalizedData.Capacity, $LocalizedData.State, $LocalizedData.Host + ColumnWidths = 40, 15, 15, 30 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vDisks | Sort-Object $LocalizedData.Host | Table @TableParams + } + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.DiskError -f $VsanCluster.Name, $_.Exception.Message) + } + } + } else { + try { + Write-PScriboMessage -Message ($LocalizedData.CollectingOSA -f $VsanCluster.Name) + # Get vSAN Disk Groups + $VsanDiskGroup = Get-VsanDiskGroup -Cluster $VsanCluster.Cluster + $NumVsanDiskGroup = $VsanDiskGroup.Count + # Get vSAN Disks + $VsanDisk = Get-VsanDisk -VsanDiskGroup $VsanDiskGroup + $VsanDiskFormat = $VsanDisk.DiskFormatVersion | Select-Object -Unique + # Count SSDs and HDDs + $NumVsanSsd = ($VsanDisk | Where-Object { $_.IsSsd -eq $true } | Measure-Object).Count + $NumVsanHdd = ($VsanDisk | Where-Object { $_.IsSsd -eq $false } | Measure-Object).Count + # Determine Storage Type + $VsanClusterType = if ($NumVsanHdd -gt 0) { $LocalizedData.HybridMode } else { $LocalizedData.AllFlash } + $VsanClusterDetail = [PSCustomObject]@{ + $LocalizedData.Cluster = $VsanCluster.Name + $LocalizedData.ID = $VsanCluster.Id + $LocalizedData.StorageType = if ($VsanCluster.VsanEsaEnabled) { + 'ESA' + } else { + 'OSA' + } + $LocalizedData.ClusterType = $VsanClusterType + $LocalizedData.Stretched = if ($VsanCluster.StretchedClusterEnabled) { + $LocalizedData.Yes + } else { + $LocalizedData.No + } + $LocalizedData.NumberOfHosts = $VsanCluster.Cluster.ExtensionData.Host.Count + $LocalizedData.NumberOfDisks = $NumVsanSsd + $NumVsanHdd + $LocalizedData.NumberOfDiskGroups = $NumVsanDiskGroup + $LocalizedData.DiskClaimMode = $VsanCluster.VsanDiskClaimMode + $LocalizedData.DisksFormat = $VsanDiskFormat + $LocalizedData.PerformanceService = if ($VsanCluster.PerformanceServiceEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.FileService = if ($VsanCluster.FileServiceEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.iSCSITargetService = if ($VsanCluster.IscsiTargetServiceEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.Deduplication = if ($VsanCluster.SpaceEfficiencyEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.Encryption = if ($VsanCluster.EncryptionEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.HistoricalHealthService = if ($VsanCluster.HistoricalHealthEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.HealthCheck = if ($VsanCluster.HealthCheckEnabled) { + $LocalizedData.Enabled + } else { + $LocalizedData.Disabled + } + $LocalizedData.TotalCapacity = Convert-DataSize $VsanSpaceUsage.CapacityGB + $LocalizedData.UsedCapacity = "{0} ({1}%)" -f (Convert-DataSize $VsanUsedCapacity), $VsanUsedPercent + $LocalizedData.FreeCapacity = "{0} ({1}%)" -f (Convert-DataSize $VsanSpaceUsage.FreeSpaceGB), $VsanFreePercent + $LocalizedData.PercentUsed = $VsanUsedPercent + $LocalizedData.HCLLastUpdated = ($VsanCluster.TimeOfHclUpdate).ToLocalTime().ToString() + } + if ($Healthcheck.vSAN.CapacityUtilization) { + $VsanClusterDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 90 } | Set-Style -Style Critical -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + $VsanClusterDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 75 -and + $_.$($LocalizedData.PercentUsed) -lt 90 } | Set-Style -Style Warning -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity + } + if ($InfoLevel.vSAN -ge 4) { + $VsanClusterDetail | Add-Member -MemberType NoteProperty -Name $LocalizedData.Hosts -Value (($VsanDiskGroup.VMHost | Select-Object -Unique | Sort-Object Name) -join ', ') + } + $TableParams = @{ + Name = ($LocalizedData.TableVSANConfiguration -f $VsanCluster.Name) + List = $true + Columns = $LocalizedData.Cluster, $LocalizedData.ID, $LocalizedData.StorageType, $LocalizedData.ClusterType, $LocalizedData.Stretched, $LocalizedData.NumberOfHosts, $LocalizedData.NumberOfDisks, $LocalizedData.NumberOfDiskGroups, $LocalizedData.DiskClaimMode, $LocalizedData.DisksFormat, $LocalizedData.PerformanceService, $LocalizedData.FileService, $LocalizedData.iSCSITargetService, $LocalizedData.Deduplication, $LocalizedData.Encryption, $LocalizedData.HistoricalHealthService, $LocalizedData.HealthCheck, $LocalizedData.TotalCapacity, $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity, $LocalizedData.HCLLastUpdated + ColumnWidths = 40, 60 + } + if ($InfoLevel.vSAN -ge 4) { + $TableParams['Columns'] += $LocalizedData.Hosts + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanClusterDetail | Table @TableParams + } catch { + Write-PScriboMessage -Message ($LocalizedData.OSAError -f $VsanCluster.Name, $_.Exception.Message) + } + + # TODO: vSAN Services + if ($VsanDiskGroup) { + Write-PScriboMessage -Message ($LocalizedData.CollectingDiskGroups -f $VsanCluster.Name) + try { + Section -Style Heading4 $LocalizedData.DiskGroupsSection { + $VsanDiskGroups = foreach ($DiskGroup in $VsanDiskGroup) { + $Disks = $DiskGroup | Get-VsanDisk + [PSCustomObject]@{ + $LocalizedData.DiskGroup = $DiskGroup.Uuid + $LocalizedData.Host = $Diskgroup.VMHost + $LocalizedData.NumDisks = $Disks.Count + $LocalizedData.State = if ($DiskGroup.IsMounted) { + $LocalizedData.Mounted + } else { + $LocalizedData.Unmounted + } + $LocalizedData.Type = switch ($DiskGroup.DiskGroupType) { + 'AllFlash' { $LocalizedData.AllFlash } + default { $DiskGroup.DiskGroupType } + } + $LocalizedData.DisksFormat = $DiskGroup.DiskFormatVersion + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSANDiskGroups -f $VsanCluster.Name) + ColumnWidths = 30, 30, 7, 11, 11, 11 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanDiskGroups | Sort-Object $LocalizedData.Host | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.DiskGroupError -f $VsanCluster.Name, $_.Exception.Message) + } + } + + if ($VsanDisk) { + Write-PScriboMessage -Message ($LocalizedData.CollectingDisks -f $VsanCluster.Name) + try { + Section -Style Heading4 $LocalizedData.DisksSection { + $vDisks = foreach ($Disk in $VsanDisk) { + [PSCustomObject]@{ + $LocalizedData.DiskName = $Disk.Name + $LocalizedData.Name = $Disk.ExtensionData.DisplayName + $LocalizedData.State = if ($Disk.IsMounted) { + $LocalizedData.Mounted + } else { + $LocalizedData.Unmounted + } + $LocalizedData.DriveType = if ($Disk.IsSsd) { + $LocalizedData.Flash + } else { + $LocalizedData.HDD + } + $LocalizedData.Host = $Disk.VsanDiskGroup.VMHost.Name + $LocalizedData.ClaimedAs = if ($Disk.IsCacheDisk) { + $LocalizedData.Cache + } else { + $LocalizedData.Capacity + } + $LocalizedData.Capacity = Convert-DataSize $Disk.CapacityGB + $LocalizedData.SerialNumber = $Disk.ExtensionData.SerialNumber + $LocalizedData.Vendor = $Disk.ExtensionData.Vendor + $LocalizedData.Model = $Disk.ExtensionData.Model + $LocalizedData.DiskGroup = $Disk.VsanDiskGroup.Uuid + $LocalizedData.DiskFormatVersion = $Disk.DiskFormatVersion + } + } + + if ($InfoLevel.vSAN -ge 4) { + $vDisks | Sort-Object $LocalizedData.Host | ForEach-Object { + $vDisk = $_ + Section -Style NOTOCHeading5 -ExcludeFromTOC "$($vDisk.$($LocalizedData.Name)) - $($vDisk.$($LocalizedData.Host))" { + $TableParams = @{ + Name = ($LocalizedData.TableDisk -f $vDisk.$($LocalizedData.Name), $vDisk.$($LocalizedData.Host)) + List = $true + Columns = $LocalizedData.Name, $LocalizedData.DriveType, $LocalizedData.ClaimedAs, $LocalizedData.Capacity, $LocalizedData.Host, $LocalizedData.DiskGroup, $LocalizedData.SerialNumber, $LocalizedData.Vendor, $LocalizedData.Model, $LocalizedData.DiskFormatVersion + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vDisk | Table @TableParams + } + } + } else { + $TableParams = @{ + Name = ($LocalizedData.TableVSANDisks -f $VsanCluster.Name) + Columns = $LocalizedData.Name, $LocalizedData.DriveType, $LocalizedData.ClaimedAs, $LocalizedData.Capacity, $LocalizedData.Host, $LocalizedData.DiskGroup + ColumnWidths = 21, 10, 10, 10, 21, 28 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vDisks | Sort-Object $LocalizedData.Host | Table @TableParams + } + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.DiskError -f $VsanCluster.Name, $_.Exception.Message) + } + } + } + + $VsanIscsiTargets = Get-VsanIscsiTarget -Cluster $VsanCluster.Cluster -ErrorAction SilentlyContinue + if ($VsanIscsiTargets) { + Write-PScriboMessage -Message ($LocalizedData.CollectingiSCSITargets -f $VsanCluster.Name) + try { + Section -Style Heading4 $LocalizedData.iSCSITargetsSection { + $VsanIscsiTargetInfo = foreach ($VsanIscsiTarget in $VsanIscsiTargets) { + [PSCustomObject]@{ + $LocalizedData.IQN = $VsanIscsiTarget.IscsiQualifiedName + $LocalizedData.Alias = $VsanIscsiTarget.Name + $LocalizedData.LUNsCount = $VsanIscsiTarget.NumLuns + $LocalizedData.NetworkInterface = $VsanIscsiTarget.NetworkInterface + $LocalizedData.IOOwnerHost = $VsanIscsiTarget.IoOwnerVMHost + $LocalizedData.TCPPort = $VsanIscsiTarget.TcpPort + $LocalizedData.Health = $TextInfo.ToTitleCase($VsanIscsiTarget.VsanHealth) + $LocalizedData.StoragePolicy = if ($VsanIscsiTarget.StoragePolicy.Name) { + $VsanIscsiTarget.StoragePolicy.Name + } else { + '--' + } + $LocalizedData.ComplianceStatus = $TextInfo.ToTitleCase($VsanIscsiTarget.SpbmComplianceStatus) + $LocalizedData.Authentication = $VsanIscsiTarget.AuthenticationType + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVSANiSCSITargets -f $VsanCluster.Name) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanIscsiTargetInfo | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.iSCSITargetError -f $VsanCluster.Name, $_.Exception.Message) + } + } + + $VsanIscsiLuns = Get-VsanIscsiLun -Cluster $VsanCluster.Cluster -ErrorAction SilentlyContinue | Sort-Object Name, LunId + if ($VsanIscsiLuns) { + Write-PScriboMessage -Message ($LocalizedData.CollectingiSCSILUNs -f $VsanCluster.Name) + try { + Section -Style Heading4 $LocalizedData.iSCSILUNsSection { + $VsanIscsiLunInfo = foreach ($VsanIscsiLun in $VsanIscsiLuns) { + [PSCustomObject]@{ + $LocalizedData.LUNName = $VsanIscsiLun.Name + $LocalizedData.LUNID = $VsanIscsiLun.LunId + $LocalizedData.Capacity = Convert-DataSize $VsanIscsiLun.CapacityGB + $LocalizedData.UsedCapacity = Convert-DataSize $VsanIscsiLun.UsedCapacityGB + $LocalizedData.State = if ($VsanIscsiLun.IsOnline) { + $LocalizedData.Online + } else { + $LocalizedData.Offline + } + $LocalizedData.Health = $TextInfo.ToTitleCase($VsanIscsiLun.VsanHealth) + $LocalizedData.StoragePolicy = if ($VsanIscsiLun.StoragePolicy.Name) { + $VsanIscsiLun.StoragePolicy.Name + } else { + '--' + } + $LocalizedData.ComplianceStatus = $TextInfo.ToTitleCase($VsanIscsiLun.SpbmComplianceStatus) + } + } + if ($InfoLevel.vSAN -ge 4) { + $TableParams = @{ + Name = ($LocalizedData.TableVSANiSCSILUNs -f $VsanCluster.Name) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanIscsiLunInfo | Table @TableParams + } else { + $TableParams = @{ + Name = ($LocalizedData.TableVSANiSCSILUNs -f $VsanCluster.Name) + ColumnWidths = 28, 18, 18, 18, 18 + Columns = $LocalizedData.LUNName, $LocalizedData.LUNID, $LocalizedData.Capacity, $LocalizedData.UsedCapacity, $LocalizedData.State + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanIscsiLunInfo | Table @TableParams + } + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.iSCSILUNError -f $VsanCluster.Name, $_.Exception.Message) + } + } + } + #endregion vSAN Cluster Section + } + } + #endregion vSAN Cluster Detailed Information + } + } + } + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/Src/Private/Get-ESXiBootDevice.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-ESXiBootDevice.ps1 similarity index 100% rename from Src/Private/Get-ESXiBootDevice.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Get-ESXiBootDevice.ps1 diff --git a/Src/Private/Get-InstallDate.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-InstallDate.ps1 similarity index 100% rename from Src/Private/Get-InstallDate.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Get-InstallDate.ps1 diff --git a/Src/Private/Get-License.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-License.ps1 similarity index 100% rename from Src/Private/Get-License.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Get-License.ps1 diff --git a/Src/Private/Get-PciDeviceDetail.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-PciDeviceDetail.ps1 similarity index 100% rename from Src/Private/Get-PciDeviceDetail.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Get-PciDeviceDetail.ps1 diff --git a/Src/Private/Get-ScsiDeviceDetail.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-ScsiDeviceDetail.ps1 similarity index 100% rename from Src/Private/Get-ScsiDeviceDetail.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Get-ScsiDeviceDetail.ps1 diff --git a/Src/Private/Get-Uptime.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-Uptime.ps1 similarity index 100% rename from Src/Private/Get-Uptime.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Get-Uptime.ps1 diff --git a/Src/Private/Get-VMHostNetworkAdapterDP.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-VMHostNetworkAdapterDP.ps1 similarity index 100% rename from Src/Private/Get-VMHostNetworkAdapterDP.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Get-VMHostNetworkAdapterDP.ps1 diff --git a/Src/Private/Get-vCenterStats.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-vCenterStats.ps1 similarity index 100% rename from Src/Private/Get-vCenterStats.ps1 rename to AsBuiltReport.VMware.vSphere/Src/Private/Get-vCenterStats.ps1 diff --git a/AsBuiltReport.VMware.vSphere/Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 b/AsBuiltReport.VMware.vSphere/Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 new file mode 100644 index 0000000..f31ef34 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 @@ -0,0 +1,190 @@ +function Invoke-AsBuiltReport.VMware.vSphere { + <# + .SYNOPSIS + PowerShell script to document the configuration of VMware vSphere infrastructure in Word/HTML/Text formats + .DESCRIPTION + Documents the configuration of VMware vSphere infrastructure in Word/HTML/Text formats using PScribo. + .NOTES + Version: 2.0.0 + Date: 4th March 2026 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman + Credits: Iain Brighton (@iainbrighton) - PScribo module + .LINK + https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere + #> + + param ( + [String[]] $Target, + [PSCredential] $Credential + ) + + # Check for required modules + Get-RequiredModule -Name 'VCF.PowerCLI' -Version '9.0' + + # Display report module information using Core function + Write-ReportModuleInfo -ModuleName 'VMware.vSphere' + + # Import Report Configuration + $Report = $ReportConfig.Report + $InfoLevel = $ReportConfig.InfoLevel + $Options = $ReportConfig.Options + $LocalizedData = $reportTranslate.InvokeAsBuiltReportVMwarevSphere + + # Used to set values to TitleCase where required + $TextInfo = (Get-Culture).TextInfo + + #region Script Body + # Connect to vCenter Server using supplied credentials + foreach ($VIServer in $Target) { + try { + Write-PScriboMessage -Message ($LocalizedData.Connecting -f $VIServer) + $vCenter = Connect-VIServer $VIServer -Credential $Credential -ErrorAction Stop + } catch { + Write-Error $_ + } + + #region Generate vSphere report + if ($vCenter) { + # Check logged in user has sufficient privileges to generate an As Built Report + Write-PScriboMessage -Message $LocalizedData.CheckPrivileges + Try { + $AuthMgr = Get-View $($vCenter.ExtensionData.Content.AuthorizationManager) + $UserPrivileges = ($AuthMgr.FetchUserPrivilegeOnEntities("Folder-group-d1", $vCenter.User)).privileges + } Catch { + Write-PScriboMessage -Message $LocalizedData.UnablePrivileges + } + + # Create a lookup hashtable to quickly link VM MoRefs to Names + # Exclude VMware Site Recovery Manager placeholder VMs + Write-PScriboMessage -Message $LocalizedData.VMHashtable + $VMs = Get-VM -Server $vCenter | Where-Object { + $_.ExtensionData.Config.ManagedBy.ExtensionKey -notlike 'com.vmware.vcDr*' + } | Sort-Object Name + $VMLookup = @{ } + foreach ($VM in $VMs) { + $VMLookup.($VM.Id) = $VM.Name + } + + # Create a lookup hashtable to link Host MoRefs to Names + # Exclude VMware HCX hosts and ESX/ESXi versions prior to vSphere 5.0 from VMHost lookup + Write-PScriboMessage -Message $LocalizedData.VMHostHashtable + $VMHosts = Get-VMHost -Server $vCenter | Where-Object { $_.Model -notlike "*VMware Mobility Platform" -and $_.Version -gt 5 } | Sort-Object Name + $VMHostLookup = @{ } + foreach ($VMHost in $VMHosts) { + $VMHostLookup.($VMHost.Id) = $VMHost.Name + } + + # Create a lookup hashtable to link Datastore MoRefs to Names + Write-PScriboMessage -Message $LocalizedData.DatastoreHashtable + $Datastores = Get-Datastore -Server $vCenter | Where-Object { ($_.State -eq 'Available') -and ($_.CapacityGB -gt 0) } | Sort-Object Name + $DatastoreLookup = @{ } + foreach ($Datastore in $Datastores) { + $DatastoreLookup.($Datastore.Id) = $Datastore.Name + } + + # Create a lookup hashtable to link VDS Portgroups MoRefs to Names + Write-PScriboMessage -Message $LocalizedData.VDPortGrpHashtable + $VDPortGroups = Get-VDPortgroup -Server $vCenter | Sort-Object Name + $VDPortGroupLookup = @{ } + foreach ($VDPortGroup in $VDPortGroups) { + $VDPortGroupLookup.($VDPortGroup.Key) = $VDPortGroup.Name + } + + # Create a lookup hashtable to link EVC Modes to Names + Write-PScriboMessage -Message $LocalizedData.EVCHashtable + $SupportedEvcModes = $vCenter.ExtensionData.Capability.SupportedEVCMode + $EvcModeLookup = @{ } + foreach ($EvcMode in $SupportedEvcModes) { + $EvcModeLookup.($EvcMode.Key) = $EvcMode.Label + } + + $si = Get-View ServiceInstance -Server $vCenter + $extMgr = Get-View -Id $si.Content.ExtensionManager -Server $vCenter + + #region VMware Update Manager Server Name + Write-PScriboMessage -Message $LocalizedData.CheckVUM + $VumServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vcIntegrity' } | + Select-Object @{ + N = 'Name'; + E = { ($_.Server | Where-Object { $_.Type -eq 'SOAP' -and $_.Company -eq 'VMware, Inc.' } | + Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } + } + #endregion VMware Update Manager Server Name + + #region VxRail Manager Server Name + Write-PScriboMessage -Message $LocalizedData.CheckVxRail + $VxRailMgr = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vxrail' } | + Select-Object @{ + N = 'Name'; + E = { ($_.Server | Where-Object { $_.Type -eq 'HTTPS' } | + Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } + } + #endregion VxRail Manager Server Name + + #region Site Recovery Manager Server Name + Write-PScriboMessage -Message $LocalizedData.CheckSRM + $SrmServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vcDr' } | + Select-Object @{ + N = 'Name'; + E = { ($_.Server | Where-Object { $_.Company -eq 'VMware, Inc.' } | + Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } + } + #endregion Site Recovery Manager Server Name + + #region NSX-T Manager Server Name + Write-PScriboMessage -Message $LocalizedData.CheckNSXT + $NsxtServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.nsx.management.nsxt' } | + Select-Object @{ + N = 'Name'; + E = { ($_.Server | Where-Object { ($_.Company -eq 'VMware') -and ($_.Type -eq 'VIP') } | + Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } + } + #endregion NSX-T Manager Server Name + + #region Tag Information + Try { + Write-PScriboMessage -Message $LocalizedData.CollectingTags + $TagAssignments = Get-TagAssignment -Server $vCenter -ErrorAction SilentlyContinue + $Tags = Get-Tag -Server $vCenter | Sort-Object Name, Category + $TagCategories = Get-TagCategory -Server $vCenter | Sort-Object Name | Select-Object Name, Description, Cardinality -Unique + } Catch { + Write-PScriboMessage -Message "$($LocalizedData.TagError). $($_.Exception.Message)" + } + #endregion Tag Information + + #region vCenter Advanced Settings + Write-PScriboMessage -Message ($LocalizedData.CollectingAdvSettings -f $vCenter) + $vCenterAdvSettings = Get-AdvancedSetting -Entity $vCenter + $vCenterServerName = ($vCenterAdvSettings | Where-Object { $_.name -eq 'VirtualCenter.FQDN' }).Value + $vCenterServerName = $vCenterServerName.ToString().ToLower() + #endregion vCenter Advanced Settings + + #region vCenter Server Heading1 Section + Section -Style Heading1 $vCenterServerName { + Get-AbrVSpherevCenter + Get-AbrVSphereCluster + Get-AbrVSphereResourcePool + Get-AbrVSphereVMHost + Get-AbrVSphereNetwork + Get-AbrVSpherevSAN + Get-AbrVSphereDatastore + Get-AbrVSphereDSCluster + Get-AbrVSphereVM + Get-AbrVSphereVUM + } + #endregion vCenter Server Heading1 Section + + # Disconnect vCenter Server + $Null = Disconnect-VIServer -Server $VIServer -Confirm:$false -ErrorAction SilentlyContinue + } # End of If $vCenter + #endregion Generate vSphere report + + #region Variable cleanup + Clear-Variable -Name vCenter + #endregion Variable cleanup + + } # End of Foreach $VIServer + #endregion Script Body +} diff --git a/CHANGELOG.md b/CHANGELOG.md index b7dda7f..275d485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,94 +5,136 @@ 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). -## 1.3.6 - 2025-08-24 +## [Unreleased] + +## [[2.0.0](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v2.0.0)] - 2026-03-05 + +### Added + +- Modular architecture: each report section is now a dedicated private function (`Get-AbrVSphere*`) +- Internationalization (i18n) support via `Language/` `.psd1` files (`en-US`, `en-GB`, `es-ES`, `fr-FR`, `de-DE`) +- Pester test suite (`AsBuiltReport.VMware.vSphere.Tests.ps1`, `LocalizationData.Tests.ps1`, `Invoke-Tests.ps1`) +- GitHub Actions Pester workflow (`.github/workflows/Pester.yml`) + +### Changed + +- Complete module rewrite for improved maintainability and extensibility +- Module source now uses nested folder structure (`AsBuiltReport.VMware.vSphere/`) +- Requires `AsBuiltReport.Core` >= 1.6.2 +- `CompatiblePSEditions` now explicitly declares `Desktop` and `Core` support + +## [[1.3.6.1](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.6.1)] - 2025-08-24 ### Fixed -- Fix divide by zero error (@rebelinux) ([Fix #129](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/129)) + +- Fix issue with gathering ESXi Host Image Profile information + +## [[1.3.6](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.6)] - 2025-08-24 + +### Fixed + +- Fix divide by zero error (@rebelinux) ([#129](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/129)) - Fix PowerCLI module dependency ([#134](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/134)) - Update colour placeholders in `README.md` ### Changed + - Improve vSAN Capacity reporting and healthchecks -- Add `VCF.PowerCLI` to `ExternalModuleDependencies` in the module manifest. +- Add `VCF.PowerCLI` to `ExternalModuleDependencies` in the module manifest ### Removed -- Remove `Get-RequiredModule` function to check for PowerCLI versions. + +- Remove `Get-RequiredModule` function to check for PowerCLI versions - Remove VMware document style script -## 1.3.5 - 2025-02-27 -### Fixed -- Fix issue with license reporting ([Fix #128](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/128)) -- Fix issue with vCenter user privileges not handling groups (@nathcoad) ([Fix #102](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/102)) -- Fix time & date outputs showing incorrect date format -- Fix VMware Update Manager reporting with PowerShell 7 +## [[1.3.5](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.5)] - 2025-02-27 ### Added + - Add Free resource capacity reporting to VMhost hardware section ### Changed + - Update VMware PowerCLI requirements to version 13.3 - Improve error reporting for vSAN section -- Improve data size reporting. Data sizes are now displayed in more appropriately sized data units. +- Improve data size reporting. Data sizes are now displayed in more appropriately sized data units - Change list tables to 40/60 column widths - Change datastore capacity reporting to include percentage used & free values - Update GitHub release workflow to add post to Bluesky social platform -## 1.3.4.1 - 2024-03-28 - ### Fixed -- Add VSAN ESA support ([Fix #113](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/113)) + +- Fix issue with license reporting ([#128](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/128)) +- Fix issue with vCenter user privileges not handling groups (@nathcoad) ([#102](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/102)) +- Fix time & date outputs showing incorrect date format +- Fix VMware Update Manager reporting with PowerShell 7 ## [[1.3.4](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.4)] - 2024-02-28 +### Added + +- Add vSAN ESA support ([#113](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/113)) + ### Changed -- Update VMware PowerCLI requirements to version 13.2 ([Fix #107](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/107)) + +- Update VMware PowerCLI requirements to version 13.2 ([#107](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/107)) - Improve bug and feature request templates (@rebelinux) - Improve TOC structure - Update VMware style script for improved TOC structure - Update GitHub action workflows ### Fixed -- Update VMHost PCI Devices reporting to fix issues with ESXi 8.x hosts (@orb71) ([Fix #105](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/105)) & ([Fix #111](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/111)) -- Add Try/Catch stated PCI Drivers and Firmware section to provide a workaround for ESXi 8.x hosts ([Fix #116](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/116)) -- Update vCenter Server alarms reporting ([Fix #106](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/106)) -- Fix issue with Platform Services Controller reporting ([Fix #103](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/103)) -- Fix NSX-T virtual switch network labels ([Fix #118](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/118)) + +- Update VMHost PCI Devices reporting to fix issues with ESXi 8.x hosts (@orb71) ([#105](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/105)) & ([#111](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/111)) +- Add Try/Catch to PCI Drivers and Firmware section to provide a workaround for ESXi 8.x hosts ([#116](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/116)) +- Update vCenter Server alarms reporting ([#106](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/106)) +- Fix issue with Platform Services Controller reporting ([#103](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/103)) +- Fix NSX-T virtual switch network labels ([#118](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/118)) ### Removed -- Removed reporting of vCenter Server OS type + +- Remove reporting of vCenter Server OS type ## [[1.3.3.1](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.3.1)] - 2022-04-21 ### Added + - Add VMHost IPMI / BMC configuration information ### Fixed + - Fix GitHub Action release workflow ## [[1.3.2](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.2)] - 2022-03-24 ### Added + - Automated tweet release workflow ### Fixed + - Fix colour placeholders in `README.md` ## [[1.3.1](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.1)] - 2021-09-03 ### Added + - VMHost network adapter LLDP reporting ## [[1.3.0](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.0)] - 2021-08-29 + ### Added + - PowerShell 7 compatibility - PSScriptAnalyzer & PublishPSModule GitHub Action workflows - Advanced detailed reporting for VI roles - Advanced detailed reporting for vSAN disks -- Support for VMware Cloud environments (VCF, VMC, AVS, GVE) ([Fix #87](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/87)) -- NSX TCP/IP stacks for VMkernel Adpater reporting +- Support for VMware Cloud environments (VCF, VMC, AVS, GVE) ([#87](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/87)) +- NSX TCP/IP stacks for VMkernel Adapter reporting - Include release and issue links in `CHANGELOG.md` + ### Fixed + - Incorrect section reporting with certain InfoLevels - Datastore table now sorts by Datastore Name - vSAN advanced detailed reporting @@ -100,156 +142,196 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Display issues with highlights in `README.md` ## [[1.2.1](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.2.1)] - 2020-09-29 -### Fixed -- Fixed sort order of VMHost PCI Devices -- Fixed VMHost reporting for InfoLevels 1 & 2 -- Fixed DSCluster reporting for InfoLevels 1 & 2 ### Changed + - Set fixed table column widths for improved formatting -- Corrected section header colours in VMware default style +- Correct section header colours in VMware default style + +### Fixed + +- Fix sort order of VMHost PCI Devices +- Fix VMHost reporting for InfoLevels 1 & 2 +- Fix DSCluster reporting for InfoLevels 1 & 2 ## [[1.2.0](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.2.0)] - 2020-08-31 + ### Added + - vCenter Server advanced system settings - vCenter Server alarm health check - Basic VM storage policy reporting - Headers, footers & table captions/numbering ### Changed -- Improved table formatting -- Enhanced vCenter alarm reporting -- Changed Tag Assignment section to separate the category and tag to their own table columns -- Changed Tag Assignment section to sort on Entity -- Renamed InfoLevel `Informative` to `Adv Summary` -- Moved script functions from main script to private functions + +- Improve table formatting +- Enhance vCenter alarm reporting +- Change Tag Assignment section to separate the category and tag to their own table columns +- Change Tag Assignment section to sort on Entity +- Rename InfoLevel `Informative` to `Adv Summary` +- Move script functions from main script to private functions ### Fixed + - Section error with vSAN InfoLevel 4 or above -- Fixed text color for highlighted cells in default VMware style -- Fixed reporting of stateless boot devices ([Fix #76](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/76)) -- Fixed issue where script was failing trying to parse vSphere Tag data ([Fix #77](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/77)) -- Fixed issue with reporting on PCI-E device drivers by adding additional filter ([Fix #75](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/75)) +- Fix text color for highlighted cells in default VMware style +- Fix reporting of stateless boot devices ([#76](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/76)) +- Fix issue where script was failing trying to parse vSphere Tag data ([#77](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/77)) +- Fix issue with reporting on PCI-E device drivers by adding additional filter ([#75](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/75)) ## [[1.1.3](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.1.3)] - 2020-02-04 + ### Added -- Added vCenter Server certificate information ([Fix #31](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/31)) -- Added VM summary information -- Added VM disk and guest volume information -- Added Virtual Switch to VMkernel adapter information -- Added Virtual Switch & Port Group Traffic Shaping information -- Added vSAN Disk Groups, iSCSI Targets & LUN reporting -- Added number of paths to SCSI LUN information -- Added VMHost CPU & Memory totals to Informative level -- Added VM Connection State information & health check -- Added number of targets, devices & paths to storage adapters -- Added VMHost storage and network adapter health checks -- Added License expiration information -- Added additional information to VMkernel adapters -- Added NTP, SSH & ESXi Shell health checks + +- Add vCenter Server certificate information ([#31](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/31)) +- Add VM summary information +- Add VM disk and guest volume information +- Add Virtual Switch to VMkernel adapter information +- Add Virtual Switch & Port Group Traffic Shaping information +- Add vSAN Disk Groups, iSCSI Targets & LUN reporting +- Add number of paths to SCSI LUN information +- Add VMHost CPU & Memory totals to Informative level +- Add VM Connection State information & health check +- Add number of targets, devices & paths to storage adapters +- Add VMHost storage and network adapter health checks +- Add License expiration information +- Add additional information to VMkernel adapters +- Add NTP, SSH & ESXi Shell health checks ### Changed -- Improved report formatting -- Improved VMHost storage adapter reporting ([Fix #32](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/32)) -- Improved VMHost network adapter CDP reporting -- Improved VM SCSI controller reporting -- Updated VMHost CPU & Memory totals/usage in Detailed level -- Updated report JSON structure & default settings. A new report JSON must be generated for this release, use `New-AsBuiltReportConfig -Report VMware.vSphere -Path -Overwrite`. -- Updated README with minimum required privileges to generate a VMware vSphere As Built Report. Full administrator privileges should no longer be required. + +- Improve report formatting +- Improve VMHost storage adapter reporting ([#32](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/32)) +- Improve VMHost network adapter CDP reporting +- Improve VM SCSI controller reporting +- Update VMHost CPU & Memory totals/usage in Detailed level +- Update report JSON structure & default settings. A new report JSON must be generated for this release, use `New-AsBuiltReportConfig -Report VMware.vSphere -Path -Overwrite` +- Update README with minimum required privileges to generate a VMware vSphere As Built Report. Full administrator privileges should no longer be required ### Fixed -- Resolved issue with VMHost PCI device reporting ([Fix #33](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/33)) -- Resolved issue with reporting of ESXi boot device size ([Fix #65](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/65)) -- Resolved issue with vSphere licensing ([Fix #68](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/68) & [Fix #69](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/69)) -- Resolved vSwitch reporting issue with physical adpaters ([Fix #27](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/27)) -- Resolved issue with VMHost uptime health check reporting + +- Resolve issue with VMHost PCI device reporting ([#33](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/33)) +- Resolve issue with reporting of ESXi boot device size ([#65](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/65)) +- Resolve issue with vSphere licensing ([#68](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/68) & [Fix #69](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/69)) +- Resolve vSwitch reporting issue with physical adapters ([#27](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/27)) +- Resolve issue with VMHost uptime health check reporting ### Removed -- Removed support for ESX/ESXi hosts prior to vSphere 5.0 ([Fix #67](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/67)) -- Removed VMHost CPU & Memory usage from Informative level + +- Remove support for ESX/ESXi hosts prior to vSphere 5.0 ([#67](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/67)) +- Remove VMHost CPU & Memory usage from Informative level ## [[1.0.7](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.0.7)] - 2019-06-21 + ### Changed -- Fixed font in default VMware style -- Updated module manifest for icon and release notes + +- Update module manifest for icon and release notes + +### Fixed + +- Fix font in default VMware style ### Removed -- Removed Services health check + +- Remove Services health check ## [[1.0.6](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.0.6)] - 2019-05-16 + ### Changed -- Fixed code errors which prevented a report from being generated -- Improved code and report readability -- Fixed vCenter Server licensing reporting -- Fixed Datastore reporting when an empty datastore cluster exists -- Fixed DRS Cluster Group reporting when group does not contain any members -- Fixed DRS Cluster Group sorting -- Fixed VMHost reporting to exclude HCX Cloud Gateway host -- Updated VMware default style to more closely align with Clarity + +- Improve code and report readability +- Update VMware default style to more closely align with Clarity + +### Fixed + +- Fix code errors which prevented a report from being generated +- Fix vCenter Server licensing reporting +- Fix Datastore reporting when an empty datastore cluster exists +- Fix DRS Cluster Group reporting when group does not contain any members +- Fix DRS Cluster Group sorting +- Fix VMHost reporting to exclude HCX Cloud Gateway host ## [[1.0.0](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.0.0)] - 2019-03-27 + ### Added -- Added Update Manager Server name to vCenter Server detailed information -### Changed -- Fixed VMHost count for Distributed Virtual Switches -- Fixed vCenter Server licensing for vCenter Server 5.5/6.0 -- Fixed script termination where ESXi hosts do not have a datastore +- Add Update Manager Server name to vCenter Server detailed information + +### Fixed + +- Fix VMHost count for Distributed Virtual Switches +- Fix vCenter Server licensing for vCenter Server 5.5/6.0 +- Fix script termination where ESXi hosts do not have a datastore ## [0.4.0] - 2019-03-15 + ### Changed -- Refactored into PowerShell module -- Updated default VMware style sheet to include page orientation -- Changed VM Snapshot reporting to be per VM for InfoLevel 3 + +- Refactor into PowerShell module +- Update default VMware style sheet to include page orientation +- Change VM Snapshot reporting to be per VM for InfoLevel 3 ### Removed -- Removed NSX-V reporting + +- Remove NSX-V reporting ## [0.3.0] - 2019-02-01 + ### Added -- Added Cluster VM Overrides section + +- Add Cluster VM Overrides section ### Changed -- Improved code structure & readability -- Improved output formatting -- Improved vSphere HA/DRS Cluster reporting and health checks -- Improved VM reporting and health checks -- Fixed sorting of numerous table entries -- Fixed VMHost & VM uptime calculations -- Fixed display of 3rd party Multipath Policy plugins -- Fixed vSAN type & disk count -- Updated Get-Uptime & Get-License functions + +- Improve code structure & readability +- Improve output formatting +- Improve vSphere HA/DRS Cluster reporting and health checks +- Improve VM reporting and health checks +- Fix sorting of numerous table entries +- Fix VMHost & VM uptime calculations +- Fix display of 3rd party Multipath Policy plugins +- Fix vSAN type & disk count +- Update `Get-Uptime` & `Get-License` functions ## [0.2.2] - 2018-09-19 + ### Added -- Added new VM health checks for CPU Hot Add/Remove, Memory Hot Add & Change Block Tracking -- Improved VM reporting for Guest OS, CPU Hot Add/Remove, Memory Hot Add & Change Block Tracking + +- Add new VM health checks for CPU Hot Add/Remove, Memory Hot Add & Change Block Tracking +- Improve VM reporting for Guest OS, CPU Hot Add/Remove, Memory Hot Add & Change Block Tracking - Minor updates to section paragraph text ## [0.2.1] + ### Added -- Added SDRS VM Overrides to Datastore Cluster section + +- Add SDRS VM Overrides to Datastore Cluster section ### Changed -- SCSI LUN section rewritten to improve script performance -- Fixed issues with current working directory paths -- Changed InfoLevel settings and definitions -- Script formatting improvements to some sections to align with PowerShell best practice guidelines -- vCenter Server SSL Certificate section removed temporarily + +- Rewrite SCSI LUN section to improve script performance +- Fix issues with current working directory paths +- Change InfoLevel settings and definitions +- Improve script formatting to align with PowerShell best practice guidelines +- Temporarily remove vCenter Server SSL Certificate section ## [0.2.0] + ### Added -- Added regions/endregions to all sections of script -- Added Resource Pool summary information -- Added vSAN summary information -- Added vCenter Server mail settings health check -- Added DSCluster health checks -- Added VM Power State health check -- Added support for NSX-V reporting + +- Add regions/endregions to all sections of script +- Add Resource Pool summary information +- Add vSAN summary information +- Add vCenter Server mail settings health check +- Add DSCluster health checks +- Add VM Power State health check +- Add support for NSX-V reporting ### Changed -- Updated about_Requires to PScribo module 0.7.24 -- Formatting improvements -- Datastore Clusters now has it's own dedicated section -- Renamed Storage section to Datastores -- Renamed Storage health checks section to Datastore + +- Update about_Requires to PScribo module 0.7.24 +- Improve formatting +- Move Datastore Clusters to its own dedicated section +- Rename Storage section to Datastores +- Rename Storage health checks section to Datastore diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f1e6c92 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,240 @@ +# AsBuiltReport.VMware.vSphere – Developer Guide + +## Module Architecture + +This module follows the nested-folder pattern established by `AsBuiltReport.Microsoft.Azure`. + +``` +AsBuiltReport.VMware.vSphere/ ← repo root +├── AsBuiltReport.VMware.vSphere/ ← module source (published to PSGallery) +│ ├── AsBuiltReport.VMware.vSphere.psd1 +│ ├── AsBuiltReport.VMware.vSphere.psm1 +│ ├── AsBuiltReport.VMware.vSphere.json ← report configuration schema +│ ├── Language/ +│ │ ├── en-US/VMwarevSphere.psd1 ← primary translation template +│ │ ├── en-GB/VMwarevSphere.psd1 ← British English +│ │ ├── es-ES/VMwarevSphere.psd1 ← Spanish +│ │ ├── fr-FR/VMwarevSphere.psd1 ← French +│ │ └── de-DE/VMwarevSphere.psd1 ← German +│ └── Src/ +│ ├── Public/ +│ │ └── Invoke-AsBuiltReport.VMware.vSphere.ps1 ← entry point +│ └── Private/ +│ ├── Convert-DataSize.ps1 ← helper functions +│ ├── Get-ESXiBootDevice.ps1 +│ ├── Get-InstallDate.ps1 +│ ├── Get-License.ps1 +│ ├── Get-PciDeviceDetail.ps1 +│ ├── Get-ScsiDeviceDetail.ps1 +│ ├── Get-Uptime.ps1 +│ ├── Get-vCenterStats.ps1 +│ ├── Get-VMHostNetworkAdapterDP.ps1 +│ ├── Get-AbrVSpherevCenter.ps1 ← section functions +│ ├── Get-AbrVSphereCluster.ps1 +│ ├── Get-AbrVSphereClusterHA.ps1 +│ ├── Get-AbrVSphereClusterProactiveHA.ps1 +│ ├── Get-AbrVSphereClusterDRS.ps1 +│ ├── Get-AbrVSphereResourcePool.ps1 +│ ├── Get-AbrVSphereVMHost.ps1 +│ ├── Get-AbrVSphereVMHostHardware.ps1 +│ ├── Get-AbrVSphereVMHostSystem.ps1 +│ ├── Get-AbrVSphereVMHostStorage.ps1 +│ ├── Get-AbrVSphereVMHostNetwork.ps1 +│ ├── Get-AbrVSphereVMHostSecurity.ps1 +│ ├── Get-AbrVSphereNetwork.ps1 +│ ├── Get-AbrVSpherevSAN.ps1 +│ ├── Get-AbrVSphereDatastore.ps1 +│ ├── Get-AbrVSphereDSCluster.ps1 +│ ├── Get-AbrVSphereVM.ps1 +│ └── Get-AbrVSphereVUM.ps1 +├── Tests/ +│ ├── AsBuiltReport.VMware.vSphere.Tests.ps1 +│ ├── LocalizationData.Tests.ps1 +│ └── Invoke-Tests.ps1 +├── CHANGELOG.md +├── README.md +└── LICENSE +``` + +## Naming Conventions + +| Type | Pattern | Example | +|------|---------|---------| +| Top-level section function | `Get-AbrVSphere{Component}` | `Get-AbrVSpherevCenter` | +| Sub-section function | `Get-AbrVSphere{Parent}{Sub}` | `Get-AbrVSphereClusterHA` | +| Language key (section) | `GetAbrVSphere{Component}` | `GetAbrVSpherevCenter` | +| Language key (sub-section) | `GetAbrVSphere{Parent}{Sub}` | `GetAbrVSphereClusterHA` | + +The language key is derived from the function name by removing the hyphen: `Get-AbrVSpherevCenter` → `GetAbrVSpherevCenter`. + +## Function Structure + +Every section function follows this begin/process/end pattern: + +```powershell +function Get-AbrVSphere{Name} { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere {Name} information. + .NOTES + Version: 2.0.0 + Author: Tim Carman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphere{Name} + Write-PScriboMessage ($LocalizedData.InfoLevel -f $InfoLevel.{Section}) + } + + process { + Try { + if ($InfoLevel.{Section} -ge 1) { + Write-PScriboMessage $LocalizedData.Collecting + Section -Style Heading2 $LocalizedData.SectionHeading { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + # ... section content + } + } + } Catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} +``` + +**Key rules:** +- Functions use variables from parent scope (not parameters) — `$vCenter`, `$InfoLevel`, `$Report`, `$Healthcheck`, `$TextInfo`, `$vCenterServerName`, `$reportTranslate`, etc. +- `$LocalizedData` is always set in `begin {}` from `$reportTranslate.{KeyName}` +- All PSCustomObject property names (column headers), section headings, table names, and user-visible strings use `$LocalizedData` keys +- Table names (`Name =`) in `$TableParams` also use `$LocalizedData` keys for localization + +## Language / i18n Structure + +### Loading mechanism +The `AsBuiltReport.Core` module loads `Language/{culture}/VMwarevSphere.psd1` automatically. The `$reportTranslate` variable is set globally by `New-AsBuiltReport.ps1` before the Invoke- function runs. + +The file name `VMwarevSphere` is derived from the report name `VMware.vSphere` by removing the dot. + +### File format +```powershell +# culture = 'en-US' +@{ + GetAbrVSpherevCenter = ConvertFrom-StringData @' + InfoLevel = Tab:2 vCenter InfoLevel set to {0}. + Collecting = Collecting vCenter Server information. + SectionHeading = vCenter Server + ParagraphSummary = The following sections detail the configuration of vCenter Server {0}. + # ... more keys + '@ + + GetAbrVSphereCluster = ConvertFrom-StringData @' + # ... + '@ +} +``` + +### Adding a new locale +1. Copy `Language/en-US/VMwarevSphere.psd1` to `Language/{culture}/VMwarevSphere.psd1` +2. Update the culture comment at the top +3. Translate string values (keep keys identical) +4. Run `Tests/Invoke-Tests.ps1` — `LocalizationData.Tests.ps1` will verify key consistency + +## Development Commands + +### Lint (PSScriptAnalyzer) +```powershell +# From repo root +Invoke-ScriptAnalyzer -Path .\AsBuiltReport.VMware.vSphere\Src -Recurse -Severity Warning, Error +``` + +### Run tests +```powershell +.\Tests\Invoke-Tests.ps1 + +# With code coverage +.\Tests\Invoke-Tests.ps1 -CodeCoverage + +# With JUnit output (for CI) +.\Tests\Invoke-Tests.ps1 -OutputFormat JUnitXml +``` + +### Import module (smoke test, no vCenter required) +```powershell +Import-Module .\AsBuiltReport.VMware.vSphere\AsBuiltReport.VMware.vSphere.psd1 -Force +Get-Command -Module AsBuiltReport.VMware.vSphere +``` + +### Reload module during development +```powershell +Remove-Module AsBuiltReport.VMware.vSphere -ErrorAction SilentlyContinue +Import-Module .\AsBuiltReport.VMware.vSphere\AsBuiltReport.VMware.vSphere.psd1 -Force +``` + +## How to Add a New Section Function + +1. **Create the function file** in `Src/Private/Get-AbrVSphere{Name}.ps1` + - Follow the begin/process/end template above + - Use `$InfoLevel.{Section}` for the info level check + +2. **Add the language key** to `Language/en-US/VMwarevSphere.psd1`: + ```powershell + GetAbrVSphere{Name} = ConvertFrom-StringData @' + InfoLevel = {Section} InfoLevel set to {0}. + Collecting = Collecting {Section} information. + SectionHeading = {Section Heading} + ParagraphSummary = The following sections detail... + '@ + ``` + +3. **Copy the key** to all other locale files (en-GB, etc.) with identical keys (translate values only) + +4. **Call the function** from `Invoke-AsBuiltReport.VMware.vSphere.ps1` inside the `Section -Style Heading1` block + +5. **Run tests** to verify: + - `LocalizationData.Tests.ps1` checks all locale files have matching keys + - `AsBuiltReport.VMware.vSphere.Tests.ps1` checks the .ps1 file exists in Private/ + +## InfoLevel Reference + +| Level | Description | +|-------|-------------| +| 0 | Disabled (section not rendered) | +| 1 | Enabled / Summary | +| 2 | Advanced Summary | +| 3 | Detailed | +| 4 | Advanced Detailed | +| 5 | Comprehensive | + +## Report JSON Configuration + +The report configuration is in `AsBuiltReport.VMware.vSphere.json`. InfoLevel keys correspond to section names: `vCenter`, `Cluster`, `ResourcePool`, `VMHost`, `Network`, `vSAN`, `Datastore`, `DSCluster`, `VM`, `VUM`. + +## Known Gotchas + +### Pester 5 Discovery vs. Runtime Scoping +The `-Skip:()` expression in `It` blocks is evaluated at **discovery** time, before `BeforeAll {}` runs. Variables set in `BeforeAll` are not available to `-Skip:()` conditions. Use `BeforeDiscovery {}` for variables that control skip logic. Example in `AsBuiltReport.VMware.vSphere.Tests.ps1`: + +```powershell +# Correct — BeforeDiscovery runs before -Skip:() is evaluated +BeforeDiscovery { + $AnalyzerAvailable = $null -ne (Get-Module -Name PSScriptAnalyzer -ListAvailable | Select-Object -First 1) +} +``` + +### psm1 Module Exports +The `.psm1` only exports public functions (`Export-ModuleMember -Function $Public.BaseName`). Private section functions (`Get-AbrVSphere*`) are dot-sourced and available in module scope without being exported. The `FunctionsToExport` in the `.psd1` manifest acts as the final authoritative allow-list. + +### Locale Key Consistency +The `en-US` locale file is the authoritative template. All other locales must have **identical key sets** — the `LocalizationData.Tests.ps1` Pester test enforces this. To quickly check parity without running Pester, compare key counts: +```powershell +# Count keys per locale manually +foreach ($locale in 'en-US','en-GB','es-ES','fr-FR','de-DE') { + $count = (Get-Content ".\AsBuiltReport.VMware.vSphere\Language\$locale\VMwarevSphere.psd1" | + Where-Object { $_ -match '^\s+\w+\s*=' -and $_ -notmatch "^'@" }).Count + Write-Host "$locale`: $count keys" +} +``` diff --git a/LICENSE b/LICENSE index d8002d2..53f3c82 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 AsBuiltReport +Copyright (c) 2026 AsBuiltReport Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 19fce59..b898776 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +

@@ -24,8 +25,9 @@

- Buy Me a Coffee at ko-fi.com + Want to keep alive this project? Support me on Ko-fi

+ # VMware vSphere As Built Report @@ -35,41 +37,59 @@ VMware vSphere As Built Report is a PowerShell module which works in conjunction The VMware vSphere As Built Report module is used to generate as built documentation for VMware vSphere / vCenter Server environments. -Please refer to the [VMware ESXi AsBuiltReport](https://github.com/AsBuiltReport/AsBuiltReport.VMware.ESXi) for reporting of standalone VMware ESXi servers. +> [!TIP] +> Please refer to the [VMware ESXi](https://github.com/AsBuiltReport/AsBuiltReport.VMware.ESXi) AsBuiltReport for reporting of standalone VMware ESXi servers. Please refer to the AsBuiltReport [website](https://www.asbuiltreport.com) for more detailed information about this project. + # :beginner: Getting Started Below are the instructions on how to install, configure and generate a VMware vSphere As Built report. ## :floppy_disk: Supported Versions - -### VMware vSphere The VMware vSphere As Built Report supports the following vSphere versions; -- vSphere 7.0 - vSphere 8.0 -#### End of Support +> [!IMPORTANT] +> Ongoing VMware product and licensing restrictions, combined with limited access to development environments and resources, prevent further development and support of this report module beyond VMware vSphere 8.0. + +## :no_entry_sign: Unsupported Versions The following VMware vSphere versions are no longer being tested and/or supported; - vSphere 5.5 - vSphere 6.0 - vSphere 6.5 - vSphere 6.7 +- vSphere 7.0 ### PowerShell This report is compatible with the following PowerShell versions; + | Windows PowerShell 5.1 | PowerShell 7 | |:----------------------:|:--------------------:| | :white_check_mark: | :white_check_mark: | +## 🌐 Language Support + +The VMware vSphere As Built Report supports the following languages; + +| Language | Culture Code | +|----------|--------------| +| English (US) | en-US (Default) | +| English (GB) | en-GB | +| French | fr-FR | +| German | de-DE | +| Spanish | es-ES | + ## :wrench: System Requirements + PowerShell 5.1 or PowerShell 7, and the following PowerShell modules are required for generating a VMware vSphere As Built report. Each of these modules can be easily downloaded and installed via the PowerShell Gallery @@ -78,6 +98,8 @@ Each of these modules can be easily downloaded and installed via the PowerShell - [AsBuiltReport.VMware.vSphere Module](https://www.powershellgallery.com/packages/AsBuiltReport.VMware.vSphere/) ### :closed_lock_with_key: Required Privileges + + A VMware vSphere As Built Report can be generated with read-only privileges, however the following sections will be skipped; @@ -95,19 +117,40 @@ For a complete report, the following role assigned privileges are required; ## :package: Module Installation +### PowerShell + Open a PowerShell terminal window and install each of the required modules. -:warning: VCF PowerCLI 9.0 or higher is required. Please ensure older PowerCLI versions have been uninstalled. +> [!NOTE] +> VCF PowerCLI 9.0 or higher is required. Please ensure older PowerCLI versions have been uninstalled. ```powershell +# Install install-module VCF.PowerCLI -MinimumVersion 9.0 -AllowClobber -SkipPublisherCheck install-module AsBuiltReport.VMware.vSphere ``` +### GitHub +If you are unable to use the PowerShell Gallery, you can still install the module manually. Ensure you repeat the following steps for the [system requirements](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere#wrench-system-requirements) also. + +1. Download the code package / [latest release](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/latest) zip from GitHub +2. Extract the zip file +3. Copy the folder `AsBuiltReport.VMware.vSphere` to a path that is set in `$env:PSModulePath`. +4. Open a PowerShell terminal window and unblock the downloaded files with + ```powershell + $path = (Get-Module -Name AsBuiltReport.VMware.vSphere -ListAvailable).ModuleBase; Unblock-File -Path $path\*.psd1; Unblock-File -Path $path\Src\Public\*.ps1; Unblock-File -Path $path\Src\Private\*.ps1 + ``` +5. Close and reopen the PowerShell terminal window. + +_Note: You are not limited to installing the module to those example paths, you can add a new entry to the environment variable PSModulePath if you want to use another path._ + ## :pencil2: Configuration The vSphere As Built Report utilises a JSON file to allow configuration of report information, options, detail and healthchecks. -A vSphere report configuration file can be generated by executing the following command; +> [!IMPORTANT] +> Please remember to generate a new report JSON configuration file after each module update to ensure the report functions correctly. + +A VMware vSphere report configuration file can be generated by executing the following command; ```powershell New-AsBuiltReportConfig -Report VMware.vSphere -FolderPath -Filename ``` @@ -118,6 +161,7 @@ All report settings can then be configured via the JSON file. The following provides information of how to configure each schema within the report's JSON file. + ### Report The **Report** schema provides configuration of the vSphere report information. @@ -125,7 +169,8 @@ The **Report** schema provides configuration of the vSphere report information. |---------------------|--------------|--------------------------------|--------------------------------------------------------------| | Name | User defined | VMware vSphere As Built Report | The name of the As Built Report | | Version | User defined | 1.0 | The report version | -| Status | User defined | Released | The report release status | +| Status | User defined | Released | The report release status +| Language | User defined | en-US | The default report language. This can be customised if the report module provides multilingual support | | | ShowCoverPageImage | true / false | true | Toggle to enable/disable the display of the cover page image | | ShowTableOfContents | true / false | true | Toggle to enable/disable table of contents | | ShowHeaderFooter | true / false | true | Toggle to enable/disable document headers & footers | @@ -139,6 +184,7 @@ The **Options** schema allows certain options within the report to be toggled on | ShowLicenseKeys | true / false | false | Toggle to mask/unmask vSphere license keys

**Masked License Key**
\*\*\*\*\*-\*\*\*\*\*-\*\*\*\*\*-\*\*\*\*\*-AS12K

**Unmasked License Key**
AKLU4-PFG8M-W2D8J-56YDM-AS12K | | ShowVMSnapshots | true / false | true | Toggle to enable/disable reporting of VM snapshots | + ### InfoLevel The **InfoLevel** schema allows configuration of each section of the report at a granular level. The following sections can be set. @@ -176,89 +222,90 @@ The **vCenter** schema is used to configure health checks for vCenter Server. | Sub-Schema | Setting | Default | Description | Highlight | |------------|--------------|---------|-----------------------------------------------------|-------------------------------------------------------------------------------------------| -| Mail | true / false | true | Highlights mail settings which are not configured | ![Critical](https://place-hold.it/15/ffb38f/ffb38f) Not Configured | -| Licensing | true / false | true | Highlights product evaluation licenses | ![Warning](https://place-hold.it/15/ffe860/ffe860) Product evaluation license in use | -| Alarms | true / false | true | Highlights vCenter Server alarms which are disabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) Alarm disabled | +| Mail | true / false | true | Highlights mail settings which are not configured | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Not Configured | +| Licensing | true / false | true | Highlights product evaluation licenses | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Product evaluation license in use | +| Alarms | true / false | true | Highlights vCenter Server alarms which are disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Alarm disabled | #### Cluster The **Cluster** schema is used to configure health checks for vSphere Clusters. | Sub-Schema | Setting | Default | Description | Highlight | |-----------------------------|--------------|---------|--------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| -| HAEnabled | true / false | true | Highlights vSphere Clusters which do not have vSphere HA enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere HA disabled | -| HAAdmissionControl | true / false | true | Highlights vSphere Clusters which do not have vSphere HA Admission Control enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere HA Admission Control disabled | -| HostFailureResponse | true / false | true | Highlights vSphere Clusters which have vSphere HA Failure Response set to disabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere HA Host Failure Response disabled | -| HostMonitoring | true / false | true | Highlights vSphere Clusters which do not have vSphere HA Host Monitoring enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere HA Host Monitoring disabled | -| DatastoreOnPDL | true / false | true | Highlights vSphere Clusters which do not have Datastore on PDL enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere HA Datastore on PDL disabled | -| DatastoreOnAPD | true / false | true | Highlights vSphere Clusters which do not have Datastore on APD enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere HA Datastore on APD disabled | -| APDTimeOut | true / false | true | Highlights vSphere Clusters which do not have APDTimeOut enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) APDTimeOut disabled | -| vmMonitoing | true / false | true | Highlights vSphere Clusters which do not have VM Monitoting enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) VM Monitoring disabled | -| DRSEnabled | true / false | true | Highlights vSphere Clusters which do not have vSphere DRS enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere DRS disabled | -| DRSAutomationLevelFullyAuto | true / false | true | Checks the vSphere DRS Automation Level is set to 'Fully Automated' | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere DRS Automation Level not set to 'Fully Automated' | -| PredictiveDRS | true / false | false | Highlights vSphere Clusters which do not have Predictive DRS enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) Predictive DRS disabled | -| DRSVMHostRules | true / false | true | Highlights DRS VMHost rules which are disabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) DRS VMHost rule disabled | -| DRSRules | true / false | true | Highlights DRS rules which are disabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) DRS rule disabled | -| vSANEnabled | true / false | true | Highlights vSphere Clusters which do not have Virtual SAN enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) Virtual SAN disabled | -| EVCEnabled | true / false | true | Highlights vSphere Clusters which do not have Enhanced vMotion Compatibility (EVC) enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) vSphere EVC disabled | -| VUMCompliance | true / false | true | Highlights vSphere Clusters which do not comply with VMware Update Manager baselines | ![Warning](https://place-hold.it/15/ffe860/ffe860) Unknown
![Critical](https://place-hold.it/15/ffb38f/ffb38f) Not Compliant | +| HAEnabled | true / false | true | Highlights vSphere Clusters which do not have vSphere HA enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere HA disabled | +| HAAdmissionControl | true / false | true | Highlights vSphere Clusters which do not have vSphere HA Admission Control enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere HA Admission Control disabled | +| HostFailureResponse | true / false | true | Highlights vSphere Clusters which have vSphere HA Failure Response set to disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere HA Host Failure Response disabled | +| HostMonitoring | true / false | true | Highlights vSphere Clusters which do not have vSphere HA Host Monitoring enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere HA Host Monitoring disabled | +| DatastoreOnPDL | true / false | true | Highlights vSphere Clusters which do not have Datastore on PDL enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere HA Datastore on PDL disabled | +| DatastoreOnAPD | true / false | true | Highlights vSphere Clusters which do not have Datastore on APD enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere HA Datastore on APD disabled | +| APDTimeOut | true / false | true | Highlights vSphere Clusters which do not have APDTimeOut enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) APDTimeOut disabled | +| vmMonitoring | true / false | true | Highlights vSphere Clusters which do not have VM Monitoring enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) VM Monitoring disabled | +| DRSEnabled | true / false | true | Highlights vSphere Clusters which do not have vSphere DRS enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere DRS disabled | +| DRSAutomationLevelFullyAuto | true / false | true | Checks the vSphere DRS Automation Level is set to 'Fully Automated' | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere DRS Automation Level not set to 'Fully Automated' | +| PredictiveDRS | true / false | false | Highlights vSphere Clusters which do not have Predictive DRS enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Predictive DRS disabled | +| DRSVMHostRules | true / false | true | Highlights DRS VMHost rules which are disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) DRS VMHost rule disabled | +| DRSRules | true / false | true | Highlights DRS rules which are disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) DRS rule disabled | +| vSANEnabled | true / false | true | Highlights vSphere Clusters which do not have Virtual SAN enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Virtual SAN disabled | +| EVCEnabled | true / false | true | Highlights vSphere Clusters which do not have Enhanced vMotion Compatibility (EVC) enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere EVC disabled | +| VUMCompliance | true / false | true | Highlights vSphere Clusters which do not comply with VMware Update Manager baselines | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Unknown
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Not Compliant | #### VMHost The **VMHost** schema is used to configure health checks for VMHosts. | Sub-Schema | Setting | Default | Description | Highlight | |-----------------|--------------|---------|--------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ConnectionState | true / false | true | Highlights VMHosts which are in maintenance mode or disconnected | ![Warning](https://place-hold.it/15/ffe860/ffe860) Maintenance
![Critical](https://place-hold.it/15/ffb38f/ffb38f) Disconnected | -| HyperThreading | true / false | true | Highlights VMHosts which have HyperThreading disabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) HyperThreading disabled
| -| ScratchLocation | true / false | true | Highlights VMHosts which are configured with the default scratch location | ![Warning](https://place-hold.it/15/ffe860/ffe860) Scratch location is /tmp/scratch | -| IPv6 | true / false | true | Highlights VMHosts which do not have IPv6 enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) IPv6 disabled | -| UpTimeDays | true / false | true | Highlights VMHosts with uptime days greater than 9 months | ![Warning](https://place-hold.it/15/ffe860/ffe860) 9 - 12 months
![Critical](https://place-hold.it/15/ffb38f/ffb38f) >12 months | -| Licensing | true / false | true | Highlights VMHosts which are using production evaluation licenses | ![Warning](https://place-hold.it/15/ffe860/ffe860) Product evaluation license in use | -| SSH | true / false | true | Highlights if the SSH service is enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) TSM / TSM-SSH service enabled | -| ESXiShell | true / false | true | Highlights if the ESXi Shell service is enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) TSM / TSM-EsxiShell service enabled | -| NTP | true / false | true | Highlights if the NTP service has stopped or is disabled on a VMHost | ![Critical](https://place-hold.it/15/ffb38f/ffb38f) NTP service stopped / disabled | -| StorageAdapter | true / false | true | Highlights storage adapters which are not 'Online' | ![Warning](https://place-hold.it/15/ffe860/ffe860) Storage adapter status is 'Unknown'
![Critical](https://place-hold.it/15/ffb38f/ffb38f) Storage adapter status is 'Offline' | -| NetworkAdapter | true / false | true | Highlights physical network adapters which are not 'Connected'
Highlights physical network adapters which are 'Down' | ![Critical](https://place-hold.it/15/ffb38f/ffb38f) Network adapter is 'Disconnected'
![Critical](https://place-hold.it/15/ffb38f/ffb38f) Network adapter is 'Down' | -| LockdownMode | true / false | true | Highlights VMHosts which do not have Lockdown mode enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) Lockdown Mode disabled
| -| VUMCompliance | true / false | true | Highlights VMHosts which are not compliant with VMware Update Manager software packages | ![Warning](https://place-hold.it/15/ffe860/ffe860) Unknown
![Critical](https://place-hold.it/15/ffb38f/ffb38f) Incompatible | +| ConnectionState | true / false | true | Highlights VMHosts which are in maintenance mode or disconnected | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Maintenance
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Disconnected | +| HyperThreading | true / false | true | Highlights VMHosts which have HyperThreading disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) HyperThreading disabled
| +| ScratchLocation | true / false | true | Highlights VMHosts which are configured with the default scratch location | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Scratch location is /tmp/scratch | +| IPv6 | true / false | true | Highlights VMHosts which do not have IPv6 enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) IPv6 disabled | +| UpTimeDays | true / false | true | Highlights VMHosts with uptime days greater than 9 months | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) 9 - 12 months
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) >12 months | +| Licensing | true / false | true | Highlights VMHosts which are using production evaluation licenses | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Product evaluation license in use | +| SSH | true / false | true | Highlights if the SSH service is enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) TSM / TSM-SSH service enabled | +| ESXiShell | true / false | true | Highlights if the ESXi Shell service is enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) TSM / TSM-EsxiShell service enabled | +| NTP | true / false | true | Highlights if the NTP service has stopped or is disabled on a VMHost | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) NTP service stopped / disabled | +| StorageAdapter | true / false | true | Highlights storage adapters which are not 'Online' | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Storage adapter status is 'Unknown'
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Storage adapter status is 'Offline' | +| NetworkAdapter | true / false | true | Highlights physical network adapters which are not 'Connected'
Highlights physical network adapters which are 'Down' | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Network adapter is 'Disconnected'
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Network adapter is 'Down' | +| LockdownMode | true / false | true | Highlights VMHosts which do not have Lockdown mode enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Lockdown Mode disabled
| +| VUMCompliance | true / false | true | Highlights VMHosts which are not compliant with VMware Update Manager software packages | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Unknown
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Incompatible | #### vSAN The **vSAN** schema is used to configure health checks for vSAN. | Sub-Schema | Setting | Default | Description | Highlight | |---------------------|--------------|---------|------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| CapacityUtilization | true / false | true | Highlights vSAN clusters with storage capacity utilization over 75% | ![Warning](https://place-hold.it/15/ffe860/ffe860) 75 - 90% utilized
![Critical](https://place-hold.it/15/ffb38f/ffb38f) >90% utilized | +| CapacityUtilization | true / false | true | Highlights vSAN clusters with storage capacity utilization over 75% | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) 75 - 90% utilized
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) >90% utilized | #### Datastore The **Datastore** schema is used to configure health checks for Datastores. | Sub-Schema | Setting | Default | Description | Highlight | |---------------------|--------------|---------|------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| CapacityUtilization | true / false | true | Highlights datastores with storage capacity utilization over 75% | ![Warning](https://place-hold.it/15/ffe860/ffe860) 75 - 90% utilized
![Critical](https://place-hold.it/15/ffb38f/ffb38f) >90% utilized | +| CapacityUtilization | true / false | true | Highlights datastores with storage capacity utilization over 75% | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) 75 - 90% utilized
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) >90% utilized | #### DSCluster The **DSCluster** schema is used to configure health checks for Datastore Clusters. | Sub-Schema | Setting | Default | Description | Highlight | |------------------------------|--------------|---------|-------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| CapacityUtilization | true / false | true | Highlights datastore clusters with storage capacity utilization over 75% | ![Warning](https://place-hold.it/15/ffe860/ffe860) 75 - 90% utilized
![Critical](https://place-hold.it/15/ffb38f/ffb38f) >90% utilized | -| SDRSAutomationLevelFullyAuto | true / false | true | Highlights if the Datastore Cluster SDRS Automation Level is not set to 'Fully Automated' | ![Warning](https://place-hold.it/15/ffe860/ffe860) Storage DRS Automation Level not set to 'Fully Automated' | +| CapacityUtilization | true / false | true | Highlights datastore clusters with storage capacity utilization over 75% | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) 75 - 90% utilized
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) >90% utilized | +| SDRSAutomationLevelFullyAuto | true / false | true | Highlights if the Datastore Cluster SDRS Automation Level is not set to 'Fully Automated' | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Storage DRS Automation Level not set to 'Fully Automated' | #### VM The **VM** schema is used to configure health checks for virtual machines. | Sub-Schema | Setting | Default | Description | Highlight | |----------------------|--------------|---------|------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| PowerState | true / false | true | Highlights VMs which are powered off | ![Warning](https://place-hold.it/15/ffe860/ffe860) VM is powered off | -| ConnectionState | true / false | true | Highlights VMs which are orphaned or inaccessible | ![Critical](https://place-hold.it/15/ffb38f/ffb38f) VM is orphaned or inaccessible | -| CpuHotAdd | true / false | true | Highlights virtual machines which have CPU Hot Add enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) CPU Hot Add enabled | -| CpuHotRemove | true / false | true | Highlights virtual machines which have CPU Hot Remove enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) CPU Hot Remove enabled | -| MemoryHotAdd | true / false | true | Highlights VMs which have Memory Hot Add enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) Memory Hot Add enabled | -| ChangeBlockTracking | true / false | true | Highlights VMs which do not have Change Block Tracking enabled | ![Warning](https://place-hold.it/15/ffe860/ffe860) Change Block Tracking disabled | -| SpbmPolicyCompliance | true / false | true | Highlights VMs which do not comply with storage based policies | ![Warning](https://place-hold.it/15/ffe860/ffe860) VM storage based policy compliance is unknown
![Critical](https://place-hold.it/15/ffb38f/ffb38f) VM does not comply with storage based policies | -| VMToolsStatus | true / false | true | Highlights Virtual Machines which do not have VM Tools installed, are out of date or are not running | ![Warning](https://place-hold.it/15/ffe860/ffe860) VM Tools not installed, out of date or not running | -| VMSnapshots | true / false | true | Highlights Virtual Machines which have snapshots older than 7 days | ![Warning](https://place-hold.it/15/ffe860/ffe860) VM Snapshot age >= 7 days
![Critical](https://place-hold.it/15/ffb38f/ffb38f) VM Snapshot age >= 14 days | +| PowerState | true / false | true | Highlights VMs which are powered off | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) VM is powered off | +| ConnectionState | true / false | true | Highlights VMs which are orphaned or inaccessible | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) VM is orphaned or inaccessible | +| CpuHotAdd | true / false | true | Highlights virtual machines which have CPU Hot Add enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) CPU Hot Add enabled | +| CpuHotRemove | true / false | true | Highlights virtual machines which have CPU Hot Remove enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) CPU Hot Remove enabled | +| MemoryHotAdd | true / false | true | Highlights VMs which have Memory Hot Add enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Memory Hot Add enabled | +| ChangeBlockTracking | true / false | true | Highlights VMs which do not have Change Block Tracking enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Change Block Tracking disabled | +| SpbmPolicyCompliance | true / false | true | Highlights VMs which do not comply with storage based policies | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) VM storage based policy compliance is unknown
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) VM does not comply with storage based policies | +| VMToolsStatus | true / false | true | Highlights Virtual Machines which do not have VM Tools installed, are out of date or are not running | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) VM Tools not installed, out of date or not running | +| VMSnapshots | true / false | true | Highlights Virtual Machines which have snapshots older than 7 days | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) VM Snapshot age >= 7 days
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) VM Snapshot age >= 14 days | ## :computer: Examples + ```powershell # Generate a vSphere As Built Report for vCenter Server 'vcenter-01.corp.local' using specified credentials. Export report to HTML & DOCX formats. Use default report style. Append timestamp to report filename. Save reports to 'C:\Users\Tim\Documents' diff --git a/Samples/Sample vSphere As-Built Report.html b/Samples/Sample vSphere As-Built Report.html deleted file mode 100644 index 3e3c695..0000000 --- a/Samples/Sample vSphere As-Built Report.html +++ /dev/null @@ -1,686 +0,0 @@ - - -Sample vSphere As-Built Report - -
-










Sample vSphere As-Built Report
ACME & Co



























Author:Tim Carman
Date:05 August 2018
Version:1.0
-
-

Table of Contents

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1192.168.1.110
1.1   vCenter Server
1.1.1      Database Settings
1.1.2      Mail Settings
1.1.3      Historical Statistics
1.1.4      Licensing
1.1.5      SSL Certificate
1.1.6      Roles
1.2   Clusters
1.2.1      New Cluster
1.2.1.1         HA Configuration
1.2.1.2         DRS Configuration
1.2.1.3         Permissions
1.2.2      Test Cluster
1.2.2.1         HA Configuration
1.2.2.2         DRS Configuration
1.2.2.3         Permissions
1.2.3      VSAN-Cluster
1.2.3.1         HA Configuration
1.2.3.2         DRS Configuration
1.2.3.2.1            DRS Cluster Groups
1.2.3.2.2            DRS VM/Host Rules
1.2.3.2.3            DRS Rules
1.2.3.3         Update Manager Baselines
1.2.3.4         Permissions
1.3   Resource Pools
1.4   Hosts
1.4.1      192.168.1.111
1.4.1.1         Hardware
1.4.1.1.1            Boot Devices
1.4.1.1.2            PCI Devices
1.4.1.2         System
1.4.1.2.1            Licensing
1.4.1.2.2            Host Profile
1.4.1.2.3            Image Profile
1.4.1.2.4            Time Configuration
1.4.1.2.5            Syslog Configuration
1.4.1.2.6            Update Manager Compliance
1.4.1.3         Storage
1.4.1.3.1            Datastores
1.4.1.4         Network
1.4.1.4.1            Physical Adapters
1.4.1.4.2            Cisco Discovery Protocol
1.4.1.4.3            VMkernel Adapters
1.4.1.4.4            Standard Virtual Switches
1.4.1.4.5            Virtual Switch Security Policy
1.4.1.4.6            Virtual Switch NIC Teaming
1.4.1.4.7            Virtual Port Groups
1.4.1.4.8            Virtual Port Group Security Policy
1.4.1.4.9            Virtual Port Group NIC Teaming
1.4.1.5         Security
1.4.1.5.1            Lockdown Mode
1.4.1.5.2            Services
1.4.1.5.3            Authentication Services
1.4.2      192.168.1.112
1.4.2.1         Hardware
1.4.2.1.1            Boot Devices
1.4.2.1.2            PCI Devices
1.4.2.2         System
1.4.2.2.1            Licensing
1.4.2.2.2            Host Profile
1.4.2.2.3            Image Profile
1.4.2.2.4            Time Configuration
1.4.2.2.5            Syslog Configuration
1.4.2.2.6            Update Manager Compliance
1.4.2.3         Storage
1.4.2.3.1            Datastores
1.4.2.4         Network
1.4.2.4.1            Physical Adapters
1.4.2.4.2            Cisco Discovery Protocol
1.4.2.4.3            VMkernel Adapters
1.4.2.4.4            Standard Virtual Switches
1.4.2.4.5            Virtual Switch Security Policy
1.4.2.4.6            Virtual Switch NIC Teaming
1.4.2.4.7            Virtual Port Groups
1.4.2.4.8            Virtual Port Group Security Policy
1.4.2.4.9            Virtual Port Group NIC Teaming
1.4.2.5         Security
1.4.2.5.1            Lockdown Mode
1.4.2.5.2            Services
1.4.2.5.3            Authentication Services
1.4.3      192.168.1.113
1.4.3.1         Hardware
1.4.3.1.1            Boot Devices
1.4.3.1.2            PCI Devices
1.4.3.2         System
1.4.3.2.1            Licensing
1.4.3.2.2            Host Profile
1.4.3.2.3            Image Profile
1.4.3.2.4            Time Configuration
1.4.3.2.5            Syslog Configuration
1.4.3.2.6            Update Manager Compliance
1.4.3.3         Storage
1.4.3.3.1            Datastores
1.4.3.4         Network
1.4.3.4.1            Physical Adapters
1.4.3.4.2            Cisco Discovery Protocol
1.4.3.4.3            VMkernel Adapters
1.4.3.4.4            Standard Virtual Switches
1.4.3.4.5            Virtual Switch Security Policy
1.4.3.4.6            Virtual Switch NIC Teaming
1.4.3.4.7            Virtual Port Groups
1.4.3.4.8            Virtual Port Group Security Policy
1.4.3.4.9            Virtual Port Group NIC Teaming
1.4.3.5         Security
1.4.3.5.1            Lockdown Mode
1.4.3.5.2            Services
1.4.3.5.3            Authentication Services
1.5   Distributed Virtual Switches
1.5.1      DSwitch
1.5.1.1         General Properties
1.5.1.2         Uplinks
1.5.1.3         Security
1.5.1.4         Traffic Shaping
1.5.1.5         Port Groups
1.5.1.6         Port Group Security
1.5.1.7         Port Group NIC Teaming
1.5.1.8         Private VLANs
1.5.2      DSwitch 1
1.5.2.1         General Properties
1.5.2.2         Security
1.5.2.3         Traffic Shaping
1.5.2.4         Port Groups
1.5.2.5         Port Group Security
1.5.2.6         Port Group NIC Teaming
1.6   vSAN
1.6.1      VSAN-Cluster
1.7   Storage
1.7.1      Datastore Specifications
1.8   Virtual Machines
1.8.1      Test_1
1.8.2      Test_2
1.8.3      Test_3
1.8.4      VM Snapshots
1.9   VMware Update Manager
1.9.1      Baselines
-
-

1 192.168.1.110

1.1 vCenter Server

The following section provides detailed information on the configuration of vCenter server 192.168.1.110.

-
Name192.168.1.110
IP Address192.168.1.110
Version6.5.0
Build5973321
OS Typelinux-x64
Instance Id42
Password Expiry in Days30
HTTP Port80
HTTPS Port443
Platform Services Controller192.168.1.110
-

1.1.1 Database Settings

-
Database Typeembedded
Data Source NameVMware VirtualCenter
Maximum Database Connections50
-

1.1.2 Mail Settings

-
SMTP Serversmtp.gmail.com
SMTP Port25
Mail Sender 
-

1.1.3 Historical Statistics

- - - - - -
Interval DurationInterval EnabledSave DurationStatistics Level
5 MinutesTruePast day1
30 MinutesTruePast week1
2 HoursTruePast month1
1 DayTruePast year1
-

1.1.4 Licensing

- - - - - -
Product NameLicense KeyTotalUsedAvailable
Product Evaluation*****-*****-*****-00000-000000 0
VMware vCenter Server 6 Standard*****-*****-*****-0J9U6-0NF2M211
VMware vSAN Enterprise*****-*****-*****-061A0-2NA7H1064
VMware vSphere 6 Enterprise Plus*****-*****-*****-079HM-29JHN3006294
-

1.1.5 SSL Certificate

-
CountryUS
StateCalifornia
LocalityPalo Alto
OrganizationVMware
Organizational UnitVMware Engineering
Emailvmca@vmware.com
Validity5 Years
-

1.1.6 Roles

- - - - - - - - - - - - - - - - -
NameSystem Role
AdminTrue
AnonymousTrue
com.vmware.Content.AdminFalse
DatastoreConsumerFalse
InventoryService.Tagging.TaggingAdminFalse
NetworkConsumerFalse
NoAccessTrue
NoCryptoAdminTrue
ReadOnlyTrue
ResourcePoolAdministratorFalse
ViewTrue
VirtualMachineConsoleUserFalse
VirtualMachinePowerUserFalse
VirtualMachineUserFalse
VMwareConsolidatedBackupUserFalse
-
-

1.2 Clusters

The following section provides information on the configuration of each vSphere HA/DRS cluster.

- - - -
NameDatacenterHost CountHA EnabledDRS EnabledvSAN EnabledEVC ModeVM Swap File PolicyVM Count
New ClusterDatacenter 10TrueTrueFalse WithVM0
Test ClusterDatacenter0FalseFalseFalseintel-ivybridgeWithVM0
VSAN-ClusterDatacenter3TrueTrueTrue WithVM3
-

1.2.1 New Cluster

The following table details the cluster configuration for cluster New Cluster.

-
NameNew Cluster
DatacenterDatacenter 1
Number of Hosts0
Number of VMs0
HA EnabledTrue
DRS EnabledTrue
vSAN EnabledFalse
EVC Mode 
VM Swap File PolicyWithVM
Connected Hosts 
-

1.2.1.1 HA Configuration

The following table details the vSphere HA configuration for cluster New Cluster.

-
HA EnabledTrue
HA Admission Control EnabledTrue
HA Failover Level1
HA Restart PriorityMedium
HA Isolation ResponseDoNothing
Heartbeat Selection PolicyallFeasibleDsWithUserPreference
Heartbeat Datastores 
-

1.2.1.2 DRS Configuration

The following table details the vSphere DRS configuration for cluster New Cluster.

-
DRS EnabledTrue
DRS Automation LevelFullyAutomated
DRS Migration Threshold3
-
-
VM Distribution 
Memory Metric for Load Balancing 
CPU Over-Commitment 
-

1.2.1.3 Permissions

The following table details the permissions assigned to cluster New Cluster.

- - - - - -
User/GroupIs Group?RoleDefined InPropagate
VSPHERE.LOCAL\vpxd-extension-0cd516db-cbaa-434b-8b6f-90452748e378FalseAdminDatacentersTrue
VSPHERE.LOCAL\vpxd-0cd516db-cbaa-434b-8b6f-90452748e378FalseAdminDatacentersTrue
VSPHERE.LOCAL\vsphere-webclient-0cd516db-cbaa-434b-8b6f-90452748e378FalseReadOnlyDatacentersTrue
VSPHERE.LOCAL\AdministratorFalseAdminDatacentersTrue
VSPHERE.LOCAL\AdministratorsTrueAdminDatacentersTrue
-

1.2.2 Test Cluster

The following table details the cluster configuration for cluster Test Cluster.

-
NameTest Cluster
DatacenterDatacenter
Number of Hosts0
Number of VMs0
HA EnabledFalse
DRS EnabledFalse
vSAN EnabledFalse
EVC Modeintel-ivybridge
VM Swap File PolicyWithVM
Connected Hosts 
-

1.2.2.1 HA Configuration

The following table details the vSphere HA configuration for cluster Test Cluster.

-
HA EnabledFalse
HA Admission Control EnabledFalse
HA Failover Level1
HA Restart PriorityMedium
HA Isolation ResponseDoNothing
Heartbeat Selection PolicyallFeasibleDsWithUserPreference
Heartbeat Datastores 
-

1.2.2.2 DRS Configuration

The following table details the vSphere DRS configuration for cluster Test Cluster.

-
DRS EnabledFalse
DRS Automation LevelFullyAutomated
DRS Migration Threshold3
-
-
VM Distribution 
Memory Metric for Load Balancing 
CPU Over-Commitment 
-

1.2.2.3 Permissions

The following table details the permissions assigned to cluster Test Cluster.

- - - - - -
User/GroupIs Group?RoleDefined InPropagate
VSPHERE.LOCAL\vpxd-extension-0cd516db-cbaa-434b-8b6f-90452748e378FalseAdminDatacentersTrue
VSPHERE.LOCAL\vpxd-0cd516db-cbaa-434b-8b6f-90452748e378FalseAdminDatacentersTrue
VSPHERE.LOCAL\vsphere-webclient-0cd516db-cbaa-434b-8b6f-90452748e378FalseReadOnlyDatacentersTrue
VSPHERE.LOCAL\AdministratorFalseAdminDatacentersTrue
VSPHERE.LOCAL\AdministratorsTrueAdminDatacentersTrue
-

1.2.3 VSAN-Cluster

The following table details the cluster configuration for cluster VSAN-Cluster.

-
NameVSAN-Cluster
DatacenterDatacenter
Number of Hosts3
Number of VMs3
HA EnabledTrue
DRS EnabledTrue
vSAN EnabledTrue
EVC Mode 
VM Swap File PolicyWithVM
Connected Hosts192.168.1.111, 192.168.1.112, 192.168.1.113
-

1.2.3.1 HA Configuration

The following table details the vSphere HA configuration for cluster VSAN-Cluster.

-
HA EnabledTrue
HA Admission Control EnabledTrue
HA Failover Level1
HA Restart PriorityMedium
HA Isolation ResponseDoNothing
Heartbeat Selection PolicyallFeasibleDsWithUserPreference
Heartbeat Datastores 
-

1.2.3.2 DRS Configuration

The following table details the vSphere DRS configuration for cluster VSAN-Cluster.

-
DRS EnabledTrue
DRS Automation LevelFullyAutomated
DRS Migration Threshold3
-
-
VM Distribution1
Memory Metric for Load Balancing100
CPU Over-Commitment 
-
1.2.3.2.1 DRS Cluster Groups
- - -
NameGroup TypeMembers
Test_VMsVMGroupTest_1, Test_2
Prod_HostsVMHostGroup192.168.1.111, 192.168.1.112, 192.168.1.113
-
1.2.3.2.2 DRS VM/Host Rules
- - -
NameTypeEnabledVM GroupVMHost Group
VM not on HostShouldNotRunOnFalseTest_VMsProd_Hosts
VM_to_HostShouldRunOnFalseTest_VMsProd_Hosts
-
1.2.3.2.3 DRS Rules
- - -
NameTypeEnabledMandatoryVirtual Machines
Test_VMsVMAntiAffinityTrueFalseTest_1, Test_2
TestVMAffinityFalseFalseTest_1, Test_2
-

1.2.3.3 Update Manager Baselines

- - - -
NameDescriptionTypeTarget TypeLast Update TimeNumber of Patches
Critical Host Patches (Predefined)A predefined baseline for all critical patches for HostsPatchHost28/02/2017 9:35:00 PM71
Non-Critical Host Patches (Predefined)A predefined baseline for all non-critical patches for HostsPatchHost28/02/2017 9:35:00 PM236
VMware ESXi 6.5.0 U2 (vSAN 6.6.1 Update 2, build 8294253)VMware ESXi 6.5.0 U2 (vSAN 6.6.1 Update 2, build 8294253)PatchHost23/05/2018 9:28:38 AM1
-

1.2.3.4 Permissions

The following table details the permissions assigned to cluster VSAN-Cluster.

- - - - - - -
User/GroupIs Group?RoleDefined InPropagate
VSPHERE.LOCAL\SolutionUsersTrueReadOnlyVSAN-ClusterFalse
VSPHERE.LOCAL\vpxd-extension-0cd516db-cbaa-434b-8b6f-90452748e378FalseAdminDatacentersTrue
VSPHERE.LOCAL\vpxd-0cd516db-cbaa-434b-8b6f-90452748e378FalseAdminDatacentersTrue
VSPHERE.LOCAL\vsphere-webclient-0cd516db-cbaa-434b-8b6f-90452748e378FalseReadOnlyDatacentersTrue
VSPHERE.LOCAL\AdministratorFalseAdminDatacentersTrue
VSPHERE.LOCAL\AdministratorsTrueAdminDatacentersTrue
-
-

1.3 Resource Pools

The following section provides information on the configuration of resource pools.

-
NameResources
IdResourcePool-resgroup-35
ParentNew Cluster
CPU Shares LevelNormal
Number of CPU Shares4000
CPU Reservation0 MHz
CPU Expandable ReservationTrue
CPU LimitUnlimited
Memory Shares LevelNormal
Number of Memory Shares163840
Memory Reservation0 GB
Memory Expandable ReservationTrue
Memory LimitUnlimited
-

-

-
NameTesting
IdResourcePool-resgroup-62
ParentResources
CPU Shares LevelNormal
Number of CPU Shares4000
CPU Reservation0 MHz
CPU Expandable ReservationTrue
CPU Limit1000 MHz
Memory Shares LevelNormal
Number of Memory Shares163840
Memory Reservation0 GB
Memory Expandable ReservationTrue
Memory LimitUnlimited
-

-

-
NameResources
IdResourcePool-resgroup-28
ParentTest Cluster
CPU Shares LevelNormal
Number of CPU Shares4000
CPU Reservation0 MHz
CPU Expandable ReservationTrue
CPU LimitUnlimited
Memory Shares LevelNormal
Number of Memory Shares163840
Memory Reservation0 GB
Memory Expandable ReservationTrue
Memory LimitUnlimited
-

-

-
NameResources
IdResourcePool-resgroup-8
ParentVSAN-Cluster
CPU Shares LevelNormal
Number of CPU Shares4000
CPU Reservation6612 MHz
CPU Expandable ReservationTrue
CPU Limit6612 MHz
Memory Shares LevelNormal
Number of Memory Shares163840
Memory Reservation0.70 GB
Memory Expandable ReservationTrue
Memory Limit0.70 GB
-
-

1.4 Hosts

The following section provides information on the configuration of VMware ESXi hosts.

- - - -
NameVersionBuildParentConnection StateCPU Usage MHzMemory Usage GBVM Count
192.168.1.1116.5.05969303VSAN-ClusterConnected1703.331
192.168.1.1126.5.05969303VSAN-ClusterConnected1353.331
192.168.1.1136.5.05969303VSAN-ClusterConnected1263.331
-

1.4.1 192.168.1.111

1.4.1.1 Hardware

The following section provides information on the host hardware configuration of 192.168.1.111.

-
Name192.168.1.111
ParentVSAN-Cluster
ManufacturerVMware, Inc.
ModelVMware Virtual Platform
Serial Number 
Asset Tag 
Processor TypeIntel(R) Xeon(R) CPU D-1528 @ 1.90GHz
HyperThreadingFalse
CPU Socket Count2
CPU Core Count2
CPU Thread Count2
CPU Speed1.9 GHz
Memory6 GB
NUMA Nodes1
NIC Count2
Maximum EVC Modeintel-broadwell
Power Management PolicyHigh Performance
Scratch Location/tmp/scratch
Bios Version6.00
Bios Release Date28/07/2017 12:00:00 AM
ESXi Version6.5.0
ESXi Build5969303
Uptime Days74.7
-
1.4.1.1.1 Boot Devices
-
Host192.168.1.111
Devicenaa.6000c29e041dd7e2fea7da7634a3eaf0
Boot Typelocal
VendorVMware
ModelVirtual disk
Size MB2048
Is SASfalse
Is SSDfalse
Is USBfalse
-
1.4.1.1.2 PCI Devices
- - - - -
VMkernel NamePCI AddressDevice ClassDevice NameVendor NameSlot Description
vmhba00000:13:00.0Serial Attached SCSI controllerPVSCSI SCSI ControllerVMware Inc.SCSI0
vmhba10000:00:07.1IDE interfacePIIX4 for 430TX/440BX/MX IDE ControllerIntel Corporation 
vmnic00000:03:00.0Ethernet controllervmxnet3 Virtual Ethernet ControllerVMware Inc.Ethernet0
vmnic10000:0b:00.0Ethernet controllervmxnet3 Virtual Ethernet ControllerVMware Inc.Ethernet1
-

1.4.1.2 System

The following section provides information on the host system configuration of 192.168.1.111.
1.4.1.2.1 Licensing
- - -
License TypeLicense Key
VMware vSphere 6 Enterprise Plus*****-*****-*****-079HM-29JHN
-
1.4.1.2.2 Host Profile
- - -
NameDescription
Host Profile TestHost Profile for doco report
-
1.4.1.2.3 Image Profile
- - -
Image ProfileVendorInstallation Date
(Updated) ESXi-6.5.0-4564106-standardVMware, Inc.28/02/2017 9:16:02 PM
-
1.4.1.2.4 Time Configuration
- - -
Time ZoneNTP Service RunningNTP Server(s)
UTCFalse0.au.pool.ntp.org, 1.au.pool.ntp.org
-
1.4.1.2.5 Syslog Configuration
- - -
SysLog ServerPort
192.168.1.110 
-
1.4.1.2.6 Update Manager Compliance
- - - - -
BaselineStatus
VMware ESXi 6.5.0 U2 (vSAN 6.6.1 Update 2, build 8294253)NotCompliant
Non-Critical Host Patches (Predefined)NotCompliant
Critical Host Patches (Predefined)NotCompliant
-

1.4.1.3 Storage

The following section provides information on the host storage configuration of 192.168.1.111.
1.4.1.3.1 Datastores
- -
NameTypeVersionTotal Capacity GBUsed Capacity GBFree Space GB% Used
vsanDatastorevsan 23.982.6321.3410.98
-

1.4.1.4 Network

The following section provides information on the host network configuration of 192.168.1.111.

-
VMHost192.168.1.111
Virtual SwitchesvSwitch0
VMKernel Adaptersvmk0
Physical Adaptersvmnic0, vmnic1
VMKernel Gateway192.168.1.1
IPv6 EnabledTrue
VMKernel IPv6 Gateway 
DNS Servers192.168.1.10, 8.8.8.8
Host Namevesxi65-1
Domain Name 
Search Domainlab.local
-
1.4.1.4.1 Physical Adapters
The following table details the physical network adapters for 192.168.1.111.

- - - -
Device NameMAC AddressBitrate/SecondFull DuplexWake on LAN Support
vmnic000:0c:29:57:f3:9810000TrueFalse
vmnic100:0c:29:57:f3:a210000TrueFalse
-
1.4.1.4.2 Cisco Discovery Protocol
- - - -
NICConnectedSwitchHardware PlatformPort ID
vmnic0Truedca5f4a56b91Cisco SG200-26P (PID:SLM2024PT)-VSDgi1
vmnic1Truedca5f4a56b91Cisco SG200-26P (PID:SLM2024PT)-VSDgi1
-
1.4.1.4.3 VMkernel Adapters
The following table details the VMkernel adapters for 192.168.1.111

-
Device Namevmk0
Network LabelManagement Network
MTU1500
MAC Address00:0c:29:57:f3:98
IP Address192.168.1.111
Subnet Mask255.255.255.0
vMotion TrafficFalse
FT LoggingFalse
Management TrafficTrue
vSAN TrafficTrue
-
1.4.1.4.4 Standard Virtual Switches
The following sections detail the standard virtual switch configuration for 192.168.1.111.

-
NamevSwitch0
MTU1500
Number of Ports1536
Number of Ports Available1528
Load BalancingLoadBalanceSrcId
Failover DetectionLinkStatus
Notify SwitchesTrue
Failback EnabledTrue
Active NICsvmnic0
Standby NICs 
Unused NICs 
-
1.4.1.4.5 Virtual Switch Security Policy
- -
vSwitchMAC Address ChangesForged TransmitsPromiscuous Mode
vSwitch0TrueTrueFalse
-
1.4.1.4.6 Virtual Switch NIC Teaming
- -
vSwitchLoad BalancingFailover DetectionNotify SwitchesFailback EnabledActive NICsStandby NICsUnused NICs
vSwitch0LoadBalanceSrcIdLinkStatusTrueTruevmnic0  
-
1.4.1.4.7 Virtual Port Groups
- - -
vSwitchPortgroupVLAN ID
vSwitch0Management Network0
vSwitch0VM Network0
-
1.4.1.4.8 Virtual Port Group Security Policy
- - -
vSwitchPortgroupMAC ChangesForged TransmitsPromiscuous Mode
vSwitch0VM NetworkTrueTrueFalse
vSwitch0Management NetworkTrueTrueFalse
-
1.4.1.4.9 Virtual Port Group NIC Teaming
- - -
vSwitchPortgroupLoad BalancingFailover DetectionNotify SwitchesFailback EnabledActive NICsStandby NICsUnused NICs
vSwitch0VM NetworkLoadBalanceSrcIdLinkStatusTrueTruevmnic0  
vSwitch0Management NetworkLoadBalanceSrcIdLinkStatusTrueTruevmnic0  
-

1.4.1.5 Security

The following section provides information on the host security configuration of 192.168.1.111.
1.4.1.5.1 Lockdown Mode
-
Lockdown ModeTrue
-
1.4.1.5.2 Services
- - - - - - - - - - - - - -
NameLabelPolicyRunningRequired
DCUIDirect Console UIonTrueFalse
lbtdLoad-Based Teaming DaemononTrueFalse
lwsmdActive Directory ServiceonTrueFalse
ntpdNTP DaemonoffFalseFalse
pcscdPC/SC Smart Card DaemonoffFalseFalse
sfcbd-watchdogCIM ServeronFalseFalse
snmpdSNMP ServeronFalseFalse
TSMESXi ShellonTrueFalse
TSM-SSHSSHonTrueFalse
vmsyslogdSyslog ServeronTrueTrue
vmware-fdmvSphere High Availability AgentonTrueFalse
vpxaVMware vCenter AgentonTrueFalse
xorgX.Org ServeronFalseFalse
-
1.4.1.5.3 Authentication Services
- - -
DomainDomain MembershipTrusted Domains
LAB.LOCALOk 
-

1.4.2 192.168.1.112

1.4.2.1 Hardware

The following section provides information on the host hardware configuration of 192.168.1.112.

-
Name192.168.1.112
ParentVSAN-Cluster
ManufacturerVMware, Inc.
ModelVMware Virtual Platform
Serial Number 
Asset Tag 
Processor TypeIntel(R) Xeon(R) CPU D-1528 @ 1.90GHz
HyperThreadingFalse
CPU Socket Count2
CPU Core Count2
CPU Thread Count2
CPU Speed1.9 GHz
Memory6 GB
NUMA Nodes1
NIC Count2
Maximum EVC Modeintel-broadwell
Power Management PolicyHigh Performance
Scratch Location/tmp/scratch
Bios Version6.00
Bios Release Date28/07/2017 12:00:00 AM
ESXi Version6.5.0
ESXi Build5969303
Uptime Days74.7
-
1.4.2.1.1 Boot Devices
-
Host192.168.1.112
Devicenaa.6000c29a60075960e1284ed911495826
Boot Typelocal
VendorVMware
ModelVirtual disk
Size MB2048
Is SASfalse
Is SSDfalse
Is USBfalse
-
1.4.2.1.2 PCI Devices
- - - - -
VMkernel NamePCI AddressDevice ClassDevice NameVendor NameSlot Description
vmhba00000:13:00.0Serial Attached SCSI controllerPVSCSI SCSI ControllerVMware Inc.SCSI0
vmhba10000:00:07.1IDE interfacePIIX4 for 430TX/440BX/MX IDE ControllerIntel Corporation 
vmnic00000:03:00.0Ethernet controllervmxnet3 Virtual Ethernet ControllerVMware Inc.Ethernet0
vmnic10000:0b:00.0Ethernet controllervmxnet3 Virtual Ethernet ControllerVMware Inc.Ethernet1
-

1.4.2.2 System

The following section provides information on the host system configuration of 192.168.1.112.
1.4.2.2.1 Licensing
- - -
License TypeLicense Key
VMware vSphere 6 Enterprise Plus*****-*****-*****-079HM-29JHN
-
1.4.2.2.2 Host Profile
- - -
NameDescription
Host Profile TestHost Profile for doco report
-
1.4.2.2.3 Image Profile
- - -
Image ProfileVendorInstallation Date
(Updated) ESXi-6.5.0-4564106-standardVMware, Inc.28/02/2017 9:16:28 PM
-
1.4.2.2.4 Time Configuration
- - -
Time ZoneNTP Service RunningNTP Server(s)
UTCFalse0.au.pool.ntp.org, 1.au.pool.ntp.org
-
1.4.2.2.5 Syslog Configuration
- - -
SysLog ServerPort
192.168.1.110 
-
1.4.2.2.6 Update Manager Compliance
- - - - -
BaselineStatus
VMware ESXi 6.5.0 U2 (vSAN 6.6.1 Update 2, build 8294253)NotCompliant
Non-Critical Host Patches (Predefined)NotCompliant
Critical Host Patches (Predefined)NotCompliant
-

1.4.2.3 Storage

The following section provides information on the host storage configuration of 192.168.1.112.
1.4.2.3.1 Datastores
- -
NameTypeVersionTotal Capacity GBUsed Capacity GBFree Space GB% Used
vsanDatastorevsan 23.982.6321.3410.98
-

1.4.2.4 Network

The following section provides information on the host network configuration of 192.168.1.112.

-
VMHost192.168.1.112
Virtual SwitchesvSwitch0
VMKernel Adaptersvmk0
Physical Adaptersvmnic0, vmnic1
VMKernel Gateway192.168.1.1
IPv6 EnabledTrue
VMKernel IPv6 Gateway 
DNS Servers192.168.1.10, 8.8.8.8
Host Namevesxi65-2
Domain Name 
Search Domainlab.local
-
1.4.2.4.1 Physical Adapters
The following table details the physical network adapters for 192.168.1.112.

- - - -
Device NameMAC AddressBitrate/SecondFull DuplexWake on LAN Support
vmnic000:0c:29:0a:c0:5710000TrueFalse
vmnic100:0c:29:0a:c0:6110000TrueFalse
-
1.4.2.4.2 Cisco Discovery Protocol
- - - -
NICConnectedSwitchHardware PlatformPort ID
vmnic0Truedca5f4a56b91Cisco SG200-26P (PID:SLM2024PT)-VSDgi1
vmnic1Truedca5f4a56b91Cisco SG200-26P (PID:SLM2024PT)-VSDgi1
-
1.4.2.4.3 VMkernel Adapters
The following table details the VMkernel adapters for 192.168.1.112

-
Device Namevmk0
Network LabelManagement Network
MTU1500
MAC Address00:0c:29:0a:c0:57
IP Address192.168.1.112
Subnet Mask255.255.255.0
vMotion TrafficFalse
FT LoggingFalse
Management TrafficTrue
vSAN TrafficTrue
-
1.4.2.4.4 Standard Virtual Switches
The following sections detail the standard virtual switch configuration for 192.168.1.112.

-
NamevSwitch0
MTU1500
Number of Ports1536
Number of Ports Available1528
Load BalancingLoadBalanceSrcId
Failover DetectionLinkStatus
Notify SwitchesTrue
Failback EnabledTrue
Active NICsvmnic0
Standby NICs 
Unused NICs 
-
1.4.2.4.5 Virtual Switch Security Policy
- -
vSwitchMAC Address ChangesForged TransmitsPromiscuous Mode
vSwitch0TrueTrueFalse
-
1.4.2.4.6 Virtual Switch NIC Teaming
- -
vSwitchLoad BalancingFailover DetectionNotify SwitchesFailback EnabledActive NICsStandby NICsUnused NICs
vSwitch0LoadBalanceSrcIdLinkStatusTrueTruevmnic0  
-
1.4.2.4.7 Virtual Port Groups
- - -
vSwitchPortgroupVLAN ID
vSwitch0Management Network0
vSwitch0VM Network0
-
1.4.2.4.8 Virtual Port Group Security Policy
- - -
vSwitchPortgroupMAC ChangesForged TransmitsPromiscuous Mode
vSwitch0VM NetworkTrueTrueFalse
vSwitch0Management NetworkTrueTrueFalse
-
1.4.2.4.9 Virtual Port Group NIC Teaming
- - -
vSwitchPortgroupLoad BalancingFailover DetectionNotify SwitchesFailback EnabledActive NICsStandby NICsUnused NICs
vSwitch0VM NetworkLoadBalanceSrcIdLinkStatusTrueTruevmnic0  
vSwitch0Management NetworkLoadBalanceSrcIdLinkStatusTrueTruevmnic0  
-

1.4.2.5 Security

The following section provides information on the host security configuration of 192.168.1.112.
1.4.2.5.1 Lockdown Mode
-
Lockdown ModeFalse
-
1.4.2.5.2 Services
- - - - - - - - - - - - - -
NameLabelPolicyRunningRequired
DCUIDirect Console UIonTrueFalse
lbtdLoad-Based Teaming DaemononTrueFalse
lwsmdActive Directory ServiceonTrueFalse
ntpdNTP DaemonoffFalseFalse
pcscdPC/SC Smart Card DaemonoffFalseFalse
sfcbd-watchdogCIM ServeronFalseFalse
snmpdSNMP ServeronFalseFalse
TSMESXi ShellonTrueFalse
TSM-SSHSSHonTrueFalse
vmsyslogdSyslog ServeronTrueTrue
vmware-fdmvSphere High Availability AgentonTrueFalse
vpxaVMware vCenter AgentonTrueFalse
xorgX.Org ServeronFalseFalse
-
1.4.2.5.3 Authentication Services
- - -
DomainDomain MembershipTrusted Domains
LAB.LOCALOk 
-

1.4.3 192.168.1.113

1.4.3.1 Hardware

The following section provides information on the host hardware configuration of 192.168.1.113.

-
Name192.168.1.113
ParentVSAN-Cluster
ManufacturerVMware, Inc.
ModelVMware Virtual Platform
Serial Number 
Asset Tag 
Processor TypeIntel(R) Xeon(R) CPU D-1528 @ 1.90GHz
HyperThreadingFalse
CPU Socket Count2
CPU Core Count2
CPU Thread Count2
CPU Speed1.9 GHz
Memory6 GB
NUMA Nodes1
NIC Count2
Maximum EVC Modeintel-broadwell
Power Management PolicyHigh Performance
Scratch Location/tmp/scratch
Bios Version6.00
Bios Release Date28/07/2017 12:00:00 AM
ESXi Version6.5.0
ESXi Build5969303
Uptime Days74.7
-
1.4.3.1.1 Boot Devices
-
Host192.168.1.113
Devicenaa.6000c2994ed56a57b2049108de88effd
Boot Typelocal
VendorVMware
ModelVirtual disk
Size MB2048
Is SASfalse
Is SSDfalse
Is USBfalse
-
1.4.3.1.2 PCI Devices
- - - - -
VMkernel NamePCI AddressDevice ClassDevice NameVendor NameSlot Description
vmhba00000:13:00.0Serial Attached SCSI controllerPVSCSI SCSI ControllerVMware Inc.SCSI0
vmhba10000:00:07.1IDE interfacePIIX4 for 430TX/440BX/MX IDE ControllerIntel Corporation 
vmnic00000:03:00.0Ethernet controllervmxnet3 Virtual Ethernet ControllerVMware Inc.Ethernet0
vmnic10000:0b:00.0Ethernet controllervmxnet3 Virtual Ethernet ControllerVMware Inc.Ethernet1
-

1.4.3.2 System

The following section provides information on the host system configuration of 192.168.1.113.
1.4.3.2.1 Licensing
- - -
License TypeLicense Key
VMware vSphere 6 Enterprise Plus*****-*****-*****-079HM-29JHN
-
1.4.3.2.2 Host Profile
- - -
NameDescription
Host Profile TestHost Profile for doco report
-
1.4.3.2.3 Image Profile
- - -
Image ProfileVendorInstallation Date
(Updated) ESXi-6.5.0-4564106-standardVMware, Inc.28/02/2017 9:17:02 PM
-
1.4.3.2.4 Time Configuration
- - -
Time ZoneNTP Service RunningNTP Server(s)
UTCFalse0.au.pool.ntp.org, 1.au.pool.ntp.org
-
1.4.3.2.5 Syslog Configuration
- - -
SysLog ServerPort
192.168.1.110 
-
1.4.3.2.6 Update Manager Compliance
- - - - -
BaselineStatus
VMware ESXi 6.5.0 U2 (vSAN 6.6.1 Update 2, build 8294253)NotCompliant
Non-Critical Host Patches (Predefined)NotCompliant
Critical Host Patches (Predefined)NotCompliant
-

1.4.3.3 Storage

The following section provides information on the host storage configuration of 192.168.1.113.
1.4.3.3.1 Datastores
- -
NameTypeVersionTotal Capacity GBUsed Capacity GBFree Space GB% Used
vsanDatastorevsan 23.982.6321.3410.98
-

1.4.3.4 Network

The following section provides information on the host network configuration of 192.168.1.113.

-
VMHost192.168.1.113
Virtual SwitchesvSwitch0
VMKernel Adaptersvmk0
Physical Adaptersvmnic0, vmnic1
VMKernel Gateway192.168.1.1
IPv6 EnabledTrue
VMKernel IPv6 Gateway 
DNS Servers192.168.1.10, 8.8.8.8
Host Namevesxi65-3
Domain Name 
Search Domainlab.local
-
1.4.3.4.1 Physical Adapters
The following table details the physical network adapters for 192.168.1.113.

- - - -
Device NameMAC AddressBitrate/SecondFull DuplexWake on LAN Support
vmnic000:0c:29:a2:26:0410000TrueFalse
vmnic100:0c:29:a2:26:0e10000TrueFalse
-
1.4.3.4.2 Cisco Discovery Protocol
- - - -
NICConnectedSwitchHardware PlatformPort ID
vmnic0Truedca5f4a56b91Cisco SG200-26P (PID:SLM2024PT)-VSDgi1
vmnic1Truedca5f4a56b91Cisco SG200-26P (PID:SLM2024PT)-VSDgi1
-
1.4.3.4.3 VMkernel Adapters
The following table details the VMkernel adapters for 192.168.1.113

-
Device Namevmk0
Network LabelManagement Network
MTU1500
MAC Address00:0c:29:a2:26:04
IP Address192.168.1.113
Subnet Mask255.255.255.0
vMotion TrafficFalse
FT LoggingFalse
Management TrafficTrue
vSAN TrafficTrue
-
1.4.3.4.4 Standard Virtual Switches
The following sections detail the standard virtual switch configuration for 192.168.1.113.

-
NamevSwitch0
MTU1500
Number of Ports1536
Number of Ports Available1528
Load BalancingLoadBalanceSrcId
Failover DetectionLinkStatus
Notify SwitchesTrue
Failback EnabledTrue
Active NICsvmnic0
Standby NICs 
Unused NICs 
-
1.4.3.4.5 Virtual Switch Security Policy
- -
vSwitchMAC Address ChangesForged TransmitsPromiscuous Mode
vSwitch0TrueTrueFalse
-
1.4.3.4.6 Virtual Switch NIC Teaming
- -
vSwitchLoad BalancingFailover DetectionNotify SwitchesFailback EnabledActive NICsStandby NICsUnused NICs
vSwitch0LoadBalanceSrcIdLinkStatusTrueTruevmnic0  
-
1.4.3.4.7 Virtual Port Groups
- - -
vSwitchPortgroupVLAN ID
vSwitch0Management Network0
vSwitch0VM Network0
-
1.4.3.4.8 Virtual Port Group Security Policy
- - -
vSwitchPortgroupMAC ChangesForged TransmitsPromiscuous Mode
vSwitch0VM NetworkTrueTrueFalse
vSwitch0Management NetworkTrueTrueFalse
-
1.4.3.4.9 Virtual Port Group NIC Teaming
- - -
vSwitchPortgroupLoad BalancingFailover DetectionNotify SwitchesFailback EnabledActive NICsStandby NICsUnused NICs
vSwitch0VM NetworkLoadBalanceSrcIdLinkStatusTrueTruevmnic0  
vSwitch0Management NetworkLoadBalanceSrcIdLinkStatusTrueTruevmnic0  
-

1.4.3.5 Security

The following section provides information on the host security configuration of 192.168.1.113.
1.4.3.5.1 Lockdown Mode
-
Lockdown ModeFalse
-
1.4.3.5.2 Services
- - - - - - - - - - - - - -
NameLabelPolicyRunningRequired
DCUIDirect Console UIonTrueFalse
lbtdLoad-Based Teaming DaemononTrueFalse
lwsmdActive Directory ServiceonTrueFalse
ntpdNTP DaemonoffFalseFalse
pcscdPC/SC Smart Card DaemonoffFalseFalse
sfcbd-watchdogCIM ServeronFalseFalse
snmpdSNMP ServeronFalseFalse
TSMESXi ShellonTrueFalse
TSM-SSHSSHonTrueFalse
vmsyslogdSyslog ServeronTrueTrue
vmware-fdmvSphere High Availability AgentonTrueFalse
vpxaVMware vCenter AgentonTrueFalse
xorgX.Org ServeronFalseFalse
-
1.4.3.5.3 Authentication Services
- - -
DomainDomain MembershipTrusted Domains
LAB.LOCALOk 
-
-

1.5 Distributed Virtual Switches

The following section provides information on the Distributed Virtual Switch configuration.

- - -
VDSwitchDatacenterManufacturerVersionNumber of UplinksNumber of PortsHost Count
DSwitchDatacenterVMware, Inc.6.5.04203
DSwitch 1DatacenterVMware, Inc.5.5.0280
-

1.5.1 DSwitch

1.5.1.1 General Properties

-
NameDSwitch
DatacenterDatacenter
ManufacturerVMware, Inc.
Version6.5.0
Number of Uplinks4
Number of Ports20
MTU1500
Network I/O Control EnabledFalse
Discovery ProtocolCDP
Discovery Protocol OperationListen
Connected Hosts192.168.1.111, 192.168.1.112, 192.168.1.113
-

1.5.1.2 Uplinks

- - - - - - - - - - - - -
VDSwitchVM HostUplink NamePhysical Network AdapterUplink Port Group
DSwitch192.168.1.111Uplink 1 DSwitch-DVUplinks-21
DSwitch192.168.1.111Uplink 2vmnic1DSwitch-DVUplinks-21
DSwitch192.168.1.111Uplink 3 DSwitch-DVUplinks-21
DSwitch192.168.1.111Uplink 4 DSwitch-DVUplinks-21
DSwitch192.168.1.112Uplink 1 DSwitch-DVUplinks-21
DSwitch192.168.1.112Uplink 2vmnic1DSwitch-DVUplinks-21
DSwitch192.168.1.112Uplink 3 DSwitch-DVUplinks-21
DSwitch192.168.1.112Uplink 4 DSwitch-DVUplinks-21
DSwitch192.168.1.113Uplink 1 DSwitch-DVUplinks-21
DSwitch192.168.1.113Uplink 2vmnic1DSwitch-DVUplinks-21
DSwitch192.168.1.113Uplink 3 DSwitch-DVUplinks-21
DSwitch192.168.1.113Uplink 4 DSwitch-DVUplinks-21
-

1.5.1.3 Security

- -
VDSwitchAllow PromiscuousForged TransmitsMAC Address Changes
DSwitchFalseFalseFalse
-

1.5.1.4 Traffic Shaping

- - -
VDSwitchDirectionEnabledAverage Bandwidth (kbit/s)Peak Bandwidth (kbit/s)Burst Size (KB)
DSwitchInFalse100000000100000000104857600
DSwitchOutFalse100000000100000000104857600
-

1.5.1.5 Port Groups

- - -
VDSwitchPortgroupDatacenterVLAN ConfigurationPort BindingNumber of Ports
DSwitchDPortGroupDatacenterVLAN 8Static8
DSwitchDSwitch-DVUplinks-21DatacenterVLAN Trunk [0-4094]Static12
-

1.5.1.6 Port Group Security

- - -
VDSwitchPort GroupAllow PromiscuousForged TransmitsMAC Address Changes
DSwitchDSwitch-DVUplinks-21FalseTrueFalse
DSwitchDPortGroupFalseFalseFalse
-

1.5.1.7 Port Group NIC Teaming

- - -
VDSwitchPort GroupLoad BalancingFailover DetectionNotify SwitchesFailback EnabledActive UplinksStandby UplinksUnused Uplinks
DSwitchDPortGroupLoadBalanceSrcIdLinkStatusTrueTrueUplink 1
Uplink 2
Uplink 3
Uplink 4
  
DSwitchDSwitch-DVUplinks-21LoadBalanceSrcIdLinkStatusTrueTrue  Uplink 1
Uplink 2
Uplink 3
Uplink 4
-

1.5.1.8 Private VLANs

- - - -
Primary VLAN IDPrivate VLAN TypeSecondary VLAN ID
1Promiscuous1
1Isolated2
1Community3
-

1.5.2 DSwitch 1

1.5.2.1 General Properties

-
NameDSwitch 1
DatacenterDatacenter
ManufacturerVMware, Inc.
Version5.5.0
Number of Uplinks2
Number of Ports8
MTU1500
Network I/O Control EnabledFalse
Discovery ProtocolCDP
Discovery Protocol OperationListen
Connected Hosts 
-

1.5.2.2 Security

- -
VDSwitchAllow PromiscuousForged TransmitsMAC Address Changes
DSwitch 1FalseFalseFalse
-

1.5.2.3 Traffic Shaping

- - -
VDSwitchDirectionEnabledAverage Bandwidth (kbit/s)Peak Bandwidth (kbit/s)Burst Size (KB)
DSwitch 1InFalse100000000100000000104857600
DSwitch 1OutFalse100000000100000000104857600
-

1.5.2.4 Port Groups

- - -
VDSwitchPortgroupDatacenterVLAN ConfigurationPort BindingNumber of Ports
DSwitch 1DPortGroup 1Datacenter Static8
DSwitch 1DSwitch 1-DVUplinks-24DatacenterVLAN Trunk [0-4094]Static0
-

1.5.2.5 Port Group Security

- - -
VDSwitchPort GroupAllow PromiscuousForged TransmitsMAC Address Changes
DSwitch 1DPortGroup 1FalseFalseFalse
DSwitch 1DSwitch 1-DVUplinks-24FalseTrueFalse
-

1.5.2.6 Port Group NIC Teaming

- - -
VDSwitchPort GroupLoad BalancingFailover DetectionNotify SwitchesFailback EnabledActive UplinksStandby UplinksUnused Uplinks
DSwitch 1DPortGroup 1LoadBalanceSrcIdLinkStatusTrueTrueUplink 1
Uplink 2
  
DSwitch 1DSwitch 1-DVUplinks-24LoadBalanceSrcIdLinkStatusTrueTrue  Uplink 1
Uplink 2
-
-

1.6 vSAN

The following section provides information on the vSAN configuration.

1.6.1 VSAN-Cluster

-
NameVSAN-Cluster
TypeAll-Flash
Version6.6.1
Number of Hosts3
Stretched ClusterFalse
Disk Format Version5
Total Number of Disks6
Total Number of Disk Groups3
Disk Claim ModeManual
Deduplication and CompressionFalse
Encryption EnabledFalse
Health Check EnabledTrue
HCL Last Updated5/08/2018 5:03:21 AM
-

1.7 Storage

The following section provides information on the VMware vSphere storage configuration.

- -
NameTypeTotal Capacity GBUsed Capacity GBFree Space GB% UsedHost Count
vsanDatastorevsan23.982.6321.3410.983
-

1.7.1 Datastore Specifications

- -
NameDatacenterTypeVersionStateSIOC EnabledCongestion Threshold ms
vsanDatastoreDatacentervsan AvailableFalse 
-
-

1.8 Virtual Machines

The following section provides detailed information on Virtual Machines.

1.8.1 Test_1

-
NameTest_1
Operating System 
Hardware Versionv13
Power StatePoweredOff
VM Tools StatustoolsNotInstalled
Host192.168.1.112
Parent FolderDiscovered virtual machine
Parent Resource PoolResources
vCPUs1
Cores per Socket1
Total vCPUs1
CPU ResourcesNormal / 1000
CPU Reservation0
CPU Limit0 MHz
Memory Allocation4 GB
Memory ResourcesNormal / 40960
-

1.8.2 Test_2

-
NameTest_2
Operating System 
Hardware Versionv13
Power StatePoweredOff
VM Tools StatustoolsNotInstalled
Host192.168.1.111
Parent Foldervm
Parent Resource PoolResources
vCPUs1
Cores per Socket1
Total vCPUs1
CPU ResourcesNormal / 1000
CPU Reservation0
CPU Limit0 MHz
Memory Allocation4 GB
Memory ResourcesNormal / 40960
-

1.8.3 Test_3

-
NameTest_3
Operating System 
Hardware Versionv13
Power StatePoweredOff
VM Tools Status 
Host192.168.1.113
Parent Foldervm
Parent Resource PoolResources
vCPUs1
Cores per Socket1
Total vCPUs1
CPU ResourcesNormal / 1000
CPU Reservation0
CPU Limit0 MHz
Memory Allocation4 GB
Memory ResourcesNormal / 40960
-

1.8.4 VM Snapshots

- -
Virtual MachineNameDescriptionDays Old
Test_2VM Snapshot 3%252f9%252f2018 3:00 PM UTC+11:00 149
-
-

1.9 VMware Update Manager

The following section provides information on VMware Update Manager.

1.9.1 Baselines

- - - -
NameDescriptionTypeTarget TypeLast Update TimeNumber of Patches
Critical Host Patches (Predefined)A predefined baseline for all critical patches for HostsPatchHost28/02/2017 9:35:00 PM71
Non-Critical Host Patches (Predefined)A predefined baseline for all non-critical patches for HostsPatchHost28/02/2017 9:35:00 PM236
VMware ESXi 6.5.0 U2 (vSAN 6.6.1 Update 2, build 8294253)VMware ESXi 6.5.0 U2 (vSAN 6.6.1 Update 2, build 8294253)PatchHost23/05/2018 9:28:38 AM1
-
diff --git a/Samples/Sample_vSphere_Report_1.png b/Samples/Sample_vSphere_Report_1.png deleted file mode 100644 index db1bf3f21ed7110731f94fa5cf377d38d0d95ad3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58176 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%0DrDsvlnfr{)>d%0 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 f^ZzYbHeS78e1WL9a1GDDf`8 - - param ( - [String[]] $Target, - [PSCredential] $Credential - ) - - Write-PScriboMessage -Plugin "Module" -IsWarning "Please refer to www.asbuiltreport.com for more detailed information about this project." - Write-PScriboMessage -Plugin "Module" -IsWarning "Do not forget to update your report configuration file after each new version release." - Write-PScriboMessage -Plugin "Module" -IsWarning "Documentation: https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere" - Write-PScriboMessage -Plugin "Module" -IsWarning "Issues or bug reporting: https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues" - - # Check the current AsBuiltReport.VMware.vSphere module - Try { - $InstalledVersion = Get-Module -ListAvailable -Name AsBuiltReport.VMware.vSphere -ErrorAction SilentlyContinue | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version - - if ($InstalledVersion) { - Write-PScriboMessage -Plugin "Module" -IsWarning "AsBuiltReport.VMware.vSphere $($InstalledVersion.ToString()) is currently installed." - $LatestVersion = Find-Module -Name AsBuiltReport.VMware.vSphere -Repository PSGallery -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Version - if ($LatestVersion -gt $InstalledVersion) { - Write-PScriboMessage -Plugin "Module" -IsWarning "AsBuiltReport.VMware.vSphere $($LatestVersion.ToString()) is available." - Write-PScriboMessage -Plugin "Module" -IsWarning "Run 'Update-Module -Name AsBuiltReport.VMware.vSphere -Force' to install the latest version." - } - } - } Catch { - Write-PScriboMessage -Plugin "Module" -IsWarning $_.Exception.Message - } - - # Import Report Configuration - $Report = $ReportConfig.Report - $InfoLevel = $ReportConfig.InfoLevel - $Options = $ReportConfig.Options - # Used to set values to TitleCase where required - $TextInfo = (Get-Culture).TextInfo - - #region Script Body - #---------------------------------------------------------------------------------------------# - # SCRIPT BODY # - #---------------------------------------------------------------------------------------------# - # Connect to vCenter Server using supplied credentials - foreach ($VIServer in $Target) { - try { - Write-PScriboMessage "Connecting to vCenter Server '$VIServer'." - $vCenter = Connect-VIServer $VIServer -Credential $Credential -ErrorAction Stop - } catch { - Write-Error $_ - } - - #region Generate vSphere report - if ($vCenter) { - # Check logged in user has sufficient privileges to generate an As Built Report - Write-PScriboMessage 'Checking vCenter user privileges.' - Try { - $AuthMgr = Get-View $($vCenter.ExtensionData.Content.AuthorizationManager) - $UserPrivileges = ($authMgr.FetchUserPrivilegeOnEntities("Folder-group-d1", $vCenter.User)).privileges - } Catch { - Write-PScriboMessage 'Unable to obtain vCenter user privileges.' - } - - # Create a lookup hashtable to quickly link VM MoRefs to Names - # Exclude VMware Site Recovery Manager placeholder VMs - Write-PScriboMessage 'Creating VM lookup hashtable.' - $VMs = Get-VM -Server $vCenter | Where-Object { - $_.ExtensionData.Config.ManagedBy.ExtensionKey -notlike 'com.vmware.vcDr*' - } | Sort-Object Name - $VMLookup = @{ } - foreach ($VM in $VMs) { - $VMLookup.($VM.Id) = $VM.Name - } - - # Create a lookup hashtable to link Host MoRefs to Names - # Exclude VMware HCX hosts and ESX/ESXi versions prior to vSphere 5.0 from VMHost lookup - Write-PScriboMessage 'Creating VMHost lookup hashtable.' - $VMHosts = Get-VMHost -Server $vCenter | Where-Object { $_.Model -notlike "*VMware Mobility Platform" -and $_.Version -gt 5 } | Sort-Object Name - $VMHostLookup = @{ } - foreach ($VMHost in $VMHosts) { - $VMHostLookup.($VMHost.Id) = $VMHost.Name - } - - # Create a lookup hashtable to link Datastore MoRefs to Names - Write-PScriboMessage 'Creating Datastore lookup hashtable.' - $Datastores = Get-Datastore -Server $vCenter | Where-Object { ($_.State -eq 'Available') -and ($_.CapacityGB -gt 0) } | Sort-Object Name - $DatastoreLookup = @{ } - foreach ($Datastore in $Datastores) { - $DatastoreLookup.($Datastore.Id) = $Datastore.Name - } - - # Create a lookup hashtable to link VDS Portgroups MoRefs to Names - Write-PScriboMessage 'Creating VDPortGroup lookup hashtable.' - $VDPortGroups = Get-VDPortgroup -Server $vCenter | Sort-Object Name - $VDPortGroupLookup = @{ } - foreach ($VDPortGroup in $VDPortGroups) { - $VDPortGroupLookup.($VDPortGroup.Key) = $VDPortGroup.Name - } - - # Create a lookup hashtable to link EVC Modes to Names - Write-PScriboMessage 'Creating EVC lookup hashtable.' - $SupportedEvcModes = $vCenter.ExtensionData.Capability.SupportedEVCMode - $EvcModeLookup = @{ } - foreach ($EvcMode in $SupportedEvcModes) { - $EvcModeLookup.($EvcMode.Key) = $EvcMode.Label - } - - $si = Get-View ServiceInstance -Server $vCenter - $extMgr = Get-View -Id $si.Content.ExtensionManager -Server $vCenter - - #region VMware Update Manager Server Name - Write-PScriboMessage 'Checking for VMware Update Manager Server.' - $VumServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vcIntegrity' } | - Select-Object @{ - N = 'Name'; - E = { ($_.Server | Where-Object { $_.Type -eq 'SOAP' -and $_.Company -eq 'VMware, Inc.' } | - Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } - } - #endregion VMware Update Manager Server Name - - #region VxRail Manager Server Name - Write-PScriboMessage 'Checking for VxRail Manager Server.' - $VxRailMgr = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vxrail' } | - Select-Object @{ - N = 'Name'; - E = { ($_.Server | Where-Object { $_.Type -eq 'HTTPS' } | - Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } - } - #endregion VxRail Manager Server Name - - #region Site Recovery Manager Server Name - Write-PScriboMessage 'Checking for VMware Site Recovery Manager Server.' - $SrmServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vcDr' } | - Select-Object @{ - N = 'Name'; - E = { ($_.Server | Where-Object { $_.Company -eq 'VMware, Inc.' } | - Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } - } - #endregion Site Recovery Manager Server Name - - #region NSX-T Manager Server Name - Write-PScriboMessage 'Checking for VMware NSX-T Manager Server.' - $NsxtServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.nsx.management.nsxt' } | - Select-Object @{ - N = 'Name'; - E = { ($_.Server | Where-Object { ($_.Company -eq 'VMware') -and ($_.Type -eq 'VIP') } | - Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } - } - #endregion NSX-T Manager Server Name - - #region Tag Information - Try { - Write-PScriboMessage "Collecting tag information." - $TagAssignments = Get-TagAssignment -Server $vCenter -ErrorAction SilentlyContinue - $Tags = Get-Tag -Server $vCenter | Sort-Object Name, Category - $TagCategories = Get-TagCategory -Server $vCenter | Sort-Object Name | Select-Object Name, Description, Cardinality -Unique - } Catch { - Write-PScriboMessage -IsWarning "Error collecting tag information. $($_.Exception.Message)" - } - #endregion Tag Information - - #region vCenter Advanced Settings - Write-PScriboMessage "Collecting $vCenter advanced settings." - $vCenterAdvSettings = Get-AdvancedSetting -Entity $vCenter - $vCenterServerName = ($vCenterAdvSettings | Where-Object { $_.name -eq 'VirtualCenter.FQDN' }).Value - $vCenterServerName = $vCenterServerName.ToString().ToLower() - #endregion vCenter Advanced Settings - - #region vCenter Server Heading1 Section - Section -Style Heading1 $vCenterServerName { - #region vCenter Server Section - Write-PScriboMessage "vCenter InfoLevel set at $($InfoLevel.vCenter)." - if ($InfoLevel.vCenter -ge 1) { - Section -Style Heading2 'vCenter Server' { - Paragraph "The following sections detail the configuration of vCenter Server $vCenterServerName." - BlankLine - # Gather basic vCenter Server Information - $vCenterServerInfo = [PSCustomObject]@{ - 'vCenter Server' = $vCenterServerName - 'IP Address' = ($vCenterAdvSettings | Where-Object { $_.name -like 'VirtualCenter.AutoManagedIPV4' }).Value - 'Version' = $vCenter.Version - 'Build' = $vCenter.Build - } - #region vCenter Server Summary & Advanced Summary - if ($InfoLevel.vCenter -le 2) { - $TableParams = @{ - Name = "vCenter Server Summary - $vCenterServerName" - ColumnWidths = 25, 25, 25, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vCenterServerInfo | Table @TableParams - } - #endregion vCenter Server Summary & Advanced Summary - - #region vCenter Server Detailed Information - if ($InfoLevel.vCenter -ge 3) { - $MemberProps = @{ - 'InputObject' = $vCenterServerInfo - 'MemberType' = 'NoteProperty' - } - #region vCenter Server Detail - if ($UserPrivileges -contains 'Global.Licenses') { - $vCenterLicense = Get-License -vCenter $vCenter - Add-Member @MemberProps -Name 'Product' -Value $vCenterLicense.Product - Add-Member @MemberProps -Name 'License Key' -Value $vCenterLicense.LicenseKey - Add-Member @MemberProps -Name 'License Expiration' -Value $vCenterLicense.Expiration - } else { - Write-PScriboMessage "Insufficient user privileges to report vCenter Server licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned." - } - - Add-Member @MemberProps -Name 'Instance ID' -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'instance.id' }).Value - - if ($vCenter.Version -ge 6) { - Add-Member @MemberProps -Name 'HTTP Port' -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.rhttpproxy.httpport' }).Value - Add-Member @MemberProps -Name 'HTTPS Port' -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.rhttpproxy.httpsport' }).Value - Add-Member @MemberProps -Name 'Platform Services Controller' -Value ((($vCenterAdvSettings).Where{ $_.name -eq 'config.vpxd.sso.admin.uri' }).Value).Split('/')[2] - } - if ($VumServer.Name) { - Add-Member @MemberProps -Name 'Update Manager Server' -Value $VumServer.Name - } - if ($SrmServer.Name) { - Add-Member @MemberProps -Name 'Site Recovery Manager Server' -Value $SrmServer.Name - } - if ($NsxtServer.Name) { - Add-Member @MemberProps -Name 'NSX-T Manager Server' -Value $NsxtServer.Name - } - if ($VxRailMgr.Name) { - Add-Member @MemberProps -Name 'VxRail Manager Server' -Value $VxRailMgr.Name - } - if ($Healthcheck.vCenter.Licensing) { - $vCenterServerInfo | Where-Object { $_.'Product' -like '*Evaluation*' } | Set-Style -Style Warning -Property 'Product' - $vCenterServerInfo | Where-Object { $null -eq $_.'Product' } | Set-Style -Style Warning -Property 'Product' - $vCenterServerInfo | Where-Object { $_.'License Key' -like '*-00000-00000' } | Set-Style -Style Warning -Property 'License Key' - $vCenterServerInfo | Where-Object { $_.'License Expiration' -eq 'Expired' } | Set-Style -Style Critical -Property 'License Expiration' - } - $TableParams = @{ - Name = "vCenter Server Configuration - $vCenterServerName" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vCenterServerInfo | Table @TableParams - #endregion vCenter Server Detail - - #region vCenter Server Database Settings - Section -Style Heading3 'Database Settings' { - $vCenterDbInfo = [PSCustomObject]@{ - 'Database Type' = $TextInfo.ToTitleCase(($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.odbc.dbtype' }).Value) - 'Data Source Name' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.odbc.dsn' }).Value - 'Maximum Database Connection' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'VirtualCenter.MaxDBConnection' }).Value - } - $TableParams = @{ - Name = "Database Settings - $vCenterServerName" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vCenterDbInfo | Table @TableParams - } - #endregion vCenter Server Database Settings - - #region vCenter Server Mail Settings - Section -Style Heading3 'Mail Settings' { - $vCenterMailInfo = [PSCustomObject]@{ - 'SMTP Server' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.smtp.server' }).Value - 'SMTP Port' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.smtp.port' }).Value - 'Mail Sender' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.sender' }).Value - } - if ($Healthcheck.vCenter.Mail) { - $vCenterMailInfo | Where-Object { !($_.'SMTP Server') } | Set-Style -Style Critical -Property 'SMTP Server' - $vCenterMailInfo | Where-Object { !($_.'SMTP Port') } | Set-Style -Style Critical -Property 'SMTP Port' - $vCenterMailInfo | Where-Object { !($_.'Mail Sender') } | Set-Style -Style Critical -Property 'Mail Sender' - } - $TableParams = @{ - Name = "Mail Settings - $vCenterServerName" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vCenterMailInfo | Table @TableParams - } - #endregion vCenter Server Mail Settings - - #region vCenter Server Historical Statistics - Section -Style Heading3 'Historical Statistics' { - $vCenterHistoricalStats = Get-vCenterStats | Select-Object @{L = 'Interval Duration'; E = { $_.IntervalDuration } }, @{L = 'Interval Enabled'; E = { $_.IntervalEnabled } }, - @{L = 'Save Duration'; E = { $_.SaveDuration } }, @{L = 'Statistics Level'; E = { $_.StatsLevel } } -Unique - $TableParams = @{ - Name = "Historical Statistics - $vCenterServerName" - ColumnWidths = 25, 25, 25, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vCenterHistoricalStats | Table @TableParams - } - #endregion vCenter Server Historical Statistics - - #region vCenter Server Licensing - if ($UserPrivileges -contains 'Global.Licenses') { - Section -Style Heading3 'Licensing' { - $Licenses = Get-License -Licenses | Select-Object Product, @{L = 'License Key'; E = { ($_.LicenseKey) } }, Total, Used, @{L = 'Available'; E = { ($_.total) - ($_.Used) } }, Expiration -Unique - if ($Healthcheck.vCenter.Licensing) { - $Licenses | Where-Object { $_.Product -eq 'Product Evaluation' } | Set-Style -Style Warning - $Licenses | Where-Object { $_.Expiration -eq 'Expired' } | Set-Style -Style Critical - } - $TableParams = @{ - Name = "Licensing - $vCenterServerName" - ColumnWidths = 25, 25, 12, 12, 12, 14 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $Licenses | Sort-Object 'Product', 'License Key' | Table @TableParams - } - } else { - Write-PScriboMessage "Insufficient user privileges to report vCenter Server licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned." - } - #endregion vCenter Server Licensing - - #region vCenter Server Certificate - if ($vCenter.Version -ge 6) { - Section -Style Heading3 'Certificate' { - $VcenterCertMgmt = [PSCustomObject]@{ - 'Country' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.country' }).Value - 'Email' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.email' }).Value - 'Locality' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.localityName' }).Value - 'State' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.state' }).Value - 'Organization' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.organizationName' }).Value - 'Organization Unit' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.organizationalUnitName' }).Value - 'Validity' = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.daysValid'}).Value / 365) years" - 'Mode' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.mode' }).Value - 'Soft Threshold' = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.softThreshold'}).Value) days" - 'Hard Threshold' = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.hardThreshold'}).Value) days" - 'Minutes Before' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.minutesBefore' }).Value - 'Poll Interval' = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.pollIntervalDays'}).Value) days" - } - $TableParams = @{ - Name = "Certificate - $vCenterServerName" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VcenterCertMgmt | Table @TableParams - } - } - #endregion vCenter Server Certificate - - #region vCenter Server Roles - Section -Style Heading3 'Roles' { - $VIRoles = Get-VIRole -Server $vCenter | Where-Object { $null -ne $_.PrivilegeList } | Sort-Object Name - $VIRoleInfo = foreach ($VIRole in $VIRoles) { - [PSCustomObject]@{ - 'Role' = $VIRole.Name - 'System Role' = if ($VIRole.IsSystem) { - 'Yes' - } else { - 'No' - } - 'Privilege List' = ($VIRole.PrivilegeList).Replace(".", " > ") | Select-Object -Unique - } - } - if ($InfoLevel.vCenter -ge 4) { - $VIRoleInfo | ForEach-Object { - Section -Style NOTOCHeading5 -ExcludeFromTOC $($_.Role) { - $TableParams = @{ - Name = "Role $($_.Role) - $vCenterServerName" - ColumnWidths = 35, 15, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $_ | Table @TableParams - } - } - } else { - $TableParams = @{ - Name = "Roles - $vCenterServerName" - Columns = 'Role', 'System Role' - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VIRoleInfo | Table @TableParams - } - } - #endregion vCenter Server Roles - - #region vCenter Server Tags - if ($Tags) { - Section -Style Heading3 'Tags' { - $TagInfo = foreach ($Tag in $Tags) { - [PSCustomObject] @{ - 'Tag' = $Tag.Name - 'Description' = if ($Tag.Description) { - $Tag.Description - } else { - 'None' - } - 'Category' = if ($Tag.Category) { - $Tag.Category - } else { - 'None' - } - } - } - $TableParams = @{ - Name = "Tags - $vCenterServerName" - ColumnWidths = 30, 40, 30 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $TagInfo | Table @TableParams - } - } - #endregion vCenter Server Tags - - #region vCenter Server Tag Categories - if ($TagCategories) { - Section -Style Heading3 'Tag Categories' { - $TagCategoryInfo = foreach ($TagCategory in $TagCategories) { - [PSCustomObject] @{ - 'Category' = $TagCategory.Name - 'Description' = if ($TagCategory.Description) { - $TagCategory.Description - } else { - 'None' - } - 'Cardinality' = if ($TagCategory.Cardinality) { - $TagCategory.Cardinality - } else { - 'None' - } - } - } - $TableParams = @{ - Name = "Tag Categories - $vCenterServerName" - ColumnWidths = 30, 40, 30 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $TagCategoryInfo | Table @TableParams - } - } - #endregion vCenter Server Tag Categories - - #region vCenter Server Tag Assignments - if ($TagAssignments) { - Section -Style Heading3 'Tag Assignments' { - $TagAssignmentInfo = foreach ($TagAssignment in $TagAssignments) { - [PSCustomObject]@{ - 'Entity' = $TagAssignment.Entity.Name - 'Tag' = $TagAssignment.Tag.Name - 'Category' = $TagAssignment.Tag.Category - } - } - $TableParams = @{ - Name = "Tag Assignments - $vCenterServerName" - ColumnWidths = 30, 40, 30 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $TagAssignmentInfo | Sort-Object Entity | Table @TableParams - } - } - #endregion vCenter Server Tag Assignments - - #region VM Storage Policies - if ($UserPrivileges -contains 'StorageProfile.View') { - $SpbmStoragePolicies = Get-SpbmStoragePolicy | Sort-Object Name - if ($SpbmStoragePolicies) { - Section -Style Heading3 'VM Storage Policies' { - $VmStoragePolicies = foreach ($SpbmStoragePolicy in $SpbmStoragePolicies) { - [PSCustomObject]@{ - 'VM Storage Policy' = $SpbmStoragePolicy.Name - 'Description' = $SpbmStoragePolicy.Description - } - } - $TableParams = @{ - Name = "VM Storage Policies - $vCenterServerName" - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VmStoragePolicies | Table @TableParams - } - } - } else { - Write-PScriboMessage "Insufficient user privileges to report VM storage policies. Please ensure the user account has the 'Storage Profile > View' privilege assigned." - } - #endregion VM Storage Policies - } - #endregion vCenter Server Detailed Information - - #region vCenter Server Advanced Detail Information - if ($InfoLevel.vCenter -ge 4) { - #region vCenter Alarms - Section -Style Heading3 'Alarms' { - $Alarms = Get-AlarmDefinition -PipelineVariable alarm | ForEach-Object -Process { - Get-AlarmAction -AlarmDefinition $_ -PipelineVariable action | ForEach-Object -Process { - Get-AlarmActionTrigger -AlarmAction $_ | - Select-Object @{N = 'Alarm'; E = { $alarm.Name } }, - @{N = 'Description'; E = { $alarm.Description } }, - @{N = 'Enabled'; E = { if ($alarm.Enabled) { 'Enabled' } else { 'Disabled' } } }, - @{N = 'Entity'; E = { $alarm.Entity.Type } }, - @{N = 'Trigger'; E = { - "{0}:{1}->{2} (Repeat={3})" -f $action.ActionType, - $_.StartStatus, - $_.EndStatus, - $_.Repeat - } - }, - @{N = 'Trigger Info'; E = { Switch ($action.ActionType) { - 'SendEmail' { - "To: $($action.To -join ', ') ` - Cc: $($action.Cc -join ', ') ` - Subject: $($action.Subject) ` - Body: $($action.Body)" - } - 'ExecuteScript' { - "$($action.ScriptFilePath)" - } - default { '--' } - } - } - } - } - } - $Alarms = ($Alarms).Where{ $_.alarm -ne "" } | Sort-Object 'Alarm', 'Trigger' - if ($Healthcheck.vCenter.Alarms) { - $Alarms | Where-Object { $_.'Enabled' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Enabled' - } - if ($InfoLevel.vCenter -ge 5) { - foreach ($Alarm in $Alarms) { - Section -Style NOTOCHeading5 -ExcludeFromTOC $($Alarm.Alarm) { - $TableParams = @{ - Name = "$($Alarm.Alarm) - $vCenterServerName" - List = $true - ColumnWidths = 25, 75 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $Alarm | Table @TableParams - } - } - } else { - $TableParams = @{ - Name = "Alarms - $vCenterServerName" - Columns = 'Alarm', 'Description', 'Enabled', 'Entity', 'Trigger' - ColumnWidths = 20, 20, 20, 20, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $Alarms | Table @TableParams - } - } - #endregion vCenter Alarms - } - #endregion vCenter Server Advanced Detail Information - - #region vCenter Server Comprehensive Information - if ($InfoLevel.vCenter -ge 5) { - #region vCenter Advanced System Settings - Section -Style Heading3 'Advanced System Settings' { - $TableParams = @{ - Name = "vCenter Advanced System Settings - $vCenterServerName" - Columns = 'Name', 'Value' - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vCenterAdvSettings | Sort-Object Name | Table @TableParams - } - #endregion vCenter Advanced System Settings - } - #endregion vCenter Server Comprehensive Information - } - } - #endregion vCenter Server Section - - #region Clusters - Write-PScriboMessage "Cluster InfoLevel set at $($InfoLevel.Cluster)." - if ($InfoLevel.Cluster -ge 1) { - $Clusters = Get-Cluster -Server $vCenter | Sort-Object Name - if ($Clusters) { - #region Cluster Section - Section -Style Heading2 'Clusters' { - Paragraph "The following sections detail the configuration of vSphere HA/DRS clusters managed by vCenter Server $vCenterServerName." - #region Cluster Advanced Summary - if ($InfoLevel.Cluster -le 2) { - BlankLine - $ClusterInfo = foreach ($Cluster in $Clusters) { - [PSCustomObject]@{ - 'Cluster' = $Cluster.Name - 'Datacenter' = $Cluster | Get-Datacenter - '# of Hosts' = $Cluster.ExtensionData.Host.Count - '# of VMs' = $Cluster.ExtensionData.VM.Count - 'vSphere HA' = if ($Cluster.HAEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'vSphere DRS' = if ($Cluster.DrsEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Virtual SAN' = if ($Cluster.VsanEnabled -or $VsanCluster.VsanEsaEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'EVC Mode' = if ($Cluster.EVCMode) { - $EvcModeLookup."$($Cluster.EVCMode)" - } else { - 'Disabled' - } - 'VM Swap File Policy' = Switch ($Cluster.VMSwapfilePolicy) { - 'WithVM' { 'With VM' } - 'InHostDatastore' { 'In Host Datastore' } - default { $Cluster.VMSwapfilePolicy } - } - } - } - if ($Healthcheck.Cluster.HAEnabled) { - $ClusterInfo | Where-Object { $_.'vSphere HA' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere HA' - } - if ($Healthcheck.Cluster.DrsEnabled) { - $ClusterInfo | Where-Object { $_.'vSphere DRS' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere DRS' - } - if ($Healthcheck.Cluster.VsanEnabled) { - $ClusterInfo | Where-Object { $_.'Virtual SAN' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Virtual SAN' - } - if ($Healthcheck.Cluster.EvcEnabled) { - $ClusterInfo | Where-Object { $_.'EVC Mode' -eq 'Disabled' } | Set-Style -Style Warning -Property 'EVC Mode' - } - $TableParams = @{ - Name = "Cluster Summary - $vCenterServerName" - ColumnWidths = 15, 15, 7, 7, 11, 11, 11, 15, 8 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterInfo | Table @TableParams - } - #endregion Cluster Advanced Summary - - #region Cluster Detailed Information - # TODO: Test Tags - if ($InfoLevel.Cluster -ge 3) { - foreach ($Cluster in $Clusters) { - $ClusterDasConfig = $Cluster.ExtensionData.Configuration.DasConfig - $ClusterDrsConfig = $Cluster.ExtensionData.Configuration.DrsConfig - $ClusterConfigEx = $Cluster.ExtensionData.ConfigurationEx - #region Cluster Section - Section -Style Heading3 $Cluster { - Paragraph "The following table details the configuration for cluster $Cluster." - BlankLine - #region Cluster Configuration - $ClusterDetail = [PSCustomObject]@{ - 'Cluster' = $Cluster.Name - 'ID' = $Cluster.Id - 'Datacenter' = $Cluster | Get-Datacenter - 'Number of Hosts' = $Cluster.ExtensionData.Host.Count - 'Number of VMs' = ($Cluster | Get-VM).Count - 'vSphere HA' = if ($Cluster.HAEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'vSphere DRS' = if ($Cluster.DrsEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Virtual SAN' = if ($Cluster.VsanEnabled -or $Cluster.VsanEsaEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'EVC Mode' = if ($Cluster.EVCMode) { - $EvcModeLookup."$($Cluster.EVCMode)" - } else { - 'Disabled' - } - 'VM Swap File Policy' = Switch ($Cluster.VMSwapfilePolicy) { - 'WithVM' { 'Virtual machine directory' } - 'InHostDatastore' { 'Datastore specified by host' } - default { $Cluster.VMSwapfilePolicy } - } - } - $MemberProps = @{ - 'InputObject' = $ClusterDetail - 'MemberType' = 'NoteProperty' - } - <# - if ($TagAssignments | Where-Object {$_.entity -eq $Cluster}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $Cluster}).Tag -join ',') - } - #> - if ($Healthcheck.Cluster.HAEnabled) { - $ClusterDetail | Where-Object { $_.'vSphere HA' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere HA' - } - if ($Healthcheck.Cluster.DrsEnabled) { - $ClusterDetail | Where-Object { $_.'vSphere DRS' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere DRS' - } - if ($Healthcheck.Cluster.VsanEnabled) { - $ClusterDetail | Where-Object { $_.'Virtual SAN' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Virtual SAN' - } - if ($Healthcheck.Cluster.EvcEnabled) { - $ClusterDetail | Where-Object { $_.'EVC Mode' -eq 'Disabled' } | Set-Style -Style Warning -Property 'EVC Mode' - } - #region Cluster Advanced Detailed Information - if ($InfoLevel.Cluster -ge 4) { - $ClusterDetail | ForEach-Object { - $ClusterHosts = $Cluster | Get-VMHost | Sort-Object Name - Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Hosts' -Value ($ClusterHosts.Name -join ', ') - $ClusterVMs = $Cluster | Get-VM | Sort-Object Name - Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Virtual Machines' -Value ($ClusterVMs.Name -join ', ') - } - } - #endregion Cluster Advanced Detailed Information - $TableParams = @{ - Name = "Cluster Configuration - $Cluster" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterDetail | Table @TableParams - #endregion Cluster Configuration - - #region vSphere HA Cluster Configuration - if ($Cluster.HAEnabled) { - Section -Style Heading4 'vSphere HA Configuration' { - Paragraph "The following section details the vSphere HA configuration for $Cluster cluster." - #region vSphere HA Cluster Failures and Responses - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Failures and Responses' { - $HAClusterResponses = [PSCustomObject]@{ - 'Host Monitoring' = $TextInfo.ToTitleCase($ClusterDasConfig.HostMonitoring) - } - if ($ClusterDasConfig.HostMonitoring -eq 'Enabled') { - $MemberProps = @{ - 'InputObject' = $HAClusterResponses - 'MemberType' = 'NoteProperty' - } - if ($ClusterDasConfig.DefaultVmSettings.RestartPriority -eq 'Disabled') { - Add-Member @MemberProps -Name 'Host Failure Response' -Value 'Disabled' - } else { - Add-Member @MemberProps -Name 'Host Failure Response' -Value 'Restart VMs' - Switch ($Cluster.HAIsolationResponse) { - 'DoNothing' { - Add-Member @MemberProps -Name 'Host Isolation Response' -Value 'Disabled' - } - 'Shutdown' { - Add-Member @MemberProps -Name 'Host Isolation Response' -Value 'Shutdown and restart VMs' - } - 'PowerOff' { - Add-Member @MemberProps -Name 'Host Isolation Response' -Value 'Power off and restart VMs' - } - } - Add-Member @MemberProps -Name 'VM Restart Priority' -Value $Cluster.HARestartPriority - Switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmStorageProtectionForPDL) { - 'disabled' { - Add-Member @MemberProps -Name 'Datastore with Permanent Device Loss' -Value 'Disabled' - } - 'warning' { - Add-Member @MemberProps -Name 'Datastore with Permanent Device Loss' -Value 'Issue events' - } - 'restartAggressive' { - Add-Member @MemberProps -Name 'Datastore with Permanent Device Loss' -Value 'Power off and restart VMs' - } - } - Switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmStorageProtectionForAPD) { - 'disabled' { - Add-Member @MemberProps -Name 'Datastore with All Paths Down' -Value 'Disabled' - } - 'warning' { - Add-Member @MemberProps -Name 'Datastore with All Paths Down' -Value 'Issue events' - } - 'restartConservative' { - Add-Member @MemberProps -Name 'Datastore with All Paths Down' -Value 'Power off and restart VMs (conservative)' - } - 'restartAggressive' { - Add-Member @MemberProps -Name 'Datastore with All Paths Down' -Value 'Power off and restart VMs (aggressive)' - } - } - Switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmReactionOnAPDCleared) { - 'none' { - Add-Member @MemberProps -Name 'APD recovery after APD timeout' -Value 'Disabled' - } - 'reset' { - Add-Member @MemberProps -Name 'APD recovery after APD timeout' -Value 'Reset VMs' - } - } - } - Switch ($ClusterDasConfig.VmMonitoring) { - 'vmMonitoringDisabled' { - Add-Member @MemberProps -Name 'VM Monitoring' -Value 'Disabled' - } - 'vmMonitoringOnly' { - Add-Member @MemberProps -Name 'VM Monitoring' -Value 'VM monitoring only' - } - 'vmAndAppMonitoring' { - Add-Member @MemberProps -Name 'VM Monitoring' -Value 'VM and application monitoring' - } - } - } - if ($Healthcheck.Cluster.HostFailureResponse) { - $HAClusterResponses | Where-Object { $_.'Host Failure Response' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Host Failure Response' - } - if ($Healthcheck.Cluster.HostMonitoring) { - $HAClusterResponses | Where-Object { $_.'Host Monitoring' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Host Monitoring' - } - if ($Healthcheck.Cluster.DatastoreOnPDL) { - $HAClusterResponses | Where-Object { $_.'Datastore with Permanent Device Loss' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Datastore with Permanent Device Loss' - } - if ($Healthcheck.Cluster.DatastoreOnAPD) { - $HAClusterResponses | Where-Object { $_.'Datastore with All Paths Down' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Datastore with All Paths Down' - } - if ($Healthcheck.Cluster.APDTimeout) { - $HAClusterResponses | Where-Object { $_.'APD recovery after APD timeout' -eq 'Disabled' } | Set-Style -Style Warning -Property 'APD recovery after APD timeout' - } - if ($Healthcheck.Cluster.vmMonitoring) { - $HAClusterResponses | Where-Object { $_.'VM Monitoring' -eq 'Disabled' } | Set-Style -Style Warning -Property 'VM Monitoring' - } - $TableParams = @{ - Name = "vSphere HA Failures and Responses - $Cluster" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $HAClusterResponses | Table @TableParams - } - #endregion vSphere HA Cluster Failures and Responses - - #region vSphere HA Cluster Admission Control - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Admission Control' { - $HAAdmissionControl = [PSCustomObject]@{ - 'Admission Control' = if ($Cluster.HAAdmissionControlEnabled) { - 'Enabled' - } else { - 'Disabled' - } - } - if ($Cluster.HAAdmissionControlEnabled) { - $MemberProps = @{ - 'InputObject' = $HAAdmissionControl - 'MemberType' = 'NoteProperty' - } - Add-Member @MemberProps -Name 'Host Failures Cluster Tolerates' -Value $Cluster.HAFailoverLevel - Switch ($ClusterDasConfig.AdmissionControlPolicy.GetType().Name) { - 'ClusterFailoverHostAdmissionControlPolicy' { - Add-Member @MemberProps -Name 'Host Failover Capacity Policy' -Value 'Dedicated failover hosts' - } - 'ClusterFailoverResourcesAdmissionControlPolicy' { - Add-Member @MemberProps -Name 'Host Failover Capacity Policy' -Value 'Cluster resource percentage' - } - 'ClusterFailoverLevelAdmissionControlPolicy' { - Add-Member @MemberProps -Name 'Host Failover Capacity Policy' -Value 'Slot policy' - } - } - if ($ClusterDasConfig.AdmissionControlPolicy.AutoComputePercentages) { - Add-Member @MemberProps -Name 'Override Calculated Failover Capacity' -Value 'No' - } else { - Add-Member @MemberProps -Name 'Override Calculated Failover Capacity' -Value 'Yes' - Add-Member @MemberProps -Name 'CPU %' -Value $ClusterDasConfig.AdmissionControlPolicy.CpuFailoverResourcesPercent - Add-Member @MemberProps -Name 'Memory %' -Value $ClusterDasConfig.AdmissionControlPolicy.MemoryFailoverResourcesPercent - } - if ($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy) { - Add-Member @MemberProps -Name 'Slot Policy' -Value 'Fixed slot size' - Add-Member @MemberProps -Name 'CPU Slot Size' -Value "$($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy.Cpu) MHz" - Add-Member @MemberProps -Name 'Memory Slot Size' -Value "$($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy.Memory) MB" - } else { - Add-Member @MemberProps -Name 'Slot Policy' -Value 'Cover all powered-on virtual machines' - } - if ($ClusterDasConfig.AdmissionControlPolicy.ResourceReductionToToleratePercent) { - Add-Member @MemberProps -Name 'Performance Degradation VMs Tolerate' -Value "$($ClusterDasConfig.AdmissionControlPolicy.ResourceReductionToToleratePercent)%" - } - } - if ($Healthcheck.Cluster.HAAdmissionControl) { - $HAAdmissionControl | Where-Object { $_.'Admission Control' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Admission Control' - } - $TableParams = @{ - Name = "vSphere HA Admission Control - $Cluster" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $HAAdmissionControl | Table @TableParams - } - #endregion vSphere HA Cluster Admission Control - - #region vSphere HA Cluster Heartbeat Datastores - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Heartbeat Datastores' { - $HeartbeatDatastores = [PSCustomObject]@{ - 'Heartbeat Selection Policy' = Switch ($ClusterDasConfig.HBDatastoreCandidatePolicy) { - 'allFeasibleDsWithUserPreference' { 'Use datastores from the specified list and complement automatically if needed' } - 'allFeasibleDs' { 'Automatically select datastores accessible from the host' } - 'userSelectedDs' { 'Use datastores only from the specified list' } - default { $ClusterDasConfig.HBDatastoreCandidatePolicy } - } - 'Heartbeat Datastores' = try { - (((Get-View -Id $ClusterDasConfig.HeartbeatDatastore -Property Name).Name | Sort-Object) -join ', ') - } catch { - 'None specified' - } - } - $TableParams = @{ - Name = "vSphere HA Heartbeat Datastores - $Cluster" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $HeartbeatDatastores | Table @TableParams - } - #endregion vSphere HA Cluster Heartbeat Datastores - - #region vSphere HA Cluster Advanced Options - $HAAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterHA' } - if ($HAAdvancedSettings) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'vSphere HA Advanced Options' { - $HAAdvancedOptions = @() - foreach ($HAAdvancedSetting in $HAAdvancedSettings) { - $HAAdvancedOption = [PSCustomObject]@{ - 'Option' = $HAAdvancedSetting.Name - 'Value' = $HAAdvancedSetting.Value - } - $HAAdvancedOptions += $HAAdvancedOption - } - $TableParams = @{ - Name = "vSphere HA Advanced Options - $Cluster" - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $HAAdvancedOptions | Sort-Object Option | Table @TableParams - } - } - #endregion vSphere HA Cluster Advanced Options - } - } - #endregion vSphere HA Cluster Configuration - - #region Proactive HA Configuration - # TODO: Proactive HA Providers - # Proactive HA is only available in vSphere 6.5 and above - if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled -and $vCenter.Version -ge 6.5) { - Section -Style Heading4 'Proactive HA' { - Paragraph "The following section details the Proactive HA configuration for $Cluster cluster." - #region Proactive HA Failures and Responses Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Failures and Responses' { - $ProactiveHa = [PSCustomObject]@{ - 'Proactive HA' = if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled) { - 'Enabled' - } else { - 'Disabled' - } - } - if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled) { - $ProactiveHaModerateRemediation = Switch ($ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation) { - 'MaintenanceMode' { 'Maintenance Mode' } - 'QuarantineMode' { 'Quarantine Mode' } - default { $ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation } - } - $ProactiveHaSevereRemediation = Switch ($ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation) { - 'MaintenanceMode' { 'Maintenance Mode' } - 'QuarantineMode' { 'Quarantine Mode' } - default { $ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation } - } - $MemberProps = @{ - 'InputObject' = $ProactiveHa - 'MemberType' = 'NoteProperty' - } - Add-Member @MemberProps -Name 'Automation Level' -Value $ClusterConfigEx.InfraUpdateHaConfig.Behavior - if ($ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation -eq $ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation) { - Add-Member @MemberProps -Name 'Remediation' -Value $ProactiveHaModerateRemediation - } else { - Add-Member @MemberProps -Name 'Remediation' -Value 'Mixed Mode' - Add-Member @MemberProps -Name 'Moderate Remediation' -Value $ProactiveHaModerateRemediation - Add-Member @MemberProps -Name 'Severe Remediation' -Value $ProactiveHaSevereRemediation - } - } - if ($Healthcheck.Cluster.ProactiveHA) { - $ProactiveHa | Where-Object { $_.'Proactive HA' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Proactive HA' - } - $TableParams = @{ - Name = "Proactive HA - $Cluster" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ProactiveHa | Table @TableParams - } - #endregion Proactive HA Failures and Responses Section - } - } - #endregion Proactive HA Configuration - - #region vSphere DRS Cluster Configuration - if ($Cluster.DrsEnabled) { - Section -Style Heading4 'vSphere DRS Configuration' { - Paragraph ("The following table details the vSphere DRS configuration " + - "for cluster $Cluster.") - BlankLine - - #region vSphere DRS Cluster Specifications - $DrsCluster = [PSCustomObject]@{ - 'vSphere DRS' = if ($Cluster.DrsEnabled) { - 'Enabled' - } else { - 'Disabled' - } - } - $MemberProps = @{ - 'InputObject' = $DrsCluster - 'MemberType' = 'NoteProperty' - } - Switch ($Cluster.DrsAutomationLevel) { - 'Manual' { - Add-Member @MemberProps -Name 'Automation Level' -Value 'Manual' - } - 'PartiallyAutomated' { - Add-Member @MemberProps -Name 'Automation Level' -Value 'Partially Automated' - } - 'FullyAutomated' { - Add-Member @MemberProps -Name 'Automation Level' -Value 'Fully Automated' - } - } - Add-Member @MemberProps -Name 'Migration Threshold' -Value $ClusterDrsConfig.VmotionRate - if ($ClusterConfigEx.ProactiveDrsConfig.Enabled) { - Add-Member @MemberProps -Name 'Predictive DRS' -Value 'Enabled' - } else { - Add-Member @MemberProps -Name 'Predictive DRS' -Value 'Disabled' - } - if ($ClusterDrsConfig.EnableVmBehaviorOverrides) { - Add-Member @MemberProps -Name 'Virtual Machine Automation' -Value 'Enabled' - } else { - Add-Member @MemberProps -Name 'Virtual Machine Automation' -Value 'Disabled' - } - if ($Healthcheck.Cluster.DrsEnabled) { - $DrsCluster | Where-Object { $_.'vSphere DRS' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere DRS' - } - if ($Healthcheck.Cluster.DrsAutomationLevelFullyAuto) { - $DrsCluster | Where-Object { $_.'Automation Level' -ne 'Fully Automated' } | Set-Style -Style Warning -Property 'Automation Level' - } - $TableParams = @{ - Name = "vSphere DRS Configuration - $Cluster" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DrsCluster | Table @TableParams - #endregion vSphere DRS Cluster Specfications - - #region DRS Cluster Additional Options - $DrsAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterDRS' } - if ($DrsAdvancedSettings) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Additional Options' { - $DrsAdditionalOptions = [PSCustomObject] @{ - 'VM Distribution' = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'TryBalanceVmsPerHost' }).Value) { - '1' { 'Enabled' } - $null { 'Disabled' } - } - 'Memory Metric for Load Balancing' = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'PercentIdleMBInMemDemand' }).Value) { - '100' { 'Enabled' } - $null { 'Disabled' } - } - 'CPU Over-Commitment' = if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerCore' }).Value) { - 'Enabled' - } else { - 'Disabled' - } - } - $MemberProps = @{ - 'InputObject' = $DrsAdditionalOptions - 'MemberType' = 'NoteProperty' - } - if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerCore' }).Value) { - Add-Member @MemberProps -Name 'Over-Commitment Ratio' -Value "$(($DrsAdvancedSettings | Where-Object {$_.name -eq 'MaxVcpusPerCore'}).Value):1 (vCPU:pCPU)" - } - if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerClusterPct' }).Value) { - Add-Member @MemberProps -Name 'Over-Commitment Ratio (% of cluster capacity)' -Value "$(($DrsAdvancedSettings | Where-Object {$_.name -eq 'MaxVcpusPerClusterPct'}).Value) %" - } - $TableParams = @{ - Name = "DRS Additional Options - $Cluster" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DrsAdditionalOptions | Table @TableParams - } - } - #endregion DRS Cluster Additional Options - - #region vSphere DPM Configuration - if ($ClusterConfigEx.DpmConfigInfo.Enabled) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Power Management' { - $DpmConfig = [PSCustomObject]@{ - 'DPM' = if ($ClusterConfigEx.DpmConfigInfo.Enabled) { - 'Enabled' - } else { - 'Disabled' - } - } - $MemberProps = @{ - 'InputObject' = $DpmConfig - 'MemberType' = 'NoteProperty' - } - Switch ($ClusterConfigEx.DpmConfigInfo.DefaultDpmBehavior) { - 'manual' { - Add-Member @MemberProps -Name 'Automation Level' -Value 'Manual' - } - 'automated' { - Add-Member @MemberProps -Name 'Automation Level' -Value 'Automated' - } - } - if ($ClusterConfigEx.DpmConfigInfo.DefaultDpmBehavior -eq 'automated') { - Add-Member @MemberProps -Name 'DPM Threshold' -Value $ClusterConfigEx.DpmConfigInfo.HostPowerActionRate - } - $TableParams = @{ - Name = "vSphere DPM - $Cluster" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DpmConfig | Table @TableParams - } - } - #endregion vSphere DPM Configuration - - #region vSphere DRS Cluster Advanced Options - $DrsAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterDRS' } - if ($DrsAdvancedSettings) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Advanced Options' { - $DrsAdvancedOptions = @() - foreach ($DrsAdvancedSetting in $DrsAdvancedSettings) { - $DrsAdvancedOption = [PSCustomObject]@{ - 'Option' = $DrsAdvancedSetting.Name - 'Value' = $DrsAdvancedSetting.Value - } - $DrsAdvancedOptions += $DrsAdvancedOption - } - $TableParams = @{ - Name = "vSphere DRS Advanced Options - $Cluster" - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DrsAdvancedOptions | Sort-Object Option | Table @TableParams - } - } - #endregion vSphere DRS Cluster Advanced Options - - #region vSphere DRS Cluster Group - $DrsClusterGroups = $Cluster | Get-DrsClusterGroup - if ($DrsClusterGroups) { - #region vSphere DRS Cluster Group Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'DRS Cluster Groups' { - $DrsGroups = foreach ($DrsClusterGroup in $DrsClusterGroups) { - [PSCustomObject]@{ - 'DRS Cluster Group' = $DrsClusterGroup.Name - 'Type' = Switch ($DrsClusterGroup.GroupType) { - 'VMGroup' { 'VM Group' } - 'VMHostGroup' { 'Host Group' } - default { $DrsClusterGroup.GroupType } - } - 'Members' = if (($DrsClusterGroup.Member).Count -gt 0) { - ($DrsClusterGroup.Member | Sort-Object) -join ', ' - } else { - "None" - } - } - } - $TableParams = @{ - Name = "DRS Cluster Groups - $Cluster" - ColumnWidths = 42, 16, 42 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DrsGroups | Sort-Object 'DRS Cluster Group', 'Type' | Table @TableParams - } - #endregion vSphere DRS Cluster Group Section - - #region vSphere DRS Cluster VM/Host Rules - $DrsVMHostRules = $Cluster | Get-DrsVMHostRule - if ($DrsVMHostRules) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'DRS VM/Host Rules' { - $DrsVMHostRuleDetail = foreach ($DrsVMHostRule in $DrsVMHostRules) { - [PSCustomObject]@{ - 'DRS VM/Host Rule' = $DrsVMHostRule.Name - 'Type' = Switch ($DrsVMHostRule.Type) { - 'MustRunOn' { 'Must run on hosts in group' } - 'ShouldRunOn' { 'Should run on hosts in group' } - 'MustNotRunOn' { 'Must not run on hosts in group' } - 'ShouldNotRunOn' { 'Should not run on hosts in group' } - default { $DrsVMHostRule.Type } - } - 'Enabled' = if ($DrsVMHostRule.Enabled) { - 'Yes' - } else { - 'No' - } - 'VM Group' = $DrsVMHostRule.VMGroup - 'Host Group' = $DrsVMHostRule.VMHostGroup - } - } - if ($Healthcheck.Cluster.DrsVMHostRules) { - $DrsVMHostRuleDetail | Where-Object { $_.Enabled -eq 'No' } | Set-Style -Style Warning -Property 'Enabled' - } - $TableParams = @{ - Name = "DRS VM/Host Rules - $Cluster" - ColumnWidths = 22, 22, 12, 22, 22 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DrsVMHostRuleDetail | Sort-Object 'DRS VM/Host Rule' | Table @TableParams - } - } - #endregion vSphere DRS Cluster VM/Host Rules - - #region vSphere DRS Cluster Rules - $DrsRules = $Cluster | Get-DrsRule - if ($DrsRules) { - #region vSphere DRS Cluster Rules Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'DRS Rules' { - $DrsRuleDetail = foreach ($DrsRule in $DrsRules) { - [PSCustomObject]@{ - 'DRS Rule' = $DrsRule.Name - 'Type' = Switch ($DrsRule.Type) { - 'VMAffinity' { 'Keep Vitrual Machines Together' } - 'VMAntiAffinity' { 'Separate Virtual Machines' } - } - 'Enabled' = if ($DrsRule.Enabled) { - 'Yes' - } else { - 'No' - } - 'Mandatory' = $DrsRule.Mandatory - 'Virtual Machines' = ($DrsRule.VMIds | ForEach-Object { (Get-View -Id $_).name }) -join ', ' - } - if ($Healthcheck.Cluster.DrsRules) { - $DrsRuleDetail | Where-Object { $_.Enabled -eq 'No' } | Set-Style -Style Warning -Property 'Enabled' - } - } - $TableParams = @{ - Name = "DRS Rules - $Cluster" - ColumnWidths = 26, 25, 12, 12, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DrsRuleDetail | Sort-Object Type | Table @TableParams - } - #endregion vSphere DRS Cluster Rules Section - } - #endregion vSphere DRS Cluster Rules - } - #endregion vSphere DRS Cluster Group - - #region Cluster VM Overrides - $DrsVmOverrides = $Cluster.ExtensionData.Configuration.DrsVmConfig - $DasVmOverrides = $Cluster.ExtensionData.Configuration.DasVmConfig - if ($DrsVmOverrides -or $DasVmOverrides) { - #region VM Overrides Section - Section -Style NOTOCHeading4 -ExcludeFromTOC 'VM Overrides' { - #region vSphere DRS VM Overrides - if ($DrsVmOverrides) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'vSphere DRS' { - $DrsVmOverrideDetails = foreach ($DrsVmOverride in $DrsVmOverrides) { - [PSCustomObject]@{ - 'Virtual Machine' = $VMLookup."$($DrsVmOverride.Key.Type)-$($DrsVmOverride.Key.Value)" - 'vSphere DRS Automation Level' = if ($DrsVmOverride.Enabled -eq $false) { - 'Disabled' - } else { - Switch ($DrsVmOverride.Behavior) { - 'manual' { 'Manual' } - 'partiallyAutomated' { 'Partially Automated' } - 'fullyAutomated' { 'Fully Automated' } - default { $DrsVmOverride.Behavior } - } - } - } - } - $TableParams = @{ - Name = "DRS VM Overrides - $Cluster" - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DrsVmOverrideDetails | Sort-Object 'Virtual Machine' | Table @TableParams - } - } - #endregion vSphere DRS VM Overrides - - #region vSphere HA VM Overrides - if ($DasVmOverrides) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'vSphere HA' { - $DasVmOverrideDetails = foreach ($DasVmOverride in $DasVmOverrides) { - [PSCustomObject]@{ - 'Virtual Machine' = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" - 'VM Restart Priority' = Switch ($DasVmOverride.DasSettings.RestartPriority) { - $null { '--' } - 'lowest' { 'Lowest' } - 'low' { 'Low' } - 'medium' { 'Medium' } - 'high' { 'High' } - 'highest' { 'Highest' } - 'disabled' { 'Disabled' } - 'clusterRestartPriority' { 'Cluster default' } - } - 'VM Dependency Restart Condition Timeout' = Switch ($DasVmOverride.DasSettings.RestartPriorityTimeout) { - $null { '--' } - '-1' { 'Disabled' } - default { "$($DasVmOverride.DasSettings.RestartPriorityTimeout) seconds" } - } - 'Host Isolation Response' = Switch ($DasVmOverride.DasSettings.IsolationResponse) { - $null { '--' } - 'none' { 'Disabled' } - 'powerOff' { 'Power off and restart VMs' } - 'shutdown' { 'Shutdown and restart VMs' } - 'clusterIsolationResponse' { 'Cluster default' } - } - } - } - $TableParams = @{ - Name = "HA VM Overrides - $Cluster" - ColumnWidths = 25, 25, 25, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DasVmOverrideDetails | Sort-Object 'Virtual Machine' | Table @TableParams - - #region PDL/APD Protection Settings Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'PDL/APD Protection Settings' { - $DasVmOverridePdlApd = foreach ($DasVmOverride in $DasVmOverrides) { - $DasVmComponentProtection = $DasVmOverride.DasSettings.VmComponentProtectionSettings - [PSCustomObject]@{ - 'Virtual Machine' = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" - 'PDL Failure Response' = Switch ($DasVmComponentProtection.VmStorageProtectionForPDL) { - $null { '--' } - 'clusterDefault' { 'Cluster default' } - 'warning' { 'Issue events' } - 'restartAggressive' { 'Power off and restart VMs' } - 'disabled' { 'Disabled' } - } - 'APD Failure Response' = Switch ($DasVmComponentProtection.VmStorageProtectionForAPD) { - $null { '--' } - 'clusterDefault' { 'Cluster default' } - 'warning' { 'Issue events' } - 'restartConservative' { 'Power off and restart VMs - Conservative restart policy' } - 'restartAggressive' { 'Power off and restart VMs - Aggressive restart policy' } - 'disabled' { 'Disabled' } - } - 'VM Failover Delay' = Switch ($DasVmComponentProtection.VmTerminateDelayForAPDSec) { - $null { '--' } - '-1' { 'Disabled' } - default { "$(($DasVmComponentProtection.VmTerminateDelayForAPDSec)/60) minutes" } - } - 'Response Recovery' = Switch ($DasVmComponentProtection.VmReactionOnAPDCleared) { - $null { '--' } - 'reset' { 'Reset VMs' } - 'disabled' { 'Disabled' } - 'useClusterDefault' { 'Cluster default' } - } - } - } - $TableParams = @{ - Name = "HA VM Overrides PDL/APD Settings - $Cluster" - ColumnWidths = 20, 20, 20, 20, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DasVmOverridePdlApd | Sort-Object 'Virtual Machine' | Table @TableParams - } - #endregion PDL/APD Protection Settings Section - - #region VM Monitoring Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'VM Monitoring' { - $DasVmOverrideVmMonitoring = foreach ($DasVmOverride in $DasVmOverrides) { - $DasVmMonitoring = $DasVmOverride.DasSettings.VmToolsMonitoringSettings - [PSCustomObject]@{ - 'Virtual Machine' = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" - 'VM Monitoring' = Switch ($DasVmMonitoring.VmMonitoring) { - $null { '--' } - 'vmMonitoringDisabled' { 'Disabled' } - 'vmMonitoringOnly' { 'VM Monitoring Only' } - 'vmAndAppMonitoring' { 'VM and App Monitoring' } - } - 'Failure Interval' = Switch ($DasVmMonitoring.FailureInterval) { - $null { '--' } - default { - if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { - '--' - } else { - "$($DasVmMonitoring.FailureInterval) seconds" - } - } - } - 'Minimum Uptime' = Switch ($DasVmMonitoring.MinUptime) { - $null { '--' } - default { - if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { - '--' - } else { - "$($DasVmMonitoring.MinUptime) seconds" - } - } - } - 'Maximum Per-VM Resets' = Switch ($DasVmMonitoring.MaxFailures) { - $null { '--' } - default { - if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { - '--' - } else { - $DasVmMonitoring.MaxFailures - } - } - } - 'Maximum Resets Time Window' = Switch ($DasVmMonitoring.MaxFailureWindow) { - $null { '--' } - '-1' { 'No window' } - default { - if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { - '--' - } else { - "Within $(($DasVmMonitoring.MaxFailureWindow)/3600) hrs" - } - } - } - } - } - $TableParams = @{ - Name = "HA VM Overrides VM Monitoring - $Cluster" - ColumnWidths = 40, 12, 12, 12, 12, 12 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DasVmOverrideVmMonitoring | Sort-Object 'Virtual Machine' | Table @TableParams - } - #endregion VM Monitoring Section - } - } - #endregion vSphere HA VM Overrides - } - #endregion VM Overrides Section - } - #endregion Cluster VM Overrides - - #region Cluster VUM Baselines - if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { - if ($VUMConnection) { - Try { - $ClusterPatchBaselines = $Cluster | Get-PatchBaseline - } Catch { - Write-PScriboMessage 'Cluster VUM baseline information is not currently available with your version of PowerShell.' - } - if ($ClusterPatchBaselines) { - Section -Style Heading4 'Update Manager Baselines' { - $ClusterBaselines = foreach ($ClusterBaseline in $ClusterPatchBaselines) { - [PSCustomObject]@{ - 'Baseline' = $ClusterBaseline.Name - 'Description' = $ClusterBaseline.Description - 'Type' = $ClusterBaseline.BaselineType - 'Target Type' = $ClusterBaseline.TargetType - 'Last Update Time' = ($ClusterBaseline.LastUpdateTime).ToLocalTime().ToString() - '# of Patches' = $ClusterBaseline.CurrentPatches.Count - } - } - $TableParams = @{ - Name = "Update Manager Baselines - $Cluster" - ColumnWidths = 25, 25, 10, 10, 20, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterBaselines | Sort-Object 'Baseline' | Table @TableParams - } - } - if ($Healthcheck.Cluster.VUMCompliance) { - $ClusterComplianceInfo | Where-Object { $_.Status -eq 'Unknown' } | Set-Style -Style Warning - $ClusterComplianceInfo | Where-Object { $_.Status -eq 'Not Compliant' -or $_.Status -eq 'Incompatible' } | Set-Style -Style Critical - } - $TableParams = @{ - Name = "Update Manager Compliance - $Cluster" - ColumnWidths = 25, 50, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterComplianceInfo | Sort-Object Name, Baseline | Table @TableParams - } - } else { - Write-PScriboMessage "Insufficient user privileges to report Cluster baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned." - } - #endregion Cluster VUM Baselines - - #region Cluster VUM Compliance (Advanced Detail Information) - if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { - if ($InfoLevel.Cluster -ge 4 -and $VumServer.Name) { - Try { - $ClusterCompliances = $Cluster | Get-Compliance - } Catch { - Write-PScriboMessage 'Cluster VUM compliance information is not currently available with your version of PowerShell.' - } - if ($ClusterCompliances) { - Section -Style Heading4 'Update Manager Compliance' { - $ClusterComplianceInfo = foreach ($ClusterCompliance in $ClusterCompliances) { - [PSCustomObject]@{ - 'Entity' = $ClusterCompliance.Entity - 'Baseline' = $ClusterCompliance.Baseline.Name - 'Status' = Switch ($ClusterCompliance.Status) { - 'NotCompliant' { 'Not Compliant' } - default { $ClusterCompliance.Status } - } - } - } - if ($Healthcheck.Cluster.VUMCompliance) { - $ClusterComplianceInfo | Where-Object { $_.Status -eq 'Unknown' } | Set-Style -Style Warning - $ClusterComplianceInfo | Where-Object { $_.Status -eq 'Not Compliant' -or $_.Status -eq 'Incompatible' } | Set-Style -Style Critical - } - $TableParams = @{ - Name = "Update Manager Compliance - $Cluster" - ColumnWidths = 25, 50, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterComplianceInfo | Sort-Object Entity, Baseline | Table @TableParams - } - } - } - } else { - Write-PScriboMessage "Insufficient user privileges to report Cluster compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned." - } - - #endregion Cluster VUM Compliance (Advanced Detail Information) - - #region Cluster Permissions - Section -Style NOTOCHeading4 -ExcludeFromTOC 'Permissions' { - Paragraph "The following table details the permissions assigned to cluster $Cluster." - BlankLine - $VIPermissions = $Cluster | Get-VIPermission - $ClusterVIPermissions = foreach ($VIPermission in $VIPermissions) { - [PSCustomObject]@{ - 'User/Group' = $VIPermission.Principal - 'Is Group?' = if ($VIPermission.IsGroup) { - 'Yes' - } else { - 'No' - } - 'Role' = $VIPermission.Role - 'Defined In' = $VIPermission.Entity - 'Propagate' = if ($VIPermission.Propagate) { - 'Yes' - } else { - 'No' - } - } - } - $TableParams = @{ - Name = "Permissions - $Cluster" - ColumnWidths = 42, 12, 20, 14, 12 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterVIPermissions | Sort-Object 'User/Group' | Table @TableParams - } - #endregion Cluster Permissions - } - } - #endregion vSphere DRS Cluster Configuration - } - #endregion Cluster Section - } - } - #endregion Cluster Detailed Information - } - #endregion Cluster Section - } - } - #endregion Clusters - - #region Resource Pool Section - Write-PScriboMessage "ResourcePool InfoLevel set at $($InfoLevel.ResourcePool)." - if ($InfoLevel.ResourcePool -ge 1) { - $ResourcePools = Get-ResourcePool -Server $vCenter | Sort-Object Parent, Name - if ($ResourcePools) { - #region Resource Pools Section - Section -Style Heading2 'Resource Pools' { - Paragraph "The following sections detail the configuration of resource pools managed by vCenter Server $vCenterServerName." - #region Resource Pool Advanced Summary - if ($InfoLevel.ResourcePool -le 2) { - BlankLine - $ResourcePoolInfo = foreach ($ResourcePool in $ResourcePools) { - [PSCustomObject]@{ - 'Resource Pool' = $ResourcePool.Name - 'Parent' = $ResourcePool.Parent - 'CPU Shares Level' = $ResourcePool.CpuSharesLevel - 'CPU Reservation MHz' = $ResourcePool.CpuReservationMHz - 'CPU Limit MHz' = Switch ($ResourcePool.CpuLimitMHz) { - '-1' { 'Unlimited' } - default { $ResourcePool.CpuLimitMHz } - } - 'Memory Shares Level' = $ResourcePool.MemSharesLevel - 'Memory Reservation' = Convert-DataSize $ResourcePool.MemReservationGB -RoundUnits 0 - 'Memory Limit' = Switch ($ResourcePool.MemLimitGB) { - '-1' { 'Unlimited' } - default { Convert-DataSize $ResourcePool.MemLimitGB -RoundUnits 0 } - } - } - } - $TableParams = @{ - Name = "Resource Pool Summary - $($vCenterServerName)" - ColumnWidths = 20, 20, 10, 10, 10, 10, 10, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ResourcePoolInfo | Sort-Object Name | Table @TableParams - } - #endregion Resource Pool Advanced Summary - - #region Resource Pool Detailed Information - # TODO: Test Tags - if ($InfoLevel.ResourcePool -ge 3) { - foreach ($ResourcePool in $ResourcePools) { - Section -Style Heading3 $ResourcePool.Name { - $ResourcePoolDetail = [PSCustomObject]@{ - 'Resource Pool' = $ResourcePool.Name - 'ID' = $ResourcePool.Id - 'Parent' = $ResourcePool.Parent - 'CPU Shares Level' = $ResourcePool.CpuSharesLevel - 'Number of CPU Shares' = $ResourcePool.NumCpuShares - 'CPU Reservation' = "$($ResourcePool.CpuReservationMHz) MHz" - 'CPU Expandable Reservation' = if ($ResourcePool.CpuExpandableReservation) { - 'Enabled' - } else { - 'Disabled' - } - 'CPU Limit MHz' = Switch ($ResourcePool.CpuLimitMHz) { - '-1' { 'Unlimited' } - default { "$($ResourcePool.CpuLimitMHz) MHz" } - } - 'Memory Shares Level' = $ResourcePool.MemSharesLevel - 'Number of Memory Shares' = $ResourcePool.NumMemShares - 'Memory Reservation' = Convert-DataSize $ResourcePool.MemReservationGB -RoundUnits 0 - 'Memory Expandable Reservation' = if ($ResourcePool.MemExpandableReservation) { - 'Enabled' - } else { - 'Disabled' - } - 'Memory Limit' = Switch ($ResourcePool.MemLimitGB) { - '-1' { 'Unlimited' } - default { Convert-DataSize $ResourcePool.MemLimitGB -RoundUnits 0 } - } - 'Number of VMs' = $ResourcePool.ExtensionData.VM.Count - } - <# - $MemberProps = @{ - 'InputObject' = $ResourcePoolDetail - 'MemberType' = 'NoteProperty' - } - - if ($TagAssignments | Where-Object {$_.entity -eq $ResourcePool}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $ResourcePool}).Tag -join ',') - } - #> - #region Resource Pool Advanced Detail Information - if ($InfoLevel.ResourcePool -ge 4) { - $ResourcePoolDetail | ForEach-Object { - # Query for VMs by resource pool Id - $ResourcePoolId = $_.Id - $ResourcePoolVMs = $VMs | Where-Object { $_.ResourcePoolId -eq $ResourcePoolId } | Sort-Object Name - Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Virtual Machines' -Value ($ResourcePoolVMs.Name -join ', ') - } - } - #endregion Resource Pool Advanced Detail Information - $TableParams = @{ - Name = "Resource Pool Configuration - $($ResourcePool.Name)" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ResourcePoolDetail | Table @TableParams - } - } - } - #endregion Resource Pool Detailed Information - } - #endregion Resource Pools Section - } - } - #endregion Resource Pool Section - - #region ESXi VMHost Section - Write-PScriboMessage "VMHost InfoLevel set at $($InfoLevel.VMHost)." - if ($InfoLevel.VMHost -ge 1) { - if ($VMHosts) { - #region Hosts Section - Section -Style Heading2 'Hosts' { - Paragraph "The following sections detail the configuration of VMware ESXi hosts managed by vCenter Server $vCenterServerName." - #region ESXi Host Advanced Summary - if ($InfoLevel.VMHost -le 2) { - BlankLine - $VMHostInfo = foreach ($VMHost in $VMHosts) { - [PSCustomObject]@{ - 'Host' = $VMHost.Name - 'Version' = $VMHost.Version - 'Build' = $VMHost.Build - 'Parent' = $VMHost.Parent - 'Connection State' = Switch ($VMHost.ConnectionState) { - 'NotResponding' { 'Not Responding' } - default { $TextInfo.ToTitleCase($VMHost.ConnectionState) } - } - 'CPU Sockets' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuPackages - 'CPU Cores' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuCores - 'Memory' = Convert-DataSize $VMHost.MemoryTotalGB -RoundUnits 0 - '# of VMs' = $VMHost.ExtensionData.Vm.Count - } - } - if ($Healthcheck.VMHost.ConnectionState) { - $VMHostInfo | Where-Object { $_.'Connection State' -eq 'Maintenance' } | Set-Style -Style Warning - $VMHostInfo | Where-Object { $_.'Connection State' -eq 'Not Responding' } | Set-Style -Style Critical - $VMHostInfo | Where-Object { $_.'Connection State' -eq 'Disconnected' } | Set-Style -Style Critical - } - $TableParams = @{ - Name = "Host Summary - $($vCenterServerName)" - ColumnWidths = 17, 9, 11, 15, 13, 9, 9, 9, 8 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostInfo | Table @TableParams - } - #endregion ESXi Host Advanced Summary - - #region ESXi Host Detailed Information - if ($InfoLevel.VMHost -ge 3) { - #region foreach VMHost Detailed Information loop - foreach ($VMHost in ($VMHosts | Where-Object { $_.ConnectionState -eq 'Connected' -or $_.ConnectionState -eq 'Maintenance' })) { - #region VMHost Section - Section -Style Heading3 $VMHost { - # TODO: Host Certificate, Swap File Location - # TODO: Test Tags - #region ESXi Host Hardware Section - Section -Style Heading4 'Hardware' { - Paragraph "The following section details the host hardware configuration for $VMHost." - BlankLine - - #region ESXi Host Specifications - $VMHostUptime = Get-Uptime -VMHost $VMHost - $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter - $ScratchLocation = Get-AdvancedSetting -Entity $VMHost | Where-Object { $_.Name -eq 'ScratchConfig.CurrentScratchLocation' } - $VMHostCpuTotal = [math]::Round(($VMHost.CpuTotalMhz) / 1000, 2) - $VMHostCpuUsed = [math]::Round(($VMHost.CpuUsageMhz) / 1000, 2) - $VMHostCpuFree = $VMHostCpuTotal - $VMHostCpuUsed - $VMHostDetail = [PSCustomObject]@{ - 'Host' = $VMHost.Name - 'Connection State' = Switch ($VMHost.ConnectionState) { - 'NotResponding' { 'Not Responding' } - default { $TextInfo.ToTitleCase($VMHost.ConnectionState) } - } - 'ID' = $VMHost.Id - 'Parent' = $VMHost.Parent - 'Manufacturer' = $VMHost.Manufacturer - 'Model' = $VMHost.Model - 'Serial Number' = if ($VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber) { - $VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber - } else { - '--' - } - 'Asset Tag' = if ($VMHost.ExtensionData.Summary.Hardware.OtherIdentifyingInfo[0].IdentifierValue) { - $VMHost.ExtensionData.Summary.Hardware.OtherIdentifyingInfo[0].IdentifierValue - } else { - 'Unknown' - } - 'Processor Type' = $VMHost.Processortype - 'HyperThreading' = if ($VMHost.HyperthreadingActive) { - 'Enabled' - } else { - 'Disabled' - } - 'Number of CPU Sockets' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuPackages - 'Number of CPU Cores' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuCores - 'Number of CPU Threads' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuThreads - 'CPU Total / Used / Free' = "{0:N2} GHz / {1:N2} GHz / {2:N2} GHz" -f $VMHostCpuTotal, $VMHostCpuUsed, $VMHostCpuFree - 'Memory Total / Used / Free' = "{0} / {1} / {2}" -f (Convert-DataSize $VMHost.MemoryTotalGB), (Convert-DataSize $VMHost.MemoryUsageGB), (Convert-DataSize ($VMHost.MemoryTotalGB - $VMHost.MemoryUsageGB)) - 'NUMA Nodes' = $VMHost.ExtensionData.Hardware.NumaInfo.NumNodes - 'Number of NICs' = $VMHost.ExtensionData.Summary.Hardware.NumNics - 'Number of HBAs' = $VMHost.ExtensionData.Summary.Hardware.NumHBAs - 'Number of Datastores' = ($VMHost.ExtensionData.Datastore).Count - 'Number of VMs' = $VMHost.ExtensionData.VM.Count - 'Maximum EVC Mode' = $EvcModeLookup."$($VMHost.MaxEVCMode)" - 'EVC Graphics Mode' = if ($VMHost.ExtensionData.Summary.CurrentEVCGraphicsModeKey) { - $VMHost.ExtensionData.Summary.CurrentEVCGraphicsModeKey - } else { - 'Not applicable' - } - 'Power Management Policy' = $VMHost.ExtensionData.Hardware.CpuPowerManagementInfo.CurrentPolicy - 'Scratch Location' = $ScratchLocation.Value - 'Bios Version' = $VMHost.ExtensionData.Hardware.BiosInfo.BiosVersion - 'Bios Release Date' = ($VMHost.ExtensionData.Hardware.BiosInfo.ReleaseDate).ToString() - 'ESXi Version' = $VMHost.Version - 'ESXi Build' = $VMHost.build - 'Boot Time' = ($VMHost.ExtensionData.Runtime.Boottime).ToLocalTime().ToString() - 'Uptime Days' = $VMHostUptime.UptimeDays - } - $MemberProps = @{ - 'InputObject' = $VMHostDetail - 'MemberType' = 'NoteProperty' - } - if ($UserPrivileges -contains 'Global.Licenses') { - $VMHostLicense = Get-License -VMHost $VMHost - Add-Member @MemberProps -Name 'Product' -Value $VMHostLicense.Product - Add-Member @MemberProps -Name 'License Key' -Value $VMHostLicense.LicenseKey - Add-Member @MemberProps -Name 'License Expiration' -Value $VMHostLicense.Expiration - } else { - Write-PScriboMessage "Insufficient user privileges to report ESXi host licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned." - } - <# - if ($TagAssignments | Where-Object {$_.entity -eq $VMHost}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VMHost}).Tag -join ',') - } - #> - if ($Healthcheck.VMHost.ConnectionState) { - $VMHostDetail | Where-Object { $_.'Connection State' -eq 'Maintenance' } | Set-Style -Style Warning -Property 'Connection State' - } - if ($Healthcheck.VMHost.HyperThreading) { - $VMHostDetail | Where-Object { $_.'HyperThreading' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Disabled' - } - if ($Healthcheck.VMHost.Licensing) { - $VMHostDetail | Where-Object { $_.'Product' -like '*Evaluation*' } | Set-Style -Style Warning -Property 'Product' - $VMHostDetail | Where-Object { $_.'License Key' -like '*-00000-00000' } | Set-Style -Style Warning -Property 'License Key' - $VMHostDetail | Where-Object { $_.'License Expiration' -eq 'Expired' } | Set-Style -Style Critical -Property 'License Expiration' - } - if ($Healthcheck.VMHost.ScratchLocation) { - $VMHostDetail | Where-Object { $_.'Scratch Location' -eq '/tmp/scratch' } | Set-Style -Style Warning -Property 'Scratch Location' - } - if ($Healthcheck.VMHost.UpTimeDays) { - $VMHostDetail | Where-Object { $_.'Uptime Days' -ge 275 -and $_.'Uptime Days' -lt 365 } | Set-Style -Style Warning -Property 'Uptime Days' - $VMHostDetail | Where-Object { $_.'Uptime Days' -ge 365 } | Set-Style -Style Critical -Property 'Uptime Days' - } - $TableParams = @{ - Name = "Hardware Configuration - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostDetail | Table @TableParams - #endregion ESXi Host Specifications - - #region ESXi IPMI/BMC Settings - Try { - $VMHostIPMI = $esxcli.hardware.ipmi.bmc.get.invoke() - } Catch { - Write-PScriboMessage -IsWarning "Unable to collect IPMI / BMC configuration from $VMHost." - } - if ($VMHostIPMI) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'IPMI / BMC' { - $VMHostIPMIInfo = [PSCustomObject]@{ - 'Manufacturer' = $VMHostIPMI.Manufacturer - 'MAC Address' = $VMHostIPMI.MacAddress - 'IP Address' = $VMHostIPMI.IPv4Address - 'Subnet Mask' = $VMHostIPMI.IPv4Subnet - 'Gateway' = $VMHostIPMI.IPv4Gateway - 'Firmware Version' = $VMHostIPMI.BMCFirmwareVersion - } - - $TableParams = @{ - Name = "IPMI / BMC - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostIPMIInfo | Table @TableParams - } - } - #endregion ESXi IPMI/BMC Settings - - #region ESXi Host Boot Device - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Boot Device' { - $ESXiBootDevice = Get-ESXiBootDevice -VMHost $VMHost - $VMHostBootDevice = [PSCustomObject]@{ - 'Host' = $ESXiBootDevice.Host - 'Device' = $ESXiBootDevice.Device - 'Boot Type' = $ESXiBootDevice.BootType - 'Vendor' = $ESXiBootDevice.Vendor - 'Model' = $ESXiBootDevice.Model - 'Size' = Switch ($ESXiBootDevice.SizeMB) { - 'N/A' { 'N/A' } - default { Convert-DataSize $ESXiBootDevice.SizeMB -InputUnit MB -RoundUnits 0 } - } - 'Is SAS' = $ESXiBootDevice.IsSAS - 'Is SSD' = $ESXiBootDevice.IsSSD - 'Is USB' = $ESXiBootDevice.IsUSB - } - $TableParams = @{ - Name = "Boot Device - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostBootDevice | Table @TableParams - } - #endregion ESXi Host Boot Devices - - #region ESXi Host PCI Devices - Section -Style NOTOCHeading5 -ExcludeFromTOC 'PCI Devices' { - <# Move away from esxcli.v2 implementation to be compatible with 8.x branch. - 'Slot Description' information does not seem to be available through the API - Create an array with PCI Address and VMware Devices (vmnic,vmhba,?vmgfx?) - #> - $PciToDeviceMapping = @{} - $NetworkAdapters = Get-VMHostNetworkAdapter -VMHost $VMHost -Physical - foreach ($adapter in $NetworkAdapters) { - $PciToDeviceMapping[$adapter.PciId] = $adapter.DeviceName - } - $hbAdapters = Get-VMHostHba -VMHost $VMHost - foreach ($adapter in $hbAdapters) { - $PciToDeviceMapping[$adapter.Pci] = $adapter.Device - } - <# Data Object - HostGraphicsInfo(vim.host.GraphicsInfo) - This function has been available since version 5.5, but we can't be sure if it is still valid. - I don't have access to a vGPU-enabled system. - #> - $GpuAdapters = (Get-VMHost $VMhost | Get-View -Property Config).Config.GraphicsInfo - foreach ($adapter in $GpuAdapters) { - $PciToDeviceMapping[$adapter.pciId] = $adapter.deviceName - } - - $VMHostPciDevice = @{ - VMHost = $VMHost - DeviceClass = @('MassStorageController', 'NetworkController', 'DisplayController', 'SerialBusController') - } - $PciDevices = Get-VMHostPciDevice @VMHostPciDevice - - # Combine PciDevices and PciToDeviceMapping - - $VMHostPciDevices = $PciDevices | ForEach-Object { - $PciDevice = $_ - $device = $PCIToDeviceMapping[$pciDevice.Id] - - if ($device) { - [PSCustomObject]@{ - 'Device' = $device - 'PCI Address' = $PciDevice.Id - 'Device Class' = $PciDevice.DeviceClass -replace ('Controller', "") - 'Device Name' = $PciDevice.DeviceName - 'Vendor Name' = $PciDevice.VendorName - } - } - } - - $TableParams = @{ - Name = "PCI Devices - $VMHost" - ColumnWidths = 17, 18, 15, 30, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostPciDevices | Sort-Object 'Device' | Table @TableParams - } - #endregion ESXi Host PCI Devices - - #region ESXi Host PCI Devices Drivers & Firmware - $VMHostPciDevicesDetails = Get-PciDeviceDetail -Server $vCenter -esxcli $esxcli | Sort-Object 'Device' - if ($VMHostPciDevicesDetails) { - Try { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'PCI Devices Drivers & Firmware' { - $TableParams = @{ - Name = "PCI Devices Drivers & Firmware - $VMHost" - ColumnWidths = 12, 20, 11, 19, 11, 11, 16 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostPciDevicesDetails | Table @TableParams - } - } Catch { - Write-PScriboMessage -IsWarning "Unable to collect PCI Devices Drivers & Firmware configuration from $VMhost." - } - } - #endregion ESXi Host PCI Devices Drivers & Firmware - } - #endregion ESXi Host Hardware Section - - #region ESXi Host System Section - Section -Style Heading4 'System' { - Paragraph "The following section details the host system configuration for $VMHost." - #region ESXi Host Profile Information - if ($VMHost | Get-VMHostProfile) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Host Profile' { - $VMHostProfile = $VMHost | Get-VMHostProfile | Select-Object Name, Description - $TableParams = @{ - Name = "Host Profile - $VMHost" - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostProfile | Sort-Object Name | Table @TableParams - } - } - #endregion ESXi Host Profile Information - - #region ESXi Host Image Profile Information - if ($UserPrivileges -contains 'Host.Config.Settings') { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Image Profile' { - $installdate = Get-InstallDate - $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter - $ImageProfile = $esxcli.software.profile.get.Invoke() - $SecurityProfile = [PSCustomObject]@{ - 'Image Profile' = $ImageProfile.Name - 'Vendor' = $ImageProfile.Vendor - 'Installation Date' = $InstallDate.InstallDate - } - $TableParams = @{ - Name = "Image Profile - $VMHost" - #ColumnWidths = 50, 25, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $SecurityProfile | Table @TableParams - } - } else { - Write-PScriboMessage "Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned." - } - #endregion ESXi Host Image Profile Information - - #region ESXi Host Time Configuration - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Time Configuration' { - $VMHostTimeSettings = [PSCustomObject]@{ - 'Time Zone' = $VMHost.timezone - 'NTP Service' = if ((Get-VMHostService -VMHost $VMHost | Where-Object { $_.key -eq 'ntpd' }).Running) { - 'Running' - } else { - 'Stopped' - } - 'NTP Server(s)' = (Get-VMHostNtpServer -VMHost $VMHost | Sort-Object) -join ', ' - } - if ($Healthcheck.VMHost.NTP) { - $VMHostTimeSettings | Where-Object { $_.'NTP Service' -eq 'Stopped' } | Set-Style -Style Critical -Property 'NTP Service' - } - $TableParams = @{ - Name = "Time Configuration - $VMHost" - ColumnWidths = 30, 30, 40 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostTimeSettings | Table @TableParams - } - #endregion ESXi Host Time Configuration - - #region ESXi Host Syslog Configuration - $SyslogConfig = $VMHost | Get-VMHostSysLogServer - if ($SyslogConfig) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Syslog Configuration' { - # TODO: Syslog Rotate & Size, Log Directory (Adv Settings) - $SyslogConfig = $SyslogConfig | Select-Object @{L = 'SysLog Server'; E = { $_.Host } }, Port - $TableParams = @{ - Name = "Syslog Configuration - $VMHost" - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $SyslogConfig | Table @TableParams - } - } - #endregion ESXi Host Syslog Configuration - - #region ESXi Update Manager Baseline Information - if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { - if ($VumServer.Name) { - Try { - $VMHostPatchBaselines = $VMHost | Get-PatchBaseline - } Catch { - Write-PScriboMessage 'ESXi VUM baseline information is not currently available with your version of PowerShell.' - } - if ($VMHostPatchBaselines) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Update Manager Baselines' { - $VMHostBaselines = foreach ($VMHostBaseline in $VMHostPatchBaselines) { - [PSCustomObject]@{ - 'Baseline' = $VMHostBaseline.Name - 'Description' = $VMHostBaseline.Description - 'Type' = $VMHostBaseline.BaselineType - 'Target Type' = $VMHostBaseline.TargetType - 'Last Update Time' = ($VMHostBaseline.LastUpdateTime).ToLocalTime().ToString() - '# of Patches' = $VMHostBaseline.CurrentPatches.Count - } - } - $TableParams = @{ - Name = "Update Manager Baselines - $VMHost" - ColumnWidths = 25, 25, 10, 10, 20, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostBaselines | Sort-Object 'Baseline' | Table @TableParams - } - } - } - } else { - Write-PScriboMessage "Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned." - } - #endregion ESXi Update Manager Baseline Information - - #region ESXi Update Manager Compliance Information - if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { - if ($VumServer.Name) { - Try { - $VMHostCompliances = $VMHost | Get-Compliance - } Catch { - Write-PScriboMessage 'ESXi VUM compliance information is not currently available with your version of PowerShell.' - } - if ($VMHostCompliances) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Update Manager Compliance' { - $VMHostComplianceInfo = foreach ($VMHostCompliance in $VMHostCompliances) { - [PSCustomObject]@{ - 'Baseline' = $VMHostCompliance.Baseline.Name - 'Status' = Switch ($VMHostCompliance.Status) { - 'NotCompliant' { 'Not Compliant' } - default { $VMHostCompliance.Status } - } - } - } - if ($Healthcheck.VMHost.VUMCompliance) { - $VMHostComplianceInfo | Where-Object { $_.Status -eq 'Unknown' } | Set-Style -Style Warning - $VMHostComplianceInfo | Where-Object { $_.Status -eq 'Not Compliant' -or $_.Status -eq 'Incompatible' } | Set-Style -Style Critical - } - $TableParams = @{ - Name = "Update Manager Compliance - $VMHost" - ColumnWidths = 75, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostComplianceInfo | Sort-Object Baseline | Table @TableParams - } - } - } - } else { - Write-PScriboMessage "Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned." - } - #endregion ESXi Update Manager Compliance Information - - #region ESXi Host Comprehensive Information Section - if ($InfoLevel.VMHost -ge 5) { - #region ESXi Host Advanced System Settings - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Advanced System Settings' { - $AdvSettings = $VMHost | Get-AdvancedSetting | Select-Object Name, Value - $TableParams = @{ - Name = "Advanced System Settings - $VMHost" - ColumnWidths = 50, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $AdvSettings | Sort-Object Name | Table @TableParams - } - #endregion ESXi Host Advanced System Settings - - #region ESXi Host Software VIBs - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Software VIBs' { - $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter - $VMHostVibs = $esxcli.software.vib.list.Invoke() - $VMHostVibs = foreach ($VMHostVib in $VMHostVibs) { - [PSCustomObject]@{ - 'VIB' = $VMHostVib.Name - 'ID' = $VMHostVib.Id - 'Version' = $VMHostVib.Version - 'Acceptance Level' = $VMHostVib.AcceptanceLevel - 'Creation Date' = $VMHostVib.CreationDate - 'Install Date' = $VMHostVib.InstallDate - } - } - $TableParams = @{ - Name = "Software VIBs - $VMHost" - ColumnWidths = 15, 25, 15, 15, 15, 15 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostVibs | Sort-Object 'Install Date' -Descending | Table @TableParams - } - #endregion ESXi Host Software VIBs - } - #endregion ESXi Host Comprehensive Information Section - } - #endregion ESXi Host System Section - - #region ESXi Host Storage Section - Section -Style Heading4 'Storage' { - Paragraph "The following section details the host storage configuration for $VMHost." - - #region ESXi Host Datastore Specifications - $VMHostDatastores = $VMHost | Get-Datastore | Where-Object { ($_.State -eq 'Available') -and ($_.CapacityGB -gt 0) } | Sort-Object Name - if ($VMHostDatastores) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Datastores' { - $VMHostDsSpecs = foreach ($VMHostDatastore in $VMHostDatastores) { - - $VMHostDsUsedPercent = if (0 -in @($VMHostDatastore.FreeSpaceGB, $VMHostDatastore.CapacityGB)) {0} else {[math]::Round((100 - (($VMHostDatastore.FreeSpaceGB) / ($VMHostDatastore.CapacityGB) * 100)), 2)} - $VMHostDsFreePercent = if (0 -in @($VMHostDatastore.FreeSpaceGB, $VMHostDatastore.CapacityGB)) {0} else {[math]::Round(($VMHostDatastore.FreeSpaceGB / $VMHostDatastore.CapacityGB) * 100, 2)} - $VMHostDsUsedCapacityGB = ($VMHostDatastore.CapacityGB) - ($VMHostDatastore.FreeSpaceGB) - - [PSCustomObject]@{ - 'Datastore' = $VMHostDatastore.Name - 'Type' = $VMHostDatastore.Type - 'Version' = if ($VMHostDatastore.FileSystemVersion) { - $VMHostDatastore.FileSystemVersion - } else { - '--' - } - '# of VMs' = $VMHostDatastore.ExtensionData.VM.Count - 'Total Capacity' = Convert-DataSize $VMHostDatastore.CapacityGB - 'Used Capacity' = "{0} ({1}%)" -f (Convert-DataSize $VMHostDsUsedCapacityGB), $VMHostDsUsedPercent - 'Free Capacity' = "{0} ({1}%)" -f (Convert-DataSize $Datastore.FreeSpaceGB), $VMHostDsFreePercent - '% Used' = $VMHostDsUsedPercent - } - } - if ($Healthcheck.Datastore.CapacityUtilization) { - $VMHostDsSpecs | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property 'Used Capacity', 'Free Capacity' - $VMHostDsSpecs | Where-Object { $_.'% Used' -ge 75 -and $_.'% Used' -lt 90 } | Set-Style -Style Warning -Property 'Used Capacity', 'Free Capacity' - } - $TableParams = @{ - Name = "Datastores - $VMHost" - Columns = 'Datastore', 'Type', 'Version', '# of VMs', 'Total Capacity', 'Used Capacity', 'Free Capacity' - ColumnWidths = 21, 10, 9, 9, 17, 17, 17 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostDsSpecs | Sort-Object 'Datastore' | Table @TableParams - } - } - #endregion ESXi Host Datastore Specifications - - #region ESXi Host Storage Adapter Information - $VMHostHbas = $VMHost | Get-VMHostHba | Sort-Object Device - if ($VMHostHbas) { - #region ESXi Host Storage Adapters Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Storage Adapters' { - Paragraph "The following section details the storage adapter configuration for $VMHost." - foreach ($VMHostHba in $VMHostHbas) { - $Target = ((Get-View $VMHostHba.VMhost).Config.StorageDevice.ScsiTopology.Adapter | Where-Object { $_.Adapter -eq $VMHostHba.Key }).Target - $LUNs = Get-ScsiLun -Hba $VMHostHba -LunType "disk" -ErrorAction SilentlyContinue - $Paths = ($Target | ForEach-Object { $_.Lun.Count } | Measure-Object -Sum) - Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostHba.Device)" { - $VMHostStorageAdapter = [PSCustomObject]@{ - 'Adapter' = $VMHostHba.Device - 'Type' = Switch ($VMHostHba.Type) { - 'FibreChannel' { 'Fibre Channel' } - 'IScsi' { 'iSCSI' } - 'ParallelScsi' { 'Parallel SCSI' } - default { $TextInfo.ToTitleCase($VMHostHba.Type) } - } - 'Model' = $VMHostHba.Model - 'Status' = $TextInfo.ToTitleCase($VMHostHba.Status) - 'Targets' = $Target.Count - 'Devices' = $LUNs.Count - 'Paths' = $Paths.Sum - } - $MemberProps = @{ - 'InputObject' = $VMHostStorageAdapter - 'MemberType' = 'NoteProperty' - } - if ($VMHostStorageAdapter.Type -eq 'iSCSI') { - $iScsiAuthenticationMethod = Switch ($VMHostHba.ExtensionData.AuthenticationProperties.ChapAuthenticationType) { - 'chapProhibited' { 'None' } - 'chapPreferred' { 'Use unidirectional CHAP unless prohibited by target' } - 'chapDiscouraged' { 'Use unidirectional CHAP if required by target' } - 'chapRequired' { - Switch ($VMHostHba.ExtensionData.AuthenticationProperties.MutualChapAuthenticationType) { - 'chapProhibited' { 'Use unidirectional CHAP' } - 'chapRequired' { 'Use bidirectional CHAP' } - } - } - default { $VMHostHba.ExtensionData.AuthenticationProperties.ChapAuthenticationType } - } - Add-Member @MemberProps -Name 'iSCSI Name' -Value $VMHostHba.IScsiName - if ($VMHostHba.IScsiAlias) { - Add-Member @MemberProps -Name 'iSCSI Alias' -Value $VMHostHba.IScsiAlias - } else { - Add-Member @MemberProps -Name 'iSCSI Alias' -Value '--' - } - if ($VMHostHba.CurrentSpeedMb) { - Add-Member @MemberProps -Name 'Speed' -Value "$($VMHostHba.CurrentSpeedMb) Mb" - } else { - Add-Member @MemberProps -Name 'Speed' -Value '--' - } - if ($VMHostHba.ExtensionData.ConfiguredSendTarget) { - Add-Member @MemberProps -Name 'Dynamic Discovery' -Value (($VMHostHba.ExtensionData.ConfiguredSendTarget | ForEach-Object { "$($_.Address)" + ":" + "$($_.Port)" }) -join [Environment]::NewLine) - } else { - Add-Member @MemberProps -Name 'Dynamic Discovery' -Value '--' - } - if ($VMHostHba.ExtensionData.ConfiguredStaticTarget) { - Add-Member @MemberProps -Name 'Static Discovery' -Value (($VMHostHba.ExtensionData.ConfiguredStaticTarget | ForEach-Object { "$($_.Address)" + ":" + "$($_.Port)" + " " + "$($_.IScsiName)" }) -join [Environment]::NewLine) - } else { - Add-Member @MemberProps -Name 'Static Discovery' -Value '--' - } - if ($iScsiAuthenticationMethod -eq 'None') { - Add-Member @MemberProps -Name 'Authentication Method' -Value $iScsiAuthenticationMethod - } elseif ($iScsiAuthenticationMethod -eq 'Use bidirectional CHAP') { - Add-Member @MemberProps -Name 'Authentication Method' -Value $iScsiAuthenticationMethod - Add-Member @MemberProps -Name 'Outgoing CHAP Name' -Value $VMHostHba.ExtensionData.AuthenticationProperties.ChapName - Add-Member @MemberProps -Name 'Incoming CHAP Name' -Value $VMHostHba.ExtensionData.AuthenticationProperties.MutualChapName - } else { - Add-Member @MemberProps -Name 'Authentication Method' -Value $iScsiAuthenticationMethod - Add-Member @MemberProps -Name 'Outgoing CHAP Name' -Value $VMHostHba.ExtensionData.AuthenticationProperties.ChapName - } - if ($InfoLevel.VMHost -ge 4) { - Add-Member @MemberProps -Name 'Advanced Options' -Value (($VMHostHba.ExtensionData.AdvancedOptions | ForEach-Object { "$($_.Key) = $($_.Value)" }) -join [Environment]::NewLine) - } - } - if ($VMHostStorageAdapter.Type -eq 'Fibre Channel') { - Add-Member @MemberProps -Name 'Node WWN' -Value (([String]::Format("{0:X}", $VMHostHba.NodeWorldWideName) -split "(\w{2})" | Where-Object { $_ -ne "" }) -join ":") - Add-Member @MemberProps -Name 'Port WWN' -Value (([String]::Format("{0:X}", $VMHostHba.PortWorldWideName) -split "(\w{2})" | Where-Object { $_ -ne "" }) -join ":") - Add-Member @MemberProps -Name 'Speed' -Value $VMHostHba.Speed - } - if ($Healthcheck.VMHost.StorageAdapter) { - $VMHostStorageAdapter | Where-Object { $_.'Status' -ne 'Online' } | Set-Style -Style Warning -Property 'Status' - $VMHostStorageAdapter | Where-Object { $_.'Status' -eq 'Offline' } | Set-Style -Style Critical -Property 'Status' - } - $TableParams = @{ - Name = "Storage Adapter $($VMHostStorageAdapter.Adapter) - $VMHost" - List = $true - ColumnWidths = 25, 75 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostStorageAdapter | Table @TableParams - } - } - } - #endregion ESXi Host Storage Adapters Section - } - #endregion ESXi Host Storage Adapter Information - } - #endregion ESXi Host Storage Section - - #region ESXi Host Network Section - Section -Style Heading4 'Network' { - Paragraph "The following section details the host network configuration for $VMHost." - BlankLine - #region ESXi Host Network Configuration - $VMHostNetwork = $VMHost.ExtensionData.Config.Network - $VMHostVirtualSwitch = @() - $VMHostVss = foreach ($vSwitch in $VMHost.ExtensionData.Config.Network.Vswitch) { - $VMHostVirtualSwitch += $vSwitch.Name - } - $VMHostDvs = foreach ($dvSwitch in $VMHost.ExtensionData.Config.Network.ProxySwitch) { - $VMHostVirtualSwitch += $dvSwitch.DvsName - } - $VMHostNetworkDetail = [PSCustomObject]@{ - 'Host' = $VMHost.Name - 'Virtual Switches' = ($VMHostVirtualSwitch | Sort-Object) -join ', ' - 'VMkernel Adapters' = ($VMHostNetwork.Vnic.Device | Sort-Object) -join ', ' - 'Physical Adapters' = ($VMHostNetwork.Pnic.Device | Sort-Object) -join ', ' - 'VMkernel Gateway' = $VMHostNetwork.IpRouteConfig.DefaultGateway - 'IPv6' = if ($VMHostNetwork.IPv6Enabled) { - 'Enabled' - } else { - 'Disabled' - } - 'VMkernel IPv6 Gateway' = if ($VMHostNetwork.IpRouteConfig.IpV6DefaultGateway) { - $VMHostNetwork.IpRouteConfig.IpV6DefaultGateway - } else { - '--' - } - 'DNS Servers' = ($VMHostNetwork.DnsConfig.Address | Sort-Object) -join ', ' - 'Host Name' = $VMHostNetwork.DnsConfig.HostName - 'Domain Name' = $VMHostNetwork.DnsConfig.DomainName - 'Search Domain' = ($VMHostNetwork.DnsConfig.SearchDomain | Sort-Object) -join ', ' - } - if ($Healthcheck.VMHost.IPv6) { - $VMHostNetworkDetail | Where-Object { $_.'IPv6' -eq $false } | Set-Style -Style Warning -Property 'IPv6' - } - $TableParams = @{ - Name = "Network Configuration - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostNetworkDetail | Table @TableParams - #endregion ESXi Host Network Configuration - - #region ESXi Host Physical Adapters - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Physical Adapters' { - Paragraph "The following section details the physical network adapter configuration for $VMHost." - $PhysicalNetAdapters = $VMHost.ExtensionData.Config.Network.Pnic | Sort-Object Device - $VMHostPhysicalNetAdapters = foreach ($PhysicalNetAdapter in $PhysicalNetAdapters) { - [PSCustomObject]@{ - 'Adapter' = $PhysicalNetAdapter.Device - 'Status' = if ($PhysicalNetAdapter.Linkspeed) { - 'Connected' - } else { - 'Disconnected' - } - 'Virtual Switch' = $( - if ($VMHost.ExtensionData.Config.Network.Vswitch.Pnic -contains $PhysicalNetAdapter.Key) { - ($VMHost.ExtensionData.Config.Network.Vswitch | Where-Object { $_.Pnic -eq $PhysicalNetAdapter.Key }).Name - } elseif ($VMHost.ExtensionData.Config.Network.ProxySwitch.Pnic -contains $PhysicalNetAdapter.Key) { - ($VMHost.ExtensionData.Config.Network.ProxySwitch | Where-Object { $_.Pnic -eq $PhysicalNetAdapter.Key }).DvsName - } else { - '--' - } - ) - 'MAC Address' = $PhysicalNetAdapter.Mac - 'Actual Speed, Duplex' = if ($PhysicalNetAdapter.LinkSpeed.SpeedMb) { - if ($PhysicalNetAdapter.LinkSpeed.Duplex) { - "$($PhysicalNetAdapter.LinkSpeed.SpeedMb) Mbps, Full Duplex" - } else { - 'Auto negotiate' - } - } else { - 'Down' - } - 'Configured Speed, Duplex' = if ($PhysicalNetAdapter.Spec.LinkSpeed) { - if ($PhysicalNetAdapter.Spec.LinkSpeed.Duplex) { - "$($PhysicalNetAdapter.Spec.LinkSpeed.SpeedMb) Mbps, Full Duplex" - } else { - "$($PhysicalNetAdapter.Spec.LinkSpeed.SpeedMb) Mbps" - } - } else { - 'Auto negotiate' - } - 'Wake on LAN' = if ($PhysicalNetAdapter.WakeOnLanSupported) { - 'Supported' - } else { - 'Not Supported' - } - } - } - if ($Healthcheck.VMHost.NetworkAdapter) { - $VMHostPhysicalNetAdapters | Where-Object { $_.'Status' -ne 'Connected' } | Set-Style -Style Critical -Property 'Status' - $VMHostPhysicalNetAdapters | Where-Object { $_.'Actual Speed, Duplex' -eq 'Down' } | Set-Style -Style Critical -Property 'Actual Speed, Duplex' - } - if ($InfoLevel.VMHost -ge 4) { - foreach ($VMHostPhysicalNetAdapter in $VMHostPhysicalNetAdapters) { - Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostPhysicalNetAdapter.Adapter)" { - $TableParams = @{ - Name = "Physical Adapter $($VMHostPhysicalNetAdapter.Adapter) - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostPhysicalNetAdapter | Table @TableParams - } - } - } else { - BlankLine - $TableParams = @{ - Name = "Physical Adapters - $VMHost" - ColumnWidths = 11, 13, 15, 19, 14, 14, 14 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostPhysicalNetAdapters | Table @TableParams - } - } - #endregion ESXi Host Physical Adapters - - #region ESXi Host Cisco Discovery Protocol - $VMHostNetworkAdapterCDP = $VMHost | Get-VMHostNetworkAdapterDP | Where-Object { $_.Status -eq 'Connected' } | Sort-Object Device - if ($VMHostNetworkAdapterCDP) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Cisco Discovery Protocol' { - Paragraph "The following section details the CDP information for $VMHost." - if ($InfoLevel.VMHost -ge 4) { - foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterCDP) { - Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostNetworkAdapter.Device)" { - $VMHostCDP = [PSCustomObject]@{ - 'Status' = $VMHostNetworkAdapter.Status - 'System Name' = $VMHostNetworkAdapter.SystemName - 'Hardware Platform' = $VMHostNetworkAdapter.HardwarePlatform - 'Switch ID' = $VMHostNetworkAdapter.SwitchId - 'Software Version' = $VMHostNetworkAdapter.SoftwareVersion - 'Management Address' = $VMHostNetworkAdapter.ManagementAddress - 'Address' = $VMHostNetworkAdapter.Address - 'Port ID' = $VMHostNetworkAdapter.PortId - 'VLAN' = $VMHostNetworkAdapter.Vlan - 'MTU' = $VMHostNetworkAdapter.Mtu - } - $TableParams = @{ - Name = "Network Adapter $($VMHostNetworkAdapter.Device) CDP Information - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostCDP | Table @TableParams - } - } - } else { - BlankLine - $VMHostCDP = foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterCDP) { - [PSCustomObject]@{ - 'Adapter' = $VMHostNetworkAdapter.Device - 'Status' = $VMHostNetworkAdapter.Status - 'Hardware Platform' = $VMHostNetworkAdapter.HardwarePlatform - 'Switch ID' = $VMHostNetworkAdapter.SwitchId - 'Address' = $VMHostNetworkAdapter.Address - 'Port ID' = $VMHostNetworkAdapter.PortId - } - } - $TableParams = @{ - Name = "Network Adapter CDP Information - $VMHost" - ColumnWidths = 11, 13, 26, 22, 17, 11 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostCDP | Table @TableParams - } - } - } - #endregion ESXi Host Cisco Discovery Protocol - - #region ESXi Host Link Layer Discovery Protocol - $VMHostNetworkAdapterLLDP = $VMHost | Get-VMHostNetworkAdapterDP | Where-Object { $null -ne $_.ChassisId } | Sort-Object Device - if ($VMHostNetworkAdapterLLDP) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Link Layer Discovery Protocol' { - Paragraph "The following section details the LLDP information for $VMHost." - if ($InfoLevel.VMHost -ge 4) { - foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterLLDP) { - Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostNetworkAdapter.Device)" { - $VMHostLLDP = [PSCustomObject]@{ - 'Chassis ID' = $VMHostNetworkAdapter.ChassisId - 'Port ID' = $VMHostNetworkAdapter.PortId - 'Time to live' = $VMHostNetworkAdapter.TimeToLive - 'TimeOut' = $VMHostNetworkAdapter.TimeOut - 'Samples' = $VMHostNetworkAdapter.Samples - 'Management Address' = $VMHostNetworkAdapter.ManagementAddress - 'Port Description' = $VMHostNetworkAdapter.PortDescription - 'System Description' = $VMHostNetworkAdapter.SystemDescription - 'System Name' = $VMHostNetworkAdapter.SystemName - } - $TableParams = @{ - Name = "Network Adapter $($VMHostNetworkAdapter.Device) LLDP Information - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostLLDP | Table @TableParams - } - } - } else { - BlankLine - $VMHostLLDP = foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterLLDP) { - [PSCustomObject]@{ - 'Adapter' = $VMHostNetworkAdapter.Device - 'Chassis ID' = $VMHostNetworkAdapter.ChassisId - 'Port ID' = $VMHostNetworkAdapter.PortId - 'Management Address' = $VMHostNetworkAdapter.ManagementAddress - 'Port Description' = $VMHostNetworkAdapter.PortDescription - 'System Name' = $VMHostNetworkAdapter.SystemName - } - } - $TableParams = @{ - Name = "Network Adapter LLDP Information - $VMHost" - ColumnWidths = 11, 19, 16, 19, 18, 17 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostLLDP | Table @TableParams - } - } - } - #endregion ESXi Host Link Layer Discovery Protocol - - #region ESXi Host VMkernel Adapaters - Section -Style NOTOCHeading5 -ExcludeFromTOC 'VMkernel Adapters' { - Paragraph "The following section details the VMkernel adapter configuration for $VMHost" - $VMkernelAdapters = $VMHost | Get-View | ForEach-Object -Process { - #$esx = $_ - $netSys = Get-View -Id $_.ConfigManager.NetworkSystem - $vnicMgr = Get-View -Id $_.ConfigManager.VirtualNicManager - $netSys.NetworkInfo.Vnic | - ForEach-Object -Process { - $device = $_.Device - [PSCustomObject]@{ - 'Adapter' = $_.Device - 'Network Label' = & { - if ($_.Spec.Portgroup) { - $script:pg = $_.Spec.Portgroup - $script:pg - } elseif ($_.Spec.DistributedVirtualPort.Portgroupkey) { - $script:pg = Get-View -ViewType DistributedVirtualPortgroup -Property Name, Key -Filter @{'Key' = "$($_.Spec.DistributedVirtualPort.PortgroupKey)" } | Select-Object -ExpandProperty Name - $script:pg - } else { - '--' - } - } - 'Virtual Switch' = & { - if ($_.Spec.Portgroup) { - (Get-VirtualPortGroup -Standard -Name $script:pg -VMHost $VMHost).VirtualSwitchName - } elseif ($_.Spec.DistributedVirtualPort.Portgroupkey) { - (Get-VDPortgroup -Name $script:pg).VDSwitch.Name | Select-Object -Unique - } else { - # Haven't figured out how to gather this yet! - '--' - } - } - 'TCP/IP Stack' = Switch ($_.Spec.NetstackInstanceKey) { - 'defaultTcpipStack' { 'Default' } - 'vSphereProvisioning' { 'Provisioning' } - 'vmotion' { 'vMotion' } - 'vxlan' { 'nsx-overlay' } - 'hyperbus' { 'nsx-hyperbus' } - $null { 'Not Applicable' } - default { $_.Spec.NetstackInstanceKey } - } - 'MTU' = $_.Spec.Mtu - 'MAC Address' = $_.Spec.Mac - 'DHCP' = if ($_.Spec.Ip.Dhcp) { - 'Enabled' - } else { - 'Disabled' - } - 'IP Address' = & { - if ($_.Spec.IP.IPAddress) { - $script:ip = $_.Spec.IP.IPAddress - } else { - $script:ip = '--' - } - $script:ip - } - 'Subnet Mask' = & { - if ($_.Spec.IP.SubnetMask) { - $script:netmask = $_.Spec.IP.SubnetMask - } else { - $script:netmask = '--' - } - $script:netmask - } - 'Default Gateway' = if ($_.Spec.IpRouteSpec.IpRouteConfig.DefaultGateway) { - $_.Spec.IpRouteSpec.IpRouteConfig.DefaultGateway - } else { - '--' - } - 'vMotion' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vmotion' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - 'Provisioning' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereProvisioning' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - 'FT Logging' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'faultToleranceLogging' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - 'Management' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'management' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - 'vSphere Replication' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereReplication' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - 'vSphere Replication NFC' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereReplicationNFC' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - 'vSAN' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vsan' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - 'vSAN Witness' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vsanWitness' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - 'vSphere Backup NFC' = if ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereBackupnNFC' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { - 'Enabled' - } else { - 'Disabled' - } - } - } - } - foreach ($VMkernelAdapter in ($VMkernelAdapters | Sort-Object 'Adapter')) { - Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMkernelAdapter.Adapter)" { - $TableParams = @{ - Name = "VMkernel Adapter $($VMkernelAdapter.Adapter) - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMkernelAdapter | Table @TableParams - } - } - } - #endregion ESXi Host VMkernel Adapaters - - #region ESXi Host Standard Virtual Switches - $VSSwitches = $VMHost | Get-VirtualSwitch -Standard | Sort-Object Name - if ($VSSwitches) { - #region Section Standard Virtual Switches - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Standard Virtual Switches' { - Paragraph "The following section details the standard virtual switch configuration for $VMHost." - BlankLine - $VSSwitchNicTeaming = $VSSwitches | Get-NicTeamingPolicy - #region ESXi Host Standard Virtual Switch Properties - $VSSProperties = foreach ($VSSwitchNicTeam in $VSSwitchNicTeaming) { - [PSCustomObject]@{ - 'Virtual Switch' = $VSSwitchNicTeam.VirtualSwitch - 'MTU' = $VSSwitchNicTeam.VirtualSwitch.Mtu - 'Number of Ports' = $VSSwitchNicTeam.VirtualSwitch.NumPorts - 'Number of Ports Available' = $VSSwitchNicTeam.VirtualSwitch.NumPortsAvailable - } - } - $TableParams = @{ - Name = "Standard Virtual Switches - $VMHost" - ColumnWidths = 25, 25, 25, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VSSProperties | Table @TableParams - #endregion ESXi Host Standard Virtual Switch Properties - - #region ESXi Host Virtual Switch Security Policy - $VssSecurity = $VSSwitches | Get-SecurityPolicy - if ($VssSecurity) { - #region Virtual Switch Security Policy - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Security' { - $VssSecurity = foreach ($VssSec in $VssSecurity) { - [PSCustomObject]@{ - 'Virtual Switch' = $VssSec.VirtualSwitch - 'Promiscuous Mode' = if ($VssSec.AllowPromiscuous) { - 'Accept' - } else { - 'Reject' - } - 'MAC Address Changes' = if ($VssSec.MacChanges) { - 'Accept' - } else { - 'Reject' - } - 'Forged Transmits' = if ($VssSec.ForgedTransmits) { - 'Accept' - } else { - 'Reject' - } - } - } - $TableParams = @{ - Name = "Virtual Switch Security Policy - $VMHost" - ColumnWidths = 25, 25, 25, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VssSecurity | Sort-Object 'Virtual Switch' | Table @TableParams - } - #endregion Virtual Switch Security Policy - } - #endregion ESXi Host Virtual Switch Security Policy - - #region ESXi Host Virtual Switch Traffic Shaping Policy - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Traffic Shaping' { - $VssTrafficShapingPolicy = foreach ($VSSwitch in $VSSwitches) { - [PSCustomObject]@{ - 'Virtual Switch' = $VSSwitch.Name - 'Status' = if ($VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.Enabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Average Bandwidth (kbit/s)' = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.AverageBandwidth - 'Peak Bandwidth (kbit/s)' = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.PeakBandwidth - 'Burst Size (KB)' = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.BurstSize - } - } - $TableParams = @{ - Name = "Virtual Switch Traffic Shaping Policy - $VMHost" - ColumnWidths = 25, 15, 20, 20, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VssTrafficShapingPolicy | Sort-Object 'Virtual Switch' | Table @TableParams - } - #endregion ESXi Host Virtual Switch Traffic Shaping Policy - - #region ESXi Host Virtual Switch Teaming & Failover - $VssNicTeamingPolicy = $VSSwitches | Get-NicTeamingPolicy - if ($VssNicTeamingPolicy) { - #region Virtual Switch Teaming & Failover Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Teaming & Failover' { - $VssNicTeaming = foreach ($VssNicTeam in $VssNicTeamingPolicy) { - [PSCustomObject]@{ - 'Virtual Switch' = $VssNicTeam.VirtualSwitch - 'Load Balancing' = Switch ($VssNicTeam.LoadBalancingPolicy) { - 'LoadbalanceSrcId' { 'Route based on the originating port ID' } - 'LoadbalanceSrcMac' { 'Route based on source MAC hash' } - 'LoadbalanceIP' { 'Route based on IP hash' } - 'ExplicitFailover' { 'Explicit Failover' } - default { $VssNicTeam.LoadBalancingPolicy } - } - 'Network Failure Detection' = Switch ($VssNicTeam.NetworkFailoverDetectionPolicy) { - 'LinkStatus' { 'Link status only' } - 'BeaconProbing' { 'Beacon probing' } - default { $VssNicTeam.NetworkFailoverDetectionPolicy } - } - 'Notify Switches' = if ($VssNicTeam.NotifySwitches) { - 'Yes' - } else { - 'No' - } - 'Failback' = if ($VssNicTeam.FailbackEnabled) { - 'Yes' - } else { - 'No' - } - 'Active NICs' = ($VssNicTeam.ActiveNic | Sort-Object) -join [Environment]::NewLine - 'Standby NICs' = ($VssNicTeam.StandbyNic | Sort-Object) -join [Environment]::NewLine - 'Unused NICs' = ($VssNicTeam.UnusedNic | Sort-Object) -join [Environment]::NewLine - } - } - $TableParams = @{ - Name = "Virtual Switch Teaming & Failover - $VMHost" - ColumnWidths = 20, 17, 12, 11, 10, 10, 10, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VssNicTeaming | Sort-Object 'Virtual Switch' | Table @TableParams - } - #endregion Virtual Switch Teaming & Failover Section - } - #endregion ESXi Host Virtual Switch Teaming & Failover - - #region ESXi Host Virtual Switch Port Groups - $VssPortgroups = $VSSwitches | Get-VirtualPortGroup -Standard - if ($VssPortgroups) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Port Groups' { - $VssPortgroups = foreach ($VssPortgroup in $VssPortgroups) { - [PSCustomObject]@{ - 'Port Group' = $VssPortgroup.Name - 'VLAN ID' = $VssPortgroup.VLanId - 'Virtual Switch' = $VssPortgroup.VirtualSwitchName - '# of VMs' = ($VssPortgroup | Get-VM).Count - } - } - $TableParams = @{ - Name = "Virtual Switch Port Groups - $VMHost" - ColumnWidths = 40, 10, 40, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VssPortgroups | Sort-Object 'Port Group', 'VLAN ID', 'Virtual Switch' | Table @TableParams - } - #endregion ESXi Host Virtual Switch Port Groups - - #region ESXi Host Virtual Switch Port Group Security Policy - $VssPortgroupSecurity = $VSSwitches | Get-VirtualPortGroup | Get-SecurityPolicy - if ($VssPortgroupSecurity) { - #region Virtual Port Group Security Policy Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Port Group Security' { - $VssPortgroupSecurity = foreach ($VssPortgroupSec in $VssPortgroupSecurity) { - [PSCustomObject]@{ - 'Port Group' = $VssPortgroupSec.VirtualPortGroup - 'Virtual Switch' = $VssPortgroupSec.virtualportgroup.virtualswitchname - 'Promiscuous Mode' = if ($VssPortgroupSec.AllowPromiscuous) { - 'Accept' - } else { - 'Reject' - } - 'MAC Changes' = if ($VssPortgroupSec.MacChanges) { - 'Accept' - } else { - 'Reject' - } - 'Forged Transmits' = if ($VssPortgroupSec.ForgedTransmits) { - 'Accept' - } else { - 'Reject' - } - } - } - $TableParams = @{ - Name = "Virtual Switch Port Group Security Policy - $VMHost" - ColumnWidths = 27, 25, 16, 16, 16 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VssPortgroupSecurity | Sort-Object 'Port Group', 'Virtual Switch' | Table @TableParams - } - #endregion Virtual Port Group Security Policy Section - } - #endregion ESXi Host Virtual Switch Port Group Security Policy - - #region ESXi Host Virtual Switch Port Group Traffic Shaping Policy - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Port Group Traffic Shaping' { - $VssPortgroupTrafficShapingPolicy = foreach ($VssPortgroup in $VssPortgroups) { - [PSCustomObject]@{ - 'Port Group' = $VssPortgroup.Name - 'Virtual Switch' = $VssPortgroup.VirtualSwitchName - 'Status' = Switch ($VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.Enabled) { - $True { 'Enabled' } - $False { 'Disabled' } - $null { 'Inherited' } - } - 'Average Bandwidth (kbit/s)' = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.AverageBandwidth - 'Peak Bandwidth (kbit/s)' = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.PeakBandwidth - 'Burst Size (KB)' = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.BurstSize - } - } - $TableParams = @{ - Name = "Virtual Switch Port Group Traffic Shaping Policy - $VMHost" - ColumnWidths = 19, 19, 11, 17, 17, 17 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VssPortgroupTrafficShapingPolicy | Sort-Object 'Port Group', 'Virtual Switch' | Table @TableParams - } - #endregion ESXi Host Virtual Switch Port Group Traffic Shaping Policy - - #region ESXi Host Virtual Switch Port Group Teaming & Failover - $VssPortgroupNicTeaming = $VSSwitches | Get-VirtualPortGroup | Get-NicTeamingPolicy - if ($VssPortgroupNicTeaming) { - #region Virtual Switch Port Group Teaming & Failover Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Port Group Teaming & Failover' { - $VssPortgroupNicTeaming = foreach ($VssPortgroupNicTeam in $VssPortgroupNicTeaming) { - [PSCustomObject]@{ - 'Port Group' = $VssPortgroupNicTeam.VirtualPortGroup - 'Virtual Switch' = $VssPortgroupNicTeam.virtualportgroup.virtualswitchname - 'Load Balancing' = Switch ($VssPortgroupNicTeam.LoadBalancingPolicy) { - 'LoadbalanceSrcId' { 'Route based on the originating port ID' } - 'LoadbalanceSrcMac' { 'Route based on source MAC hash' } - 'LoadbalanceIP' { 'Route based on IP hash' } - 'ExplicitFailover' { 'Explicit Failover' } - default { $VssPortgroupNicTeam.LoadBalancingPolicy } - } - 'Network Failure Detection' = Switch ($VssPortgroupNicTeam.NetworkFailoverDetectionPolicy) { - 'LinkStatus' { 'Link status only' } - 'BeaconProbing' { 'Beacon probing' } - default { $VssPortgroupNicTeam.NetworkFailoverDetectionPolicy } - } - 'Notify Switches' = if ($VssPortgroupNicTeam.NotifySwitches) { - 'Yes' - } else { - 'No' - } - 'Failback' = if ($VssPortgroupNicTeam.FailbackEnabled) { - 'Yes' - } else { - 'No' - } - 'Active NICs' = ($VssPortgroupNicTeam.ActiveNic | Sort-Object) -join [Environment]::NewLine - 'Standby NICs' = ($VssPortgroupNicTeam.StandbyNic | Sort-Object) -join [Environment]::NewLine - 'Unused NICs' = ($VssPortgroupNicTeam.UnusedNic | Sort-Object) -join [Environment]::NewLine - } - } - $TableParams = @{ - Name = "Virtual Switch Port Group Teaming & Failover - $VMHost" - ColumnWidths = 12, 11, 11, 11, 11, 11, 11, 11, 11 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VssPortgroupNicTeaming | Sort-Object 'Port Group', 'Virtual Switch' | Table @TableParams - } - #endregion Virtual Switch Port Group Teaming & Failover Section - } - #endregion ESXi Host Virtual Switch Port Group Teaming & Failover - } - } - #endregion Section Standard Virtual Switches - } - #endregion ESXi Host Standard Virtual Switches - } - #endregion ESXi Host Network Section - - #region ESXi Host Security Section - Section -Style Heading4 'Security' { - Paragraph "The following section details the host security configuration for $VMHost." - #region ESXi Host Lockdown Mode - if ($null -ne $VMHost.ExtensionData.Config.LockdownMode) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Lockdown Mode' { - $LockdownMode = [PSCustomObject]@{ - 'Lockdown Mode' = Switch ($VMHost.ExtensionData.Config.LockdownMode) { - 'lockdownDisabled' { 'Disabled' } - 'lockdownNormal' { 'Enabled (Normal)' } - 'lockdownStrict' { 'Enabled (Strict)' } - default { $VMHost.ExtensionData.Config.LockdownMode } - } - } - if ($Healthcheck.VMHost.LockdownMode) { - $LockdownMode | Where-Object { $_.'Lockdown Mode' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Lockdown Mode' - } - $TableParams = @{ - Name = "Lockdown Mode - $VMHost" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $LockdownMode | Table @TableParams - } - } - #endregion ESXi Host Lockdown Mode - - #region ESXi Host Services - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Services' { - $VMHostServices = $VMHost | Get-VMHostService - $Services = foreach ($VMHostService in $VMHostServices) { - [PSCustomObject]@{ - 'Service' = $VMHostService.Label - 'Daemon' = if ($VMHostService.Running) { - 'Running' - } else { - 'Stopped' - } - 'Startup Policy' = Switch ($VMHostService.Policy) { - 'automatic' { 'Start and stop with port usage' } - 'on' { 'Start and stop with host' } - 'off' { 'Start and stop manually' } - default { $VMHostService.Policy } - } - } - } - if ($Healthcheck.VMHost.NTP) { - $Services | Where-Object { ($_.'Service' -eq 'NTP Daemon') -and ($_.Daemon -eq 'Stopped') } | Set-Style -Style Critical -Property 'Daemon' - $Services | Where-Object { ($_.'Service' -eq 'NTP Daemon') -and ($_.'Startup Policy' -ne 'Start and stop with host') } | Set-Style -Style Critical -Property 'Startup Policy' - } - if ($Healthcheck.VMHost.SSH) { - $Services | Where-Object { ($_.'Service' -eq 'SSH') -and ($_.Daemon -eq 'Running') } | Set-Style -Style Warning -Property 'Daemon' - $Services | Where-Object { ($_.'Service' -eq 'SSH') -and ($_.'Startup Policy' -ne 'Start and stop manually') } | Set-Style -Style Warning -Property 'Startup Policy' - } - if ($Healthcheck.VMHost.ESXiShell) { - $Services | Where-Object { ($_.'Service' -eq 'ESXi Shell') -and ($_.Daemon -eq 'Running') } | Set-Style -Style Warning -Property 'Daemon' - $Services | Where-Object { ($_.'Service' -eq 'ESXi Shell') -and ($_.'Startup Policy' -ne 'Start and stop manually') } | Set-Style -Style Warning -Property 'Startup Policy' - } - $TableParams = @{ - Name = "Services - $VMHost" - ColumnWidths = 40, 20, 40 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $Services | Sort-Object 'Service' | Table @TableParams - } - #endregion ESXi Host Services - - #region ESXi Host Advanced Detail Information - if ($InfoLevel.VMHost -ge 4) { - #region ESXi Host Firewall - $VMHostFirewallExceptions = $VMHost | Get-VMHostFirewallException - if ($VMHostFirewallExceptions) { - #region Friewall Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Firewall' { - $VMHostFirewall = foreach ($VMHostFirewallException in $VMHostFirewallExceptions) { - [PScustomObject]@{ - 'Service' = $VMHostFirewallException.Name - 'Status' = if ($VMHostFirewallException.Enabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Incoming Ports' = $VMHostFirewallException.IncomingPorts - 'Outgoing Ports' = $VMHostFirewallException.OutgoingPorts - 'Protocols' = $VMHostFirewallException.Protocols - 'Daemon' = Switch ($VMHostFirewallException.ServiceRunning) { - $true { 'Running' } - $false { 'Stopped' } - $null { 'N/A' } - default { $VMHostFirewallException.ServiceRunning } - } - } - } - $TableParams = @{ - Name = "Firewall Configuration - $VMHost" - ColumnWidths = 22, 12, 21, 21, 12, 12 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostFirewall | Sort-Object 'Service' | Table @TableParams - } - #endregion Friewall Section - } - #endregion ESXi Host Firewall - - #region ESXi Host Authentication - $AuthServices = $VMHost | Get-VMHostAuthentication - if ($AuthServices.DomainMembershipStatus) { - Section -Style NOTOCHeading5 -ExcludeFromTOC 'Authentication Services' { - $AuthServices = $AuthServices | Select-Object Domain, @{L = 'Domain Membership'; E = { $_.DomainMembershipStatus } }, @{L = 'Trusted Domains'; E = { $_.TrustedDomains } } - $TableParams = @{ - Name = "Authentication Services - $VMHost" - ColumnWidths = 25, 25, 50 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $AuthServices | Table @TableParams - } - } - #endregion ESXi Host Authentication - } - #endregion ESXi Host Advanced Detail Information - } - #endregion ESXi Host Security Section - - #region ESXi Host Virtual Machines Advanced Detail Information - if ($InfoLevel.VMHost -ge 4) { - $VMHostVMs = $VMHost | Get-VM | Sort-Object Name - if ($VMHostVMs) { - #region Virtual Machines Section - Section -Style Heading4 'Virtual Machines' { - Paragraph "The following section details the virtual machine configuration for $VMHost." - BlankLine - #region ESXi Host Virtual Machine Information - $VMHostVMInfo = foreach ($VMHostVM in $VMHostVMs) { - $VMHostVMView = $VMHostVM | Get-View - [PSCustomObject]@{ - 'Virtual Machine' = $VMHostVM.Name - 'Power State' = Switch ($VMHostVM.PowerState) { - 'PoweredOn' { 'On' } - 'PoweredOff' { 'Off' } - default { $VMHostVM.PowerState } - } - 'IP Address' = if ($VMHostVMView.Guest.IpAddress) { - $VMHostVMView.Guest.IpAddress - } else { - '--' - } - 'CPUs' = $VMHostVM.NumCpu - #'Cores per Socket' = $VMHostVM.CoresPerSocket - 'Memory' = Convert-DataSize $VMHostVM.memoryGB -RoundUnits 0 - 'Provisioned' = Convert-DataSize $VMHostVM.ProvisionedSpaceGB - 'Used' = Convert-DataSize $VMHostVM.UsedSpaceGB - 'HW Version' = ($VMHostVM.HardwareVersion).Replace('vmx-', 'v') - 'VM Tools Status' = Switch ($VMHostVM.ExtensionData.Guest.ToolsStatus) { - 'toolsOld' { 'Old' } - 'toolsOK' { 'OK' } - 'toolsNotRunning' { 'Not Running' } - 'toolsNotInstalled' { 'Not Installed' } - default { $VMHostVM.ExtensionData.Guest.ToolsStatus } - } - } - } - if ($Healthcheck.VM.VMToolsStatus) { - $VMHostVMInfo | Where-Object { $_.'VM Tools Status' -ne 'OK' } | Set-Style -Style Warning -Property 'VM Tools Status' - } - if ($Healthcheck.VM.PowerState) { - $VMHostVMInfo | Where-Object { $_.'Power State' -ne 'On' } | Set-Style -Style Warning -Property 'Power State' - } - $TableParams = @{ - Name = "Virtual Machines - $VMHost" - ColumnWidths = 21, 8, 16, 9, 9, 9, 9, 9, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHostVMInfo | Table @TableParams - #endregion ESXi Host Virtual Machine Information - - #region ESXi Host VM Startup/Shutdown Information - $VMStartPolicy = $VMHost | Get-VMStartPolicy | Where-Object { $_.StartAction -ne 'None' } - if ($VMStartPolicy) { - #region VM Startup/Shutdown Section - Section -Style NOTOCHeading5 -ExcludeFromTOC 'VM Startup/Shutdown' { - $VMStartPolicies = foreach ($VMStartPol in $VMStartPolicy) { - [PSCustomObject]@{ - 'Start Order' = $VMStartPol.StartOrder - 'VM Name' = $VMStartPol.VirtualMachineName - 'Startup' = Switch ($VMStartPol.StartAction) { - 'PowerOn' { 'Enabled' } - 'None' { 'Disabled' } - default { $VMStartPol.StartAction } - } - 'Startup Delay' = "$($VMStartPol.StartDelay) seconds" - 'VMware Tools' = if ($VMStartPol.WaitForHeartbeat) { - 'Continue if VMware Tools is started' - } else { - 'Wait for startup delay' - } - 'Shutdown Behavior' = Switch ($VMStartPol.StopAction) { - 'PowerOff' { 'Power Off' } - 'GuestShutdown' { 'Guest Shutdown' } - default { $VMStartPol.StopAction } - } - 'Shutdown Delay' = "$($VMStartPol.StopDelay) seconds" - } - } - $TableParams = @{ - Name = "VM Startup/Shutdown Policy - $VMHost" - ColumnWidths = 11, 34, 11, 11, 11, 11, 11 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMStartPolicies | Table @TableParams - } - #endregion VM Startup/Shutdown Section - } - #endregion ESXi Host VM Startup/Shutdown Information - } - #endregion Virtual Machines Section - } - } - #endregion ESXi Host Virtual Machines Advanced Detail Information - } - #endregion VMHost Section - } - #endregion foreach VMHost Detailed Information loop - } - #endregion ESXi Host Detailed Information - } - #endregion Hosts Section - } - } - #endregion ESXi VMHost Section - - #region Distributed Switch Section - Write-PScriboMessage "Network InfoLevel set at $($InfoLevel.Network)." - if ($InfoLevel.Network -ge 1) { - # Create Distributed Switch Section if they exist - $VDSwitches = Get-VDSwitch -Server $vCenter | Sort-Object Name - if ($VDSwitches) { - Section -Style Heading2 'Distributed Switches' { - Paragraph "The following sections detail the configuration of distributed switches managed by vCenter Server $vCenterServerName." - #region Distributed Switch Advanced Summary - if ($InfoLevel.Network -le 2) { - BlankLine - $VDSInfo = foreach ($VDS in $VDSwitches) { - [PSCustomObject]@{ - 'VDSwitch' = $VDS.Name - 'Datacenter' = $VDS.Datacenter - 'Manufacturer' = $VDS.Vendor - 'Version' = $VDS.Version - '# of Uplinks' = $VDS.NumUplinkPorts - '# of Ports' = $VDS.NumPorts - '# of Hosts' = $VDS.ExtensionData.Summary.HostMember.Count - '# of VMs' = $VDS.ExtensionData.Summary.VM.Count - } - } - $TableParams = @{ - Name = "Distributed Switch Summary - $($vCenterServerName)" - ColumnWidths = 20, 18, 18, 10, 10, 8, 8, 8 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSInfo | Table @TableParams - } - #endregion Distributed Switch Advanced Summary - - #region Distributed Switch Detailed Information - if ($InfoLevel.Network -ge 3) { - # TODO: LACP, NetFlow, NIOC - # TODO: Test Tags - foreach ($VDS in ($VDSwitches)) { - #region VDS Section - Section -Style Heading3 $VDS { - #region Distributed Switch General Properties - $VDSwitchDetail = [PSCustomObject]@{ - 'Distributed Switch' = $VDS.Name - 'ID' = $VDS.Id - 'Datacenter' = $VDS.Datacenter - 'Manufacturer' = $VDS.Vendor - 'Version' = $VDS.Version - 'Number of Uplinks' = $VDS.NumUplinkPorts - 'Number of Ports' = $VDS.NumPorts - 'Number of Port Groups' = $VDS.ExtensionData.Summary.PortGroupName.Count - 'Number of Hosts' = $VDS.ExtensionData.Summary.HostMember.Count - 'Number of VMs' = $VDS.ExtensionData.Summary.VM.Count - 'MTU' = $VDS.Mtu - 'Network I/O Control' = if ($VDS.ExtensionData.Config.NetworkResourceManagementEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Discovery Protocol' = $VDS.LinkDiscoveryProtocol - 'Discovery Protocol Operation' = $VDS.LinkDiscoveryProtocolOperation - } - <# - $MemberProps = @{ - 'InputObject' = $VDSwitchDetail - 'MemberType' = 'NoteProperty' - } - if ($TagAssignments | Where-Object {$_.entity -eq $VDS}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VDS}).Tag -join ',') - } - #> - #region Network Advanced Detail Information - if ($InfoLevel.Network -ge 4) { - $VDSwitchDetail | ForEach-Object { - $VDSwitchHosts = $VDS | Get-VMHost | Sort-Object Name - Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Hosts' -Value ($VDSwitchHosts.Name -join ', ') - $VDSwitchVMs = $VDS | Get-VM | Sort-Object - Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Virtual Machines' -Value ($VDSwitchVMs.Name -join ', ') - } - } - #endregion Network Advanced Detail Information - $TableParams = @{ - Name = "Distributed Switch General Properties - $VDS" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSwitchDetail | Table @TableParams - #endregion Distributed Switch General Properties - - #region Distributed Switch Uplink Ports - $VdsUplinks = $VDS | Get-VDPortgroup | Where-Object { $_.IsUplink -eq $true } | Get-VDPort - if ($VdsUplinks) { - Section -Style Heading4 'Distributed Switch Uplink Ports' { - $VdsUplinkDetail = foreach ($VdsUplink in $VdsUplinks) { - [PSCustomObject]@{ - 'Distributed Switch' = $VdsUplink.Switch - 'Host' = $VdsUplink.ProxyHost - 'Uplink Name' = $VdsUplink.Name - 'Physical Network Adapter' = $VdsUplink.ConnectedEntity - 'Uplink Port Group' = $VdsUplink.Portgroup - } - } - $TableParams = @{ - Name = "Distributed Switch Uplink Ports - $VDS" - ColumnWidths = 20, 20, 20, 20, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VdsUplinkDetail | Sort-Object 'Distributed Switch', 'Host', 'Uplink Name' | Table @TableParams - } - } - #endregion Distributed Switch Uplink Ports - - #region Distributed Switch Security - $VDSecurityPolicy = $VDS | Get-VDSecurityPolicy - if ($VDSecurityPolicy) { - Section -Style Heading4 'Distributed Switch Security' { - $VDSecurityPolicyDetail = [PSCustomObject]@{ - 'Distributed Switch' = $VDSecurityPolicy.VDSwitch - 'Allow Promiscuous' = if ($VDSecurityPolicy.AllowPromiscuous) { - 'Accept' - } else { - 'Reject' - } - 'Forged Transmits' = if ($VDSecurityPolicy.ForgedTransmits) { - 'Accept' - } else { - 'Reject' - } - 'MAC Address Changes' = if ($VDSecurityPolicy.MacChanges) { - 'Accept' - } else { - 'Reject' - } - } - $TableParams = @{ - Name = "Distributed Switch Security - $VDS" - ColumnWidths = 25, 25, 25, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSecurityPolicyDetail | Table @TableParams - } - } - #endregion Distributed Switch Security - - #region Distributed Switch Traffic Shaping - $VDSTrafficShaping = @() - $VDSTrafficShapingIn = $VDS | Get-VDTrafficShapingPolicy -Direction In - $VDSTrafficShapingOut = $VDS | Get-VDTrafficShapingPolicy -Direction Out - $VDSTrafficShaping += $VDSTrafficShapingIn - $VDSTrafficShaping += $VDSTrafficShapingOut - if ($VDSTrafficShapingIn -or $VDSTrafficShapingOut) { - Section -Style Heading4 'Distributed Switch Traffic Shaping' { - $VDSTrafficShapingDetail = foreach ($VDSTrafficShape in $VDSTrafficShaping) { - [PSCustomObject]@{ - 'Distributed Switch' = $VDSTrafficShape.VDSwitch - 'Direction' = $VDSTrafficShape.Direction - 'Status' = if ($VDSTrafficShape.Enabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Average Bandwidth (kbit/s)' = $VDSTrafficShape.AverageBandwidth - 'Peak Bandwidth (kbit/s)' = $VDSTrafficShape.PeakBandwidth - 'Burst Size (KB)' = $VDSTrafficShape.BurstSize - } - } - $TableParams = @{ - Name = "Distributed Switch Traffic Shaping - $VDS" - ColumnWidths = 25, 13, 11, 17, 17, 17 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSTrafficShapingDetail | Sort-Object 'Direction' | Table @TableParams - } - } - #endregion Distributed Switch Traffic Shaping - - #region Distributed Switch Port Groups - # TODO: Test Tags - $VDSPortgroups = $VDS | Get-VDPortgroup - if ($VDSPortgroups) { - Section -Style Heading4 'Distributed Switch Port Groups' { - $VDSPortgroupDetail = foreach ($VDSPortgroup in $VDSPortgroups) { - [PSCustomObject]@{ - 'Port Group' = $VDSPortgroup.Name - 'Distributed Switch' = $VDSPortgroup.VDSwitch - 'Datacenter' = $VDSPortgroup.Datacenter - 'VLAN Configuration' = if ($VDSPortgroup.VlanConfiguration) { - $VDSPortgroup.VlanConfiguration - } else { - '--' - } - 'Port Binding' = $VDSPortgroup.PortBinding - '# of Ports' = $VDSPortgroup.NumPorts - <# - # Tags on portgroups cause Get-TagAssignments to error - 'Tags' = & { - if ($TagAssignments | Where-Object {$_.entity -eq $VDSPortgroup}) { - ($TagAssignments | Where-Object {$_.entity -eq $VDSPortgroup}).Tag -join ',' - } else { - '--' - } - } - #> - } - } - $TableParams = @{ - Name = "Distributed Switch Port Groups - $VDS" - ColumnWidths = 20, 20, 20, 15, 15, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSPortgroupDetail | Sort-Object 'Port Group' | Table @TableParams - } - } - #endregion Distributed Switch Port Groups - - #region Distributed Switch Port Group Security - $VDSPortgroupSecurity = $VDS | Get-VDPortgroup | Get-VDSecurityPolicy - if ($VDSPortgroupSecurity) { - Section -Style NOTOCHeading5 -ExcludeFromTOC "Distributed Switch Port Group Security" { - $VDSSecurityPolicies = foreach ($VDSSecurityPolicy in $VDSPortgroupSecurity) { - [PSCustomObject]@{ - 'Port Group' = $VDSSecurityPolicy.VDPortgroup - 'Distributed Switch' = $VDS.Name - 'Allow Promiscuous' = if ($VDSSecurityPolicy.AllowPromiscuous) { - 'Accept' - } else { - 'Reject' - } - 'Forged Transmits' = if ($VDSSecurityPolicy.ForgedTransmits) { - 'Accept' - } else { - 'Reject' - } - 'MAC Address Changes' = if ($VDSSecurityPolicy.MacChanges) { - 'Accept' - } else { - 'Reject' - } - } - } - $TableParams = @{ - Name = "Distributed Switch Port Group Security - $VDS" - ColumnWidths = 20, 20, 20, 20, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSSecurityPolicies | Sort-Object 'Port Group' | Table @TableParams - } - } - #endregion Distributed Switch Port Group Security - - #region Distributed Switch Port Group Traffic Shaping - $VDSPortgroupTrafficShaping = @() - $VDSPortgroupTrafficShapingIn = $VDS | Get-VDPortgroup | Get-VDTrafficShapingPolicy -Direction In - $VDSPortgroupTrafficShapingOut = $VDS | Get-VDPortgroup | Get-VDTrafficShapingPolicy -Direction Out - $VDSPortgroupTrafficShaping += $VDSPortgroupTrafficShapingIn - $VDSPortgroupTrafficShaping += $VDSPortgroupTrafficShapingOut - if ($VDSPortgroupTrafficShaping) { - Section -Style NOTOCHeading5 -ExcludeFromTOC "Distributed Switch Port Group Traffic Shaping" { - $VDSPortgroupTrafficShapingDetail = foreach ($VDSPortgroupTrafficShape in $VDSPortgroupTrafficShaping) { - [PSCustomObject]@{ - 'Port Group' = $VDSPortgroupTrafficShape.VDPortgroup - 'Distributed Switch' = $VDS.Name - 'Direction' = $VDSPortgroupTrafficShape.Direction - 'Status' = if ($VDSPortgroupTrafficShape.Enabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Average Bandwidth (kbit/s)' = $VDSPortgroupTrafficShape.AverageBandwidth - 'Peak Bandwidth (kbit/s)' = $VDSPortgroupTrafficShape.PeakBandwidth - 'Burst Size (KB)' = $VDSPortgroupTrafficShape.BurstSize - } - } - $TableParams = @{ - Name = "Distributed Switch Port Group Traffic Shaping - $VDS" - ColumnWidths = 16, 16, 10, 10, 16, 16, 16 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSPortgroupTrafficShapingDetail | Sort-Object 'Port Group', 'Direction', 'Port Group' | Table @TableParams - - } - } - #endregion Distributed Switch Port Group Traffic Shaping - - #region Distributed Switch Port Group Teaming & Failover - $VDUplinkTeamingPolicy = $VDS | Get-VDPortgroup | Get-VDUplinkTeamingPolicy - if ($VDUplinkTeamingPolicy) { - Section -Style NOTOCHeading5 -ExcludeFromTOC "Distributed Switch Port Group Teaming & Failover" { - $VDSPortgroupNICTeaming = foreach ($VDUplink in $VDUplinkTeamingPolicy) { - [PSCustomObject]@{ - 'Port Group' = $VDUplink.VDPortgroup - 'Distributed Switch' = $VDS.Name - 'Load Balancing' = Switch ($VDUplink.LoadBalancingPolicy) { - 'LoadbalanceSrcId' { 'Route based on the originating port ID' } - 'LoadbalanceSrcMac' { 'Route based on source MAC hash' } - 'LoadbalanceIP' { 'Route based on IP hash' } - 'ExplicitFailover' { 'Explicit Failover' } - default { $VDUplink.LoadBalancingPolicy } - } - 'Network Failure Detection' = Switch ($VDUplink.FailoverDetectionPolicy) { - 'LinkStatus' { 'Link status only' } - 'BeaconProbing' { 'Beacon probing' } - default { $VDUplink.FailoverDetectionPolicy } - } - 'Notify Switches' = if ($VDUplink.NotifySwitches) { - 'Yes' - } else { - 'No' - } - 'Failback Enabled' = if ($VDUplink.EnableFailback) { - 'Yes' - } else { - 'No' - } - 'Active Uplinks' = $VDUplink.ActiveUplinkPort -join [Environment]::NewLine - 'Standby Uplinks' = $VDUplink.StandbyUplinkPort -join [Environment]::NewLine - 'Unused Uplinks' = $VDUplink.UnusedUplinkPort -join [Environment]::NewLine - } - } - $TableParams = @{ - Name = "Distributed Switch Port Group Teaming & Failover - $VDS" - ColumnWidths = 12, 12, 12, 11, 10, 10, 11, 11, 11 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSPortgroupNICTeaming | Sort-Object 'Port Group' | Table @TableParams - } - } - #endregion Distributed Switch Port Group Teaming & Failover - - #region Distributed Switch Private VLANs - $VDSwitchPrivateVLANs = $VDS | Get-VDSwitchPrivateVlan - if ($VDSwitchPrivateVLANs) { - Section -Style Heading4 'Distributed Switch Private VLANs' { - $VDSPvlan = foreach ($VDSwitchPrivateVLAN in $VDSwitchPrivateVLANs) { - [PSCustomObject]@{ - 'Primary VLAN ID' = $VDSwitchPrivateVLAN.PrimaryVlanId - 'Private VLAN Type' = $VDSwitchPrivateVLAN.PrivateVlanType - 'Secondary VLAN ID' = $VDSwitchPrivateVLAN.SecondaryVlanId - } - } - $TableParams = @{ - Name = "Distributed Switch Private VLANs - $VDS" - ColumnWidths = 33, 34, 33 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VDSPvlan | Sort-Object 'Primary VLAN ID', 'Secondary VLAN ID' | Table @TableParams - } - } - #endregion Distributed Switch Private VLANs - } - #endregion VDS Section - } - } - #endregion Distributed Switch Detailed Information - } - } - } - #endregion Distributed Switch Section - - #region vSAN Section - Write-PScriboMessage "vSAN InfoLevel set at $($InfoLevel.vSAN)." - if (($InfoLevel.vSAN -ge 1) -and ($vCenter.Version -gt 6)) { - $VsanClusters = Get-VsanClusterConfiguration -Server $vCenter | Where-Object { $_.vsanenabled -eq $true } | Sort-Object Name - if ($VsanClusters) { - Section -Style Heading2 'vSAN' { - Paragraph "The following sections detail the configuration of vSAN managed by vCenter Server $vCenterServerName." - #region vSAN Cluster Advanced Summary - if ($InfoLevel.vSAN -le 2) { - BlankLine - $VsanClusterInfo = foreach ($VsanCluster in $VsanClusters) { - [PSCustomObject]@{ - 'Cluster' = $VsanCluster.Name - 'Storage Type' = if ($VsanCluster.VsanEsaEnabled) { - 'ESA' - } else { - 'OSA' - } - '# of Hosts' = $VsanCluster.Cluster.ExtensionData.Host.Count - 'Stretched Cluster' = if ($VsanCluster.StretchedClusterEnabled) { - 'Yes' - } else { - 'No' - } - 'Deduplication & Compression' = if ($VsanCluster.SpaceEfficiencyEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Encryption' = if ($VsanCluster.EncryptionEnabled) { - 'Enabled' - } else { - 'Disabled' - } - } - } - $TableParams = @{ - Name = "vSAN Cluster Summary - $($vCenterServerName)" - ColumnWidths = 25, 15, 15, 15, 15, 15 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VsanClusterInfo | Table @TableParams - } - #endregion vSAN Cluster Advanced Summary - - #region vSAN Cluster Detailed Information - if ($InfoLevel.vSAN -ge 3) { - foreach ($VsanCluster in $VsanClusters) { - $VsanSpaceUsage = Get-VsanSpaceUsage -Cluster $VsanCluster.Name - $VsanUsedCapacity = $VsanSpaceUsage.CapacityGB - $VsanSpaceUsage.FreeSpaceGB - - # Calculate percentages - $VsanUsedPercent = if (0 -in @($VsanUsedCapacity, $VsanSpaceUsage.CapacityGB)) {0} else {[math]::Round(($VsanUsedCapacity / $VsanSpaceUsage.CapacityGB) * 100, 2)} - $VsanFreePercent = if (0 -in @($VsanUsedCapacity, $VsanSpaceUsage.CapacityGB)) {0} else {[math]::Round(($VsanSpaceUsage.FreeSpaceGB / $VsanSpaceUsage.CapacityGB) * 100, 2)} - - #region vSAN Cluster Section - Section -Style Heading3 $VsanCluster.Name { - if ($VsanCluster.VsanEsaEnabled) { - Write-PScriboMessage "Collecting vSAN ESA information for $($VsanCluster.Name)." - Try { - $VsanStoragePoolDisk = Get-VsanStoragePoolDisk -Cluster $VsanCluster.Cluster - $VsanDiskFormat = $VsanStoragePoolDisk.DiskFormatVersion | Select-Object -First 1 -Unique - $VsanClusterDetail = [PSCustomObject]@{ - 'Cluster' = $VsanCluster.Name - 'ID' = $VsanCluster.Id - 'Storage Type' = if ($VsanCluster.VsanEsaEnabled) { - 'ESA' - } else { - 'OSA' - } - 'Stretched Cluster' = if ($VsanCluster.StretchedClusterEnabled) { - 'Yes' - } else { - 'No' - } - 'Number of Hosts' = $VsanCluster.Cluster.ExtensionData.Host.Count - 'Number of Disks' = $VsanStoragePoolDisk.Count - 'Disk Claim Mode' = $VsanCluster.VsanDiskClaimMode - 'Disk Format Version' = $VsanDiskFormat - 'Performance Service' = if ($VsanCluster.PerformanceServiceEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'File Service' = if ($VsanCluster.FileServiceEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'iSCSI Target Service' = if ($VsanCluster.IscsiTargetServiceEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Deduplication & Compression' = if ($VsanCluster.SpaceEfficiencyEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Encryption' = if ($VsanCluster.EncryptionEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Historical Health Service' = if ($VsanCluster.HistoricalHealthEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Health Check' = if ($VsanCluster.HealthCheckEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Total Capacity' = Convert-DataSize $VsanSpaceUsage.CapacityGB - 'Used Capacity' = "{0} ({1}%)" -f (Convert-DataSize $VsanUsedCapacity), $VsanUsedPercent - 'Free Capacity' = "{0} ({1}%)" -f (Convert-DataSize $VsanSpaceUsage.FreeSpaceGB), $VsanFreePercent - '% Used' = $VsanUsedPercent - 'HCL Last Updated' = ($VsanCluster.TimeOfHclUpdate).ToLocalTime().ToString() - } - if ($Healthcheck.vSAN.CapacityUtilization) { - $VsanClusterDetail | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property 'Used Capacity', 'Free Capacity' - $VsanClusterDetail | Where-Object { $_.'% Used' -ge 75 -and - $_.'% Used' -lt 90 } | Set-Style -Style Warning -Property 'Used Capacity', 'Free Capacity' - } - if ($InfoLevel.vSAN -ge 4) { - $VsanClusterDetail | Add-Member -MemberType NoteProperty -Name 'Hosts' -Value (($VsanStoragePoolDisk.Host | Select-Object -Unique | Sort-Object Name) -join ', ') - } - - $TableParams = @{ - Name = "vSAN Configuration - $($VsanCluster.Name)" - List = $true - Columns = 'Cluster', 'ID', 'Storage Type', 'Stretched Cluster', 'Number of Hosts', 'Number of Disks', 'Disk Claim Mode', 'Disk Format Version', 'Performance Service', 'File Service', 'iSCSI Target Service', 'Deduplication & Compression', 'Encryption', 'Historical Health Service', 'Health Check', 'Total Capacity', 'Used Capacity', 'Free Capacity', 'HCL Last Updated' - ColumnWidths = 40, 60 - } - If ($InfoLevel.vSAN -ge 4) { - $TableParams['Columns'] += 'Hosts' - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VsanClusterDetail | Table @TableParams - } Catch { - Write-PScriboMessage -IsWarning "Error collecting vSAN ESA information for $($VsanCluster.Name). $($_.Exception.Message)" - } - - if ($VsanStoragePoolDisk) { - Write-PScriboMessage "Collecting vSAN disk information for $($VsanCluster.Name)." - Try { - Section -Style Heading4 'Disks' { - $vDisks = foreach ($Disk in $VsanStoragePoolDisk) { - [PSCustomObject]@{ - 'Disk' = $Disk.Name - 'Name' = $Disk.ExtensionData.DisplayName - 'Drive Type' = if ($Disk.IsSsd) { - 'Flash' - } else { - 'HDD' - } - 'Host' = $Disk.Host.Name - 'State' = if ($Disk.IsMounted) { - 'Mounted' - } else { - 'Unmounted' - } - 'Encrypted' = if ($Disk.IsEncryped) { - 'Yes' - } else { - 'No' - } - 'Capacity' = Convert-DataSize $Disk.CapacityGB - 'Serial Number' = $Disk.ExtensionData.SerialNumber - 'Vendor' = $Disk.ExtensionData.Vendor - 'Model' = $Disk.ExtensionData.Model - 'Disk Type' = $Disk.DiskType - 'Disk Format Version' = $Disk.DiskFormatVersion - } - } - - if ($InfoLevel.vSAN -ge 4) { - $vDisks | Sort-Object Host | ForEach-Object { - $vDisk = $_ - Section -Style NOTOCHeading5 -ExcludeFromTOC "$($vDisk.Name) - $($vDisk.Host)" { - $TableParams = @{ - Name = "Disk $($vDisk.Name) - $($vDisk.Host)" - List = $true - Columns = 'Name', 'State', 'Drive Type', 'Encrypted', 'Capacity', 'Host', 'Serial Number', 'Vendor', 'Model', 'Disk Format Version', 'Disk Type' - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vDisk | Table @TableParams - } - } - } else { - $TableParams = @{ - Name = "vSAN Disks - $($VsanCluster.Name)" - Columns = 'Disk', 'Capacity', 'State', 'Host' - ColumnWidths = 40, 15, 15, 30 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vDisks | Sort-Object Host | Table @TableParams - } - } - } Catch { - Write-PScriboMessage -IsWarning "Error collecting vSAN disk information for $($VsanCluster.Name). $($_.Exception.Message)" - } - } - } else { - Try { - Write-PScriboMessage "Collecting vSAN OSA information for $($VsanCluster.Name)." - # Get vSAN Disk Groups - $VsanDiskGroup = Get-VsanDiskGroup -Cluster $VsanCluster.Cluster - $NumVsanDiskGroup = $VsanDiskGroup.Count - # Get vSAN Disks - $VsanDisk = Get-VsanDisk -VsanDiskGroup $VsanDiskGroup - $VsanDiskFormat = $VsanDisk.DiskFormatVersion | Select-Object -Unique - # Count SSDs and HDDs - $NumVsanSsd = ($VsanDisk | Where-Object { $_.IsSsd -eq $true } | Measure-Object).Count - $NumVsanHdd = ($VsanDisk | Where-Object { $_.IsSsd -eq $false } | Measure-Object).Count - # Determine Storage Type - $VsanClusterType = if ($NumVsanHdd -gt 0) { "Hybrid" } else { "All Flash" } - $VsanClusterDetail = [PSCustomObject]@{ - 'Cluster' = $VsanCluster.Name - 'ID' = $VsanCluster.Id - 'Storage Type' = if ($VsanCluster.VsanEsaEnabled) { - 'ESA' - } else { - 'OSA' - } - 'Cluster Type' = $VsanClusterType - 'Stretched Cluster' = if ($VsanCluster.StretchedClusterEnabled) { - 'Yes' - } else { - 'No' - } - 'Number of Hosts' = $VsanCluster.Cluster.ExtensionData.Host.Count - 'Number of Disks' = $NumVsanSsd + $NumVsanHdd - 'Number of Disk Groups' = $NumVsanDiskGroup - 'Disk Claim Mode' = $VsanCluster.VsanDiskClaimMode - 'Disk Format Version' = $VsanDiskFormat - 'Performance Service' = if ($VsanCluster.PerformanceServiceEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'File Service' = if ($VsanCluster.FileServiceEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'iSCSI Target Service' = if ($VsanCluster.IscsiTargetServiceEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Deduplication & Compression' = if ($VsanCluster.SpaceEfficiencyEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Encryption' = if ($VsanCluster.EncryptionEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Historical Health Service' = if ($VsanCluster.HistoricalHealthEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Health Check' = if ($VsanCluster.HealthCheckEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Total Capacity' = Convert-DataSize $VsanSpaceUsage.CapacityGB - 'Used Capacity' = "{0} ({1}%)" -f (Convert-DataSize $VsanUsedCapacity), $VsanUsedPercent - 'Free Capacity' = "{0} ({1}%)" -f (Convert-DataSize $VsanSpaceUsage.FreeSpaceGB), $VsanFreePercent - '% Used' = $VsanUsedPercent - 'HCL Last Updated' = ($VsanCluster.TimeOfHclUpdate).ToLocalTime().ToString() - } - if ($Healthcheck.vSAN.CapacityUtilization) { - $VsanClusterDetail | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property 'Used Capacity', 'Free Capacity' - $VsanClusterDetail | Where-Object { $_.'% Used' -ge 75 -and - $_.'% Used' -lt 90 } | Set-Style -Style Warning -Property 'Used Capacity', 'Free Capacity' - } - if ($InfoLevel.vSAN -ge 4) { - $VsanClusterDetail | Add-Member -MemberType NoteProperty -Name 'Hosts' -Value (($VsanDiskGroup.VMHost | Select-Object -Unique | Sort-Object Name) -join ', ') - } - $TableParams = @{ - Name = "vSAN Configuration - $($VsanCluster.Name)" - List = $true - Columns = 'Cluster', 'ID', 'Storage Type', 'Cluster Type', 'Stretched Cluster', 'Number of Hosts', 'Number of Disks', 'Number of Disk Groups', 'Disk Claim Mode', 'Disk Format Version', 'Performance Service', 'File Service', 'iSCSI Target Service', 'Deduplication & Compression', 'Encryption', 'Historical Health Service', 'Health Check', 'Total Capacity', 'Used Capacity', 'Free Capacity', 'HCL Last Updated' - ColumnWidths = 40, 60 - } - If ($InfoLevel.vSAN -ge 4) { - $TableParams['Columns'] += 'Hosts' - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VsanClusterDetail | Table @TableParams - } Catch { - Write-PScriboMessage -IsWarning "Error collecting vSAN OSA information for $($VsanCluster.Name). $($_.Exception.Message)" - } - - # TODO: vSAN Services - if ($VsanDiskGroup) { - Write-PScriboMessage "Collecting vSAN disk group information for $($VsanCluster.Name)." - Try { - Section -Style Heading4 'Disk Groups' { - $VsanDiskGroups = foreach ($DiskGroup in $VsanDiskGroup) { - $Disks = $DiskGroup | Get-VsanDisk - [PSCustomObject]@{ - 'Disk Group' = $DiskGroup.Uuid - 'Host' = $Diskgroup.VMHost - '# of Disks' = $Disks.Count - 'State' = if ($DiskGroup.IsMounted) { - 'Mounted' - } else { - 'Unmounted' - } - 'Type' = Switch ($DiskGroup.DiskGroupType) { - 'AllFlash' { 'All Flash' } - default { $DiskGroup.DiskGroupType } - } - 'Disk Format Version' = $DiskGroup.DiskFormatVersion - } - } - $TableParams = @{ - Name = "vSAN Disk Groups - $($VsanCluster.Name)" - ColumnWidths = 30, 30, 7, 11, 11, 11 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VsanDiskGroups | Sort-Object Host | Table @TableParams - } - } Catch { - Write-PScriboMessage -IsWarning "Error collecting vSAN disk group information for $($VsanCluster.Name). $($_.Exception.Message)" - } - } - - if ($VsanDisk) { - Write-PScriboMessage "Collecting vSAN disk information for $($VsanCluster.Name)." - Try { - Section -Style Heading4 'Disks' { - $vDisks = foreach ($Disk in $VsanDisk) { - [PSCustomObject]@{ - 'Disk' = $Disk.Name - 'Name' = $Disk.ExtensionData.DisplayName - 'State' = if ($Disk.IsMounted) { - 'Mounted' - } else { - 'Unmounted' - } - 'Drive Type' = if ($Disk.IsSsd) { - 'Flash' - } else { - 'HDD' - } - 'Host' = $Disk.VsanDiskGroup.VMHost.Name - 'Claimed As' = if ($Disk.IsCacheDisk) { - 'Cache' - } else { - 'Capacity' - } - 'Capacity' = Convert-DataSize $Disk.CapacityGB - 'Serial Number' = $Disk.ExtensionData.SerialNumber - 'Vendor' = $Disk.ExtensionData.Vendor - 'Model' = $Disk.ExtensionData.Model - 'Disk Group' = $Disk.VsanDiskGroup.Uuid - 'Disk Format Version' = $Disk.DiskFormatVersion - } - } - - if ($InfoLevel.vSAN -ge 4) { - $vDisks | Sort-Object Host | ForEach-Object { - $vDisk = $_ - Section -Style NOTOCHeading5 -ExcludeFromTOC "$($vDisk.Name) - $($vDisk.Host)" { - $TableParams = @{ - Name = "Disk $($vDisk.Name) - $($vDisk.Host)" - List = $true - Columns = 'Name', 'Drive Type', 'Claimed As', 'Capacity', 'Host', 'Disk Group', 'Serial Number', 'Vendor', 'Model', 'Disk Format Version' - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vDisk | Table @TableParams - } - } - } else { - $TableParams = @{ - Name = "vSAN Disks - $($VsanCluster.Name)" - Columns = 'Name', 'Drive Type', 'Claimed As', 'Capacity', 'Host', 'Disk Group' - ColumnWidths = 21, 10, 10, 10, 21, 28 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $vDisks | Sort-Object Host | Table @TableParams - } - } - } Catch { - Write-PScriboMessage -IsWarning "Error collecting vSAN disk information for $($VsanCluster.Name). $($_.Exception.Message)" - } - } - } - - $VsanIscsiTargets = Get-VsanIscsiTarget -Cluster $VsanCluster.Cluster -ErrorAction SilentlyContinue - if ($VsanIscsiTargets) { - Write-PScriboMessage "Collecting vSAN iSCSI target information for $($VsanCluster.Name)." - Try { - Section -Style Heading4 'iSCSI Targets' { - $VsanIscsiTargetInfo = foreach ($VsanIscsiTarget in $VsanIscsiTargets) { - [PSCustomObject]@{ - 'IQN' = $VsanIscsiTarget.IscsiQualifiedName - 'Alias' = $VsanIscsiTarget.Name - 'LUNs' = $VsanIscsiTarget.NumLuns - 'Network Interface' = $VsanIscsiTarget.NetworkInterface - 'I/O Owner Host' = $VsanIscsiTarget.IoOwnerVMHost - 'TCP Port' = $VsanIscsiTarget.TcpPort - 'Health' = $TextInfo.ToTitleCase($VsanIscsiTarget.VsanHealth) - 'Storage Policy' = if ($VsanIscsiTarget.StoragePolicy.Name) { - $VsanIscsiTarget.StoragePolicy.Name - } else { - '--' - } - 'Compliance Status' = $TextInfo.ToTitleCase($VsanIscsiTarget.SpbmComplianceStatus) - 'Authentication' = $VsanIscsiTarget.AuthenticationType - } - } - $TableParams = @{ - Name = "vSAN iSCSI Targets - $($VsanCluster.Name)" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VsanIscsiTargetInfo | Table @TableParams - } - } Catch { - Write-PScriboMessage -IsWarning "Error collecting vSAN iSCSI target information for $($VsanCluster.Name). $($_.Exception.Message)" - } - } - - $VsanIscsiLuns = Get-VsanIscsiLun -Cluster $VsanCluster.Cluster -ErrorAction SilentlyContinue | Sort-Object Name, LunId - if ($VsanIscsiLuns) { - Write-PScriboMessage "Collecting vSAN iSCSI LUN information for $($VsanCluster.Name)." - Try { - Section -Style Heading4 'iSCSI LUNs' { - $VsanIscsiLunInfo = foreach ($VsanIscsiLun in $VsanIscsiLuns) { - [PSCustomobject]@{ - 'LUN' = $VsanIscsiLun.Name - 'LUN ID' = $VsanIscsiLun.LunId - 'Capacity' = Convert-DataSize $VsanIscsiLun.CapacityGB - 'Used Capacity' = Convert-DataSize $VsanIscsiLun.UsedCapacityGB - 'State' = if ($VsanIscsiLun.IsOnline) { - 'Online' - } else { - 'Offline' - } - 'Health' = $TextInfo.ToTitleCase($VsanIscsiLun.VsanHealth) - 'Storage Policy' = if ($VsanIscsiLun.StoragePolicy.Name) { - $VsanIscsiLun.StoragePolicy.Name - } else { - '--' - } - 'Compliance Status' = $TextInfo.ToTitleCase($VsanIscsiLun.SpbmComplianceStatus) - } - } - if ($InfoLevel.vSAN -ge 4) { - $TableParams = @{ - Name = "vSAN iSCSI LUNs - $($VsanCluster.Name)" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VsanIscsiLunInfo | Table @TableParams - } else { - $TableParams = @{ - Name = "vSAN iSCSI LUNs - $($VsanCluster.Name)" - ColumnWidths = 28 , 18, 18, 18, 18 - Columns = 'LUN', 'LUN ID', 'Capacity', 'Used Capacity', 'State' - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VsanIscsiLunInfo | Table @TableParams - } - } - } Catch { - Write-PScriboMessage -IsWarning "Error collecting vSAN iSCSI LUN information for $($VsanCluster.Name). $($_.Exception.Message)" - } - } - } - #endregion vSAN Cluster Section - } - } - #endregion vSAN Cluster Detailed Information - } - } - } - #endregion vSAN Section - - #region Datastore Section - Write-PScriboMessage "Datastore InfoLevel set at $($InfoLevel.Datastore)." - if ($InfoLevel.Datastore -ge 1) { - if ($Datastores) { - Section -Style Heading2 'Datastores' { - Paragraph "The following sections detail the configuration of datastores managed by vCenter Server $vCenterServerName." - #region Datastore Infomative Information - if ($InfoLevel.Datastore -le 2) { - BlankLine - $DatastoreInfo = foreach ($Datastore in $Datastores) { - - $DsUsedPercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) {0} else {[math]::Round((100 - (($Datastore.FreeSpaceGB) / ($Datastore.CapacityGB) * 100)), 2)} - $DsFreePercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) {0} else {[math]::Round(($Datastore.FreeSpaceGB / $Datastore.CapacityGB) * 100, 2)} - $DsUsedCapacityGB = ($Datastore.CapacityGB) - ($Datastore.FreeSpaceGB) - - [PSCustomObject]@{ - 'Datastore' = $Datastore.Name - 'Type' = $Datastore.Type - 'Version' = if ($Datastore.FileSystemVersion) { - $Datastore.FileSystemVersion - } else { - '--' - } - '# of Hosts' = $Datastore.ExtensionData.Host.Count - '# of VMs' = $Datastore.ExtensionData.VM.Count - 'Total Capacity' = Convert-DataSize $Datastore.CapacityGB - 'Used Capacity' = "{0} ({1}%)" -f (Convert-DataSize $DsUsedCapacityGB), $DsUsedPercent - 'Free Capacity' = "{0} ({1}%)" -f (Convert-DataSize $Datastore.FreeSpaceGB), $DsFreePercent - '% Used' = $DsUsedPercent - } - } - if ($Healthcheck.Datastore.CapacityUtilization) { - $DatastoreInfo | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property 'Used Capacity', 'Free Capacity' - $DatastoreInfo | Where-Object { $_.'% Used' -ge 75 -and - $_.'% Used' -lt 90 } | Set-Style -Style Warning -Property 'Used Capacity', 'Free Capacity' - } - $TableParams = @{ - Name = "Datastore Summary - $($vCenterServerName)" - Columns = 'Datastore', 'Type', 'Version', '# of Hosts', '# of VMs', 'Total Capacity', 'Used Capacity', 'Free Capacity' - ColumnWidths = 21, 10, 9, 9, 9, 14, 14, 14 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DatastoreInfo | Sort-Object Datastore | Table @TableParams - } - #endregion Datastore Advanced Summary - - #region Datastore Detailed Information - if ($InfoLevel.Datastore -ge 3) { - foreach ($Datastore in $Datastores) { - # TODO: Test Tags - - $DsUsedPercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) {0} else {[math]::Round((100 - (($Datastore.FreeSpaceGB) / ($Datastore.CapacityGB) * 100)), 2)} - $DSFreePercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) {0} else {[math]::Round(($Datastore.FreeSpaceGB / $Datastore.CapacityGB) * 100, 2)} - $UsedCapacityGB = ($Datastore.CapacityGB) - ($Datastore.FreeSpaceGB) - - #region Datastore Section - Section -Style Heading3 $Datastore.Name { - $DatastoreDetail = [PSCustomObject]@{ - 'Datastore' = $Datastore.Name - 'ID' = $Datastore.Id - 'Datacenter' = $Datastore.Datacenter - 'Type' = $Datastore.Type - 'Version' = if ($Datastore.FileSystemVersion) { - $Datastore.FileSystemVersion - } else { - '--' - } - 'State' = $Datastore.State - 'Number of Hosts' = $Datastore.ExtensionData.Host.Count - 'Number of VMs' = $Datastore.ExtensionData.VM.Count - 'Storage I/O Control' = if ($Datastore.StorageIOControlEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Congestion Threshold' = if ($Datastore.CongestionThresholdMillisecond) { - "$($Datastore.CongestionThresholdMillisecond) ms" - } else { - '--' - } - 'Total Capacity' = Convert-DataSize $Datastore.CapacityGB - 'Used Capacity' = "{0} ({1}%)" -f (Convert-DataSize $UsedCapacityGB), $DsUsedPercent - 'Free Capacity' = "{0} ({1}%)" -f (Convert-DataSize $Datastore.FreeSpaceGB), $DSFreePercent - '% Used' = $DsUsedPercent - } - if ($Healthcheck.Datastore.CapacityUtilization) { - $DatastoreDetail | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property 'Used Capacity', 'Free Capacity' - $DatastoreDetail | Where-Object { $_.'% Used' -ge 75 -and - $_.'% Used' -lt 90 } | Set-Style -Style Warning -Property 'Used Capacity', 'Free Capacity' - } - $MemberProps = @{ - 'InputObject' = $DatastoreDetail - 'MemberType' = 'NoteProperty' - } - <# - if ($TagAssignments | Where-Object {$_.entity -eq $Datastore}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $Datastore}).Tag -join ',') - } - #> - - #region Datastore Advanced Detailed Information - if ($InfoLevel.Datastore -ge 4) { - $DatastoreHosts = foreach ($DatastoreHost in $Datastore.ExtensionData.Host.Key) { - $VMHostLookup."$($DatastoreHost.Type)-$($DatastoreHost.Value)" - } - Add-Member @MemberProps -Name 'Hosts' -Value (($DatastoreHosts | Sort-Object) -join ', ') - $DatastoreVMs = foreach ($DatastoreVM in $Datastore.ExtensionData.VM) { - $VMLookup."$($DatastoreVM.Type)-$($DatastoreVM.Value)" - } - Add-Member @MemberProps -Name 'Virtual Machines' -Value (($DatastoreVMs | Sort-Object) -join ', ') - } - #endregion Datastore Advanced Detailed Information - $TableParams = @{ - Name = "Datastore Configuration - $($Datastore.Name)" - List = $true - Columns = 'Datastore', 'ID', 'Datacenter', 'Type', 'Version', 'State', 'Number of Hosts', 'Number of VMs', 'Storage I/O Control', 'Congestion Threshold', 'Total Capacity', 'Used Capacity', 'Free Capacity' - ColumnWidths = 40, 60 - } - if ($InfoLevel.Datastore -ge 4) { - $TableParams['Columns'] += 'Hosts', 'Virtual Machines' - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DatastoreDetail | Sort-Object Datacenter, Name | Table @TableParams - - # Get VMFS volumes. Ignore local SCSILuns. - if (($Datastore.Type -eq 'VMFS') -and ($Datastore.ExtensionData.Info.Vmfs.Local -eq $false)) { - #region SCSI LUN Information Section - Section -Style Heading4 'SCSI LUN Information' { - $ScsiLuns = foreach ($DatastoreHost in $Datastore.ExtensionData.Host.Key) { - $DiskName = $Datastore.ExtensionData.Info.Vmfs.Extent.DiskName - $ScsiDeviceDetailProps = @{ - 'VMHosts' = $VMHosts - 'VMHostMoRef' = "$($DatastoreHost.Type)-$($DatastoreHost.Value)" - 'DatastoreDiskName' = $DiskName - } - $ScsiDeviceDetail = Get-ScsiDeviceDetail @ScsiDeviceDetailProps - - [PSCustomObject]@{ - 'Host' = $VMHostLookup."$($DatastoreHost.Type)-$($DatastoreHost.Value)" - 'Canonical Name' = $DiskName - 'Capacity' = Convert-DataSize $ScsiDeviceDetail.CapacityGB - 'Vendor' = $ScsiDeviceDetail.Vendor - 'Model' = $ScsiDeviceDetail.Model - 'Is SSD' = $ScsiDeviceDetail.Ssd - 'Multipath Policy' = $ScsiDeviceDetail.MultipathPolicy - 'Paths' = $ScsiDeviceDetail.Paths - } - } - $TableParams = @{ - Name = "SCSI LUN Information - $($vCenterServerName)" - ColumnWidths = 19, 19, 10, 10, 10, 10, 14, 8 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ScsiLuns | Sort-Object Host | Table @TableParams - } - #endregion SCSI LUN Information Section - } - } - #endregion Datastore Section - } - } - #endregion Datastore Detailed Information - } - } - } - #endregion Datastore Section - - #region Datastore Clusters - Write-PScriboMessage "DSCluster InfoLevel set at $($InfoLevel.DSCluster)." - if ($InfoLevel.DSCluster -ge 1) { - $DSClusters = Get-DatastoreCluster -Server $vCenter - if ($DSClusters) { - #region Datastore Clusters Section - Section -Style Heading2 'Datastore Clusters' { - Paragraph "The following sections detail the configuration of datastore clusters managed by vCenter Server $vCenterServerName." - #region Datastore Cluster Advanced Summary - if ($InfoLevel.DSCluster -le 2) { - BlankLine - $DSClusterInfo = foreach ($DSCluster in $DSClusters) { - [PSCustomObject]@{ - 'Datastore Cluster' = $DSCluster.Name - 'SDRS Automation Level' = Switch ($DSCluster.SdrsAutomationLevel) { - 'FullyAutomated' { 'Fully Automated' } - 'Manual' { 'Manual' } - default { $DSCluster.SdrsAutomationLevel } - } - 'Space Utilization Threshold' = "$($DSCluster.SpaceUtilizationThresholdPercent)%" - 'I/O Load Balance' = if ($DSCluster.IOLoadBalanceEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'I/O Latency Threshold' = "$($DSCluster.IOLatencyThresholdMillisecond) ms" - } - } - if ($Healthcheck.DSCluster.SDRSAutomationLevelFullyAuto) { - $DSClusterInfo | Where-Object { $_.'SDRS Automation Level' -ne 'Fully Automated' } | Set-Style -Style Warning -Property 'SDRS Automation Level' - } - $TableParams = @{ - Name = "Datastore Cluster Configuration - $($DSCluster.Name)" - ColumnWidths = 20, 20, 20, 20, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DSClusterInfo | Sort-Object Name | Table @TableParams - } - #endregion Datastore Cluster Advanced Summary - - #region Datastore Cluster Detailed Information - if ($InfoLevel.DSCluster -ge 3) { - foreach ($DSCluster in $DSClusters) { - # TODO: Space Load Balance Config, IO Load Balance Config, Rules - # TODO: Test Tags - - $DSCUsedPercent = if (0 -in @($DSCluster.FreeSpaceGB, $DSCluster.CapacityGB)) {0} else {[math]::Round((100 - (($DSCluster.FreeSpaceGB) / ($DSCluster.CapacityGB) * 100)), 2)} - $DSCFreePercent = if (0 -in @($DSCluster.FreeSpaceGB, $DSCluster.CapacityGB)) {0} else { [math]::Round(($DSCluster.FreeSpaceGB / $DSCluster.CapacityGB) * 100, 2) } - $DSCUsedCapacityGB = ($DSCluster.CapacityGB - $DSCluster.FreeSpaceGB) - - Section -Style Heading3 $DSCluster.Name { - Paragraph ("The following table details the configuration " + - "for datastore cluster $DSCluster.") - BlankLine - - $DSClusterDetail = [PSCustomObject]@{ - 'Datastore Cluster' = $DSCluster.Name - 'ID' = $DSCluster.Id - 'SDRS Automation Level' = Switch ($DSCluster.SdrsAutomationLevel) { - 'FullyAutomated' { 'Fully Automated' } - 'Manual' { 'Manual' } - default { $DSCluster.SdrsAutomationLevel } - } - 'Space Utilization Threshold' = "$($DSCluster.SpaceUtilizationThresholdPercent)%" - 'I/O Load Balance' = if ($DSCluster.IOLoadBalanceEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'I/O Latency Threshold' = "$($DSCluster.IOLatencyThresholdMillisecond) ms" - 'Total Capacity' = Convert-DataSize $DSCluster.CapacityGB - 'Used Capacity' = "{0} ({1}%)" -f (Convert-DataSize $DSCUsedCapacityGB), $DSCUsedPercent - 'Free Capacity' = "{0} ({1}%)" -f (Convert-DataSize $DSCluster.FreeSpaceGB), $DSCFreePercent - '% Used' = $DSCUsedPercent - } - <# - $MemberProps = @{ - 'InputObject' = $DSClusterDetail - 'MemberType' = 'NoteProperty' - } - - if ($TagAssignments | Where-Object {$_.entity -eq $DSCluster}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $DSCluster}).Tag -join ',') - } - #> - if ($Healthcheck.DSCluster.CapacityUtilization) { - $DSClusterDetail | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property 'Used Capacity', 'Free Capacity' - $DSClusterDetail | Where-Object { $_.'% Used' -ge 75 -and $_.'% Used' -lt 90 } | Set-Style -Style Critical -Property 'Used Capacity', 'Free Capacity' - } - if ($Healthcheck.DSCluster.SDRSAutomationLevel) { - $DSClusterDetail | Where-Object { $_.'SDRS Automation Level' -ne 'Fully Automated' } | Set-Style -Style Warning -Property 'SDRS Automation Level' - } - $TableParams = @{ - Name = "Datastore Cluster Configuration - $($DSCluster.Name)" - List = $true - Columns = 'Datastore Cluster', 'ID', 'SDRS Automation Level', 'Space Utilization Threshold', 'I/O Load Balance', 'I/O Latency Threshold', 'Total Capacity', 'Used Capacity', 'Free Capacity' - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $DSClusterDetail | Table @TableParams - - #region SDRS VM Overrides - $StoragePodProps = @{ - 'ViewType' = 'StoragePod' - 'Filter' = @{'Name' = $DSCluster.Name } - } - $StoragePod = Get-View @StoragePodProps - if ($StoragePod) { - $PodConfig = $StoragePod.PodStorageDrsEntry.StorageDrsConfig.PodConfig - # Set default automation value variables - Switch ($PodConfig.DefaultVmBehavior) { - "automated" { $DefaultVmBehavior = "Default (Fully Automated)" } - "manual" { $DefaultVmBehavior = "Default (No Automation (Manual Mode))" } - } - if ($PodConfig.DefaultIntraVmAffinity) { - $DefaultIntraVmAffinity = "Default (Yes)" - } else { - $DefaultIntraVmAffinity = "Default (No)" - } - $VMOverrides = $StoragePod.PodStorageDrsEntry.StorageDrsConfig.VmConfig | Where-Object { - -not ( - ($null -eq $_.Enabled) -and - ($null -eq $_.IntraVmAffinity) - ) - } - } - - if ($VMOverrides) { - $VMOverrideDetails = foreach ($Override in $VMOverrides) { - [PSCustomObject]@{ - 'Virtual Machine' = $VMLookup."$($Override.Vm.Type)-$($Override.Vm.Value)" - 'SDRS Automation Level' = Switch ($Override.Enabled) { - $true { 'Fully Automated' } - $false { 'Disabled' } - $null { $DefaultVmBehavior } - } - 'Keep VMDKs Together' = Switch ($Override.IntraVmAffinity) { - $true { 'Yes' } - $false { 'No' } - $null { $DefaultIntraVmAffinity } - } - } - } - Section -Style Heading4 'SDRS VM Overrides' { - $TableParams = @{ - Name = "SDRS VM Overrides - $($DSCluster.Name)" - ColumnWidths = 50, 30, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMOverrideDetails | Sort-Object 'Virtual Machine' | Table @TableParams - } - } - #endregion SDRS VM Overrides - } - } - } - #endregion Datastore Cluster Detailed Information - } - #endregion Datastore Clusters Section - } - } - #endregion Datastore Clusters - - #region Virtual Machine Section - Write-PScriboMessage "VM InfoLevel set at $($InfoLevel.VM)." - if ($InfoLevel.VM -ge 1) { - if ($VMs) { - Section -Style Heading2 'Virtual Machines' { - Paragraph "The following sections detail the configuration of virtual machines managed by vCenter Server $vCenterServerName." - #region Virtual Machine Summary Information - if ($InfoLevel.VM -eq 1) { - BlankLine - $VMSummary = [PSCustomObject]@{ - 'Total VMs' = $VMs.Count - 'Total vCPUs' = ($VMs | Measure-Object -Property NumCpu -Sum).Sum - 'Total Memory' = Convert-DataSize ($VMs | Measure-Object -Property MemoryGB -Sum).Sum - 'Total Provisioned Space' = Convert-DataSize ($VMs | Measure-Object -Property ProvisionedSpaceGB -Sum).Sum - 'Total Used Space' = Convert-DataSize ($VMs | Measure-Object -Property UsedSpaceGB -Sum).Sum - 'VMs Powered On' = ($VMs | Where-Object { $_.PowerState -eq 'PoweredOn' }).Count - 'VMs Powered Off' = ($VMs | Where-Object { $_.PowerState -eq 'PoweredOff' }).Count - 'VMs Orphaned' = ($VMs | Where-Object { $_.ExtensionData.Runtime.ConnectionState -eq 'Orphaned' }).Count - 'VMs Inaccessible' = ($VMs | Where-Object { $_.ExtensionData.Runtime.ConnectionState -eq 'Inaccessible' }).Count - 'VMs Suspended' = ($VMs | Where-Object { $_.PowerState -eq 'Suspended' }).Count - 'VMs with Snapshots' = ($VMs | Where-Object { $_.ExtensionData.Snapshot }).Count - 'Guest Operating System Types' = (($VMs | Get-View).Summary.Config.GuestFullName | Select-Object -Unique).Count - 'VM Tools OK' = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsOK' }).Count - 'VM Tools Old' = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsOld' }).Count - 'VM Tools Not Running' = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsNotRunning' }).Count - 'VM Tools Not Installed' = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsNotInstalled' }).Count - } - $TableParams = @{ - Name = "VM Summary - $($vCenterServerName)" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMSummary | Table @TableParams - } - #endregion Virtual Machine Summary Information - - #region Virtual Machine Advanced Summary - if ($InfoLevel.VM -eq 2) { - BlankLine - $VMSnapshotList = $VMs.Extensiondata.Snapshot.RootSnapshotList - $VMInfo = foreach ($VM in $VMs) { - $VMView = $VM | Get-View - [PSCustomObject]@{ - 'Virtual Machine' = $VM.Name - 'Power State' = Switch ($VM.PowerState) { - 'PoweredOn' { 'On' } - 'PoweredOff' { 'Off' } - default { $VM.PowerState } - } - 'IP Address' = if ($VMView.Guest.IpAddress) { - $VMView.Guest.IpAddress - } else { - '--' - } - 'vCPUs' = $VM.NumCpu - 'Memory' = Convert-DataSize $VM.MemoryGB -RoundUnits 0 - 'Provisioned' = Convert-DataSize $VM.ProvisionedSpaceGB - 'Used' = Convert-DataSize $VM.UsedSpaceGB - 'HW Version' = ($VM.HardwareVersion).Replace('vmx-', 'v') - 'VM Tools Status' = Switch ($VMView.Guest.ToolsStatus) { - 'toolsOld' { 'Old' } - 'toolsOK' { 'OK' } - 'toolsNotRunning' { 'Not Running' } - 'toolsNotInstalled' { 'Not Installed' } - default { $VMView.Guest.ToolsStatus } - } - } - } - if ($Healthcheck.VM.VMToolsStatus) { - $VMInfo | Where-Object { $_.'VM Tools Status' -ne 'OK' } | Set-Style -Style Warning -Property 'VM Tools Status' - } - if ($Healthcheck.VM.PowerState) { - $VMInfo | Where-Object { $_.'Power State' -ne 'On' } | Set-Style -Style Warning -Property 'Power State' - } - $TableParams = @{ - Name = "VM Advanced Summary - $($vCenterServerName)" - ColumnWidths = 21, 8, 16, 9, 9, 9, 9, 9, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMInfo | Table @TableParams - - #region VM Snapshot Information - if ($VMSnapshotList -and $Options.ShowVMSnapshots) { - Section -Style Heading3 'Snapshots' { - $VMSnapshotInfo = foreach ($VMSnapshot in $VMSnapshotList) { - [PSCustomObject]@{ - 'Virtual Machine' = $VMLookup."$($VMSnapshot.VM)" - 'Snapshot Name' = $VMSnapshot.Name - 'Description' = $VMSnapshot.Description - 'Days Old' = ((Get-Date).ToUniversalTime() - $VMSnapshot.CreateTime).Days - } - } - if ($Healthcheck.VM.VMSnapshots) { - $VMSnapshotInfo | Where-Object { $_.'Days Old' -ge 7 } | Set-Style -Style Warning - $VMSnapshotInfo | Where-Object { $_.'Days Old' -ge 14 } | Set-Style -Style Critical - } - $TableParams = @{ - Name = "VM Snapshot Summary - $($vCenterServerName)" - ColumnWidths = 30, 30, 30, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMSnapshotInfo | Table @TableParams - } - } - #endregion VM Snapshot Information - } - #endregion Virtual Machine Advanced Summary - - #region Virtual Machine Detailed Information - # TODO: Test Tags - if ($InfoLevel.VM -ge 3) { - if ($UserPrivileges -contains 'StorageProfile.View') { - $VMSpbmConfig = Get-SpbmEntityConfiguration -VM ($VMs) | Where-Object { $null -ne $_.StoragePolicy } - } else { - Write-PScriboMessage "Insufficient user privileges to report VM storage policies. Please ensure the user account has the 'Storage Profile > View' privilege assigned." - } - if ($InfoLevel.VM -ge 4) { - $VMHardDisks = Get-HardDisk -VM ($VMs) -Server $vCenter - } - foreach ($VM in $VMs) { - Section -Style Heading3 $VM.name { - $VMUptime = Get-Uptime -VM $VM - $VMSpbmPolicy = $VMSpbmConfig | Where-Object { $_.entity -eq $vm } - $VMView = $VM | Get-View - $VMSnapshotList = $vmview.Snapshot.RootSnapshotList - $VMDetail = [PSCustomObject]@{ - 'Virtual Machine' = $VM.Name - 'ID' = $VM.Id - 'Operating System' = $VMView.Summary.Config.GuestFullName - 'Hardware Version' = ($VM.HardwareVersion).Replace('vmx-', 'v') - 'Power State' = Switch ($VM.PowerState) { - 'PoweredOn' { 'On' } - 'PoweredOff' { 'Off' } - default { $TextInfo.ToTitleCase($VM.PowerState) } - } - 'Connection State' = $TextInfo.ToTitleCase($VM.ExtensionData.Runtime.ConnectionState) - 'VM Tools Status' = Switch ($VMView.Guest.ToolsStatus) { - 'toolsOld' { 'Old' } - 'toolsOK' { 'OK' } - 'toolsNotRunning' { 'Not Running' } - 'toolsNotInstalled' { 'Not Installed' } - default { $TextInfo.ToTitleCase($VMView.Guest.ToolsStatus) } - } - 'Fault Tolerance State' = Switch ($VMView.Runtime.FaultToleranceState) { - 'notConfigured' { 'Not Configured' } - 'needsSecondary' { 'Needs Secondary' } - 'running' { 'Running' } - 'disabled' { 'Disabled' } - 'starting' { 'Starting' } - 'enabled' { 'Enabled' } - default { $TextInfo.ToTitleCase($VMview.Runtime.FaultToleranceState) } - } - 'Host' = $VM.VMHost.Name - 'Parent' = $VM.VMHost.Parent.Name - 'Parent Folder' = $VM.Folder.Name - 'Parent Resource Pool' = $VM.ResourcePool.Name - 'vCPUs' = $VM.NumCpu - 'Cores per Socket' = $VM.CoresPerSocket - 'CPU Shares' = "$($VM.VMResourceConfiguration.CpuSharesLevel) / $($VM.VMResourceConfiguration.NumCpuShares)" - 'CPU Reservation' = $VM.VMResourceConfiguration.CpuReservationMhz - 'CPU Limit' = "$($VM.VMResourceConfiguration.CpuReservationMhz) MHz" - 'CPU Hot Add' = if ($VMView.Config.CpuHotAddEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'CPU Hot Remove' = if ($VMView.Config.CpuHotRemoveEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Memory Allocation' = Convert-DataSize $VM.memoryGB -RoundUnits 0 - 'Memory Shares' = "$($VM.VMResourceConfiguration.MemSharesLevel) / $($VM.VMResourceConfiguration.NumMemShares)" - 'Memory Hot Add' = if ($VMView.Config.MemoryHotAddEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'vNICs' = $VMView.Summary.Config.NumEthernetCards - 'DNS Name' = if ($VMView.Guest.HostName) { - $VMView.Guest.HostName - } else { - '--' - } - 'Networks' = if ($VMView.Guest.Net.Network) { - (($VMView.Guest.Net | Where-Object { $null -ne $_.Network } | Select-Object Network | Sort-Object Network).Network -join ', ') - } else { - '--' - } - 'IP Address' = if ($VMView.Guest.Net.IpAddress) { - (($VMView.Guest.Net | Where-Object { ($null -ne $_.Network) -and ($null -ne $_.IpAddress) } | Select-Object IpAddress | Sort-Object IpAddress).IpAddress -join ', ') - } else { - '--' - } - 'MAC Address' = if ($VMView.Guest.Net.MacAddress) { - (($VMView.Guest.Net | Where-Object { $null -ne $_.Network } | Select-Object -Property MacAddress).MacAddress -join ', ') - } else { - '--' - } - 'vDisks' = $VMView.Summary.Config.NumVirtualDisks - 'Provisioned Space' = Convert-DataSize $VM.ProvisionedSpaceGB - 'Used Space' = Convert-DataSize $VM.UsedSpaceGB - 'Changed Block Tracking' = if ($VMView.Config.ChangeTrackingEnabled) { - 'Enabled' - } else { - 'Disabled' - } - 'Storage Based Policy' = if ($VMSpbmPolicy.StoragePolicy.Name) { - $TextInfo.ToTitleCase($VMSpbmPolicy.StoragePolicy.Name) - } else { - '--' - } - 'Storage Based Policy Compliance' = Switch ($VMSpbmPolicy.ComplianceStatus) { - $null { '--' } - 'compliant' { 'Compliant' } - 'nonCompliant' { 'Non Compliant' } - 'unknown' { 'Unknown' } - default { $TextInfo.ToTitleCase($VMSpbmPolicy.ComplianceStatus) } - } - } - $MemberProps = @{ - 'InputObject' = $VMDetail - 'MemberType' = 'NoteProperty' - } - #if ($VMView.Config.CreateDate) { - # Add-Member @MemberProps -Name 'Creation Date' -Value ($VMView.Config.CreateDate).ToLocalTime().ToString() - #} - <# - if ($TagAssignments | Where-Object {$_.entity -eq $VM}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VM}).Tag -join ',') - } - #> - if ($VM.Notes) { - Add-Member @MemberProps -Name 'Notes' -Value $VM.Notes - } - if ($VMView.Runtime.BootTime) { - Add-Member @MemberProps -Name 'Boot Time' -Value ($VMView.Runtime.BootTime).ToLocalTime().ToString() - } - if ($VMUptime.UptimeDays) { - Add-Member @MemberProps -Name 'Uptime Days' -Value $VMUptime.UptimeDays - } - - #region VM Health Checks - if ($Healthcheck.VM.VMToolsStatus) { - $VMDetail | Where-Object { $_.'VM Tools Status' -ne 'OK' } | Set-Style -Style Warning -Property 'VM Tools Status' - } - if ($Healthcheck.VM.PowerState) { - $VMDetail | Where-Object { $_.'Power State' -ne 'On' } | Set-Style -Style Warning -Property 'Power State' - } - if ($Healthcheck.VM.ConnectionState) { - $VMDetail | Where-Object { $_.'Connection State' -ne 'Connected' } | Set-Style -Style Critical -Property 'Connection State' - } - if ($Healthcheck.VM.CpuHotAdd) { - $VMDetail | Where-Object { $_.'CPU Hot Add' -eq 'Enabled' } | Set-Style -Style Warning -Property 'CPU Hot Add' - } - if ($Healthcheck.VM.CpuHotRemove) { - $VMDetail | Where-Object { $_.'CPU Hot Remove' -eq 'Enabled' } | Set-Style -Style Warning -Property 'CPU Hot Remove' - } - if ($Healthcheck.VM.MemoryHotAdd) { - $VMDetail | Where-Object { $_.'Memory Hot Add' -eq 'Enabled' } | Set-Style -Style Warning -Property 'Memory Hot Add' - } - if ($Healthcheck.VM.ChangeBlockTracking) { - $VMDetail | Where-Object { $_.'Changed Block Tracking' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Changed Block Tracking' - } - if ($Healthcheck.VM.SpbmPolicyCompliance) { - $VMDetail | Where-Object { $_.'Storage Based Policy Compliance' -eq 'Unknown' } | Set-Style -Style Warning -Property 'Storage Based Policy Compliance' - $VMDetail | Where-Object { $_.'Storage Based Policy Compliance' -eq 'Non Compliant' } | Set-Style -Style Critical -Property 'Storage Based Policy Compliance' - } - #endregion VM Health Checks - $TableParams = @{ - Name = "VM Configuration - $($VM.Name)" - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMDetail | Table @TableParams - - if ($InfoLevel.VM -ge 4) { - $VMnics = $VM.Guest.Nics | Where-Object { $null -ne $_.Device } | Sort-Object Device - $VMHdds = $VMHardDisks | Where-Object { $_.ParentId -eq $VM.Id } | Sort-Object Name - $SCSIControllers = $VMView.Config.Hardware.Device | Where-Object { $_.DeviceInfo.Label -match "SCSI Controller" } - $VMGuestVols = $VM.Guest.Disks | Sort-Object Path - if ($VMnics) { - Section -Style Heading4 "Network Adapters" { - $VMnicInfo = foreach ($VMnic in $VMnics) { - [PSCustomObject]@{ - 'Adapter' = $VMnic.Device - 'Connected' = $VMnic.Connected - 'Network Name' = Switch -wildcard ($VMnic.Device.NetworkName) { - 'dvportgroup*' { $VDPortgroupLookup."$($VMnic.Device.NetworkName)" } - default { $VMnic.Device.NetworkName } - } - 'Adapter Type' = $VMnic.Device.Type - 'IP Address' = $VMnic.IpAddress -join [Environment]::NewLine - 'MAC Address' = $VMnic.Device.MacAddress - } - } - $TableParams = @{ - Name = "Network Adapters - $($VM.Name)" - ColumnWidths = 20, 12, 16, 12, 20, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMnicInfo | Table @TableParams - } - } - if ($SCSIControllers) { - Section -Style Heading4 "SCSI Controllers" { - $VMScsiControllers = foreach ($VMSCSIController in $SCSIControllers) { - [PSCustomObject]@{ - 'Device' = $VMSCSIController.DeviceInfo.Label - 'Controller Type' = $VMSCSIController.DeviceInfo.Summary - 'Bus Sharing' = Switch ($VMSCSIController.SharedBus) { - 'noSharing' { 'None' } - default { $VMSCSIController.SharedBus } - } - } - } - $TableParams = @{ - Name = "SCSI Controllers - $($VM.Name)" - ColumnWidths = 33, 34, 33 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMScsiControllers | Sort-Object 'Device' | Table @TableParams - } - } - if ($VMHdds) { - Section -Style Heading4 "Hard Disks" { - If ($InfoLevel.VM -eq 4) { - $VMHardDiskInfo = foreach ($VMHdd in $VMHdds) { - $SCSIDevice = $VMView.Config.Hardware.Device | Where-Object { $_.Key -eq $VMHdd.ExtensionData.Key -and $_.Backing.FileName -eq $VMHdd.FileName } - $SCSIController = $SCSIControllers | Where-Object { $SCSIDevice.ControllerKey -eq $_.Key } - [PSCustomObject]@{ - 'Disk' = $VMHdd.Name - 'Datastore' = $VMHdd.FileName.Substring($VMHdd.Filename.IndexOf("[") + 1, $VMHdd.Filename.IndexOf("]") - 1) - 'Capacity' = Convert-DataSize $VMHdd.CapacityGB - 'Disk Provisioning' = Switch ($VMHdd.StorageFormat) { - 'EagerZeroedThick' { 'Thick Eager Zeroed' } - 'LazyZeroedThick' { 'Thick Lazy Zeroed' } - $null { '--' } - default { $VMHdd.StorageFormat } - } - 'Disk Type' = Switch ($VMHdd.DiskType) { - 'RawPhysical' { 'Physical RDM' } - 'RawVirtual' { "Virtual RDM" } - 'Flat' { 'VMDK' } - default { $VMHdd.DiskType } - } - 'Disk Mode' = Switch ($VMHdd.Persistence) { - 'IndependentPersistent' { 'Independent - Persistent' } - 'IndependentNonPersistent' { 'Independent - Nonpersistent' } - 'Persistent' { 'Dependent' } - default { $VMHdd.Persistence } - } - } - } - $TableParams = @{ - Name = "Hard Disk Configuration - $($VM.Name)" - ColumnWidths = 15, 25, 15, 15, 15, 15 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHardDiskInfo | Table @TableParams - } else { - foreach ($VMHdd in $VMHdds) { - Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHdd.Name)" { - $SCSIDevice = $VMView.Config.Hardware.Device | Where-Object { $_.Key -eq $VMHdd.ExtensionData.Key -and $_.Backing.FileName -eq $VMHdd.FileName } - $SCSIController = $SCSIControllers | Where-Object { $SCSIDevice.ControllerKey -eq $_.Key } - $VMHardDiskInfo = [PSCustomObject]@{ - 'Datastore' = $VMHdd.FileName.Substring($VMHdd.Filename.IndexOf("[") + 1, $VMHdd.Filename.IndexOf("]") - 1) - 'Capacity' = Convert-DataSize $VMHdd.CapacityGB - 'Disk Path' = $VMHdd.Filename.Substring($VMHdd.Filename.IndexOf("]") + 2) - 'Disk Shares' = "$($TextInfo.ToTitleCase($VMHdd.ExtensionData.Shares.Level)) / $($VMHdd.ExtensionData.Shares.Shares)" - 'Disk Limit IOPs' = Switch ($VMHdd.ExtensionData.StorageIOAllocation.Limit) { - '-1' { 'Unlimited' } - default { $VMHdd.ExtensionData.StorageIOAllocation.Limit } - } - 'Disk Provisioning' = Switch ($VMHdd.StorageFormat) { - 'EagerZeroedThick' { 'Thick Eager Zeroed' } - 'LazyZeroedThick' { 'Thick Lazy Zeroed' } - $null { '--' } - default { $VMHdd.StorageFormat } - } - 'Disk Type' = Switch ($VMHdd.DiskType) { - 'RawPhysical' { 'Physical RDM' } - 'RawVirtual' { "Virtual RDM" } - 'Flat' { 'VMDK' } - default { $VMHdd.DiskType } - } - 'Disk Mode' = Switch ($VMHdd.Persistence) { - 'IndependentPersistent' { 'Independent - Persistent' } - 'IndependentNonPersistent' { 'Independent - Nonpersistent' } - 'Persistent' { 'Dependent' } - default { $VMHdd.Persistence } - } - 'SCSI Controller' = $SCSIController.DeviceInfo.Label - 'SCSI Address' = "$($SCSIController.BusNumber):$($VMHdd.ExtensionData.UnitNumber)" - } - $TableParams = @{ - Name = "Hard Disk $($VMHdd.Name) - $($VM.Name)" - List = $true - ColumnWidths = 25, 75 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMHardDiskInfo | Table @TableParams - } - } - } - } - } - if ($VMGuestVols) { - Section -Style Heading4 "Guest Volumes" { - $VMGuestDiskInfo = foreach ($VMGuestVol in $VMGuestVols) { - [PSCustomObject]@{ - 'Path' = $VMGuestVol.Path - 'Capacity' = Convert-DataSize $VMGuestVol.CapacityGB - 'Used Space' = Convert-DataSize (($VMGuestVol.CapacityGB) - ($VMGuestVol.FreeSpaceGB)) - 'Free Space' = Convert-DataSize $VMGuestVol.FreeSpaceGB - } - } - $TableParams = @{ - Name = "Guest Volumes - $($VM.Name)" - ColumnWidths = 25, 25, 25, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMGuestDiskInfo | Table @TableParams - } - } - } - - - if ($VMSnapshotList -and $Options.ShowVMSnapshots) { - Section -Style Heading4 "Snapshots" { - $VMSnapshots = foreach ($VMSnapshot in $VMSnapshotList) { - [PSCustomObject]@{ - 'Snapshot Name' = $VMSnapshot.Name - 'Description' = $VMSnapshot.Description - 'Days Old' = ((Get-Date).ToUniversalTime() - $VMSnapshot.CreateTime).Days - } - } - if ($Healthcheck.VM.VMSnapshots) { - $VMSnapshots | Where-Object { $_.'Days Old' -ge 7 } | Set-Style -Style Warning - $VMSnapshots | Where-Object { $_.'Days Old' -ge 14 } | Set-Style -Style Critical - } - $TableParams = @{ - Name = "VM Snapshots - $($VM.Name)" - ColumnWidths = 45, 45, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VMSnapshots | Table @TableParams - } - } - } - } - } - #endregion Virtual Machine Detailed Information - } - } - } - #endregion Virtual Machine Section - - #region VMware Update Manager Section - Write-PScriboMessage "VUM InfoLevel set at $($InfoLevel.VUM)." - if (($InfoLevel.VUM -ge 1) -and ($VumServer.Name)) { - Try { - $VUMBaselines = Get-PatchBaseline -Server $vCenter - } Catch { - Write-PScriboMessage 'VUM patch baseline information is not currently available with your version of PowerShell.' - } - if ($VUMBaselines) { - Section -Style Heading2 'VMware Update Manager' { - Paragraph "The following sections detail the configuration of VMware Update Manager managed by vCenter Server $vCenterServerName." - #region VUM Baseline Detailed Information - Section -Style Heading3 'Baselines' { - $VUMBaselineInfo = foreach ($VUMBaseline in $VUMBaselines) { - [PSCustomObject]@{ - 'Baseline' = $VUMBaseline.Name - 'Description' = $VUMBaseline.Description - 'Type' = $VUMBaseline.BaselineType - 'Target Type' = $VUMBaseline.TargetType - 'Last Update Time' = ($VUMBaseline.LastUpdateTime).ToLocalTime().ToString() - '# of Patches' = $VUMBaseline.CurrentPatches.Count - } - } - $TableParams = @{ - Name = "VMware Update Manager Baseline Summary - $($vCenterServerName)" - ColumnWidths = 25, 25, 10, 10, 20, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VUMBaselineInfo | Sort-Object Baseline | Table @TableParams - } - #endregion VUM Baseline Detailed Information - - #region VUM Comprehensive Information - Try { - $VUMPatches = Get-Patch -Server $vCenter | Sort-Object -Descending ReleaseDate - } Catch { - Write-PScriboMessage 'VUM patch information is not currently available with your version of PowerShell.' - } - if ($VUMPatches -and $InfoLevel.VUM -ge 5) { - BlankLine - Section -Style Heading3 'Patches' { - $VUMPatchInfo = foreach ($VUMPatch in $VUMPatches) { - [PSCustomObject]@{ - 'Patch' = $VUMPatch.Name - 'Product' = ($VUMPatch.Product).Name - 'Description' = $VUMPatch.Description - 'Release Date' = $VUMPatch.ReleaseDate - 'Vendor ID' = $VUMPatch.IdByVendor - } - } - $TableParams = @{ - Name = "VMware Update Manager Patch Information - $($vCenterServerName)" - ColumnWidths = 20, 20, 20, 20, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VUMPatchInfo | Table @TableParams - } - } - #endregion VUM Comprehensive Information - } - } - } - #endregion VMware Update Manager Section - } - #endregion vCenter Server Heading1 Section - - # Disconnect vCenter Server - $Null = Disconnect-VIServer -Server $VIServer -Confirm:$false -ErrorAction SilentlyContinue - } # End of If $vCenter - #endregion Generate vSphere report - - #region Variable cleanup - Clear-Variable -Name vCenter - #endregion Variable cleanup - - } # End of Foreach $VIServer - #endregion Script Body -} # End Invoke-AsBuiltReport.VMware.vSphere function \ No newline at end of file diff --git a/Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 b/Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 new file mode 100644 index 0000000..771df53 --- /dev/null +++ b/Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 @@ -0,0 +1,189 @@ +BeforeAll { + # Import the module + $ModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere\AsBuiltReport.VMware.vSphere.psd1' + $ModuleRoot = Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere' + try { + Import-Module $ModulePath -Force -ErrorAction Stop + } catch { + # Fallback: import .psm1 directly when required module dependencies are not available + $PsmPath = Join-Path -Path $ModuleRoot -ChildPath 'AsBuiltReport.VMware.vSphere.psm1' + Import-Module $PsmPath -Force + } +} + +Describe 'AsBuiltReport.VMware.vSphere Module Tests' { + Context 'Module Manifest' { + BeforeAll { + $ManifestPath = Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere\AsBuiltReport.VMware.vSphere.psd1' + # Use Import-PowerShellDataFile so tests pass even when required modules are not installed + $ManifestData = Import-PowerShellDataFile -Path $ManifestPath -ErrorAction Stop + } + + It 'Should have a valid module manifest' { + $ManifestData | Should -Not -BeNullOrEmpty + } + + It 'Should have the correct module name' { + [System.IO.Path]::GetFileNameWithoutExtension($ManifestPath) | Should -Be 'AsBuiltReport.VMware.vSphere' + } + + It 'Should have a valid GUID' { + $ManifestData.GUID | Should -Be 'e1cbf1ce-cf01-4b6e-9cc2-56323da3c351' + } + + It 'Should have a valid version' { + $ManifestData.ModuleVersion | Should -Not -BeNullOrEmpty + [version]::TryParse($ManifestData.ModuleVersion, [ref]$null) | Should -Be $true + } + + It 'Should have version 2.0.0 or higher' { + [version]$ManifestData.ModuleVersion | Should -BeGreaterOrEqual ([version]'2.0.0') + } + + It 'Should have a valid author' { + $ManifestData.Author | Should -Not -BeNullOrEmpty + } + + It 'Should have a valid description' { + $ManifestData.Description | Should -Not -BeNullOrEmpty + } + + It 'Should have CompatiblePSEditions defined' { + $ManifestData.CompatiblePSEditions | Should -Not -BeNullOrEmpty + } + + It 'Should support Desktop PSEdition' { + $ManifestData.CompatiblePSEditions | Should -Contain 'Desktop' + } + + It 'Should support Core PSEdition' { + $ManifestData.CompatiblePSEditions | Should -Contain 'Core' + } + + It 'Should require AsBuiltReport.Core' { + $RequiredModuleNames = $ManifestData.RequiredModules | ForEach-Object { + if ($_ -is [hashtable]) { $_['ModuleName'] } + else { $_ } + } + $RequiredModuleNames | Should -Contain 'AsBuiltReport.Core' + } + + It 'Should export Invoke-AsBuiltReport.VMware.vSphere' { + $ManifestData.FunctionsToExport | Should -Contain 'Invoke-AsBuiltReport.VMware.vSphere' + } + } + + Context 'Module Structure' { + BeforeAll { + $ModuleRoot = Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere' + } + + It 'Should have Language directory' { + $LangPath = Join-Path -Path $ModuleRoot -ChildPath 'Language' + Test-Path $LangPath | Should -Be $true + } + + It 'Should have en-US language folder' { + $EnUsPath = Join-Path -Path $ModuleRoot -ChildPath 'Language\en-US' + Test-Path $EnUsPath | Should -Be $true + } + + It 'Should have en-US VMwarevSphere.psd1 language file' { + $LangFile = Join-Path -Path $ModuleRoot -ChildPath 'Language\en-US\VMwarevSphere.psd1' + Test-Path $LangFile | Should -Be $true + } + + It 'Should have Src/Private directory' { + $PrivatePath = Join-Path -Path $ModuleRoot -ChildPath 'Src\Private' + Test-Path $PrivatePath | Should -Be $true + } + + It 'Should have Src/Public directory' { + $PublicPath = Join-Path -Path $ModuleRoot -ChildPath 'Src\Public' + Test-Path $PublicPath | Should -Be $true + } + + It 'Should have the Invoke- public function file' { + $InvokePath = Join-Path -Path $ModuleRoot -ChildPath 'Src\Public\Invoke-AsBuiltReport.VMware.vSphere.ps1' + Test-Path $InvokePath | Should -Be $true + } + } + + Context 'Private Functions' { + BeforeAll { + $ModuleRoot = Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere' + $PrivatePath = Join-Path -Path $ModuleRoot -ChildPath 'Src\Private' + } + + $ExpectedFunctions = @( + 'Convert-DataSize', + 'Get-ESXiBootDevice', + 'Get-InstallDate', + 'Get-License', + 'Get-PciDeviceDetail', + 'Get-ScsiDeviceDetail', + 'Get-Uptime', + 'Get-vCenterStats', + 'Get-VMHostNetworkAdapterDP', + 'Get-AbrVSpherevCenter', + 'Get-AbrVSphereCluster', + 'Get-AbrVSphereClusterHA', + 'Get-AbrVSphereClusterProactiveHA', + 'Get-AbrVSphereClusterDRS', + 'Get-AbrVSphereResourcePool', + 'Get-AbrVSphereVMHost', + 'Get-AbrVSphereVMHostHardware', + 'Get-AbrVSphereVMHostSystem', + 'Get-AbrVSphereVMHostStorage', + 'Get-AbrVSphereVMHostNetwork', + 'Get-AbrVSphereVMHostSecurity', + 'Get-AbrVSphereNetwork', + 'Get-AbrVSpherevSAN', + 'Get-AbrVSphereDatastore', + 'Get-AbrVSphereDSCluster', + 'Get-AbrVSphereVM', + 'Get-AbrVSphereVUM' + ) + + foreach ($FunctionName in $ExpectedFunctions) { + It "Should have a .ps1 file for function '$FunctionName'" -TestCases @(@{ FunctionName = $FunctionName }) { + param($FunctionName) + $FilePath = Join-Path -Path $PrivatePath -ChildPath "$FunctionName.ps1" + Test-Path $FilePath | Should -Be $true -Because "Expected file '$FunctionName.ps1' in Src/Private/" + } + } + } + + Context 'Module Import' { + It 'Should import without errors' { + $ModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere\AsBuiltReport.VMware.vSphere.psm1' + { Import-Module $ModulePath -Force -ErrorAction Stop } | Should -Not -Throw + } + } + + Context 'PSScriptAnalyzer' { + BeforeDiscovery { + $AnalyzerAvailable = $null -ne (Get-Module -Name PSScriptAnalyzer -ListAvailable | Select-Object -First 1) + } + BeforeAll { + $ModuleRoot = Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere' + $AnalyzerAvailable = $null -ne (Get-Module -Name PSScriptAnalyzer -ListAvailable | Select-Object -First 1) + } + + It 'PSScriptAnalyzer should be available' { + $AnalyzerAvailable | Should -Be $true + } + + It 'Public functions should pass PSScriptAnalyzer' -Skip:(-not $AnalyzerAvailable) { + $PublicPath = Join-Path -Path $ModuleRoot -ChildPath 'Src\Public' + $Results = Invoke-ScriptAnalyzer -Path $PublicPath -Recurse -Severity Error + $Results | Should -BeNullOrEmpty -Because (($Results | ForEach-Object { "$($_.RuleName): $($_.Message) at $($_.ScriptName):$($_.Line)" }) -join "`n") + } + + It 'Private functions should pass PSScriptAnalyzer' -Skip:(-not $AnalyzerAvailable) { + $PrivatePath = Join-Path -Path $ModuleRoot -ChildPath 'Src\Private' + $Results = Invoke-ScriptAnalyzer -Path $PrivatePath -Recurse -Severity Error + $Results | Should -BeNullOrEmpty -Because (($Results | ForEach-Object { "$($_.RuleName): $($_.Message) at $($_.ScriptName):$($_.Line)" }) -join "`n") + } + } +} diff --git a/Tests/Invoke-Tests.ps1 b/Tests/Invoke-Tests.ps1 new file mode 100644 index 0000000..1e63158 --- /dev/null +++ b/Tests/Invoke-Tests.ps1 @@ -0,0 +1,203 @@ +<# +.SYNOPSIS + Invoke Pester tests for AsBuiltReport.VMware.vSphere module + +.DESCRIPTION + This script runs Pester tests with optional code coverage analysis. + It's designed to work with CI/CD pipelines and local development. + +.PARAMETER CodeCoverage + Enable code coverage analysis + +.PARAMETER OutputFormat + Specify the output format for test results (Console, NUnitXml, JUnitXml) + +.EXAMPLE + .\Invoke-Tests.ps1 + +.EXAMPLE + .\Invoke-Tests.ps1 -CodeCoverage -OutputFormat NUnitXml +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $false)] + [switch]$CodeCoverage, + + [Parameter(Mandatory = $false)] + [ValidateSet('Console', 'NUnitXml', 'JUnitXml')] + [string]$OutputFormat = 'Console' +) + +# Ensure we're in the Tests directory +$TestsPath = $PSScriptRoot +if (-not (Test-Path $TestsPath)) { + Write-Error "Tests directory not found at: $TestsPath" + exit 1 +} + +# Get the module root directory +$ModuleRoot = Split-Path -Path $TestsPath -Parent +$ModuleName = 'AsBuiltReport.VMware.vSphere' +$ModulePath = Join-Path -Path $ModuleRoot -ChildPath $ModuleName + +Write-Host "Module Root: $ModuleRoot" -ForegroundColor Cyan +Write-Host "Module Path: $ModulePath" -ForegroundColor Cyan +Write-Host "Tests Path: $TestsPath" -ForegroundColor Cyan + +# Check PowerShell version +$PSVersion = $PSVersionTable.PSVersion +Write-Host "PowerShell Version: $PSVersion" -ForegroundColor Cyan + +if ($PSVersion.Major -lt 7) { + Write-Warning "PowerShell 7 or higher is recommended for optimal test execution" +} + +# Install required modules +Write-Host "`nInstalling required modules..." -ForegroundColor Yellow + +$RequiredModules = @( + @{ Name = 'Pester'; MinimumVersion = '5.0.0' } + @{ Name = 'PScribo'; MinimumVersion = '0.11.1' } + @{ Name = 'PSScriptAnalyzer'; MinimumVersion = '1.0.0' } +) + +foreach ($Module in $RequiredModules) { + $InstalledModule = Get-Module -Name $Module.Name -ListAvailable | + Where-Object { $_.Version -ge [Version]$Module.MinimumVersion } | + Sort-Object -Property Version -Descending | + Select-Object -First 1 + + if (-not $InstalledModule) { + Write-Host "Installing $($Module.Name) (minimum version $($Module.MinimumVersion))..." -ForegroundColor Yellow + Install-Module -Name $Module.Name -MinimumVersion $Module.MinimumVersion -Repository PSGallery -Force -AllowClobber -Scope CurrentUser + } else { + Write-Host "$($Module.Name) version $($InstalledModule.Version) is already installed" -ForegroundColor Green + } +} + +# Remove any pre-loaded Pester module (PowerShell 5.1 ships with old Pester 3.4.0) +Get-Module Pester | Remove-Module -Force -ErrorAction SilentlyContinue + +# Import Pester with explicit minimum version +Import-Module Pester -MinimumVersion 5.0.0 -Force -ErrorAction Stop + +# Configure Pester +$PesterConfiguration = New-PesterConfiguration + +# Run settings +$PesterConfiguration.Run.Path = $TestsPath +$PesterConfiguration.Run.Exit = $false +$PesterConfiguration.Run.PassThru = $true + +# Output settings +$PesterConfiguration.Output.Verbosity = 'Detailed' + +# TestResult settings +if ($OutputFormat -ne 'Console') { + $PesterConfiguration.TestResult.Enabled = $true + $ResultFile = Join-Path -Path $TestsPath -ChildPath 'testResults.xml' + $PesterConfiguration.TestResult.OutputPath = $ResultFile + + if ($OutputFormat -eq 'NUnitXml') { + $PesterConfiguration.TestResult.OutputFormat = 'NUnitXml' + } elseif ($OutputFormat -eq 'JUnitXml') { + $PesterConfiguration.TestResult.OutputFormat = 'JUnitXml' + } + + Write-Host "Test results will be saved to: $ResultFile" -ForegroundColor Cyan +} + +# Code Coverage settings +if ($CodeCoverage) { + Write-Host "`nEnabling code coverage analysis..." -ForegroundColor Yellow + + $PesterConfiguration.CodeCoverage.Enabled = $true + $PesterConfiguration.CodeCoverage.OutputFormat = 'JaCoCo' + $CoverageFile = Join-Path -Path $TestsPath -ChildPath 'coverage.xml' + $PesterConfiguration.CodeCoverage.OutputPath = $CoverageFile + + # Include all PowerShell files in the module + $CoverageFiles = @( + "$ModulePath\*.psm1" + "$ModulePath\Src\Public\*.ps1" + "$ModulePath\Src\Private\*.ps1" + ) + + $PesterConfiguration.CodeCoverage.Path = $CoverageFiles + + Write-Host "Code coverage will be saved to: $CoverageFile" -ForegroundColor Cyan + Write-Host "Coverage files included:" -ForegroundColor Cyan + foreach ($File in $CoverageFiles) { + Write-Host " - $File" -ForegroundColor Gray + } +} + +# Run Pester tests +Write-Host "`nRunning Pester tests..." -ForegroundColor Yellow +Write-Host "======================================" -ForegroundColor Cyan + +$TestResults = Invoke-Pester -Configuration $PesterConfiguration + +# Display results +Write-Host "`n======================================" -ForegroundColor Cyan +Write-Host "Test Results Summary" -ForegroundColor Yellow +Write-Host "======================================" -ForegroundColor Cyan +Write-Host "Total Tests: $($TestResults.TotalCount)" -ForegroundColor White +Write-Host "Passed: $($TestResults.PassedCount)" -ForegroundColor Green +Write-Host "Failed: $($TestResults.FailedCount)" -ForegroundColor $(if ($TestResults.FailedCount -gt 0) { 'Red' } else { 'Green' }) +Write-Host "Skipped: $($TestResults.SkippedCount)" -ForegroundColor Yellow +Write-Host "Duration: $($TestResults.Duration)" -ForegroundColor White + +# Display failed tests +if ($TestResults.FailedCount -gt 0) { + Write-Host "`nFailed Tests:" -ForegroundColor Red + foreach ($FailedTest in $TestResults.Failed) { + Write-Host " - $($FailedTest.Name)" -ForegroundColor Red + Write-Host " $($FailedTest.ErrorRecord)" -ForegroundColor Gray + } +} + +# Display code coverage summary +if ($CodeCoverage -and $TestResults.CodeCoverage) { + Write-Host "`n======================================" -ForegroundColor Cyan + Write-Host "Code Coverage Summary" -ForegroundColor Yellow + Write-Host "======================================" -ForegroundColor Cyan + + $Coverage = $TestResults.CodeCoverage + + if ($Coverage.NumberOfCommandsAnalyzed -gt 0) { + $CoveragePercent = [math]::Round(($Coverage.NumberOfCommandsExecuted / $Coverage.NumberOfCommandsAnalyzed) * 100, 2) + } else { + $CoveragePercent = 0 + Write-Host "Warning: No commands were analyzed for code coverage" -ForegroundColor Yellow + } + + Write-Host "Commands Analyzed: $($Coverage.NumberOfCommandsAnalyzed)" -ForegroundColor White + Write-Host "Commands Executed: $($Coverage.NumberOfCommandsExecuted)" -ForegroundColor White + Write-Host "Commands Missed: $($Coverage.NumberOfCommandsMissed)" -ForegroundColor White + Write-Host "Coverage: $CoveragePercent%" -ForegroundColor $(if ($CoveragePercent -ge 80) { 'Green' } elseif ($CoveragePercent -ge 60) { 'Yellow' } else { 'Red' }) + + # Code coverage threshold enforcement + $MinimumCoverageThreshold = 50 # Minimum 50% code coverage + if ($CoveragePercent -lt $MinimumCoverageThreshold) { + Write-Host "`nWARNING: Code coverage ($CoveragePercent%) is below minimum threshold ($MinimumCoverageThreshold%)" -ForegroundColor Red + Write-Host "Consider adding more tests to improve coverage" -ForegroundColor Yellow + + # Uncomment the line below to fail builds when coverage is too low + # exit 1 + } else { + Write-Host "`nCode coverage meets minimum threshold ($MinimumCoverageThreshold%)" -ForegroundColor Green + } +} + +Write-Host "`n======================================" -ForegroundColor Cyan + +# Exit with appropriate code +if ($TestResults.FailedCount -gt 0) { + Write-Host "Tests FAILED" -ForegroundColor Red + exit 1 +} else { + Write-Host "All tests PASSED" -ForegroundColor Green + exit 0 +} diff --git a/Tests/LocalizationData.Tests.ps1 b/Tests/LocalizationData.Tests.ps1 new file mode 100644 index 0000000..a9895b3 --- /dev/null +++ b/Tests/LocalizationData.Tests.ps1 @@ -0,0 +1,74 @@ +BeforeAll { + # Get the language folder path + $LanguagePath = Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere\Language' + + # Helper function to extract nested localization keys in Section.Key format + function Get-NestedLocalizationKeys { + param([string]$FilePath) + + $keys = @() + $content = Get-Content -Path $FilePath + $currentSection = $null + + foreach ($line in $content) { + # Match section headers like: GetAbrVSpherevCenter = ConvertFrom-StringData @' + if ($line -match '^\s*(\w+)\s*=\s*ConvertFrom-StringData') { + $currentSection = $Matches[1] + } + # Match key-value pairs within sections + elseif ($currentSection -and $line -match '^\s+(\w+)\s*=' -and $line -notmatch "^'@") { + $keys += "$currentSection.$($Matches[1])" + } + # End of section + elseif ($line -match "^'@") { + $currentSection = $null + } + } + return $keys | Sort-Object + } +} + +Describe 'Localization Data Consistency Tests' { + Context 'VMwarevSphere.psd1 Localization Files' { + BeforeAll { + $TemplateFile = Join-Path -Path $LanguagePath -ChildPath 'en-US\VMwarevSphere.psd1' + $TemplateKeys = Get-NestedLocalizationKeys -FilePath $TemplateFile + $LanguageFolders = Get-ChildItem -Path $LanguagePath -Directory | Where-Object { $_.Name -ne 'en-US' } + } + + It "Template 'en-US' should have localization keys" { + $TemplateKeys.Count | Should -BeGreaterThan 0 + } + + It "Template 'en-US' VMwarevSphere.psd1 should be valid PowerShell" { + $TemplateFile = Join-Path -Path $LanguagePath -ChildPath 'en-US\VMwarevSphere.psd1' + { Import-LocalizedData -BaseDirectory $LanguagePath -UICulture 'en-US' -FileName 'VMwarevSphere' -ErrorAction Stop } | + Should -Not -Throw + } + + foreach ($folder in (Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath '..\AsBuiltReport.VMware.vSphere\Language') -Directory | Where-Object { $_.Name -ne 'en-US' })) { + It "Language '' should have all keys from en-US template for VMwarevSphere.psd1" -TestCases @(@{ Name = $folder.Name; FolderPath = $folder.FullName }) { + param($Name, $FolderPath) + + $LocalizedFile = Join-Path -Path $FolderPath -ChildPath 'VMwarevSphere.psd1' + if (Test-Path $LocalizedFile) { + $LocalizedKeys = Get-NestedLocalizationKeys -FilePath $LocalizedFile + $TemplatePath = Join-Path -Path $LanguagePath -ChildPath 'en-US\VMwarevSphere.psd1' + $TemplateKeysForTest = Get-NestedLocalizationKeys -FilePath $TemplatePath + + $MissingKeys = $TemplateKeysForTest | Where-Object { $_ -notin $LocalizedKeys } + $ExtraKeys = $LocalizedKeys | Where-Object { $_ -notin $TemplateKeysForTest } + + if ($MissingKeys) { + $MissingKeys | Should -BeNullOrEmpty -Because "Language '$Name' is missing keys: $($MissingKeys -join ', ')" + } + if ($ExtraKeys) { + $ExtraKeys | Should -BeNullOrEmpty -Because "Language '$Name' has extra keys not in template: $($ExtraKeys -join ', ')" + } + } else { + Set-ItResult -Skipped -Because "File not found: $LocalizedFile" + } + } + } + } +} From 3b083eb7297dc952816032acbf1eac65e732603b Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 16:45:29 +1100 Subject: [PATCH 02/24] Fix 'LinkedView' duplicate key error in TEXT format reports (#130) Convert VMware managed objects stored as PSCustomObject property values to plain strings (.Name or [string] cast) before passing to PScribo's Table cmdlet, preventing .NET's Dictionary.Add() from throwing a duplicate-key exception when formatting TEXT output. Co-Authored-By: Claude Sonnet 4.6 --- .../Src/Private/Get-AbrVSphereCluster.ps1 | 38 +++++++++---------- .../Src/Private/Get-AbrVSphereNetwork.ps1 | 26 ++++++------- .../Private/Get-AbrVSphereResourcePool.ps1 | 4 +- .../Src/Private/Get-AbrVSphereVMHost.ps1 | 2 +- CHANGELOG.md | 4 ++ 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 index 0765c28..274a005 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 @@ -35,15 +35,15 @@ function Get-AbrVSphereCluster { BlankLine $ClusterInfo = foreach ($Cluster in $Clusters) { [PSCustomObject]@{ - ($LocalizedData.Cluster) = $Cluster.Name - ($LocalizedData.Datacenter) = $Cluster | Get-Datacenter - ($LocalizedData.NumHosts) = $Cluster.ExtensionData.Host.Count - ($LocalizedData.NumVMs) = $Cluster.ExtensionData.VM.Count - ($LocalizedData.HAEnabled) = if ($Cluster.HAEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } - ($LocalizedData.DRSEnabled) = if ($Cluster.DRSEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } - ($LocalizedData.VSANEnabled) = if ($Cluster.VsanEnabled -or $VsanCluster.VsanEsaEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } - ($LocalizedData.EVCMode) = if ($Cluster.EVCMode) { $EvcModeLookup."$($Cluster.EVCMode)" } else { $LocalizedData.Disabled } - ($LocalizedData.VMSwapFilePolicy) = switch ($Cluster.VMSwapfilePolicy) { + $LocalizedData.Cluster = $Cluster.Name + $LocalizedData.Datacenter = ($Cluster | Get-Datacenter).Name + $LocalizedData.NumHosts = $Cluster.ExtensionData.Host.Count + $LocalizedData.NumVMs = $Cluster.ExtensionData.VM.Count + $LocalizedData.HAEnabled = if ($Cluster.HAEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + $LocalizedData.DRSEnabled = if ($Cluster.DRSEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + $LocalizedData.VSANEnabled = if ($Cluster.VsanEnabled -or $VsanCluster.VsanEsaEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + $LocalizedData.EVCMode = if ($Cluster.EVCMode) { $EvcModeLookup."$($Cluster.EVCMode)" } else { $LocalizedData.Disabled } + $LocalizedData.VMSwapFilePolicy = switch ($Cluster.VMSwapfilePolicy) { 'WithVM' { $LocalizedData.SwapWithVM } 'InHostDatastore' { $LocalizedData.SwapInHostDatastore } default { $Cluster.VMSwapfilePolicy } @@ -87,16 +87,16 @@ function Get-AbrVSphereCluster { BlankLine #region Cluster Configuration $ClusterDetail = [PSCustomObject]@{ - ($LocalizedData.Cluster) = $Cluster.Name - ($LocalizedData.ID) = $Cluster.Id - ($LocalizedData.Datacenter) = $Cluster | Get-Datacenter - ($LocalizedData.NumberOfHosts) = $Cluster.ExtensionData.Host.Count - ($LocalizedData.NumberOfVMs) = ($Cluster | Get-VM).Count - ($LocalizedData.HAEnabled) = if ($Cluster.HAEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } - ($LocalizedData.DRSEnabled) = if ($Cluster.DRSEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } - ($LocalizedData.VSANEnabled) = if ($Cluster.VsanEnabled -or $Cluster.VsanEsaEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } - ($LocalizedData.EVCMode) = if ($Cluster.EVCMode) { $EvcModeLookup."$($Cluster.EVCMode)" } else { $LocalizedData.Disabled } - ($LocalizedData.VMSwapFilePolicy) = switch ($Cluster.VMSwapfilePolicy) { + $LocalizedData.Cluster = $Cluster.Name + $LocalizedData.ID = $Cluster.Id + $LocalizedData.Datacenter = ($Cluster | Get-Datacenter).Name + $LocalizedData.NumberOfHosts = $Cluster.ExtensionData.Host.Count + $LocalizedData.NumberOfVMs = ($Cluster | Get-VM).Count + $LocalizedData.HAEnabled = if ($Cluster.HAEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + $LocalizedData.DRSEnabled = if ($Cluster.DRSEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + $LocalizedData.VSANEnabled = if ($Cluster.VsanEnabled -or $Cluster.VsanEsaEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } + $LocalizedData.EVCMode = if ($Cluster.EVCMode) { $EvcModeLookup."$($Cluster.EVCMode)" } else { $LocalizedData.Disabled } + $LocalizedData.VMSwapFilePolicy = switch ($Cluster.VMSwapfilePolicy) { 'WithVM' { $LocalizedData.SwapVMDirectory } 'InHostDatastore' { $LocalizedData.SwapHostDatastore } default { $Cluster.VMSwapfilePolicy } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 index c97e8f6..4483cd7 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 @@ -31,7 +31,7 @@ function Get-AbrVSphereNetwork { $VDSInfo = foreach ($VDS in $VDSwitches) { [PSCustomObject]@{ $LocalizedData.VDSwitch = $VDS.Name - $LocalizedData.Datacenter = $VDS.Datacenter + $LocalizedData.Datacenter = $VDS.Datacenter.Name $LocalizedData.Manufacturer = $VDS.Vendor $LocalizedData.Version = $VDS.Version $LocalizedData.NumUplinks = $VDS.NumUplinkPorts @@ -62,7 +62,7 @@ function Get-AbrVSphereNetwork { $VDSwitchDetail = [PSCustomObject]@{ $LocalizedData.VDSwitch = $VDS.Name $LocalizedData.ID = $VDS.Id - $LocalizedData.Datacenter = $VDS.Datacenter + $LocalizedData.Datacenter = $VDS.Datacenter.Name $LocalizedData.Manufacturer = $VDS.Vendor $LocalizedData.Version = $VDS.Version $LocalizedData.NumberOfUplinks = $VDS.NumUplinkPorts @@ -121,11 +121,11 @@ function Get-AbrVSphereNetwork { Section -Style Heading4 $LocalizedData.UplinkPorts { $VdsUplinkDetail = foreach ($VdsUplink in $VdsUplinks) { [PSCustomObject]@{ - $LocalizedData.VDSwitch = $VdsUplink.Switch - $LocalizedData.Host = $VdsUplink.ProxyHost + $LocalizedData.VDSwitch = [string]$VdsUplink.Switch + $LocalizedData.Host = [string]$VdsUplink.ProxyHost $LocalizedData.UplinkName = $VdsUplink.Name - $LocalizedData.PhysicalNetworkAdapter = $VdsUplink.ConnectedEntity - $LocalizedData.UplinkPortGroup = $VdsUplink.Portgroup + $LocalizedData.PhysicalNetworkAdapter = [string]$VdsUplink.ConnectedEntity + $LocalizedData.UplinkPortGroup = [string]$VdsUplink.Portgroup } } $TableParams = @{ @@ -145,7 +145,7 @@ function Get-AbrVSphereNetwork { if ($VDSecurityPolicy) { Section -Style Heading4 $LocalizedData.VDSSecurity { $VDSecurityPolicyDetail = [PSCustomObject]@{ - $LocalizedData.VDSwitch = $VDSecurityPolicy.VDSwitch + $LocalizedData.VDSwitch = $VDSecurityPolicy.VDSwitch.Name $LocalizedData.AllowPromiscuous = if ($VDSecurityPolicy.AllowPromiscuous) { $LocalizedData.Accept } else { @@ -184,7 +184,7 @@ function Get-AbrVSphereNetwork { Section -Style Heading4 $LocalizedData.VDSTrafficShaping { $VDSTrafficShapingDetail = foreach ($VDSTrafficShape in $VDSTrafficShaping) { [PSCustomObject]@{ - $LocalizedData.VDSwitch = $VDSTrafficShape.VDSwitch + $LocalizedData.VDSwitch = $VDSTrafficShape.VDSwitch.Name $LocalizedData.Direction = $VDSTrafficShape.Direction $LocalizedData.Status = if ($VDSTrafficShape.Enabled) { $LocalizedData.Enabled @@ -216,8 +216,8 @@ function Get-AbrVSphereNetwork { $VDSPortgroupDetail = foreach ($VDSPortgroup in $VDSPortgroups) { [PSCustomObject]@{ $LocalizedData.PortGroup = $VDSPortgroup.Name - $LocalizedData.VDSwitch = $VDSPortgroup.VDSwitch - $LocalizedData.Datacenter = $VDSPortgroup.Datacenter + $LocalizedData.VDSwitch = $VDSPortgroup.VDSwitch.Name + $LocalizedData.Datacenter = $VDSPortgroup.Datacenter.Name $LocalizedData.VLANConfiguration = if ($VDSPortgroup.VlanConfiguration) { $VDSPortgroup.VlanConfiguration } else { @@ -255,7 +255,7 @@ function Get-AbrVSphereNetwork { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VDSPortGroupSecurity { $VDSSecurityPolicies = foreach ($VDSSecurityPolicy in $VDSPortgroupSecurity) { [PSCustomObject]@{ - $LocalizedData.PortGroup = $VDSSecurityPolicy.VDPortgroup + $LocalizedData.PortGroup = [string]$VDSSecurityPolicy.VDPortgroup $LocalizedData.VDSwitch = $VDS.Name $LocalizedData.AllowPromiscuous = if ($VDSSecurityPolicy.AllowPromiscuous) { $LocalizedData.Accept @@ -296,7 +296,7 @@ function Get-AbrVSphereNetwork { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VDSPortGroupTrafficShaping { $VDSPortgroupTrafficShapingDetail = foreach ($VDSPortgroupTrafficShape in $VDSPortgroupTrafficShaping) { [PSCustomObject]@{ - $LocalizedData.PortGroup = $VDSPortgroupTrafficShape.VDPortgroup + $LocalizedData.PortGroup = [string]$VDSPortgroupTrafficShape.VDPortgroup $LocalizedData.VDSwitch = $VDS.Name $LocalizedData.Direction = $VDSPortgroupTrafficShape.Direction $LocalizedData.Status = if ($VDSPortgroupTrafficShape.Enabled) { @@ -328,7 +328,7 @@ function Get-AbrVSphereNetwork { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VDSPortGroupTeaming { $VDSPortgroupNICTeaming = foreach ($VDUplink in $VDUplinkTeamingPolicy) { [PSCustomObject]@{ - $LocalizedData.PortGroup = $VDUplink.VDPortgroup + $LocalizedData.PortGroup = [string]$VDUplink.VDPortgroup $LocalizedData.VDSwitch = $VDS.Name $LocalizedData.LoadBalancing = switch ($VDUplink.LoadBalancingPolicy) { 'LoadbalanceSrcId' { $LocalizedData.LoadBalanceSrcId } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 index e62f152..46d2f54 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 @@ -31,7 +31,7 @@ function Get-AbrVSphereResourcePool { $ResourcePoolInfo = foreach ($ResourcePool in $ResourcePools) { [PSCustomObject]@{ $LocalizedData.ResourcePool = $ResourcePool.Name - $LocalizedData.Parent = $ResourcePool.Parent + $LocalizedData.Parent = $ResourcePool.Parent.Name $LocalizedData.CPUSharesLevel = $ResourcePool.CpuSharesLevel $LocalizedData.CPUReservationMHz = $ResourcePool.CpuReservationMHz $LocalizedData.CPULimitMHz = switch ($ResourcePool.CpuLimitMHz) { @@ -65,7 +65,7 @@ function Get-AbrVSphereResourcePool { $ResourcePoolDetail = [PSCustomObject]@{ $LocalizedData.ResourcePool = $ResourcePool.Name $LocalizedData.ID = $ResourcePool.Id - $LocalizedData.Parent = $ResourcePool.Parent + $LocalizedData.Parent = $ResourcePool.Parent.Name $LocalizedData.CPUSharesLevel = $ResourcePool.CpuSharesLevel $LocalizedData.NumCPUShares = $ResourcePool.NumCpuShares $LocalizedData.CPUReservation = "$($ResourcePool.CpuReservationMHz) MHz" diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 index cc10ad6..37a1606 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 @@ -33,7 +33,7 @@ function Get-AbrVSphereVMHost { $LocalizedData.VMHost = $VMHost.Name $LocalizedData.Version = $VMHost.Version $LocalizedData.Build = $VMHost.Build - $LocalizedData.Parent = $VMHost.Parent + $LocalizedData.Parent = $VMHost.Parent.Name $LocalizedData.ConnectionState = switch ($VMHost.ConnectionState) { 'NotResponding' { $LocalizedData.NotResponding } 'Maintenance' { $LocalizedData.Maintenance } diff --git a/CHANGELOG.md b/CHANGELOG.md index 275d485..e442fef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [[2.0.0](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v2.0.0)] - 2026-03-05 +### Fixed + +- Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130)) + ### Added - Modular architecture: each report section is now a dedicated private function (`Get-AbrVSphere*`) From bad523959735700d50b078ab78a1831ee9656d7a Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 17:00:17 +1100 Subject: [PATCH 03/24] Fix ESXi host licensing crash and null-MoRef errors on vCenter 8.0.2 (#123) - Guard against null LicenseAssignmentManager MoRef (Bug B) to prevent 'Index operation failed; the array index evaluated to null' crash - Replace broken reflection-based QueryAssignedLicenses call with direct method call and fix $_ out-of-scope bug in vCenter license path (Bug A) - Guard null/empty LicenseKey before masking in both VMHost and vCenter paths to avoid errors when no license is assigned (Bug C) - Wrap Get-License calls in try/catch in Get-AbrVSphereVMHostHardware and Get-AbrVSpherevCenter to prevent fatal propagation (Bug D) - Add LicenseError locale key to all five language files Co-Authored-By: Claude Sonnet 4.6 --- .../Language/de-DE/VMwarevSphere.psd1 | 1 + .../Language/en-GB/VMwarevSphere.psd1 | 1 + .../Language/en-US/VMwarevSphere.psd1 | 1 + .../Language/es-ES/VMwarevSphere.psd1 | 1 + .../Language/fr-FR/VMwarevSphere.psd1 | 1 + .../Private/Get-AbrVSphereVMHostHardware.ps1 | 12 +++-- .../Src/Private/Get-AbrVSpherevCenter.ps1 | 50 +++++++++++-------- .../Src/Private/Get-License.ps1 | 11 ++-- CHANGELOG.md | 3 +- 9 files changed, 52 insertions(+), 29 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 index f971cda..da8357d 100644 --- a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -447,6 +447,7 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' SectionHeading = Hardware ParagraphSummary = Der folgende Abschnitt beschreibt die Hardware-Konfiguration des Hosts {0}. InsufficientPrivLicense = Unzureichende Benutzerberechtigungen für den Bericht über ESXi-Host-Lizenzierung. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'Global > Lizenzen' zugewiesen ist. + LicenseError = Lizenzinformationen für VMHost '{0}' konnten nicht abgerufen werden. Fehler: {1} Specifications = Spezifikationen Manufacturer = Hersteller Model = Modell diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 index 2b43686..0c818dd 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -447,6 +447,7 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' SectionHeading = Hardware ParagraphSummary = The following section details the host hardware configuration for {0}. InsufficientPrivLicense = Insufficient user privileges to report ESXi host licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned. + LicenseError = Unable to retrieve licence information for VMHost '{0}'. Error: {1} Specifications = Specifications Manufacturer = Manufacturer Model = Model diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 index d4264ea..44e6bd1 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -447,6 +447,7 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' SectionHeading = Hardware ParagraphSummary = The following section details the host hardware configuration for {0}. InsufficientPrivLicense = Insufficient user privileges to report ESXi host licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned. + LicenseError = Unable to retrieve license information for VMHost '{0}'. Error: {1} Specifications = Specifications Manufacturer = Manufacturer Model = Model diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 index 93fbfe2..f4837a0 100644 --- a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -447,6 +447,7 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' SectionHeading = Hardware ParagraphSummary = La siguiente sección detalla la configuración de hardware del host {0}. InsufficientPrivLicense = Privilegios de usuario insuficientes para informar sobre las licencias del host ESXi. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'Global > Licencias'. + LicenseError = No se puede recuperar la información de licencia para VMHost '{0}'. Error: {1} Specifications = Especificaciones Manufacturer = Fabricante Model = Modelo diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 index 5d1e5cd..fdf7bf4 100644 --- a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -447,6 +447,7 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' SectionHeading = Matériel ParagraphSummary = La section suivante détaille la configuration matérielle de l'hôte {0}. InsufficientPrivLicense = Privilèges utilisateur insuffisants pour rapporter les licences de l'hôte ESXi. Veuillez vous assurer que le compte utilisateur dispose du privilège 'Global > Licences'. + LicenseError = Impossible de récupérer les informations de licence pour VMHost '{0}'. Erreur : {1} Specifications = Spécifications Manufacturer = Fabricant Model = Modèle diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 index 3dac618..c792bdf 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 @@ -87,10 +87,14 @@ function Get-AbrVSphereVMHostHardware { 'MemberType' = 'NoteProperty' } if ($UserPrivileges -contains 'Global.Licenses') { - $VMHostLicense = Get-License -VMHost $VMHost - Add-Member @MemberProps -Name $LocalizedData.Product -Value $VMHostLicense.Product - Add-Member @MemberProps -Name $LocalizedData.LicenseKey -Value $VMHostLicense.LicenseKey - Add-Member @MemberProps -Name $LocalizedData.LicenseExpiration -Value $VMHostLicense.Expiration + try { + $VMHostLicense = Get-License -VMHost $VMHost + Add-Member @MemberProps -Name $LocalizedData.Product -Value $VMHostLicense.Product + Add-Member @MemberProps -Name $LocalizedData.LicenseKey -Value $VMHostLicense.LicenseKey + Add-Member @MemberProps -Name $LocalizedData.LicenseExpiration -Value $VMHostLicense.Expiration + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.LicenseError -f $VMHost.Name, $_.Exception.Message) + } } else { Write-PScriboMessage -Message $LocalizedData.InsufficientPrivLicense } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 index 57e1972..e36b7ab 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 @@ -57,10 +57,14 @@ function Get-AbrVSpherevCenter { } #region vCenter Server Detail if ($UserPrivileges -contains 'Global.Licenses') { - $vCenterLicense = Get-License -vCenter $vCenter - Add-Member @MemberProps -Name $LocalizedData.Product -Value $vCenterLicense.Product - Add-Member @MemberProps -Name $LocalizedData.LicenseKey -Value $vCenterLicense.LicenseKey - Add-Member @MemberProps -Name $LocalizedData.LicenseExpiration -Value $vCenterLicense.Expiration + try { + $vCenterLicense = Get-License -vCenter $vCenter + Add-Member @MemberProps -Name $LocalizedData.Product -Value $vCenterLicense.Product + Add-Member @MemberProps -Name $LocalizedData.LicenseKey -Value $vCenterLicense.LicenseKey + Add-Member @MemberProps -Name $LocalizedData.LicenseExpiration -Value $vCenterLicense.Expiration + } catch { + Write-PScriboMessage -IsWarning $LocalizedData.InsufficientPrivLicense + } } else { Write-PScriboMessage -Message $LocalizedData.InsufficientPrivLicense } @@ -164,24 +168,28 @@ function Get-AbrVSpherevCenter { #region vCenter Server Licensing if ($UserPrivileges -contains 'Global.Licenses') { Section -Style Heading3 $LocalizedData.Licensing { - $Licenses = Get-License -Licenses | Select-Object @{L = $LocalizedData.Product; E = { $_.Product } }, - @{L = $LocalizedData.LicenseKey; E = { ($_.LicenseKey) } }, - @{L = $LocalizedData.Total; E = { $_.Total } }, - @{L = $LocalizedData.Used; E = { $_.Used } }, - @{L = $LocalizedData.Available; E = { ($_.total) - ($_.Used) } }, - @{L = $LocalizedData.Expiration; E = { $_.Expiration } } -Unique - if ($Healthcheck.vCenter.Licensing) { - $Licenses | Where-Object { $_.$($LocalizedData.Product) -eq 'Product Evaluation' } | Set-Style -Style Warning - $Licenses | Where-Object { $_.$($LocalizedData.Expiration) -eq 'Expired' } | Set-Style -Style Critical - } - $TableParams = @{ - Name = ($LocalizedData.TableLicensing -f $vCenterServerName) - ColumnWidths = 25, 25, 12, 12, 12, 14 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" + try { + $Licenses = Get-License -Licenses | Select-Object @{L = $LocalizedData.Product; E = { $_.Product } }, + @{L = $LocalizedData.LicenseKey; E = { ($_.LicenseKey) } }, + @{L = $LocalizedData.Total; E = { $_.Total } }, + @{L = $LocalizedData.Used; E = { $_.Used } }, + @{L = $LocalizedData.Available; E = { ($_.total) - ($_.Used) } }, + @{L = $LocalizedData.Expiration; E = { $_.Expiration } } -Unique + if ($Healthcheck.vCenter.Licensing) { + $Licenses | Where-Object { $_.$($LocalizedData.Product) -eq 'Product Evaluation' } | Set-Style -Style Warning + $Licenses | Where-Object { $_.$($LocalizedData.Expiration) -eq 'Expired' } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableLicensing -f $vCenterServerName) + ColumnWidths = 25, 25, 12, 12, 12, 14 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $Licenses | Sort-Object $LocalizedData.Product, $LocalizedData.LicenseKey | Table @TableParams + } catch { + Write-PScriboMessage -IsWarning $LocalizedData.InsufficientPrivLicense } - $Licenses | Sort-Object $LocalizedData.Product, $LocalizedData.LicenseKey | Table @TableParams } } else { Write-PScriboMessage -Message $LocalizedData.InsufficientPrivLicense diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-License.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-License.ps1 index 4d7c222..901d42b 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-License.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-License.ps1 @@ -42,12 +42,15 @@ function Get-License { $ServiceInstance = Get-View ServiceInstance -Server $vCenter $LicenseManager = Get-View $ServiceInstance.Content.LicenseManager -Server $vCenter $LicenseManagerAssign = Get-View $LicenseManager.LicenseAssignmentManager -Server $vCenter + if (-not $LicenseManagerAssign) { return } if ($VMHost) { $VMHostId = $VMHost.Extensiondata.Config.Host.Value $VMHostAssignedLicense = $LicenseManagerAssign.QueryAssignedLicenses($VMHostId) $VMHostLicense = $VMHostAssignedLicense.AssignedLicense $VMHostLicenseExpiration = ($VMHostLicense.Properties | Where-Object { $_.Key -eq 'expirationDate' } | Select-Object Value).Value - if ($VMHostLicense.LicenseKey -and $Options.ShowLicenseKeys) { + if ([string]::IsNullOrEmpty($VMHostLicense.LicenseKey)) { + $VMHostLicenseKey = 'N/A' + } elseif ($Options.ShowLicenseKeys) { $VMHostLicenseKey = $VMHostLicense.LicenseKey } else { $keyParts = $VMHostLicense.LicenseKey -split '-' @@ -69,10 +72,12 @@ function Get-License { } } if ($vCenter) { - $vCenterAssignedLicense = $LicenseManagerAssign.GetType().GetMethod("QueryAssignedLicenses").Invoke($LicenseManagerAssign, @($_.MoRef.Value)) | Where-Object { $_.EntityID -eq $vCenter.InstanceUuid } + $vCenterAssignedLicense = $LicenseManagerAssign.QueryAssignedLicenses($null) | Where-Object { $_.EntityId -eq $vCenter.InstanceUuid } $vCenterLicense = $vCenterAssignedLicense.AssignedLicense $vCenterLicenseExpiration = ($vCenterLicense.Properties | Where-Object { $_.Key -eq 'expirationDate' } | Select-Object Value).Value - if ($vCenterLicense.LicenseKey -and $Options.ShowLicenseKeys) { + if ([string]::IsNullOrEmpty($vCenterLicense.LicenseKey)) { + $vCenterLicenseKey = 'N/A' + } elseif ($Options.ShowLicenseKeys) { $vCenterLicenseKey = $vCenterLicense.LicenseKey } else { $keyParts = $vCenterLicense.LicenseKey -split '-' diff --git a/CHANGELOG.md b/CHANGELOG.md index e442fef..1f7fe14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [[2.0.0](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v2.0.0)] - 2026-03-05 +## [[2.0.0](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v2.0.0)] - Unreleased ### Fixed - Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130)) +- Fix "Index operation failed; the array index evaluated to null" crash and `Global.Licenses` privilege errors when querying ESXi host/vCenter licensing on vCenter 8.0.2 ([#123](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/123)) ### Added From b3eee6f49aed432bace58474842d4ff763e063d9 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 17:25:56 +1100 Subject: [PATCH 04/24] Add I/O Device Identifiers subsection to VMHost Hardware report (#126) Adds a new NOTOCHeading5 subsection under each host's Hardware section displaying VID, DID, SVID, and SSID in lowercase hex for HCL validation. Uses PCI address mapping via Get-VMHostNetworkAdapter/Get-VMHostHba instead of VMkernelName (unpopulated on ESXi 8). Guards null PCI keys to handle software HBAs (iSCSI, software NVMe) without physical PCI addresses. Adds 7 new localisation keys to all 5 locale files (en-US, en-GB, es-ES, fr-FR, de-DE). Co-Authored-By: Claude Sonnet 4.6 --- .../Language/de-DE/VMwarevSphere.psd1 | 7 +++ .../Language/en-GB/VMwarevSphere.psd1 | 7 +++ .../Language/en-US/VMwarevSphere.psd1 | 7 +++ .../Language/es-ES/VMwarevSphere.psd1 | 7 +++ .../Language/fr-FR/VMwarevSphere.psd1 | 7 +++ .../Private/Get-AbrVSphereVMHostHardware.ps1 | 44 +++++++++++++++++++ CHANGELOG.md | 1 + 7 files changed, 80 insertions(+) diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 index da8357d..537d6f9 100644 --- a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -536,6 +536,13 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' PCIDeviceError = Error collecting PCI device information for {0}. {1} PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} HardwareError = Error collecting host hardware information for {0}. {1} + IODeviceIdentifiers = E/A-Gerätekennungen + VendorID = VID + DeviceID = DID + SubVendorID = SVID + SubDeviceID = SSID + TableIODeviceIdentifiers = E/A-Gerätekennungen - {0} + IODeviceIdentifiersError = Fehler beim Sammeln der E/A-Gerätekennungen für {0}. {1} '@ # Get-AbrVSphereVMHostSystem diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 index 0c818dd..17db816 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -536,6 +536,13 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' PCIDeviceError = Error collecting PCI device information for {0}. {1} PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} HardwareError = Error collecting host hardware information for {0}. {1} + IODeviceIdentifiers = I/O Device Identifiers + VendorID = VID + DeviceID = DID + SubVendorID = SVID + SubDeviceID = SSID + TableIODeviceIdentifiers = I/O Device Identifiers - {0} + IODeviceIdentifiersError = Error collecting I/O device identifier information for {0}. {1} '@ # Get-AbrVSphereVMHostSystem diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 index 44e6bd1..2a05594 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -536,6 +536,13 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' PCIDeviceError = Error collecting PCI device information for {0}. {1} PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} HardwareError = Error collecting host hardware information for {0}. {1} + IODeviceIdentifiers = I/O Device Identifiers + VendorID = VID + DeviceID = DID + SubVendorID = SVID + SubDeviceID = SSID + TableIODeviceIdentifiers = I/O Device Identifiers - {0} + IODeviceIdentifiersError = Error collecting I/O device identifier information for {0}. {1} '@ # Get-AbrVSphereVMHostSystem diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 index f4837a0..d27876a 100644 --- a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -536,6 +536,13 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' PCIDeviceError = Error collecting PCI device information for {0}. {1} PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} HardwareError = Error collecting host hardware information for {0}. {1} + IODeviceIdentifiers = Identificadores de dispositivos de E/S + VendorID = VID + DeviceID = DID + SubVendorID = SVID + SubDeviceID = SSID + TableIODeviceIdentifiers = Identificadores de E/S - {0} + IODeviceIdentifiersError = Error al recopilar identificadores de E/S para {0}. {1} '@ # Get-AbrVSphereVMHostSystem diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 index fdf7bf4..e5e9213 100644 --- a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -536,6 +536,13 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' PCIDeviceError = Error collecting PCI device information for {0}. {1} PCIDriversFirmwareError = Error collecting PCI device driver & firmware information for {0}. {1} HardwareError = Error collecting host hardware information for {0}. {1} + IODeviceIdentifiers = Identifiants des périphériques d'E/S + VendorID = VID + DeviceID = DID + SubVendorID = SVID + SubDeviceID = SSID + TableIODeviceIdentifiers = Identifiants E/S - {0} + IODeviceIdentifiersError = Erreur lors de la collecte des identifiants E/S pour {0}. {1} '@ # Get-AbrVSphereVMHostSystem diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 index c792bdf..4e4117c 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 @@ -280,6 +280,50 @@ function Get-AbrVSphereVMHostHardware { } } #endregion ESXi Host PCI Devices Drivers & Firmware + + #region ESXi Host I/O Device Identifiers + try { + # Build PCI address -> VMkernel name mapping (VMkernelName is unpopulated on ESXi 8) + # Guard against null keys — software HBAs (iSCSI, software NVMe) have no PCI address + $IOPciToDevice = @{} + Get-VMHostNetworkAdapter -VMHost $VMHost -Physical | ForEach-Object { + if ($_.PciId) { $IOPciToDevice[$_.PciId] = $_.DeviceName } + } + Get-VMHostHba -VMHost $VMHost | ForEach-Object { + if ($_.Pci) { $IOPciToDevice[$_.Pci] = $_.Device } + } + (Get-VMHost $VMHost | Get-View -Property Config).Config.GraphicsInfo | ForEach-Object { + if ($_.pciId) { $IOPciToDevice[$_.pciId] = $_.deviceName } + } + $VMHostIODevices = $esxcli.hardware.pci.list.Invoke() | + Where-Object { $IOPciToDevice.ContainsKey($_.Address) } | + Sort-Object -Property Address + if ($VMHostIODevices) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.IODeviceIdentifiers { + $VMHostIODeviceInfo = $VMHostIODevices | ForEach-Object { + [PSCustomObject]@{ + $LocalizedData.Device = $IOPciToDevice[$_.Address] + $LocalizedData.Model = $_.DeviceName + $LocalizedData.VendorID = [String]::Format("{0:x4}", $_.VendorID) + $LocalizedData.DeviceID = [String]::Format("{0:x4}", $_.DeviceID) + $LocalizedData.SubVendorID = [String]::Format("{0:x4}", $_.SubVendorID) + $LocalizedData.SubDeviceID = [String]::Format("{0:x4}", $_.SubDeviceID) + } + } + $TableParams = @{ + Name = ($LocalizedData.TableIODeviceIdentifiers -f $VMHost) + ColumnWidths = 13, 31, 14, 14, 14, 14 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMHostIODeviceInfo | Sort-Object $LocalizedData.Device | Table @TableParams + } + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.IODeviceIdentifiersError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host I/O Device Identifiers } } catch { Write-PScriboMessage -Message ($LocalizedData.HardwareError -f $VMHost.Name, $_.Exception.Message) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f7fe14..d8e5865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add I/O Device Identifiers subsection to VMHost Hardware report, displaying VID/DID/SVID/SSID in lowercase hex for HCL validation ([#126](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/126)) - Modular architecture: each report section is now a dedicated private function (`Get-AbrVSphere*`) - Internationalization (i18n) support via `Language/` `.psd1` files (`en-US`, `en-GB`, `es-ES`, `fr-FR`, `de-DE`) - Pester test suite (`AsBuiltReport.VMware.vSphere.Tests.ps1`, `LocalizationData.Tests.ps1`, `Invoke-Tests.ps1`) From 51bddf11efec15fab077d41d5b3936fffd8225f0 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 17:36:37 +1100 Subject: [PATCH 05/24] Fix null disk group crash in OSA vSAN clusters (#113) Guard Get-VsanDisk against a null VsanDiskGroup parameter, which occurs when an OSA cluster has no claimed disks. The cluster detail table now renders correctly; Disk Groups and Disks subsections are skipped when no disk groups exist. Co-Authored-By: Claude Sonnet 4.6 --- .../Src/Private/Get-AbrVSpherevSAN.ps1 | 6 ++++-- CHANGELOG.md | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 index 8e7c73d..abd29f7 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 @@ -235,8 +235,10 @@ function Get-AbrVSpherevSAN { # Get vSAN Disk Groups $VsanDiskGroup = Get-VsanDiskGroup -Cluster $VsanCluster.Cluster $NumVsanDiskGroup = $VsanDiskGroup.Count - # Get vSAN Disks - $VsanDisk = Get-VsanDisk -VsanDiskGroup $VsanDiskGroup + # Get vSAN Disks (guard against null disk groups — e.g. unclaimed OSA cluster) + if ($VsanDiskGroup) { + $VsanDisk = Get-VsanDisk -VsanDiskGroup $VsanDiskGroup + } $VsanDiskFormat = $VsanDisk.DiskFormatVersion | Select-Object -Unique # Count SSDs and HDDs $NumVsanSsd = ($VsanDisk | Where-Object { $_.IsSsd -eq $true } | Measure-Object).Count diff --git a/CHANGELOG.md b/CHANGELOG.md index d8e5865..19447aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fix null disk group crash in OSA vSAN clusters where disk groups have not yet been claimed ([#113](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/113)) - Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130)) - Fix "Index operation failed; the array index evaluated to null" crash and `Global.Licenses` privilege errors when querying ESXi host/vCenter licensing on vCenter 8.0.2 ([#123](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/123)) From 79d8355e07b93000da6c3eaab26138e45071a163 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 17:59:35 +1100 Subject: [PATCH 06/24] Fix vCenter certificate section showing VMCA template defaults (#88) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace vpxd.certmgmt.certs.cn.* advanced settings reads with a direct TLS connection to port 443, returning the actual deployed certificate. Use SslStream with an explicit-parameter RemoteCertificateValidationCallback for reliable PS 5.1 and 7.x compatibility. Certificate status (VALID / EXPIRING_SOON / EXPIRING / EXPIRED) is computed from NotAfter vs the existing soft/hard threshold advanced settings. Fields removed: Country, Email, Locality, State, Organization, OrganizationUnit, Validity (VMCA template settings — not the live cert). Fields added: Subject, Issuer, ValidFrom, ValidTo, Thumbprint, CertStatus. All five locale files (en-US, en-GB, es-ES, fr-FR, de-DE) updated. Co-Authored-By: Claude Sonnet 4.6 --- .../Language/de-DE/VMwarevSphere.psd1 | 14 ++-- .../Language/en-GB/VMwarevSphere.psd1 | 14 ++-- .../Language/en-US/VMwarevSphere.psd1 | 14 ++-- .../Language/es-ES/VMwarevSphere.psd1 | 14 ++-- .../Language/fr-FR/VMwarevSphere.psd1 | 14 ++-- .../Src/Private/Get-AbrVSpherevCenter.ps1 | 70 +++++++++++++------ CHANGELOG.md | 1 + 7 files changed, 84 insertions(+), 57 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 index 537d6f9..67d7c38 100644 --- a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -62,13 +62,13 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' Available = Verfügbar Expiration = Ablauf Certificate = Zertifikat - Country = Land - Email = E-Mail - Locality = Ort - State = Bundesland - Organization = Organisation - OrganizationUnit = Organisationseinheit - Validity = Gültigkeit + Subject = Betreff + Issuer = Aussteller + ValidFrom = Gültig ab + ValidTo = Gültig bis + Thumbprint = Fingerabdruck + CertStatus = Status + InsufficientPrivCertificate = Das vCenter Server-Zertifikat konnte nicht abgerufen werden. {0} Mode = Modus SoftThreshold = Weicher Schwellenwert HardThreshold = Harter Schwellenwert diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 index 17db816..14115c0 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -62,13 +62,13 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' Available = Available Expiration = Expiration Certificate = Certificate - Country = Country - Email = Email - Locality = Locality - State = State - Organization = Organization - OrganizationUnit = Organization Unit - Validity = Validity + Subject = Subject + Issuer = Issuer + ValidFrom = Valid From + ValidTo = Valid To + Thumbprint = Thumbprint + CertStatus = Status + InsufficientPrivCertificate = Unable to retrieve vCenter Server certificate. {0} Mode = Mode SoftThreshold = Soft Threshold HardThreshold = Hard Threshold diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 index 2a05594..596a5a9 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -62,13 +62,13 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' Available = Available Expiration = Expiration Certificate = Certificate - Country = Country - Email = Email - Locality = Locality - State = State - Organization = Organization - OrganizationUnit = Organization Unit - Validity = Validity + Subject = Subject + Issuer = Issuer + ValidFrom = Valid From + ValidTo = Valid To + Thumbprint = Thumbprint + CertStatus = Status + InsufficientPrivCertificate = Unable to retrieve vCenter Server certificate. {0} Mode = Mode SoftThreshold = Soft Threshold HardThreshold = Hard Threshold diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 index d27876a..d5ff3fe 100644 --- a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -62,13 +62,13 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' Available = Disponible Expiration = Vencimiento Certificate = Certificado - Country = País - Email = Correo electrónico - Locality = Localidad - State = Estado - Organization = Organización - OrganizationUnit = Unidad organizativa - Validity = Validez + Subject = Asunto + Issuer = Emisor + ValidFrom = Válido desde + ValidTo = Válido hasta + Thumbprint = Huella digital + CertStatus = Estado + InsufficientPrivCertificate = No se puede recuperar el certificado del servidor vCenter. {0} Mode = Modo SoftThreshold = Umbral suave HardThreshold = Umbral estricto diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 index e5e9213..a6b109d 100644 --- a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -62,13 +62,13 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' Available = Disponible Expiration = Expiration Certificate = Certificat - Country = Pays - Email = E-mail - Locality = Localité - State = État - Organization = Organisation - OrganizationUnit = Unité organisationnelle - Validity = Validité + Subject = Sujet + Issuer = Émetteur + ValidFrom = Valide à partir de + ValidTo = Valide jusqu'au + Thumbprint = Empreinte numérique + CertStatus = Statut + InsufficientPrivCertificate = Impossible de récupérer le certificat du serveur vCenter. {0} Mode = Mode SoftThreshold = Seuil souple HardThreshold = Seuil strict diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 index e36b7ab..2a3fa97 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 @@ -199,29 +199,55 @@ function Get-AbrVSpherevCenter { #region vCenter Server Certificate if ($vCenter.Version -ge 6) { Section -Style Heading3 $LocalizedData.Certificate { - $VcenterCertMgmt = [PSCustomObject]@{ - $LocalizedData.Country = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.country' }).Value - $LocalizedData.Email = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.email' }).Value - $LocalizedData.Locality = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.localityName' }).Value - $LocalizedData.State = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.state' }).Value - $LocalizedData.Organization = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.organizationName' }).Value - $LocalizedData.OrganizationUnit = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.organizationalUnitName' }).Value - $LocalizedData.Validity = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.daysValid'}).Value / 365) years" - $LocalizedData.Mode = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.mode' }).Value - $LocalizedData.SoftThreshold = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.softThreshold'}).Value) days" - $LocalizedData.HardThreshold = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.hardThreshold'}).Value) days" - $LocalizedData.MinutesBefore = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.minutesBefore' }).Value - $LocalizedData.PollInterval = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.pollIntervalDays'}).Value) days" - } - $TableParams = @{ - Name = ($LocalizedData.TableCertificate -f $vCenterServerName) - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" + try { + $SslCallback = [System.Net.Security.RemoteCertificateValidationCallback]{ + param($sender, $cert, $chain, $errors) $true + } + $TcpClient = New-Object -TypeName System.Net.Sockets.TcpClient -ArgumentList ($vCenterServerName, 443) + $SslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList ( + $TcpClient.GetStream(), $false, $SslCallback + ) + $SslStream.AuthenticateAsClient($vCenterServerName) + $VIMachineCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]$SslStream.RemoteCertificate + $SslStream.Dispose() + $TcpClient.Dispose() + $SoftThresholdDays = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.softThreshold' }).Value + $HardThresholdDays = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.hardThreshold' }).Value + $DaysRemaining = ($VIMachineCert.NotAfter - (Get-Date)).Days + $CertificateStatus = if ($DaysRemaining -le 0) { + 'EXPIRED' + } elseif ($null -ne $HardThresholdDays -and $DaysRemaining -le [int]$HardThresholdDays) { + 'EXPIRING' + } elseif ($null -ne $SoftThresholdDays -and $DaysRemaining -le [int]$SoftThresholdDays) { + 'EXPIRING_SOON' + } else { + 'VALID' + } + $VcenterCertMgmt = [PSCustomObject]@{ + $LocalizedData.Subject = $VIMachineCert.Subject + $LocalizedData.Issuer = $VIMachineCert.Issuer + $LocalizedData.ValidFrom = $VIMachineCert.NotBefore + $LocalizedData.ValidTo = $VIMachineCert.NotAfter + $LocalizedData.Thumbprint = $VIMachineCert.Thumbprint + $LocalizedData.CertStatus = $CertificateStatus + $LocalizedData.Mode = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.mode' }).Value + $LocalizedData.SoftThreshold = "$(($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.softThreshold' }).Value) days" + $LocalizedData.HardThreshold = "$(($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.hardThreshold' }).Value) days" + $LocalizedData.MinutesBefore = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.minutesBefore' }).Value + $LocalizedData.PollInterval = "$(($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.pollIntervalDays' }).Value) days" + } + $TableParams = @{ + Name = ($LocalizedData.TableCertificate -f $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VcenterCertMgmt | Table @TableParams + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.InsufficientPrivCertificate -f $_.Exception.Message) } - $VcenterCertMgmt | Table @TableParams } } #endregion vCenter Server Certificate diff --git a/CHANGELOG.md b/CHANGELOG.md index 19447aa..a3a5478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fix vCenter Server Certificate section reporting VMCA template defaults instead of the actual deployed TLS certificate; now reads the live certificate directly from port 443 ([#88](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/88)) - Fix null disk group crash in OSA vSAN clusters where disk groups have not yet been claimed ([#113](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/113)) - Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130)) - Fix "Index operation failed; the array index evaluated to null" crash and `Global.Licenses` privilege errors when querying ESXi host/vCenter licensing on vCenter 8.0.2 ([#123](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/123)) From ccc6b7c8759c6ebcba1728e72bba24d88e89799b Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 18:35:08 +1100 Subject: [PATCH 07/24] Update CHANGELOG.md --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3a5478..a68261e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,6 @@ 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). -## [Unreleased] - ## [[2.0.0](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v2.0.0)] - Unreleased ### Fixed From 31a23bd520fc676c21dbaebb9217c4bdf3fef20b Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 19:17:09 +1100 Subject: [PATCH 08/24] Drop Windows PowerShell 5.1 (Desktop) support - CompatiblePSEditions: Core only (Desktop removed) - PowerShellVersion minimum set to 7.4 per VMware PowerCLI requirement - PSEdition_Desktop tag removed from manifest - Pester: replace separate Desktop/Core assertions with a single 'Core only' check that also asserts Desktop is absent - README: add Broadcom PowerCLI Installation Guide link and PS 7.4 minimum requirement note - CHANGELOG: document as Changed (min version) and Removed (PS 5.1) Co-Authored-By: Claude Sonnet 4.6 --- .../AsBuiltReport.VMware.vSphere.psd1 | 16 +++++----------- CHANGELOG.md | 7 ++++++- README.md | 14 ++++++++++++-- Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 | 7 ++----- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 index c9c58ea..738a0de 100644 --- a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 @@ -15,7 +15,7 @@ ModuleVersion = '2.0.0' # Supported PSEditions - CompatiblePSEditions = @('Desktop', 'Core') + CompatiblePSEditions = @('Core') # ID used to uniquely identify this module GUID = 'e1cbf1ce-cf01-4b6e-9cc2-56323da3c351' @@ -32,18 +32,12 @@ # Description of the functionality provided by this module Description = 'A PowerShell module to generate an as built report on the configuration of VMware vSphere.' - # Minimum version of the Windows PowerShell engine required by this module - # PowerShellVersion = '5.1' + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '7.4' - # Name of the Windows PowerShell host required by this module + # Name of the PowerShell host required by this module # PowerShellHostName = '' - # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # DotNetFrameworkVersion = '4.5' - - # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # CLRVersion = '' - # Processor architecture (None, X86, Amd64) required by this module # ProcessorArchitecture = '' @@ -96,7 +90,7 @@ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. - Tags = 'AsBuiltReport', 'Report', 'VMware', 'vSphere', 'vCenter', 'Documentation', 'PScribo', 'PSEdition_Desktop', 'PSEdition_Core', 'Windows', 'MacOS', 'Linux' + Tags = 'AsBuiltReport', 'Report', 'VMware', 'vSphere', 'vCenter', 'Documentation', 'PScribo', 'PSEdition_Core', 'Windows', 'MacOS', 'Linux' # A URL to the license for this module. LicenseUri = 'https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/main/LICENSE' diff --git a/CHANGELOG.md b/CHANGELOG.md index a68261e..51da68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Complete module rewrite for improved maintainability and extensibility - Module source now uses nested folder structure (`AsBuiltReport.VMware.vSphere/`) - Requires `AsBuiltReport.Core` >= 1.6.2 -- `CompatiblePSEditions` now explicitly declares `Desktop` and `Core` support +- Minimum PowerShell version raised to 7.4; refer to the [VMware PowerCLI Installation Guide](https://developer.broadcom.com/powercli/installation-guide) +- `CompatiblePSEditions` updated to `Core` only + +### Removed + +- Windows PowerShell 5.1 (Desktop edition) support dropped ## [[1.3.6.1](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/releases/tag/v1.3.6.1)] - 2025-08-24 diff --git a/README.md b/README.md index b898776..0999ce1 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,13 @@

+

+ + + + + +

@@ -74,7 +81,7 @@ This report is compatible with the following PowerShell versions; | Windows PowerShell 5.1 | PowerShell 7 | |:----------------------:|:--------------------:| -| :white_check_mark: | :white_check_mark: | +| :x: | :white_check_mark: | ## 🌐 Language Support @@ -90,7 +97,10 @@ The VMware vSphere As Built Report supports the following languages; ## :wrench: System Requirements -PowerShell 5.1 or PowerShell 7, and the following PowerShell modules are required for generating a VMware vSphere As Built report. +PowerShell 7.4 or higher, and the following PowerShell modules are required for generating a VMware vSphere As Built report. + +> [!NOTE] +> Windows PowerShell 5.1 is not supported. Please refer to the [VMware PowerCLI Installation Guide](https://developer.broadcom.com/powercli/installation-guide) for instructions on installing PowerShell 7 and PowerCLI. Each of these modules can be easily downloaded and installed via the PowerShell Gallery diff --git a/Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 b/Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 index 771df53..f3bb9dd 100644 --- a/Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 +++ b/Tests/AsBuiltReport.VMware.vSphere.Tests.ps1 @@ -52,12 +52,9 @@ Describe 'AsBuiltReport.VMware.vSphere Module Tests' { $ManifestData.CompatiblePSEditions | Should -Not -BeNullOrEmpty } - It 'Should support Desktop PSEdition' { - $ManifestData.CompatiblePSEditions | Should -Contain 'Desktop' - } - - It 'Should support Core PSEdition' { + It 'Should support Core PSEdition only' { $ManifestData.CompatiblePSEditions | Should -Contain 'Core' + $ManifestData.CompatiblePSEditions | Should -Not -Contain 'Desktop' } It 'Should require AsBuiltReport.Core' { From f74d8ea7f4373b0d566d03d57e1b28e22cc54491 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 19:22:42 +1100 Subject: [PATCH 09/24] Fix Dependabot config location Move from .github/workflows/Dependabot.yml to .github/dependabot.yml. GitHub was parsing it as an Actions workflow (requiring on:/jobs:) instead of a Dependabot configuration file. Co-Authored-By: Claude Sonnet 4.6 --- .github/{workflows/Dependabot.yml => dependabot.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows/Dependabot.yml => dependabot.yml} (100%) diff --git a/.github/workflows/Dependabot.yml b/.github/dependabot.yml similarity index 100% rename from .github/workflows/Dependabot.yml rename to .github/dependabot.yml From 693e53dd4e08299bcb9be3b4d7a20a04838ee46b Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 19:23:19 +1100 Subject: [PATCH 10/24] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0999ce1..f33e90d 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ PowerShell 7.4 or higher, and the following PowerShell modules are required for Each of these modules can be easily downloaded and installed via the PowerShell Gallery -- [VCF PowerCLI Module](https://www.powershellgallery.com/packages/VCF.PowerCLI/) +- [VCF PowerCLI Module](https://www.powershellgallery.com/packages/VCF.PowerCLI/) version 9.0 or higher - [AsBuiltReport.VMware.vSphere Module](https://www.powershellgallery.com/packages/AsBuiltReport.VMware.vSphere/) ### :closed_lock_with_key: Required Privileges @@ -333,4 +333,7 @@ PS C:\> New-AsBuiltReport -Report VMware.vSphere -Target 'vcenter-01.corp.local' # Generate a vSphere As Built Report for vCenter Server 'vcenter-01.corp.local' using specified credentials. Export report to HTML & DOCX formats. Use default report style. Reports are saved to the user profile folder by default. Attach and send reports via e-mail. PS C:\> New-AsBuiltReport -Report VMware.vSphere -Target 'vcenter-01.corp.local' -Username 'administrator@vsphere.local' -Password 'VMware1!' -Format Html,Word -OutputFolderPath 'C:\Users\Tim\Documents' -SendEmail + +# Generate a vSphere As Built Report for vCenter Server 'vcenter-01.corp.local' using specified credentials. Export report to HTML format. Use default report style. Generate the report in French. Save reports to 'C:\Users\Tim\Documents'. +PS C:\> New-AsBuiltReport -Report VMware.vSphere -Target 'vcenter-01.corp.local' -Username 'administrator@vsphere.local' -Password 'VMware1!' -Format Html -OutputFolderPath 'C:\Users\Tim\Documents' -ReportLanguage 'fr-FR' ``` From 8c2eae6470aaaef8cd3fc8e8db4c7216e9290c0c Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 19:45:28 +1100 Subject: [PATCH 11/24] Add TPM attestation state and encryption recovery key reporting (#101) - Add ESXi Host TPM & Encryption section to Get-AbrVSphereVMHostSecurity - Reports TPM attestation status via ExtensionData.Config.TpmAttestation - Reports encryption mode, RequireSecureBoot, and RequireSignedVIBs via esxcli - Recovery keys sub-table gated behind ShowEncryptionKeys option (InfoLevel >= 3) - TpmAttestation healthcheck warns when TPM present but not attested - Add ShowEncryptionKeys option (default: false) to report JSON config - Add TpmAttestation healthcheck (default: true) to report JSON config - Add i18n keys to all 5 locale files (en-US, en-GB, es-ES, fr-FR, de-DE) - Update README.md options and healthcheck tables Co-Authored-By: Claude Sonnet 4.6 --- .../AsBuiltReport.VMware.vSphere.json | Bin 5314 -> 5284 bytes .../Language/de-DE/VMwarevSphere.psd1 | 14 ++++ .../Language/en-GB/VMwarevSphere.psd1 | 14 ++++ .../Language/en-US/VMwarevSphere.psd1 | 14 ++++ .../Language/es-ES/VMwarevSphere.psd1 | 14 ++++ .../Language/fr-FR/VMwarevSphere.psd1 | 14 ++++ .../Private/Get-AbrVSphereVMHostSecurity.ps1 | 63 ++++++++++++++++++ CHANGELOG.md | 1 + README.md | 6 +- 9 files changed, 138 insertions(+), 2 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json index 90d57ba2b526b37a4accfbb7e14bc9fa7a7c5d6c..7b87742fcf1c43d1bbda83154672e4029be472b5 100644 GIT binary patch delta 785 zcmZuv&ubG=5T4!aW`AUNvw2xVtZ`}AK)3A@mKwB_QV^o_SW6Ej#}+{Y{b3D-S}LK( z-s*Vp&{OGI)Ptw?;@MLV(u0436i?zoqVLU17AdkU-@KV`X1+J`K2MxZF&M;4qexG$5%&EmWs6wqXan~2fc>hNM=x()Q!<{PqTG%aD0}$axQ)m8 zDsCG!JaZ#LW{lv4Um;f8sBqsrel!+2h8USm!X0xGPs|1WPh)5aP7Rl=I^i2D=Kc`B zSrK7qx5(eK6FPsfMQ(_dLMZbJG2z=n9aoF3tas`J}j z+JC!JOX@{<;qm&Ota0ik{48tC6l2kczXX2!(lX!QIy~Sz?7|ax0bP7tx|wNM(Y@i6 z{AT9u>DB*u`~C=NJNBEhHXfE9WjTC5hTNu{dd#G9aXT@+PfSk#CzI2dD@!~m-=p}~ b@(foUVj~dhL);1Y4gG-q`&w<-2OItZ%!bO` delta 904 zcmaJ=zfaph6!yhF$4+cxJ0=MTYSToN9}yCvAebN$LV_*@2Gpej^*`v)0ThX;L)FE7 z66%1Mn6hPMKnXKLhW-HwBVvI+AniTh9ZC?YES0eXfuSQ>fMeaKONkFF5)RYB0K3=TGF8tTgS_Po#CK>h z$(C;CbVB&{-J;O-ypUECU)D23y7fvuYx?|i6#+wSeG(cvdbjm;!6(w=3@wrz<=yfs zacZdUq%}B8<3KrkFHmHxJR<{+jWT0RGlUE3!mWXhk_9F*%-%6kh3JQgb8MP5wmzC) z*h>5mzGmi z9Jk6;!-GDj#sBX2U2I=utN0l4l<(fwkW0%Q4NZL>;>8906kdE`MNUvV<4+5{{VtF&Sn4r diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 index 67d7c38..348348b 100644 --- a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -910,6 +910,20 @@ GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' VMToolsStatus = VM Tools Status TableVMs = Virtual Machines - {0} TableStartupShutdown = VM Startup/Shutdown Policy - {0} + TpmEncryption = TPM & Verschlüsselung + TpmPresent = TPM vorhanden + TpmStatus = TPM-Attestierungsstatus + EncryptionMode = Verschlüsselungsmodus + RequireSecureBoot = Sicheres Booten erforderlich + RequireSignedVIBs = Ausführbare Dateien nur von installierten VIBs + RecoveryKeys = Verschlüsselungs-Wiederherstellungsschlüssel + RecoveryID = Wiederherstellungs-ID + RecoveryKey = Wiederherstellungsschlüssel + TpmEncryptionError = TPM- und Verschlüsselungsinformationen für Host {0} konnten nicht abgerufen werden. {1} + TableTpmEncryption = TPM & Verschlüsselung - {0} + TableRecoveryKeys = Verschlüsselungs-Wiederherstellungsschlüssel - {0} + Yes = Ja + No = Nein '@ # Get-AbrVSphereNetwork diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 index 14115c0..6771caa 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -910,6 +910,20 @@ GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' VMToolsStatus = VM Tools Status TableVMs = Virtual Machines - {0} TableStartupShutdown = VM Startup/Shutdown Policy - {0} + TpmEncryption = TPM & Encryption + TpmPresent = TPM Present + TpmStatus = TPM Attestation Status + EncryptionMode = Encryption Mode + RequireSecureBoot = Require Secure Boot + RequireSignedVIBs = Require Executables From Installed VIBs Only + RecoveryKeys = Encryption Recovery Keys + RecoveryID = Recovery ID + RecoveryKey = Recovery Key + TpmEncryptionError = Unable to retrieve TPM & Encryption information for host {0}. {1} + TableTpmEncryption = TPM & Encryption - {0} + TableRecoveryKeys = Encryption Recovery Keys - {0} + Yes = Yes + No = No '@ # Get-AbrVSphereNetwork diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 index 596a5a9..38ba0a0 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -910,6 +910,20 @@ GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' VMToolsStatus = VM Tools Status TableVMs = Virtual Machines - {0} TableStartupShutdown = VM Startup/Shutdown Policy - {0} + TpmEncryption = TPM & Encryption + TpmPresent = TPM Present + TpmStatus = TPM Attestation Status + EncryptionMode = Encryption Mode + RequireSecureBoot = Require Secure Boot + RequireSignedVIBs = Require Executables From Installed VIBs Only + RecoveryKeys = Encryption Recovery Keys + RecoveryID = Recovery ID + RecoveryKey = Recovery Key + TpmEncryptionError = Unable to retrieve TPM & Encryption information for host {0}. {1} + TableTpmEncryption = TPM & Encryption - {0} + TableRecoveryKeys = Encryption Recovery Keys - {0} + Yes = Yes + No = No '@ # Get-AbrVSphereNetwork diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 index d5ff3fe..f6e290a 100644 --- a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -910,6 +910,20 @@ GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' VMToolsStatus = VM Tools Status TableVMs = Virtual Machines - {0} TableStartupShutdown = VM Startup/Shutdown Policy - {0} + TpmEncryption = TPM y cifrado + TpmPresent = TPM presente + TpmStatus = Estado de atestación TPM + EncryptionMode = Modo de cifrado + RequireSecureBoot = Requerir arranque seguro + RequireSignedVIBs = Requerir ejecutables solo de VIBs instalados + RecoveryKeys = Claves de recuperación de cifrado + RecoveryID = ID de recuperación + RecoveryKey = Clave de recuperación + TpmEncryptionError = No se puede recuperar información de TPM y cifrado para el host {0}. {1} + TableTpmEncryption = TPM y cifrado - {0} + TableRecoveryKeys = Claves de recuperación de cifrado - {0} + Yes = Sí + No = No '@ # Get-AbrVSphereNetwork diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 index a6b109d..532cf8c 100644 --- a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -910,6 +910,20 @@ GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' VMToolsStatus = VM Tools Status TableVMs = Virtual Machines - {0} TableStartupShutdown = VM Startup/Shutdown Policy - {0} + TpmEncryption = TPM et chiffrement + TpmPresent = TPM présent + TpmStatus = Statut d'attestation TPM + EncryptionMode = Mode de chiffrement + RequireSecureBoot = Exiger le démarrage sécurisé + RequireSignedVIBs = Exiger les exécutables uniquement depuis les VIBs installés + RecoveryKeys = Clés de récupération de chiffrement + RecoveryID = ID de récupération + RecoveryKey = Clé de récupération + TpmEncryptionError = Impossible de récupérer les informations TPM et chiffrement pour l'hôte {0}. {1} + TableTpmEncryption = TPM et chiffrement - {0} + TableRecoveryKeys = Clés de récupération de chiffrement - {0} + Yes = Oui + No = Non '@ # Get-AbrVSphereNetwork diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 index 00b2025..0eb9a23 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 @@ -91,6 +91,69 @@ function Get-AbrVSphereVMHostSecurity { } #endregion ESXi Host Services + #region ESXi Host TPM & Encryption + $TpmAttestation = $VMHost.ExtensionData.Config.TpmAttestation + $EncryptionSettings = $null + try { + $esxcliTpm = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter + $EncryptionSettings = $esxcliTpm.system.settings.encryption.get.Invoke() + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.TpmEncryptionError -f $VMHost, $_.Exception.Message) + } + if ($null -ne $TpmAttestation -or ($null -ne $EncryptionSettings -and $EncryptionSettings.Mode -ne 'None')) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.TpmEncryption { + $TpmEncryptionInfo = [PSCustomObject]@{ + $LocalizedData.TpmPresent = if ($null -ne $TpmAttestation) { $LocalizedData.Yes } else { $LocalizedData.No } + $LocalizedData.TpmStatus = if ($null -ne $TpmAttestation) { $TpmAttestation.Status } else { 'N/A' } + $LocalizedData.EncryptionMode = if ($null -ne $EncryptionSettings) { $EncryptionSettings.Mode } else { 'N/A' } + $LocalizedData.RequireSecureBoot = if ($null -ne $EncryptionSettings) { $EncryptionSettings.RequireSecureBoot } else { 'N/A' } + $LocalizedData.RequireSignedVIBs = if ($null -ne $EncryptionSettings) { $EncryptionSettings.RequireExecutablesOnlyFromInstalledVIBs } else { 'N/A' } + } + if ($Healthcheck.VMHost.TpmAttestation) { + $TpmEncryptionInfo | Where-Object { + $null -ne $TpmAttestation -and $TpmAttestation.Status -ne 'attested' + } | Set-Style -Style Warning -Property $LocalizedData.TpmStatus + } + $TableParams = @{ + Name = ($LocalizedData.TableTpmEncryption -f $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $TpmEncryptionInfo | Table @TableParams + + # Recovery keys — InfoLevel >= 3 and ShowEncryptionKeys option + if ($InfoLevel.VMHost -ge 3 -and $Options.ShowEncryptionKeys -and $null -ne $esxcliTpm) { + try { + $RecoveryKeys = $esxcliTpm.system.settings.encryption.recovery.list.Invoke() + if ($RecoveryKeys) { + Section -Style NOTOCHeading6 -ExcludeFromTOC $LocalizedData.RecoveryKeys { + $RecoveryKeyInfo = foreach ($RecoveryKey in $RecoveryKeys) { + [PSCustomObject]@{ + $LocalizedData.RecoveryID = $RecoveryKey.RecoveryID + $LocalizedData.RecoveryKey = $RecoveryKey.Key + } + } + $TableParams = @{ + Name = ($LocalizedData.TableRecoveryKeys -f $VMHost) + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $RecoveryKeyInfo | Table @TableParams + } + } + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.TpmEncryptionError -f $VMHost, $_.Exception.Message) + } + } + } + } + #endregion ESXi Host TPM & Encryption + #region ESXi Host Advanced Detail Information if ($InfoLevel.VMHost -ge 4) { #region ESXi Host Firewall diff --git a/CHANGELOG.md b/CHANGELOG.md index 51da68b..abed5d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add TPM attestation state and host encryption settings to VMHost Security section; includes recovery key reporting (gated behind `ShowEncryptionKeys` option) and `TpmAttestation` healthcheck ([#101](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/101)) - Add I/O Device Identifiers subsection to VMHost Hardware report, displaying VID/DID/SVID/SSID in lowercase hex for HCL validation ([#126](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/126)) - Modular architecture: each report section is now a dedicated private function (`Get-AbrVSphere*`) - Internationalization (i18n) support via `Language/` `.psd1` files (`en-US`, `en-GB`, `es-ES`, `fr-FR`, `de-DE`) diff --git a/README.md b/README.md index f33e90d..0c18e4f 100644 --- a/README.md +++ b/README.md @@ -191,8 +191,9 @@ The **Options** schema allows certain options within the report to be toggled on | Sub-Schema | Setting | Default | Description | |-----------------|--------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ShowLicenseKeys | true / false | false | Toggle to mask/unmask vSphere license keys

**Masked License Key**
\*\*\*\*\*-\*\*\*\*\*-\*\*\*\*\*-\*\*\*\*\*-AS12K

**Unmasked License Key**
AKLU4-PFG8M-W2D8J-56YDM-AS12K | -| ShowVMSnapshots | true / false | true | Toggle to enable/disable reporting of VM snapshots | +| ShowLicenseKeys | true / false | false | Toggle to mask/unmask vSphere license keys

**Masked License Key**
\*\*\*\*\*-\*\*\*\*\*-\*\*\*\*\*-\*\*\*\*\*-AS12K

**Unmasked License Key**
AKLU4-PFG8M-W2D8J-56YDM-AS12K | +| ShowEncryptionKeys | true / false | false | Toggle to show/hide ESXi host encryption recovery keys in the report. When disabled, the recovery keys table is suppressed even at InfoLevel 3+ | +| ShowVMSnapshots | true / false | true | Toggle to enable/disable reporting of VM snapshots | ### InfoLevel @@ -276,6 +277,7 @@ The **VMHost** schema is used to configure health checks for VMHosts. | NetworkAdapter | true / false | true | Highlights physical network adapters which are not 'Connected'
Highlights physical network adapters which are 'Down' | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Network adapter is 'Disconnected'
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Network adapter is 'Down' | | LockdownMode | true / false | true | Highlights VMHosts which do not have Lockdown mode enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Lockdown Mode disabled
| | VUMCompliance | true / false | true | Highlights VMHosts which are not compliant with VMware Update Manager software packages | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Unknown
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Incompatible | +| TpmAttestation | true / false | true | Highlights VMHosts where a TPM is present but attestation status is not 'attested' | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) TPM attestation status is not attested | #### vSAN The **vSAN** schema is used to configure health checks for vSAN. From 4179c5db7e2152f796598e89c43ade6d338554f2 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 19:55:50 +1100 Subject: [PATCH 12/24] Remove misleading InfoLevel verbose messages from sub-section functions Sub-functions (ClusterHA, ClusterProactiveHA, ClusterDRS, VMHostHardware, VMHostSystem, VMHostStorage, VMHostNetwork, VMHostSecurity) all share their parent's InfoLevel setting (Cluster or VMHost) rather than having their own dedicated JSON config key. Emitting "VMHost Network InfoLevel set to 3" implied a separate configurable setting that doesn't exist. InfoLevel messages are retained only in the 10 top-level functions that correspond directly to InfoLevel keys in the report JSON config. Co-Authored-By: Claude Sonnet 4.6 --- .../Language/de-DE/VMwarevSphere.psd1 | 10 +--------- .../Language/en-GB/VMwarevSphere.psd1 | 10 +--------- .../Language/en-US/VMwarevSphere.psd1 | 10 +--------- .../Language/es-ES/VMwarevSphere.psd1 | 10 +--------- .../Language/fr-FR/VMwarevSphere.psd1 | 10 +--------- .../Src/Private/Get-AbrVSphereClusterDRS.ps1 | 1 - .../Src/Private/Get-AbrVSphereClusterHA.ps1 | 1 - .../Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 | 1 - .../Src/Private/Get-AbrVSphereVMHostHardware.ps1 | 1 - .../Src/Private/Get-AbrVSphereVMHostNetwork.ps1 | 1 - .../Src/Private/Get-AbrVSphereVMHostSecurity.ps1 | 1 - .../Src/Private/Get-AbrVSphereVMHostStorage.ps1 | 1 - .../Src/Private/Get-AbrVSphereVMHostSystem.ps1 | 1 - 13 files changed, 5 insertions(+), 53 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 index 348348b..fa508f0 100644 --- a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -164,7 +164,6 @@ GetAbrVSphereCluster = ConvertFrom-StringData @' # Get-AbrVSphereClusterHA GetAbrVSphereClusterHA = ConvertFrom-StringData @' - InfoLevel = Cluster-HA-Informationsebene auf {0} gesetzt. Collecting = Cluster-HA-Informationen werden gesammelt. SectionHeading = vSphere HA-Konfiguration ParagraphSummary = Der folgende Abschnitt beschreibt die vSphere HA-Konfiguration für Cluster {0}. @@ -224,7 +223,6 @@ GetAbrVSphereClusterHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterProactiveHA GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' - InfoLevel = Cluster Proaktive HA-Informationsebene auf {0} gesetzt. Collecting = Proaktive HA-Informationen des Clusters werden gesammelt. SectionHeading = Proaktive HA ParagraphSummary = Der folgende Abschnitt beschreibt die Konfiguration der proaktiven HA für Cluster {0}. @@ -246,7 +244,6 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterDRS GetAbrVSphereClusterDRS = ConvertFrom-StringData @' - InfoLevel = Cluster-DRS-Informationsebene auf {0} gesetzt. Collecting = Cluster-DRS-Informationen werden gesammelt. SectionHeading = vSphere DRS-Konfiguration ParagraphSummary = Die folgende Tabelle beschreibt die vSphere DRS-Konfiguration für Cluster {0}. @@ -442,7 +439,6 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' # Get-AbrVSphereVMHostHardware GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' - InfoLevel = VMHost-Hardware-Informationsebene auf {0} gesetzt. Collecting = VMHost-Hardware-Informationen werden gesammelt. SectionHeading = Hardware ParagraphSummary = Der folgende Abschnitt beschreibt die Hardware-Konfiguration des Hosts {0}. @@ -547,7 +543,6 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSystem GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' - InfoLevel = VMHost-System-Informationsebene auf {0} gesetzt. Collecting = VMHost-System-Informationen werden gesammelt. HostProfile = Host-Profil ProfileName = Host-Profil @@ -621,7 +616,6 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' # Get-AbrVSphereVMHostStorage GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' - InfoLevel = VMHost-Speicher-Informationsebene auf {0} gesetzt. Collecting = VMHost-Speicher-Informationen werden gesammelt. SectionHeading = Speicher DatastoreSpecs = Datenspeicher-Spezifikationen @@ -679,7 +673,6 @@ GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' # Get-AbrVSphereVMHostNetwork GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' - InfoLevel = VMHost-Netzwerk-Informationsebene auf {0} gesetzt. Collecting = VMHost-Netzwerk-Informationen werden gesammelt. SectionHeading = Netzwerk NetworkConfig = Netzwerkkonfiguration @@ -837,7 +830,6 @@ GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSecurity GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' - InfoLevel = VMHost-Sicherheits-Informationsebene auf {0} gesetzt. Collecting = VMHost-Sicherheits-Informationen werden gesammelt. SectionHeading = Sicherheit LockdownMode = Sperrmodus @@ -1436,4 +1428,4 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' TableVUMPatches = VMware Update Manager Patch-Informationen - {0} '@ -} \ No newline at end of file +} diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 index 6771caa..ab24282 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -164,7 +164,6 @@ GetAbrVSphereCluster = ConvertFrom-StringData @' # Get-AbrVSphereClusterHA GetAbrVSphereClusterHA = ConvertFrom-StringData @' - InfoLevel = Cluster HA InfoLevel set to {0}. Collecting = Collecting Cluster HA information. SectionHeading = vSphere HA Configuration ParagraphSummary = The following section details the vSphere HA configuration for {0} cluster. @@ -224,7 +223,6 @@ GetAbrVSphereClusterHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterProactiveHA GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' - InfoLevel = Cluster Proactive HA InfoLevel set to {0}. Collecting = Collecting Cluster Proactive HA information. SectionHeading = Proactive HA ParagraphSummary = The following section details the Proactive HA configuration for {0} cluster. @@ -246,7 +244,6 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterDRS GetAbrVSphereClusterDRS = ConvertFrom-StringData @' - InfoLevel = Cluster DRS InfoLevel set to {0}. Collecting = Collecting Cluster DRS information. SectionHeading = vSphere DRS Configuration ParagraphSummary = The following table details the vSphere DRS configuration for {0} cluster. @@ -442,7 +439,6 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' # Get-AbrVSphereVMHostHardware GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' - InfoLevel = VMHost Hardware InfoLevel set to {0}. Collecting = Collecting VMHost Hardware information. SectionHeading = Hardware ParagraphSummary = The following section details the host hardware configuration for {0}. @@ -547,7 +543,6 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSystem GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' - InfoLevel = VMHost System InfoLevel set to {0}. Collecting = Collecting VMHost System information. HostProfile = Host Profile ProfileName = Host Profile @@ -621,7 +616,6 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' # Get-AbrVSphereVMHostStorage GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' - InfoLevel = VMHost Storage InfoLevel set to {0}. Collecting = Collecting VMHost Storage information. SectionHeading = Storage DatastoreSpecs = Datastore Specifications @@ -679,7 +673,6 @@ GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' # Get-AbrVSphereVMHostNetwork GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' - InfoLevel = VMHost Network InfoLevel set to {0}. Collecting = Collecting VMHost Network information. SectionHeading = Network NetworkConfig = Network Configuration @@ -837,7 +830,6 @@ GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSecurity GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' - InfoLevel = VMHost Security InfoLevel set to {0}. Collecting = Collecting VMHost Security information. SectionHeading = Security LockdownMode = Lockdown Mode @@ -1436,4 +1428,4 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' TableVUMPatches = VMware Update Manager Patch Information - {0} '@ -} \ No newline at end of file +} diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 index 38ba0a0..fe88dba 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -1,4 +1,4 @@ -# culture = 'en-US' +# culture = 'en-US' @{ # Module-wide strings @@ -164,7 +164,6 @@ GetAbrVSphereCluster = ConvertFrom-StringData @' # Get-AbrVSphereClusterHA GetAbrVSphereClusterHA = ConvertFrom-StringData @' - InfoLevel = Cluster HA InfoLevel set to {0}. Collecting = Collecting Cluster HA information. SectionHeading = vSphere HA Configuration ParagraphSummary = The following section details the vSphere HA configuration for {0} cluster. @@ -224,7 +223,6 @@ GetAbrVSphereClusterHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterProactiveHA GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' - InfoLevel = Cluster Proactive HA InfoLevel set to {0}. Collecting = Collecting Cluster Proactive HA information. SectionHeading = Proactive HA ParagraphSummary = The following section details the Proactive HA configuration for {0} cluster. @@ -246,7 +244,6 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterDRS GetAbrVSphereClusterDRS = ConvertFrom-StringData @' - InfoLevel = Cluster DRS InfoLevel set to {0}. Collecting = Collecting Cluster DRS information. SectionHeading = vSphere DRS Configuration ParagraphSummary = The following table details the vSphere DRS configuration for {0} cluster. @@ -442,7 +439,6 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' # Get-AbrVSphereVMHostHardware GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' - InfoLevel = VMHost Hardware InfoLevel set to {0}. Collecting = Collecting VMHost Hardware information. SectionHeading = Hardware ParagraphSummary = The following section details the host hardware configuration for {0}. @@ -547,7 +543,6 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSystem GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' - InfoLevel = VMHost System InfoLevel set to {0}. Collecting = Collecting VMHost System information. HostProfile = Host Profile ProfileName = Host Profile @@ -621,7 +616,6 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' # Get-AbrVSphereVMHostStorage GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' - InfoLevel = VMHost Storage InfoLevel set to {0}. Collecting = Collecting VMHost Storage information. SectionHeading = Storage DatastoreSpecs = Datastore Specifications @@ -679,7 +673,6 @@ GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' # Get-AbrVSphereVMHostNetwork GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' - InfoLevel = VMHost Network InfoLevel set to {0}. Collecting = Collecting VMHost Network information. SectionHeading = Network NetworkConfig = Network Configuration @@ -837,7 +830,6 @@ GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSecurity GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' - InfoLevel = VMHost Security InfoLevel set to {0}. Collecting = Collecting VMHost Security information. SectionHeading = Security LockdownMode = Lockdown Mode diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 index f6e290a..58a126c 100644 --- a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -164,7 +164,6 @@ GetAbrVSphereCluster = ConvertFrom-StringData @' # Get-AbrVSphereClusterHA GetAbrVSphereClusterHA = ConvertFrom-StringData @' - InfoLevel = Nivel de información de HA del clúster establecido en {0}. Collecting = Recopilando información de HA del clúster. SectionHeading = Configuración de vSphere HA ParagraphSummary = La siguiente sección detalla la configuración de vSphere HA para el clúster {0}. @@ -224,7 +223,6 @@ GetAbrVSphereClusterHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterProactiveHA GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' - InfoLevel = Nivel de información de HA proactivo del clúster establecido en {0}. Collecting = Recopilando información de HA proactivo del clúster. SectionHeading = HA proactivo ParagraphSummary = La siguiente sección detalla la configuración de HA proactivo para el clúster {0}. @@ -246,7 +244,6 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterDRS GetAbrVSphereClusterDRS = ConvertFrom-StringData @' - InfoLevel = Nivel de información de DRS del clúster establecido en {0}. Collecting = Recopilando información de DRS del clúster. SectionHeading = Configuración de vSphere DRS ParagraphSummary = La siguiente tabla detalla la configuración de vSphere DRS para el clúster {0}. @@ -442,7 +439,6 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' # Get-AbrVSphereVMHostHardware GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' - InfoLevel = Nivel de información de hardware del host VMware establecido en {0}. Collecting = Recopilando información de hardware del host VMware. SectionHeading = Hardware ParagraphSummary = La siguiente sección detalla la configuración de hardware del host {0}. @@ -547,7 +543,6 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSystem GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' - InfoLevel = Nivel de información del sistema del host VMware establecido en {0}. Collecting = Recopilando información del sistema del host VMware. HostProfile = Perfil de host ProfileName = Perfil de host @@ -621,7 +616,6 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' # Get-AbrVSphereVMHostStorage GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' - InfoLevel = Nivel de información de almacenamiento del host VMware establecido en {0}. Collecting = Recopilando información de almacenamiento del host VMware. SectionHeading = Almacenamiento DatastoreSpecs = Especificaciones del almacén de datos @@ -679,7 +673,6 @@ GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' # Get-AbrVSphereVMHostNetwork GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' - InfoLevel = Nivel de información de red del host VMware establecido en {0}. Collecting = Recopilando información de red del host VMware. SectionHeading = Red NetworkConfig = Configuración de red @@ -837,7 +830,6 @@ GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSecurity GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' - InfoLevel = Nivel de información de seguridad del host VMware establecido en {0}. Collecting = Recopilando información de seguridad del host VMware. SectionHeading = Seguridad LockdownMode = Modo de bloqueo @@ -1436,4 +1428,4 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' TableVUMPatches = Información de parches de VMware Update Manager - {0} '@ -} \ No newline at end of file +} diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 index 532cf8c..7b9b66b 100644 --- a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -164,7 +164,6 @@ GetAbrVSphereCluster = ConvertFrom-StringData @' # Get-AbrVSphereClusterHA GetAbrVSphereClusterHA = ConvertFrom-StringData @' - InfoLevel = Niveau d'information Cluster HA défini sur {0}. Collecting = Collecte des informations sur le Cluster HA. SectionHeading = Configuration de vSphere HA ParagraphSummary = La section suivante détaille la configuration de vSphere HA pour le cluster {0}. @@ -224,7 +223,6 @@ GetAbrVSphereClusterHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterProactiveHA GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' - InfoLevel = Niveau d'information Cluster HA proactif défini sur {0}. Collecting = Collecte des informations sur le Cluster HA proactif. SectionHeading = HA proactif ParagraphSummary = La section suivante détaille la configuration du HA proactif pour le cluster {0}. @@ -246,7 +244,6 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' # Get-AbrVSphereClusterDRS GetAbrVSphereClusterDRS = ConvertFrom-StringData @' - InfoLevel = Niveau d'information Cluster DRS défini sur {0}. Collecting = Collecte des informations sur le Cluster DRS. SectionHeading = Configuration de vSphere DRS ParagraphSummary = Le tableau suivant détaille la configuration de vSphere DRS pour le cluster {0}. @@ -442,7 +439,6 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' # Get-AbrVSphereVMHostHardware GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' - InfoLevel = Niveau d'information Matériel VMHost défini sur {0}. Collecting = Collecte des informations matérielles de l'hôte VMHost. SectionHeading = Matériel ParagraphSummary = La section suivante détaille la configuration matérielle de l'hôte {0}. @@ -547,7 +543,6 @@ GetAbrVSphereVMHostHardware = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSystem GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' - InfoLevel = Niveau d'information Système VMHost défini sur {0}. Collecting = Collecte des informations système de l'hôte VMHost. HostProfile = Profil d'hôte ProfileName = Profil d'hôte @@ -621,7 +616,6 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' # Get-AbrVSphereVMHostStorage GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' - InfoLevel = Niveau d'information Stockage VMHost défini sur {0}. Collecting = Collecte des informations de stockage de l'hôte VMHost. SectionHeading = Stockage DatastoreSpecs = Spécifications des magasins de données @@ -679,7 +673,6 @@ GetAbrVSphereVMHostStorage = ConvertFrom-StringData @' # Get-AbrVSphereVMHostNetwork GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' - InfoLevel = Niveau d'information Réseau VMHost défini sur {0}. Collecting = Collecte des informations réseau de l'hôte VMHost. SectionHeading = Réseau NetworkConfig = Configuration réseau @@ -837,7 +830,6 @@ GetAbrVSphereVMHostNetwork = ConvertFrom-StringData @' # Get-AbrVSphereVMHostSecurity GetAbrVSphereVMHostSecurity = ConvertFrom-StringData @' - InfoLevel = Niveau d'information Sécurité VMHost défini sur {0}. Collecting = Collecte des informations de sécurité de l'hôte VMHost. SectionHeading = Sécurité LockdownMode = Mode de verrouillage @@ -1436,4 +1428,4 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' TableVUMPatches = Informations sur les correctifs VMware Update Manager - {0} '@ -} \ No newline at end of file +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 index 2ba1890..692ad38 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 @@ -23,7 +23,6 @@ function Get-AbrVSphereClusterDRS { param () begin { $LocalizedData = $reportTranslate.GetAbrVSphereClusterDRS - Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Cluster) } process { Try { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterHA.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterHA.ps1 index 9bffc37..69eddcc 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterHA.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterHA.ps1 @@ -21,7 +21,6 @@ function Get-AbrVSphereClusterHA { param () begin { $LocalizedData = $reportTranslate.GetAbrVSphereClusterHA - Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Cluster) } process { try { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 index 7bb8549..7f25e78 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 @@ -21,7 +21,6 @@ function Get-AbrVSphereClusterProactiveHA { param () begin { $LocalizedData = $reportTranslate.GetAbrVSphereClusterProactiveHA - Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.Cluster) } process { try { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 index 4e4117c..9e46b7e 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 @@ -13,7 +13,6 @@ function Get-AbrVSphereVMHostHardware { begin { $LocalizedData = $reportTranslate.GetAbrVSphereVMHostHardware - Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) } process { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 index f73155e..c59eb5a 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 @@ -13,7 +13,6 @@ function Get-AbrVSphereVMHostNetwork { begin { $LocalizedData = $reportTranslate.GetAbrVSphereVMHostNetwork - Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) } process { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 index 0eb9a23..42531e2 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSecurity.ps1 @@ -13,7 +13,6 @@ function Get-AbrVSphereVMHostSecurity { begin { $LocalizedData = $reportTranslate.GetAbrVSphereVMHostSecurity - Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) } process { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostStorage.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostStorage.ps1 index 8fc5d50..6b31e4a 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostStorage.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostStorage.ps1 @@ -13,7 +13,6 @@ function Get-AbrVSphereVMHostStorage { begin { $LocalizedData = $reportTranslate.GetAbrVSphereVMHostStorage - Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) } process { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 index c29db7f..125d009 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 @@ -13,7 +13,6 @@ function Get-AbrVSphereVMHostSystem { begin { $LocalizedData = $reportTranslate.GetAbrVSphereVMHostSystem - Write-PScriboMessage -Message ($LocalizedData.InfoLevel -f $InfoLevel.VMHost) } process { From 94cac1f80550d97db681d90041a7b6f5fe219fae Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 20:05:52 +1100 Subject: [PATCH 13/24] Fix PCI Drivers & Firmware section not reporting on vSphere 8 On ESXi 8.x, VMkernelName is no longer populated in the output of esxcli hardware.pci.list, causing the Where-Object filter in Get-PciDeviceDetail to return no devices and the section to be silently suppressed. Fix by adding an optional -VMHost parameter and building a PCI address to VMkernel name lookup map via the PowerCLI API (same pattern already used by the PCI Devices and I/O Device Identifiers sections). The VMkernelName field is used directly when present (ESXi < 8), with the address map as fallback (ESXi 8+). Also fix a secondary bug where $firmwareVersion and $driverVib were not reset between loop iterations, causing values from a previous device to bleed into the next device's output. Co-Authored-By: Claude Sonnet 4.6 --- .../Private/Get-AbrVSphereVMHostHardware.ps1 | 2 +- .../Src/Private/Get-PciDeviceDetail.ps1 | 92 +++++++++++++------ CHANGELOG.md | 1 + 3 files changed, 67 insertions(+), 28 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 index 9e46b7e..07a6f4e 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 @@ -261,7 +261,7 @@ function Get-AbrVSphereVMHostHardware { #endregion ESXi Host PCI Devices #region ESXi Host PCI Devices Drivers & Firmware - $VMHostPciDevicesDetails = Get-PciDeviceDetail -Server $vCenter -esxcli $esxcli | Sort-Object 'Device' + $VMHostPciDevicesDetails = Get-PciDeviceDetail -Server $vCenter -esxcli $esxcli -VMHost $VMHost | Sort-Object 'Device' if ($VMHostPciDevicesDetails) { try { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.PCIDriversFirmware { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-PciDeviceDetail.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-PciDeviceDetail.ps1 index 7c99425..728d1b1 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-PciDeviceDetail.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-PciDeviceDetail.ps1 @@ -6,12 +6,15 @@ Function Get-PciDeviceDetail { vCenter VISession object. .PARAMETER esxcli Esxcli session object associated to the host. + .PARAMETER VMHost + VMHost object. Required for ESXi 8.x compatibility where VMkernelName is no longer + populated in esxcli hardware.pci.list; used to build a PCI address to VMkernel name map. .EXAMPLE $Credentials = Get-Credential $Server = Connect-VIServer -Server vcenter01.example.com -Credentials $Credentials $VMHost = Get-VMHost -Server $Server -Name esx01.example.com $esxcli = Get-EsxCli -Server $Server -VMHost $VMHost -V2 - Get-PciDeviceDetail -Server $vCenter -esxcli $esxcli + Get-PciDeviceDetail -Server $vCenter -esxcli $esxcli -VMHost $VMHost Device : vmhba0 Model : Sunrise Point-LP AHCI Controller Driver : vmw_ahci @@ -27,55 +30,90 @@ Function Get-PciDeviceDetail { [Parameter(Mandatory = $true)] $Server, [Parameter(Mandatory = $true)] - $esxcli + $esxcli, + [Parameter(Mandatory = $false)] + $VMHost ) Begin { } Process { - # Set default results - $firmwareVersion = "N/A" - $vibName = "N/A" - $driverVib = @{ - Name = "N/A" - Version = "N/A" + # Build PCI address -> VMkernel name mapping for ESXi 8 compatibility. + # VMkernelName is no longer populated in esxcli hardware.pci.list on ESXi 8.x, + # so we derive it from the PowerCLI API using the PCI address as the join key. + $addrToVmkernel = @{} + if ($null -ne $VMHost) { + Get-VMHostNetworkAdapter -VMHost $VMHost -Physical | ForEach-Object { + if ($_.PciId) { $addrToVmkernel[$_.PciId] = $_.DeviceName } + } + Get-VMHostHba -VMHost $VMHost | ForEach-Object { + if ($_.Pci) { $addrToVmkernel[$_.Pci] = $_.Device } + } + (Get-VMHost $VMHost | Get-View -Property Config).Config.GraphicsInfo | ForEach-Object { + if ($_.pciId) { $addrToVmkernel[$_.pciId] = $_.deviceName } + } + } + + $pciDevices = $esxcli.hardware.pci.list.Invoke() | Where-Object { + $vmkName = if ($_.VMkernelName -match 'vmhba|vmnic|vmgfx') { + $_.VMkernelName + } else { + $addrToVmkernel[$_.Address] + } + $vmkName -match 'vmhba|vmnic|vmgfx' -and $_.ModuleName -ne 'None' } - $pciDevices = $esxcli.hardware.pci.list.Invoke() | Where-Object { $_.VMkernelName -match 'vmhba|vmnic|vmgfx' -and $_.ModuleName -ne 'None'} | Sort-Object -Property VMkernelName $nicList = $esxcli.network.nic.list.Invoke() | Sort-Object Name foreach ($pciDevice in $pciDevices) { + # Resolve VMkernel name: populated directly on ESXi < 8, address lookup required on ESXi 8+ + $vmkernelName = if ($pciDevice.VMkernelName -match 'vmhba|vmnic|vmgfx') { + $pciDevice.VMkernelName + } else { + $addrToVmkernel[$pciDevice.Address] + } + + # Reset per-device defaults on every iteration + $firmwareVersion = 'N/A' + $driverVib = @{ Name = 'N/A'; Version = 'N/A' } + $driverVersion = $esxcli.system.module.get.Invoke(@{module = $pciDevice.ModuleName }) | Select-Object -ExpandProperty Version # Get NIC Firmware version - if (($pciDevice.VMkernelName -like 'vmnic*') -and ($nicList.Name -contains $pciDevice.VMkernelName) ) { - $vmnicDetail = $esxcli.network.nic.get.Invoke(@{nicname = $pciDevice.VMkernelName }) + if (($vmkernelName -like 'vmnic*') -and ($nicList.Name -contains $vmkernelName)) { + $vmnicDetail = $esxcli.network.nic.get.Invoke(@{nicname = $vmkernelName }) $firmwareVersion = $vmnicDetail.DriverInfo.FirmwareVersion # Get NIC driver VIB package version - $driverVib = $esxcli.software.vib.list.Invoke() | Select-Object -Property Name, Version | Where-Object { $_.Name -eq $vmnicDetail.DriverInfo.Driver -or $_.Name -eq "net-" + $vmnicDetail.DriverInfo.Driver -or $_.Name -eq "net55-" + $vmnicDetail.DriverInfo.Driver } + $driverVib = $esxcli.software.vib.list.Invoke() | Select-Object -Property Name, Version | Where-Object { + $_.Name -eq $vmnicDetail.DriverInfo.Driver -or + $_.Name -eq "net-" + $vmnicDetail.DriverInfo.Driver -or + $_.Name -eq "net55-" + $vmnicDetail.DriverInfo.Driver + } <# If HP Smart Array vmhba* (scsi-hpsa driver) then get Firmware version - else skip if VMkernnel is vmhba*. Can't get HBA Firmware from + else skip if VMkernnel is vmhba*. Can't get HBA Firmware from Powercli at the moment only through SSH or using Putty Plink+PowerCli. #> - } elseif ($pciDevice.VMkernelName -like 'vmhba*') { - if ($pciDevice.DeviceName -match "smart array") { - $hpsa = $vmhost.ExtensionData.Runtime.HealthSystemRuntime.SystemHealthInfo.NumericSensorInfo | Where-Object { $_.Name -match "HP Smart Array" } + } elseif ($vmkernelName -like 'vmhba*') { + if ($pciDevice.DeviceName -match 'smart array') { + $hpsa = $VMHost.ExtensionData.Runtime.HealthSystemRuntime.SystemHealthInfo.NumericSensorInfo | Where-Object { $_.Name -match 'HP Smart Array' } if ($hpsa) { - $firmwareVersion = (($hpsa.Name -split "firmware")[1]).Trim() + $firmwareVersion = (($hpsa.Name -split 'firmware')[1]).Trim() } } # Get HBA driver VIB package version - $vibName = $pciDevice.ModuleName -replace "_", "-" - $driverVib = $esxcli.software.vib.list.Invoke() | Select-Object -Property Name, Version | Where-Object { $_.Name -eq "scsi-" + $VibName -or $_.Name -eq "sata-" + $VibName -or $_.Name -eq $VibName } + $vibName = $pciDevice.ModuleName -replace '_', '-' + $driverVib = $esxcli.software.vib.list.Invoke() | Select-Object -Property Name, Version | Where-Object { + $_.Name -eq "scsi-$vibName" -or $_.Name -eq "sata-$vibName" -or $_.Name -eq $vibName + } } # Output collected data [PSCustomObject]@{ - 'Device' = $pciDevice.VMkernelName - 'Model' = $pciDevice.DeviceName - 'Driver' = $pciDevice.ModuleName - 'Driver Version' = $driverVersion + 'Device' = $vmkernelName + 'Model' = $pciDevice.DeviceName + 'Driver' = $pciDevice.ModuleName + 'Driver Version' = $driverVersion 'Firmware Version' = $firmwareVersion - 'VIB Name' = $driverVib.Name - 'VIB Version' = $driverVib.Version + 'VIB Name' = $driverVib.Name + 'VIB Version' = $driverVib.Version } - } + } } End { } -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index abed5d6..049c8e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fix PCI Drivers & Firmware section not reporting on vSphere 8; `VMkernelName` is no longer populated in `esxcli hardware.pci.list` on ESXi 8.x so a PCI address to VMkernel name map is now built via the PowerCLI API as fallback. Also fixes per-device defaults not being reset between loop iterations - Fix vCenter Server Certificate section reporting VMCA template defaults instead of the actual deployed TLS certificate; now reads the live certificate directly from port 443 ([#88](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/88)) - Fix null disk group crash in OSA vSAN clusters where disk groups have not yet been claimed ([#113](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/113)) - Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130)) From bb6213a85b4be3553183d681fe903c0097a3b618 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 20:08:57 +1100 Subject: [PATCH 14/24] Update CHANGELOG to reference issue #127 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 049c8e4..399cc85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fix PCI Drivers & Firmware section not reporting on vSphere 8; `VMkernelName` is no longer populated in `esxcli hardware.pci.list` on ESXi 8.x so a PCI address to VMkernel name map is now built via the PowerCLI API as fallback. Also fixes per-device defaults not being reset between loop iterations +- Fix PCI Drivers & Firmware section not reporting on vSphere 8; `VMkernelName` is no longer populated in `esxcli hardware.pci.list` on ESXi 8.x so a PCI address to VMkernel name map is now built via the PowerCLI API as fallback. Also fixes per-device defaults not being reset between loop iterations ([#127](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/127)) - Fix vCenter Server Certificate section reporting VMCA template defaults instead of the actual deployed TLS certificate; now reads the live certificate directly from port 443 ([#88](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/88)) - Fix null disk group crash in OSA vSAN clusters where disk groups have not yet been claimed ([#113](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/113)) - Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130)) From 699ac883709d0f8be9029015609f84e3b3b3d83a Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Thu, 5 Mar 2026 20:18:29 +1100 Subject: [PATCH 15/24] Update CHANGELOG to reference issue #111 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 399cc85..85f03f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fix PCI Drivers & Firmware section not reporting on vSphere 8; `VMkernelName` is no longer populated in `esxcli hardware.pci.list` on ESXi 8.x so a PCI address to VMkernel name map is now built via the PowerCLI API as fallback. Also fixes per-device defaults not being reset between loop iterations ([#127](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/127)) +- Fix PCI Drivers & Firmware section not reporting on vSphere 8; `VMkernelName` is no longer populated in `esxcli hardware.pci.list` on ESXi 8.x so a PCI address to VMkernel name map is now built via the PowerCLI API as fallback. Also fixes per-device defaults not being reset between loop iterations ([#111](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/111), [#127](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/127)) - Fix vCenter Server Certificate section reporting VMCA template defaults instead of the actual deployed TLS certificate; now reads the live certificate directly from port 443 ([#88](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/88)) - Fix null disk group crash in OSA vSAN clusters where disk groups have not yet been claimed ([#113](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/113)) - Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130)) From 59ba5fb22c89e2ef7af78364897d5f9464626e91 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Fri, 13 Mar 2026 12:42:30 +1100 Subject: [PATCH 16/24] Add Tags, Host Certificate, Syslog, vSAN Services, VDS LACP/NetFlow/NIOC, VM Swap Location, and DSCluster SDRS improvements - Add Tags reporting to VMHost, Datastore, DSCluster, ResourcePool, Network (VDS), and VM sections - Add ESXi Host Certificate section (subject, issuer, validity dates, SHA-256 thumbprint) - Add VM Swap File Location section using Get-View with camelCase property paths - Expand Syslog section to include log directory, rotation count, and log size via esxcli - Add vSAN Services section (Performance, File, iSCSI, Deduplication, Encryption, Health) for both OSA and ESA code paths - Add VDS LACP section using ExtensionData instead of unavailable Get-VDLacpPolicy cmdlet - Add VDS NetFlow section and NIOC Resource Pools section - Fix NIOC pool limit integer comparison (was string '-1', now integer -1) - Add Cluster LCM and Cluster VUM as separate sub-section functions - Fix DSCluster SDRS rules, space/IO load balance config reporting - Update all 5 locale files (en-US, en-GB, es-ES, fr-FR, de-DE) with new keys Co-Authored-By: Claude Sonnet 4.6 --- .../AsBuiltReport.VMware.vSphere.json | Bin 5284 -> 5424 bytes .../Language/de-DE/VMwarevSphere.psd1 | 166 +++++++++++++--- .../Language/en-GB/VMwarevSphere.psd1 | 166 +++++++++++++--- .../Language/en-US/VMwarevSphere.psd1 | 166 +++++++++++++--- .../Language/es-ES/VMwarevSphere.psd1 | 166 +++++++++++++--- .../Language/fr-FR/VMwarevSphere.psd1 | 166 +++++++++++++--- .../Src/Private/Get-AbrVSphereCluster.ps1 | 4 +- .../Src/Private/Get-AbrVSphereClusterDRS.ps1 | 176 +++++------------ .../Src/Private/Get-AbrVSphereClusterLCM.ps1 | 183 ++++++++++++++++++ .../Get-AbrVSphereClusterProactiveHA.ps1 | 33 +++- .../Src/Private/Get-AbrVSphereClusterVUM.ps1 | 120 ++++++++++++ .../Src/Private/Get-AbrVSphereDSCluster.ps1 | 86 +++++++- .../Src/Private/Get-AbrVSphereDatastore.ps1 | 10 +- .../Src/Private/Get-AbrVSphereNetwork.ps1 | 87 ++++++++- .../Private/Get-AbrVSphereResourcePool.ps1 | 8 +- .../Src/Private/Get-AbrVSphereVM.ps1 | 60 +++++- .../Src/Private/Get-AbrVSphereVMHost.ps1 | 5 +- .../Private/Get-AbrVSphereVMHostHardware.ps1 | 2 +- .../Private/Get-AbrVSphereVMHostNetwork.ps1 | 10 +- .../Private/Get-AbrVSphereVMHostSystem.ps1 | 95 +++++++-- .../Src/Private/Get-AbrVSphereVUM.ps1 | 114 +++++++++-- .../Src/Private/Get-AbrVSpherevCenter.ps1 | 4 +- .../Src/Private/Get-AbrVSpherevSAN.ps1 | 57 +++++- .../Invoke-AsBuiltReport.VMware.vSphere.ps1 | 17 ++ CHANGELOG.md | 25 ++- CLAUDE.md | 46 ++++- README.md | 5 + 27 files changed, 1641 insertions(+), 336 deletions(-) create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterLCM.ps1 create mode 100644 AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterVUM.ps1 diff --git a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json index 7b87742fcf1c43d1bbda83154672e4029be472b5..ff01ba847d1415c31c803f953e128a884f2b9b8d 100644 GIT binary patch delta 50 zcmZ3Yxj}2g3+~AW*c2F@Cwua%PIeH`nykPoG+9hgVRC|y$L1cMPwYTxxyc8(Z6;3= G5dr|Sv=Fuc delta 20 ccmdm>wM28n3+~N&JkvNP>+y(8mJ<~M08_LEj{pDw diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 index fa508f0..1121cf3 100644 --- a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -123,6 +123,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarm = {0} - {1} TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} + RestApiSessionError = Unable to establish vCenter REST API session. {0} '@ # Get-AbrVSphereCluster @@ -239,7 +240,10 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' MaintenanceMode = Wartungsmodus QuarantineMode = Quarantänemodus MixedMode = Gemischter Modus - TableProactiveHA = Proactive HA - {0} + TableProactiveHA = Proactive HA - {0} + Providers = Anbieter + HealthUpdateCount = Integritätsaktualisierungen + TableProactiveHAProviders = Proaktive HA-Anbieter - {0} '@ # Get-AbrVSphereClusterDRS @@ -286,22 +290,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' VMMonitoringMinUpTime = Mindestbetriebszeit VMMonitoringMaxFailures = Maximale Fehleranzahl VMMonitoringMaxFailureWindow = Maximales Fehlerzeitfenster - UpdateManagerBaselines = Update Manager-Baselines - Baseline = Baseline - Description = Beschreibung - Type = Typ - TargetType = Zieltyp - LastUpdate = Letzte Aktualisierungszeit - NumPatches = Anzahl Patches - UpdateManagerCompliance = Update Manager-Compliance - Entity = Entität - Status = Compliance-Status - Version = Version - BaselineInfo = Baseline - VUMPrivilegeMsgBaselines = Unzureichende Benutzerberechtigungen für den Bericht über Cluster-Baselines. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'VMware Update Manager / VMware vSphere Lifecycle Manager > Patches und Upgrades verwalten > Compliance-Status anzeigen' zugewiesen ist. - VUMPrivilegeMsgCompliance = Unzureichende Benutzerberechtigungen für den Bericht über Cluster-Compliance. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'VMware Update Manager / VMware vSphere Lifecycle Manager > Patches und Upgrades verwalten > Compliance-Status anzeigen' zugewiesen ist. - VUMBaselineNotAvailable = Cluster-VUM-Baseline-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. - VUMComplianceNotAvailable = Cluster-VUM-Compliance-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. Enabled = Aktiviert Disabled = Deaktiviert Yes = Ja @@ -310,9 +298,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' PartiallyAutomated = Teilweise automatisiert Manual = Manuell Off = Aus - NotCompliant = Nicht konform - Unknown = Unbekannt - Incompatible = Inkompatibel DRS = vSphere DRS DPM = DPM Automated = Automated @@ -371,11 +356,33 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' TableHAVMOverrides = HA VM Overrides - {0} TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} - TableVUMBaselines = Update Manager Baselines - {0} - TableVUMCompliance = Update Manager Compliance - {0} TablePermissions = Permissions - {0} '@ +# Get-AbrVSphereClusterVUM +GetAbrVSphereClusterVUM = ConvertFrom-StringData @' + UpdateManagerBaselines = Update Manager-Baselines + Baseline = Baseline + Description = Beschreibung + Type = Typ + TargetType = Zieltyp + LastUpdate = Letzte Aktualisierungszeit + NumPatches = Anzahl Patches + UpdateManagerCompliance = Update Manager-Compliance + Entity = Entität + Status = Compliance-Status + BaselineInfo = Baseline + VUMPrivilegeMsgBaselines = Unzureichende Benutzerberechtigungen für den Bericht über Cluster-Baselines. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'VMware Update Manager / VMware vSphere Lifecycle Manager > Patches und Upgrades verwalten > Compliance-Status anzeigen' zugewiesen ist. + VUMPrivilegeMsgCompliance = Unzureichende Benutzerberechtigungen für den Bericht über Cluster-Compliance. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'VMware Update Manager / VMware vSphere Lifecycle Manager > Patches und Upgrades verwalten > Compliance-Status anzeigen' zugewiesen ist. + VUMBaselineNotAvailable = Cluster-VUM-Baseline-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. + VUMComplianceNotAvailable = Cluster-VUM-Compliance-Informationen sind mit Ihrer PowerShell-Version derzeit nicht verfügbar. + NotCompliant = Nicht konform + Unknown = Unbekannt + Incompatible = Inkompatibel + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} +'@ + # Get-AbrVSphereResourcePool GetAbrVSphereResourcePool = ConvertFrom-StringData @' InfoLevel = Ressourcenpool-Informationsebene auf {0} gesetzt. @@ -404,6 +411,7 @@ GetAbrVSphereResourcePool = ConvertFrom-StringData @' Unlimited = Unbegrenzt TableResourcePoolSummary = Resource Pool Summary - {0} TableResourcePoolConfig = Resource Pool Configuration - {0} + Tags = Schlagwörter '@ # Get-AbrVSphereVMHost @@ -435,6 +443,7 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' Build = Build Parent = Parent TableHostSummary = Host Summary - {0} + Tags = Schlagwörter '@ # Get-AbrVSphereVMHostHardware @@ -612,6 +621,24 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + SwapFileLocation = VM-Auslagerungsdateispeicherort + SwapFilePlacement = VM-Auslagerungsdateiplatzierung + SwapDatastore = Auslagerungs-Datenspeicher + WithVM = Mit VM (Standard) + HostLocal = Lokal auf dem Host + TableSwapFileLocation = VM-Auslagerungsdateispeicherort - {0} + SwapFileLocationError = Fehler beim Erfassen des VM-Auslagerungsdateispeicherorts für {0}. {1} + HostCertificate = Host-Zertifikat + CertSubject = Betreff + CertIssuer = Aussteller + CertValidFrom = Gültig ab + CertValidTo = Gültig bis + CertThumbprint = SHA-256-Fingerabdruck + TableHostCertificate = Host-Zertifikat - {0} + HostCertificateError = Fehler beim Erfassen der Host-Zertifikatsinformationen für {0}. {1} + LogDir = Protokollverzeichnis + LogRotations = Protokollrotationen + LogSize = Protokollgröße (KB) '@ # Get-AbrVSphereVMHostStorage @@ -1010,6 +1037,28 @@ GetAbrVSphereNetwork = ConvertFrom-StringData @' TableVDSPortGroupTrafficShaping = Datenverkehrsformung der Portgruppe des verteilten Switches - {0} TableVDSPortGroupTeaming = Teaming und Failover der Portgruppe des verteilten Switches - {0} TableVDSPrivateVLANs = Private VLANs des verteilten Switches - {0} + VDSLACP = LACP des verteilten Switches + LACPEnabled = LACP aktiviert + LACPMode = LACP-Modus + LACPActive = Aktiv + LACPPassive = Passiv + VDSNetFlow = NetFlow des verteilten Switches + CollectorIP = Collector-IP-Adresse + CollectorPort = Collector-Port + ActiveFlowTimeout = Aktiver Flow-Timeout (s) + IdleFlowTimeout = Inaktiver Flow-Timeout (s) + SamplingRate = Abtastrate + InternalFlowsOnly = Nur interne Flows + NIOCResourcePools = Netzwerk-E/A-Steuerungs-Ressourcenpools + NIOCResourcePool = Ressourcenpool + NIOCSharesLevel = Freigaben-Ebene + NIOCSharesValue = Freigaben + NIOCLimitMbps = Limit (Mbps) + Unlimited = Unbegrenzt + TableVDSLACP = LACP des verteilten Switches - {0} + TableVDSNetFlow = NetFlow des verteilten Switches - {0} + TableNIOCResourcePools = Netzwerk-E/A-Steuerungs-Ressourcenpools - {0} + Tags = Schlagwörter '@ # Get-AbrVSpherevSAN @@ -1137,6 +1186,10 @@ GetAbrVSpherevSAN = ConvertFrom-StringData @' DiskGroupError = Fehler beim Sammeln von vSAN-Festplattengruppeninformationen für '{0}'. {1} iSCSITargetError = Fehler beim Sammeln von vSAN-iSCSI-Zielinformationen für '{0}'. {1} iSCSILUNError = Fehler beim Sammeln von vSAN-iSCSI-LUN-Informationen für '{0}'. {1} + ServicesSection = vSAN-Dienste + Service = Dienst + TableVSANServices = vSAN-Dienste - {0} + ServicesError = Fehler beim Sammeln von vSAN-Dienstinformationen für '{0}'. {1} '@ # Get-AbrVSphereDatastore @@ -1189,6 +1242,7 @@ GetAbrVSphereDatastore = ConvertFrom-StringData @' TableDatastoreSummary = Datenspeicher-Übersicht - {0} TableDatastoreConfig = Datenspeicher-Konfiguration - {0} TableSCSILUN = SCSI-LUN-Informationen - {0} + Tags = Schlagwörter '@ # Get-AbrVSphereDSCluster @@ -1230,6 +1284,26 @@ GetAbrVSphereDSCluster = ConvertFrom-StringData @' VirtualMachine = Virtuelle Maschine KeepVMDKsTogether = VMDKs zusammenhalten DefaultBehavior = Standard ({0}) + RuleType = Typ + RuleAffinity = Affinität + RuleAntiAffinity = Anti-Affinität + TableSDRSRules = SDRS-Regeln - {0} + SpaceLoadBalanceConfig = Konfiguration des Speicher-Lastenausgleichs + SpaceMinDiff = Minimaler Unterschied der Speicherauslastung (%) + SpaceThresholdMode = Speicherschwellenwert-Modus + UtilizationMode = Auslastungsschwellenwert + FreeSpaceMode = Freispeicherschwellenwert + UtilizationThreshold = Speicherauslastungsschwellenwert (%) + FreeSpaceThreshold = Freispeicherschwellenwert (GB) + IOLoadBalanceConfig = Konfiguration des E/A-Lastenausgleichs + IOCongestionThreshold = E/A-Überlastungsschwellenwert (ms) + IOReservationMode = Reservierungsschwellenwert-Modus + IOPSThresholdMode = Anzahl der E/A-Operationen + ReservationMbpsMode = Reservierung in Mbps + ReservationIopsMode = Reservierung in IOPS + TableSpaceLoadBalance = Konfiguration des Speicher-Lastenausgleichs - {0} + TableIOLoadBalance = Konfiguration des E/A-Lastenausgleichs - {0} + Tags = Schlagwörter '@ # Get-AbrVSphereVM @@ -1400,7 +1474,16 @@ GetAbrVSphereVM = ConvertFrom-StringData @' TableVMHardDiskConfig = Festplattenkonfiguration - {0} TableVMHardDisk = {0} - {1} TableVMGuestVolumes = Gast-Volumes - {0} - TableVMSnapshots = VM-Snapshots - {0} + TableVMSnapshots = VM-Snapshots - {0} + VUMCompliance = VM Update Manager-Compliance + VUMBaselineName = Baseline + VUMStatus = Status + NotCompliant = Nicht konform + Incompatible = Inkompatibel + VUMComplianceError = Unable to retrieve VUM compliance information for virtual machines. + InsufficientPrivVUMCompliance = Insufficient privileges to collect VUM compliance information for virtual machines. + TableVUMCompliance = VUM-Baseline-Compliance - {0} + Tags = Schlagwörter '@ # Get-AbrVSphereVUM @@ -1426,6 +1509,43 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' PatchVendorID = Hersteller-ID TableVUMBaselines = VMware Update Manager Baseline-Zusammenfassung - {0} TableVUMPatches = VMware Update Manager Patch-Informationen - {0} + SoftwareDepots = Software-Depots + OnlineDepots = Online-Depots + OfflineDepots = Offline-Depots + DepotUrl = URL + SystemDefined = Systemdefiniert + DepotEnabled = Aktiviert + DepotLocation = Speicherort + DepotError = Software-Depot-Informationen konnten nicht abgerufen werden. {0} + TableOnlineDepots = Online-Software-Depots - {0} + TableOfflineDepots = Offline-Software-Depots - {0} +'@ + +# Get-AbrVSphereClusterLCM +GetAbrVSphereClusterLCM = ConvertFrom-StringData @' + Collecting = Lifecycle Manager-Informationen werden gesammelt. + ImageComposition = Image-Komposition + BaseImage = Basis-Image + VendorAddOn = Anbieter-Add-On + None = Keine + Components = Komponenten + ComponentName = Komponente + ComponentVersion = Version + HardwareSupportManager = Hardware-Support-Manager + HsmName = Name + HsmVersion = Version + HsmPackages = Hardware-Support-Pakete + ImageCompliance = Image-Konformität + Cluster = Cluster + VMHost = VMHost + ComplianceStatus = Konformitätsstatus + LcmError = Lifecycle Manager-Informationen für Cluster {0} konnten nicht abgerufen werden. {1} + ComplianceError = Konformitätsinformationen für Cluster {0} konnten nicht abgerufen werden. {1} + TableImageComposition = Image-Komposition - {0} + TableComponents = Image-Komponenten - {0} + TableHardwareSupportManager = Hardware-Support-Manager - {0} + TableImageCompliance = Image-Konformität - {0} + TableHostCompliance = Host-Image-Konformität - {0} '@ } diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 index ab24282..92957c0 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -123,6 +123,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarm = {0} - {1} TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} + RestApiSessionError = Unable to establish vCenter REST API session. {0} '@ # Get-AbrVSphereCluster @@ -239,7 +240,10 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' MaintenanceMode = Maintenance Mode QuarantineMode = Quarantine Mode MixedMode = Mixed Mode - TableProactiveHA = Proactive HA - {0} + TableProactiveHA = Proactive HA - {0} + Providers = Providers + HealthUpdateCount = Health Updates + TableProactiveHAProviders = Proactive HA Providers - {0} '@ # Get-AbrVSphereClusterDRS @@ -286,22 +290,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' VMMonitoringMinUpTime = Minimum Uptime VMMonitoringMaxFailures = Maximum Failures VMMonitoringMaxFailureWindow = Maximum Failure Window - UpdateManagerBaselines = Update Manager Baselines - Baseline = Baseline - Description = Description - Type = Type - TargetType = Target Type - LastUpdate = Last Update Time - NumPatches = # of Patches - UpdateManagerCompliance = Update Manager Compliance - Entity = Entity - Status = Compliance Status - Version = Version - BaselineInfo = Baseline - VUMPrivilegeMsgBaselines = Insufficient user privileges to report Cluster baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. - VUMPrivilegeMsgCompliance = Insufficient user privileges to report Cluster compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. - VUMBaselineNotAvailable = Cluster VUM baseline information is not currently available with your version of PowerShell. - VUMComplianceNotAvailable = Cluster VUM compliance information is not currently available with your version of PowerShell. Enabled = Enabled Disabled = Disabled Yes = Yes @@ -310,9 +298,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' PartiallyAutomated = Partially Automated Manual = Manual Off = Off - NotCompliant = Not Compliant - Unknown = Unknown - Incompatible = Incompatible DRS = vSphere DRS DPM = DPM Automated = Automated @@ -371,11 +356,33 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' TableHAVMOverrides = HA VM Overrides - {0} TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} - TableVUMBaselines = Update Manager Baselines - {0} - TableVUMCompliance = Update Manager Compliance - {0} TablePermissions = Permissions - {0} '@ +# Get-AbrVSphereClusterVUM +GetAbrVSphereClusterVUM = ConvertFrom-StringData @' + UpdateManagerBaselines = Update Manager Baselines + Baseline = Baseline + Description = Description + Type = Type + TargetType = Target Type + LastUpdate = Last Update Time + NumPatches = # of Patches + UpdateManagerCompliance = Update Manager Compliance + Entity = Entity + Status = Compliance Status + BaselineInfo = Baseline + VUMPrivilegeMsgBaselines = Insufficient user privileges to report Cluster baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + VUMPrivilegeMsgCompliance = Insufficient user privileges to report Cluster compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + VUMBaselineNotAvailable = Cluster VUM baseline information is not currently available with your version of PowerShell. + VUMComplianceNotAvailable = Cluster VUM compliance information is not currently available with your version of PowerShell. + NotCompliant = Not Compliant + Unknown = Unknown + Incompatible = Incompatible + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} +'@ + # Get-AbrVSphereResourcePool GetAbrVSphereResourcePool = ConvertFrom-StringData @' InfoLevel = ResourcePool InfoLevel set to {0}. @@ -404,6 +411,7 @@ GetAbrVSphereResourcePool = ConvertFrom-StringData @' Unlimited = Unlimited TableResourcePoolSummary = Resource Pool Summary - {0} TableResourcePoolConfig = Resource Pool Configuration - {0} + Tags = Tags '@ # Get-AbrVSphereVMHost @@ -435,6 +443,7 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' Build = Build Parent = Parent TableHostSummary = Host Summary - {0} + Tags = Tags '@ # Get-AbrVSphereVMHostHardware @@ -612,6 +621,24 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + SwapFileLocation = VM Swap File Location + SwapFilePlacement = VM Swap File Placement + SwapDatastore = Swap Datastore + WithVM = With VM (Default) + HostLocal = Host Local + TableSwapFileLocation = VM Swap File Location - {0} + SwapFileLocationError = Error collecting VM swap file location for {0}. {1} + HostCertificate = Host Certificate + CertSubject = Subject + CertIssuer = Issuer + CertValidFrom = Valid From + CertValidTo = Valid To + CertThumbprint = SHA-256 Thumbprint + TableHostCertificate = Host Certificate - {0} + HostCertificateError = Error collecting host certificate information for {0}. {1} + LogDir = Log Directory + LogRotations = Log Rotations + LogSize = Log Size (KB) '@ # Get-AbrVSphereVMHostStorage @@ -1010,6 +1037,28 @@ GetAbrVSphereNetwork = ConvertFrom-StringData @' TableVDSPortGroupTrafficShaping = Distributed Switch Port Group Traffic Shaping - {0} TableVDSPortGroupTeaming = Distributed Switch Port Group Teaming & Failover - {0} TableVDSPrivateVLANs = Distributed Switch Private VLANs - {0} + VDSLACP = Distributed Switch LACP + LACPEnabled = LACP Enabled + LACPMode = LACP Mode + LACPActive = Active + LACPPassive = Passive + VDSNetFlow = Distributed Switch NetFlow + CollectorIP = Collector IP Address + CollectorPort = Collector Port + ActiveFlowTimeout = Active Flow Timeout (s) + IdleFlowTimeout = Idle Flow Timeout (s) + SamplingRate = Sampling Rate + InternalFlowsOnly = Internal Flows Only + NIOCResourcePools = Network I/O Control Resource Pools + NIOCResourcePool = Resource Pool + NIOCSharesLevel = Shares Level + NIOCSharesValue = Shares + NIOCLimitMbps = Limit (Mbps) + Unlimited = Unlimited + TableVDSLACP = Distributed Switch LACP - {0} + TableVDSNetFlow = Distributed Switch NetFlow - {0} + TableNIOCResourcePools = Network I/O Control Resource Pools - {0} + Tags = Tags '@ # Get-AbrVSpherevSAN @@ -1137,6 +1186,10 @@ GetAbrVSpherevSAN = ConvertFrom-StringData @' DiskGroupError = Error collecting vSAN disk group information for '{0}'. {1} iSCSITargetError = Error collecting vSAN iSCSI target information for '{0}'. {1} iSCSILUNError = Error collecting vSAN iSCSI LUN information for '{0}'. {1} + ServicesSection = vSAN Services + Service = Service + TableVSANServices = vSAN Services - {0} + ServicesError = Error collecting vSAN services information for '{0}'. {1} '@ # Get-AbrVSphereDatastore @@ -1189,6 +1242,7 @@ GetAbrVSphereDatastore = ConvertFrom-StringData @' TableDatastoreSummary = Datastore Summary - {0} TableDatastoreConfig = Datastore Configuration - {0} TableSCSILUN = SCSI LUN Information - {0} + Tags = Tags '@ # Get-AbrVSphereDSCluster @@ -1230,6 +1284,26 @@ GetAbrVSphereDSCluster = ConvertFrom-StringData @' VirtualMachine = Virtual Machine KeepVMDKsTogether = Keep VMDKs Together DefaultBehavior = Default ({0}) + RuleType = Type + RuleAffinity = Affinity + RuleAntiAffinity = Anti-Affinity + TableSDRSRules = SDRS Rules - {0} + SpaceLoadBalanceConfig = Space Load Balance Configuration + SpaceMinDiff = Min Space Utilisation Difference (%) + SpaceThresholdMode = Space Threshold Mode + UtilizationMode = Utilisation Threshold + FreeSpaceMode = Free Space Threshold + UtilizationThreshold = Space Utilisation Threshold (%) + FreeSpaceThreshold = Free Space Threshold (GB) + IOLoadBalanceConfig = I/O Load Balance Configuration + IOCongestionThreshold = I/O Congestion Threshold (ms) + IOReservationMode = Reservation Threshold Mode + IOPSThresholdMode = I/O Operations Count + ReservationMbpsMode = Reservation in Mbps + ReservationIopsMode = Reservation in IOPS + TableSpaceLoadBalance = Space Load Balance Configuration - {0} + TableIOLoadBalance = I/O Load Balance Configuration - {0} + Tags = Tags '@ # Get-AbrVSphereVM @@ -1400,7 +1474,16 @@ GetAbrVSphereVM = ConvertFrom-StringData @' TableVMHardDiskConfig = Hard Disk Configuration - {0} TableVMHardDisk = {0} - {1} TableVMGuestVolumes = Guest Volumes - {0} - TableVMSnapshots = VM Snapshots - {0} + TableVMSnapshots = VM Snapshots - {0} + VUMCompliance = VM Update Manager Compliance + VUMBaselineName = Baseline + VUMStatus = Status + NotCompliant = Not Compliant + Incompatible = Incompatible + VUMComplianceError = Unable to retrieve VUM compliance information for virtual machines. + InsufficientPrivVUMCompliance = Insufficient privileges to collect VUM compliance information for virtual machines. + TableVUMCompliance = VUM Baseline Compliance - {0} + Tags = Tags '@ # Get-AbrVSphereVUM @@ -1426,6 +1509,43 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' PatchVendorID = Vendor ID TableVUMBaselines = VMware Update Manager Baseline Summary - {0} TableVUMPatches = VMware Update Manager Patch Information - {0} + SoftwareDepots = Software Depots + OnlineDepots = Online Depots + OfflineDepots = Offline Depots + DepotUrl = URL + SystemDefined = System Defined + DepotEnabled = Enabled + DepotLocation = Location + DepotError = Unable to retrieve software depot information. {0} + TableOnlineDepots = Online Software Depots - {0} + TableOfflineDepots = Offline Software Depots - {0} +'@ + +# Get-AbrVSphereClusterLCM +GetAbrVSphereClusterLCM = ConvertFrom-StringData @' + Collecting = Collecting Lifecycle Manager information. + ImageComposition = Image Composition + BaseImage = Base Image + VendorAddOn = Vendor Add-On + None = None + Components = Components + ComponentName = Component + ComponentVersion = Version + HardwareSupportManager = Hardware Support Manager + HsmName = Name + HsmVersion = Version + HsmPackages = Hardware Support Packages + ImageCompliance = Image Compliance + Cluster = Cluster + VMHost = VMHost + ComplianceStatus = Compliance Status + LcmError = Unable to retrieve Lifecycle Manager information for cluster {0}. {1} + ComplianceError = Unable to retrieve compliance information for cluster {0}. {1} + TableImageComposition = Image Composition - {0} + TableComponents = Image Components - {0} + TableHardwareSupportManager = Hardware Support Manager - {0} + TableImageCompliance = Image Compliance - {0} + TableHostCompliance = Host Image Compliance - {0} '@ } diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 index fe88dba..52c4c2c 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -123,6 +123,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarm = {0} - {1} TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} + RestApiSessionError = Unable to establish vCenter REST API session. {0} '@ # Get-AbrVSphereCluster @@ -239,7 +240,10 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' MaintenanceMode = Maintenance Mode QuarantineMode = Quarantine Mode MixedMode = Mixed Mode - TableProactiveHA = Proactive HA - {0} + TableProactiveHA = Proactive HA - {0} + Providers = Providers + HealthUpdateCount = Health Updates + TableProactiveHAProviders = Proactive HA Providers - {0} '@ # Get-AbrVSphereClusterDRS @@ -286,22 +290,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' VMMonitoringMinUpTime = Minimum Uptime VMMonitoringMaxFailures = Maximum Failures VMMonitoringMaxFailureWindow = Maximum Failure Window - UpdateManagerBaselines = Update Manager Baselines - Baseline = Baseline - Description = Description - Type = Type - TargetType = Target Type - LastUpdate = Last Update Time - NumPatches = # of Patches - UpdateManagerCompliance = Update Manager Compliance - Entity = Entity - Status = Compliance Status - Version = Version - BaselineInfo = Baseline - VUMPrivilegeMsgBaselines = Insufficient user privileges to report Cluster baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. - VUMPrivilegeMsgCompliance = Insufficient user privileges to report Cluster compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. - VUMBaselineNotAvailable = Cluster VUM baseline information is not currently available with your version of PowerShell. - VUMComplianceNotAvailable = Cluster VUM compliance information is not currently available with your version of PowerShell. Enabled = Enabled Disabled = Disabled Yes = Yes @@ -310,9 +298,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' PartiallyAutomated = Partially Automated Manual = Manual Off = Off - NotCompliant = Not Compliant - Unknown = Unknown - Incompatible = Incompatible DRS = vSphere DRS DPM = DPM Automated = Automated @@ -371,11 +356,33 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' TableHAVMOverrides = HA VM Overrides - {0} TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} - TableVUMBaselines = Update Manager Baselines - {0} - TableVUMCompliance = Update Manager Compliance - {0} TablePermissions = Permissions - {0} '@ +# Get-AbrVSphereClusterVUM +GetAbrVSphereClusterVUM = ConvertFrom-StringData @' + UpdateManagerBaselines = Update Manager Baselines + Baseline = Baseline + Description = Description + Type = Type + TargetType = Target Type + LastUpdate = Last Update Time + NumPatches = # of Patches + UpdateManagerCompliance = Update Manager Compliance + Entity = Entity + Status = Compliance Status + BaselineInfo = Baseline + VUMPrivilegeMsgBaselines = Insufficient user privileges to report Cluster baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + VUMPrivilegeMsgCompliance = Insufficient user privileges to report Cluster compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + VUMBaselineNotAvailable = Cluster VUM baseline information is not currently available with your version of PowerShell. + VUMComplianceNotAvailable = Cluster VUM compliance information is not currently available with your version of PowerShell. + NotCompliant = Not Compliant + Unknown = Unknown + Incompatible = Incompatible + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} +'@ + # Get-AbrVSphereResourcePool GetAbrVSphereResourcePool = ConvertFrom-StringData @' InfoLevel = ResourcePool InfoLevel set to {0}. @@ -404,6 +411,7 @@ GetAbrVSphereResourcePool = ConvertFrom-StringData @' Unlimited = Unlimited TableResourcePoolSummary = Resource Pool Summary - {0} TableResourcePoolConfig = Resource Pool Configuration - {0} + Tags = Tags '@ # Get-AbrVSphereVMHost @@ -435,6 +443,7 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' Build = Build Parent = Parent TableHostSummary = Host Summary - {0} + Tags = Tags '@ # Get-AbrVSphereVMHostHardware @@ -612,6 +621,24 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + SwapFileLocation = VM Swap File Location + SwapFilePlacement = VM Swap File Placement + SwapDatastore = Swap Datastore + WithVM = With VM (Default) + HostLocal = Host Local + TableSwapFileLocation = VM Swap File Location - {0} + SwapFileLocationError = Error collecting VM swap file location for {0}. {1} + HostCertificate = Host Certificate + CertSubject = Subject + CertIssuer = Issuer + CertValidFrom = Valid From + CertValidTo = Valid To + CertThumbprint = SHA-256 Thumbprint + TableHostCertificate = Host Certificate - {0} + HostCertificateError = Error collecting host certificate information for {0}. {1} + LogDir = Log Directory + LogRotations = Log Rotations + LogSize = Log Size (KB) '@ # Get-AbrVSphereVMHostStorage @@ -1010,6 +1037,28 @@ GetAbrVSphereNetwork = ConvertFrom-StringData @' TableVDSPortGroupTrafficShaping = Distributed Switch Port Group Traffic Shaping - {0} TableVDSPortGroupTeaming = Distributed Switch Port Group Teaming & Failover - {0} TableVDSPrivateVLANs = Distributed Switch Private VLANs - {0} + VDSLACP = Distributed Switch LACP + LACPEnabled = LACP Enabled + LACPMode = LACP Mode + LACPActive = Active + LACPPassive = Passive + VDSNetFlow = Distributed Switch NetFlow + CollectorIP = Collector IP Address + CollectorPort = Collector Port + ActiveFlowTimeout = Active Flow Timeout (s) + IdleFlowTimeout = Idle Flow Timeout (s) + SamplingRate = Sampling Rate + InternalFlowsOnly = Internal Flows Only + NIOCResourcePools = Network I/O Control Resource Pools + NIOCResourcePool = Resource Pool + NIOCSharesLevel = Shares Level + NIOCSharesValue = Shares + NIOCLimitMbps = Limit (Mbps) + Unlimited = Unlimited + TableVDSLACP = Distributed Switch LACP - {0} + TableVDSNetFlow = Distributed Switch NetFlow - {0} + TableNIOCResourcePools = Network I/O Control Resource Pools - {0} + Tags = Tags '@ # Get-AbrVSpherevSAN @@ -1137,6 +1186,10 @@ GetAbrVSpherevSAN = ConvertFrom-StringData @' DiskGroupError = Error collecting vSAN disk group information for '{0}'. {1} iSCSITargetError = Error collecting vSAN iSCSI target information for '{0}'. {1} iSCSILUNError = Error collecting vSAN iSCSI LUN information for '{0}'. {1} + ServicesSection = vSAN Services + Service = Service + TableVSANServices = vSAN Services - {0} + ServicesError = Error collecting vSAN services information for '{0}'. {1} '@ # Get-AbrVSphereDatastore @@ -1189,6 +1242,7 @@ GetAbrVSphereDatastore = ConvertFrom-StringData @' TableDatastoreSummary = Datastore Summary - {0} TableDatastoreConfig = Datastore Configuration - {0} TableSCSILUN = SCSI LUN Information - {0} + Tags = Tags '@ # Get-AbrVSphereDSCluster @@ -1230,6 +1284,26 @@ GetAbrVSphereDSCluster = ConvertFrom-StringData @' VirtualMachine = Virtual Machine KeepVMDKsTogether = Keep VMDKs Together DefaultBehavior = Default ({0}) + RuleType = Type + RuleAffinity = Affinity + RuleAntiAffinity = Anti-Affinity + TableSDRSRules = SDRS Rules - {0} + SpaceLoadBalanceConfig = Space Load Balance Configuration + SpaceMinDiff = Min Space Utilization Difference (%) + SpaceThresholdMode = Space Threshold Mode + UtilizationMode = Utilization Threshold + FreeSpaceMode = Free Space Threshold + UtilizationThreshold = Space Utilization Threshold (%) + FreeSpaceThreshold = Free Space Threshold (GB) + IOLoadBalanceConfig = I/O Load Balance Configuration + IOCongestionThreshold = I/O Congestion Threshold (ms) + IOReservationMode = Reservation Threshold Mode + IOPSThresholdMode = I/O Operations Count + ReservationMbpsMode = Reservation in Mbps + ReservationIopsMode = Reservation in IOPS + TableSpaceLoadBalance = Space Load Balance Configuration - {0} + TableIOLoadBalance = I/O Load Balance Configuration - {0} + Tags = Tags '@ # Get-AbrVSphereVM @@ -1400,7 +1474,16 @@ GetAbrVSphereVM = ConvertFrom-StringData @' TableVMHardDiskConfig = Hard Disk Configuration - {0} TableVMHardDisk = {0} - {1} TableVMGuestVolumes = Guest Volumes - {0} - TableVMSnapshots = VM Snapshots - {0} + TableVMSnapshots = VM Snapshots - {0} + VUMCompliance = VM Update Manager Compliance + VUMBaselineName = Baseline + VUMStatus = Status + NotCompliant = Not Compliant + Incompatible = Incompatible + VUMComplianceError = Unable to retrieve VUM compliance information for virtual machines. + InsufficientPrivVUMCompliance = Insufficient privileges to collect VUM compliance information for virtual machines. + TableVUMCompliance = VUM Baseline Compliance - {0} + Tags = Tags '@ # Get-AbrVSphereVUM @@ -1426,6 +1509,43 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' PatchVendorID = Vendor ID TableVUMBaselines = VMware Update Manager Baseline Summary - {0} TableVUMPatches = VMware Update Manager Patch Information - {0} + SoftwareDepots = Software Depots + OnlineDepots = Online Depots + OfflineDepots = Offline Depots + DepotUrl = URL + SystemDefined = System Defined + DepotEnabled = Enabled + DepotLocation = Location + DepotError = Unable to retrieve software depot information. {0} + TableOnlineDepots = Online Software Depots - {0} + TableOfflineDepots = Offline Software Depots - {0} +'@ + +# Get-AbrVSphereClusterLCM +GetAbrVSphereClusterLCM = ConvertFrom-StringData @' + Collecting = Collecting Lifecycle Manager information. + ImageComposition = Image Composition + BaseImage = Base Image + VendorAddOn = Vendor Add-On + None = None + Components = Components + ComponentName = Component + ComponentVersion = Version + HardwareSupportManager = Hardware Support Manager + HsmName = Name + HsmVersion = Version + HsmPackages = Hardware Support Packages + ImageCompliance = Image Compliance + Cluster = Cluster + VMHost = VMHost + ComplianceStatus = Compliance Status + LcmError = Unable to retrieve Lifecycle Manager information for cluster {0}. {1} + ComplianceError = Unable to retrieve compliance information for cluster {0}. {1} + TableImageComposition = Image Composition - {0} + TableComponents = Image Components - {0} + TableHardwareSupportManager = Hardware Support Manager - {0} + TableImageCompliance = Image Compliance - {0} + TableHostCompliance = Host Image Compliance - {0} '@ } diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 index 58a126c..45913ae 100644 --- a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -123,6 +123,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarm = {0} - {1} TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} + RestApiSessionError = Unable to establish vCenter REST API session. {0} '@ # Get-AbrVSphereCluster @@ -239,7 +240,10 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' MaintenanceMode = Modo de Mantenimiento QuarantineMode = Modo de Cuarentena MixedMode = Modo Mixto - TableProactiveHA = Proactive HA - {0} + TableProactiveHA = Proactive HA - {0} + Providers = Proveedores + HealthUpdateCount = Actualizaciones de estado + TableProactiveHAProviders = Proveedores de HA proactiva - {0} '@ # Get-AbrVSphereClusterDRS @@ -286,22 +290,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' VMMonitoringMinUpTime = Tiempo mínimo de actividad VMMonitoringMaxFailures = Número máximo de fallos VMMonitoringMaxFailureWindow = Ventana máxima de fallos - UpdateManagerBaselines = Líneas base de Update Manager - Baseline = Línea base - Description = Descripción - Type = Tipo - TargetType = Tipo de destino - LastUpdate = Última actualización - NumPatches = N.º de parches - UpdateManagerCompliance = Cumplimiento de Update Manager - Entity = Entidad - Status = Estado de cumplimiento - Version = Versión - BaselineInfo = Línea base - VUMPrivilegeMsgBaselines = Privilegios de usuario insuficientes para informar sobre las líneas base del clúster. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gestionar parches y actualizaciones > Ver estado de cumplimiento'. - VUMPrivilegeMsgCompliance = Privilegios de usuario insuficientes para informar sobre el cumplimiento del clúster. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gestionar parches y actualizaciones > Ver estado de cumplimiento'. - VUMBaselineNotAvailable = La información de líneas base de VUM del clúster no está disponible actualmente con su versión de PowerShell. - VUMComplianceNotAvailable = La información de cumplimiento de VUM del clúster no está disponible actualmente con su versión de PowerShell. Enabled = Habilitado Disabled = Deshabilitado Yes = Sí @@ -310,9 +298,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' PartiallyAutomated = Parcialmente automatizado Manual = Manual Off = Desactivado - NotCompliant = No conforme - Unknown = Desconocido - Incompatible = Incompatible DRS = vSphere DRS DPM = DPM Automated = Automated @@ -371,11 +356,33 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' TableHAVMOverrides = HA VM Overrides - {0} TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} - TableVUMBaselines = Update Manager Baselines - {0} - TableVUMCompliance = Update Manager Compliance - {0} TablePermissions = Permissions - {0} '@ +# Get-AbrVSphereClusterVUM +GetAbrVSphereClusterVUM = ConvertFrom-StringData @' + UpdateManagerBaselines = Líneas base de Update Manager + Baseline = Línea base + Description = Descripción + Type = Tipo + TargetType = Tipo de destino + LastUpdate = Última actualización + NumPatches = N.º de parches + UpdateManagerCompliance = Cumplimiento de Update Manager + Entity = Entidad + Status = Estado de cumplimiento + BaselineInfo = Línea base + VUMPrivilegeMsgBaselines = Privilegios de usuario insuficientes para informar sobre las líneas base del clúster. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gestionar parches y actualizaciones > Ver estado de cumplimiento'. + VUMPrivilegeMsgCompliance = Privilegios de usuario insuficientes para informar sobre el cumplimiento del clúster. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gestionar parches y actualizaciones > Ver estado de cumplimiento'. + VUMBaselineNotAvailable = La información de líneas base de VUM del clúster no está disponible actualmente con su versión de PowerShell. + VUMComplianceNotAvailable = La información de cumplimiento de VUM del clúster no está disponible actualmente con su versión de PowerShell. + NotCompliant = No conforme + Unknown = Desconocido + Incompatible = Incompatible + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} +'@ + # Get-AbrVSphereResourcePool GetAbrVSphereResourcePool = ConvertFrom-StringData @' InfoLevel = Nivel de información de grupo de recursos establecido en {0}. @@ -404,6 +411,7 @@ GetAbrVSphereResourcePool = ConvertFrom-StringData @' Unlimited = Ilimitado TableResourcePoolSummary = Resource Pool Summary - {0} TableResourcePoolConfig = Resource Pool Configuration - {0} + Tags = Etiquetas '@ # Get-AbrVSphereVMHost @@ -435,6 +443,7 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' Build = Build Parent = Parent TableHostSummary = Host Summary - {0} + Tags = Etiquetas '@ # Get-AbrVSphereVMHostHardware @@ -612,6 +621,24 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + SwapFileLocation = Ubicación del archivo de intercambio de VM + SwapFilePlacement = Ubicación del archivo de intercambio de VM + SwapDatastore = Almacén de datos de intercambio + WithVM = Con VM (Predeterminado) + HostLocal = Local del host + TableSwapFileLocation = Ubicación del archivo de intercambio de VM - {0} + SwapFileLocationError = Error al recopilar la ubicación del archivo de intercambio de VM para {0}. {1} + HostCertificate = Certificado del host + CertSubject = Asunto + CertIssuer = Emisor + CertValidFrom = Válido desde + CertValidTo = Válido hasta + CertThumbprint = Huella digital SHA-256 + TableHostCertificate = Certificado del host - {0} + HostCertificateError = Error al recopilar la información del certificado del host para {0}. {1} + LogDir = Directorio de registro + LogRotations = Rotaciones de registro + LogSize = Tamaño de registro (KB) '@ # Get-AbrVSphereVMHostStorage @@ -1010,6 +1037,28 @@ GetAbrVSphereNetwork = ConvertFrom-StringData @' TableVDSPortGroupTrafficShaping = Modelado de tráfico del grupo de puertos del conmutador distribuido - {0} TableVDSPortGroupTeaming = Equipos y conmutación por error del grupo de puertos del conmutador distribuido - {0} TableVDSPrivateVLANs = VLAN privadas del conmutador distribuido - {0} + VDSLACP = LACP del conmutador distribuido + LACPEnabled = LACP habilitado + LACPMode = Modo LACP + LACPActive = Activo + LACPPassive = Pasivo + VDSNetFlow = NetFlow del conmutador distribuido + CollectorIP = Dirección IP del colector + CollectorPort = Puerto del colector + ActiveFlowTimeout = Tiempo de espera de flujo activo (s) + IdleFlowTimeout = Tiempo de espera de flujo inactivo (s) + SamplingRate = Tasa de muestreo + InternalFlowsOnly = Solo flujos internos + NIOCResourcePools = Grupos de recursos de control de E/S de red + NIOCResourcePool = Grupo de recursos + NIOCSharesLevel = Nivel de participaciones + NIOCSharesValue = Participaciones + NIOCLimitMbps = Límite (Mbps) + Unlimited = Ilimitado + TableVDSLACP = LACP del conmutador distribuido - {0} + TableVDSNetFlow = NetFlow del conmutador distribuido - {0} + TableNIOCResourcePools = Grupos de recursos de control de E/S de red - {0} + Tags = Etiquetas '@ # Get-AbrVSpherevSAN @@ -1137,6 +1186,10 @@ GetAbrVSpherevSAN = ConvertFrom-StringData @' DiskGroupError = Error al recopilar información de grupos de discos vSAN para '{0}'. {1} iSCSITargetError = Error al recopilar información de destinos iSCSI vSAN para '{0}'. {1} iSCSILUNError = Error al recopilar información de LUN iSCSI vSAN para '{0}'. {1} + ServicesSection = Servicios de vSAN + Service = Servicio + TableVSANServices = Servicios de vSAN - {0} + ServicesError = Error al recopilar información de servicios vSAN para '{0}'. {1} '@ # Get-AbrVSphereDatastore @@ -1189,6 +1242,7 @@ GetAbrVSphereDatastore = ConvertFrom-StringData @' TableDatastoreSummary = Resumen del almacén de datos - {0} TableDatastoreConfig = Configuración del almacén de datos - {0} TableSCSILUN = Información de LUN SCSI - {0} + Tags = Etiquetas '@ # Get-AbrVSphereDSCluster @@ -1230,6 +1284,26 @@ GetAbrVSphereDSCluster = ConvertFrom-StringData @' VirtualMachine = Máquina virtual KeepVMDKsTogether = Mantener VMDK juntos DefaultBehavior = Predeterminado ({0}) + RuleType = Tipo + RuleAffinity = Afinidad + RuleAntiAffinity = Antiafinidad + TableSDRSRules = Reglas SDRS - {0} + SpaceLoadBalanceConfig = Configuración de equilibrio de carga de espacio + SpaceMinDiff = Diferencia mínima de utilización de espacio (%) + SpaceThresholdMode = Modo de umbral de espacio + UtilizationMode = Umbral de utilización + FreeSpaceMode = Umbral de espacio libre + UtilizationThreshold = Umbral de utilización de espacio (%) + FreeSpaceThreshold = Umbral de espacio libre (GB) + IOLoadBalanceConfig = Configuración de equilibrio de carga de E/S + IOCongestionThreshold = Umbral de congestión de E/S (ms) + IOReservationMode = Modo de umbral de reserva + IOPSThresholdMode = Recuento de operaciones de E/S + ReservationMbpsMode = Reserva en Mbps + ReservationIopsMode = Reserva en IOPS + TableSpaceLoadBalance = Configuración de equilibrio de carga de espacio - {0} + TableIOLoadBalance = Configuración de equilibrio de carga de E/S - {0} + Tags = Etiquetas '@ # Get-AbrVSphereVM @@ -1400,7 +1474,16 @@ GetAbrVSphereVM = ConvertFrom-StringData @' TableVMHardDiskConfig = Configuración de disco duro - {0} TableVMHardDisk = {0} - {1} TableVMGuestVolumes = Volúmenes del invitado - {0} - TableVMSnapshots = Instantáneas de máquinas virtuales - {0} + TableVMSnapshots = Instantáneas de máquinas virtuales - {0} + VUMCompliance = Cumplimiento de Update Manager de VM + VUMBaselineName = Línea base + VUMStatus = Estado + NotCompliant = No conforme + Incompatible = Incompatible + VUMComplianceError = Unable to retrieve VUM compliance information for virtual machines. + InsufficientPrivVUMCompliance = Insufficient privileges to collect VUM compliance information for virtual machines. + TableVUMCompliance = Cumplimiento de línea base VUM - {0} + Tags = Etiquetas '@ # Get-AbrVSphereVUM @@ -1426,6 +1509,43 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' PatchVendorID = ID de fabricante TableVUMBaselines = Resumen de líneas base de VMware Update Manager - {0} TableVUMPatches = Información de parches de VMware Update Manager - {0} + SoftwareDepots = Depósitos de software + OnlineDepots = Depósitos en línea + OfflineDepots = Depósitos sin conexión + DepotUrl = URL + SystemDefined = Definido por el sistema + DepotEnabled = Habilitado + DepotLocation = Ubicación + DepotError = No se puede recuperar información de depósitos de software. {0} + TableOnlineDepots = Depósitos de software en línea - {0} + TableOfflineDepots = Depósitos de software sin conexión - {0} +'@ + +# Get-AbrVSphereClusterLCM +GetAbrVSphereClusterLCM = ConvertFrom-StringData @' + Collecting = Recopilando información del Lifecycle Manager. + ImageComposition = Composición de imagen + BaseImage = Imagen base + VendorAddOn = Complemento del proveedor + None = Ninguno + Components = Componentes + ComponentName = Componente + ComponentVersion = Versión + HardwareSupportManager = Administrador de soporte de hardware + HsmName = Nombre + HsmVersion = Versión + HsmPackages = Paquetes de soporte de hardware + ImageCompliance = Conformidad de imagen + Cluster = Clúster + VMHost = VMHost + ComplianceStatus = Estado de conformidad + LcmError = No se puede recuperar información del Lifecycle Manager para el clúster {0}. {1} + ComplianceError = No se puede recuperar información de conformidad para el clúster {0}. {1} + TableImageComposition = Composición de imagen - {0} + TableComponents = Componentes de imagen - {0} + TableHardwareSupportManager = Administrador de soporte de hardware - {0} + TableImageCompliance = Conformidad de imagen - {0} + TableHostCompliance = Conformidad de imagen del host - {0} '@ } diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 index 7b9b66b..11430bb 100644 --- a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -123,6 +123,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarm = {0} - {1} TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} + RestApiSessionError = Unable to establish vCenter REST API session. {0} '@ # Get-AbrVSphereCluster @@ -239,7 +240,10 @@ GetAbrVSphereClusterProactiveHA = ConvertFrom-StringData @' MaintenanceMode = Mode Maintenance QuarantineMode = Mode Quarantaine MixedMode = Mode Mixte - TableProactiveHA = Proactive HA - {0} + TableProactiveHA = Proactive HA - {0} + Providers = Fournisseurs + HealthUpdateCount = Mises à jour de santé + TableProactiveHAProviders = Fournisseurs de HA proactive - {0} '@ # Get-AbrVSphereClusterDRS @@ -286,22 +290,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' VMMonitoringMinUpTime = Temps de fonctionnement minimum VMMonitoringMaxFailures = Nombre maximum d'échecs VMMonitoringMaxFailureWindow = Fenêtre d'échec maximale - UpdateManagerBaselines = Lignes de base Update Manager - Baseline = Ligne de base - Description = Description - Type = Type - TargetType = Type de cible - LastUpdate = Heure de la dernière mise à jour - NumPatches = Nb de correctifs - UpdateManagerCompliance = Conformité Update Manager - Entity = Entité - Status = État de conformité - Version = Version - BaselineInfo = Ligne de base - VUMPrivilegeMsgBaselines = Privilèges utilisateur insuffisants pour rapporter les lignes de base du cluster. Veuillez vous assurer que le compte utilisateur dispose du privilège 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gérer les correctifs et les mises à niveau > Afficher l'état de conformité'. - VUMPrivilegeMsgCompliance = Privilèges utilisateur insuffisants pour rapporter la conformité du cluster. Veuillez vous assurer que le compte utilisateur dispose du privilège 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gérer les correctifs et les mises à niveau > Afficher l'état de conformité'. - VUMBaselineNotAvailable = Les informations de ligne de base VUM du cluster ne sont pas disponibles avec votre version de PowerShell. - VUMComplianceNotAvailable = Les informations de conformité VUM du cluster ne sont pas disponibles avec votre version de PowerShell. Enabled = Activé Disabled = Désactivé Yes = Oui @@ -310,9 +298,6 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' PartiallyAutomated = Partiellement automatisé Manual = Manuel Off = Désactivé - NotCompliant = Non conforme - Unknown = Inconnu - Incompatible = Incompatible DRS = vSphere DRS DPM = DPM Automated = Automated @@ -371,11 +356,33 @@ GetAbrVSphereClusterDRS = ConvertFrom-StringData @' TableHAVMOverrides = HA VM Overrides - {0} TableHAPDLAPD = HA VM Overrides PDL/APD Settings - {0} TableHAVMMonitoring = HA VM Overrides VM Monitoring - {0} - TableVUMBaselines = Update Manager Baselines - {0} - TableVUMCompliance = Update Manager Compliance - {0} TablePermissions = Permissions - {0} '@ +# Get-AbrVSphereClusterVUM +GetAbrVSphereClusterVUM = ConvertFrom-StringData @' + UpdateManagerBaselines = Lignes de base Update Manager + Baseline = Ligne de base + Description = Description + Type = Type + TargetType = Type de cible + LastUpdate = Heure de la dernière mise à jour + NumPatches = Nb de correctifs + UpdateManagerCompliance = Conformité Update Manager + Entity = Entité + Status = État de conformité + BaselineInfo = Ligne de base + VUMPrivilegeMsgBaselines = Privilèges utilisateur insuffisants pour rapporter les lignes de base du cluster. Veuillez vous assurer que le compte utilisateur dispose du privilège 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gérer les correctifs et les mises à niveau > Afficher l'état de conformité'. + VUMPrivilegeMsgCompliance = Privilèges utilisateur insuffisants pour rapporter la conformité du cluster. Veuillez vous assurer que le compte utilisateur dispose du privilège 'VMware Update Manager / VMware vSphere Lifecycle Manager > Gérer les correctifs et les mises à niveau > Afficher l'état de conformité'. + VUMBaselineNotAvailable = Les informations de ligne de base VUM du cluster ne sont pas disponibles avec votre version de PowerShell. + VUMComplianceNotAvailable = Les informations de conformité VUM du cluster ne sont pas disponibles avec votre version de PowerShell. + NotCompliant = Non conforme + Unknown = Inconnu + Incompatible = Incompatible + TableVUMBaselines = Update Manager Baselines - {0} + TableVUMCompliance = Update Manager Compliance - {0} +'@ + # Get-AbrVSphereResourcePool GetAbrVSphereResourcePool = ConvertFrom-StringData @' InfoLevel = Niveau d'information Pool de ressources défini sur {0}. @@ -404,6 +411,7 @@ GetAbrVSphereResourcePool = ConvertFrom-StringData @' Unlimited = Illimité TableResourcePoolSummary = Resource Pool Summary - {0} TableResourcePoolConfig = Resource Pool Configuration - {0} + Tags = Étiquettes '@ # Get-AbrVSphereVMHost @@ -435,6 +443,7 @@ GetAbrVSphereVMHost = ConvertFrom-StringData @' Build = Build Parent = Parent TableHostSummary = Host Summary - {0} + Tags = Étiquettes '@ # Get-AbrVSphereVMHostHardware @@ -612,6 +621,24 @@ GetAbrVSphereVMHostSystem = ConvertFrom-StringData @' InsufficientPrivImageProfile = Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned. InsufficientPrivVUMBaseline = Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. InsufficientPrivVUMCompliance = Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned. + SwapFileLocation = Emplacement du fichier d'échange de VM + SwapFilePlacement = Emplacement du fichier d'échange de VM + SwapDatastore = Banque de données d'échange + WithVM = Avec la VM (Par défaut) + HostLocal = Local de l'hôte + TableSwapFileLocation = Emplacement du fichier d'échange de VM - {0} + SwapFileLocationError = Erreur lors de la collecte de l'emplacement du fichier d'échange pour {0}. {1} + HostCertificate = Certificat de l'hôte + CertSubject = Sujet + CertIssuer = Émetteur + CertValidFrom = Valide à partir du + CertValidTo = Valide jusqu'au + CertThumbprint = Empreinte SHA-256 + TableHostCertificate = Certificat de l'hôte - {0} + HostCertificateError = Erreur lors de la collecte des informations de certificat de l'hôte pour {0}. {1} + LogDir = Répertoire des journaux + LogRotations = Rotations des journaux + LogSize = Taille des journaux (Ko) '@ # Get-AbrVSphereVMHostStorage @@ -1010,6 +1037,28 @@ GetAbrVSphereNetwork = ConvertFrom-StringData @' TableVDSPortGroupTrafficShaping = Régulation du trafic du groupe de ports du commutateur distribué - {0} TableVDSPortGroupTeaming = Équipe et basculement du groupe de ports du commutateur distribué - {0} TableVDSPrivateVLANs = VLAN privés du commutateur distribué - {0} + VDSLACP = LACP du commutateur distribué + LACPEnabled = LACP activé + LACPMode = Mode LACP + LACPActive = Actif + LACPPassive = Passif + VDSNetFlow = NetFlow du commutateur distribué + CollectorIP = Adresse IP du collecteur + CollectorPort = Port du collecteur + ActiveFlowTimeout = Délai d'expiration des flux actifs (s) + IdleFlowTimeout = Délai d'expiration des flux inactifs (s) + SamplingRate = Taux d'échantillonnage + InternalFlowsOnly = Flux internes uniquement + NIOCResourcePools = Pools de ressources de contrôle d'E/S réseau + NIOCResourcePool = Pool de ressources + NIOCSharesLevel = Niveau de partages + NIOCSharesValue = Partages + NIOCLimitMbps = Limite (Mbps) + Unlimited = Illimité + TableVDSLACP = LACP du commutateur distribué - {0} + TableVDSNetFlow = NetFlow du commutateur distribué - {0} + TableNIOCResourcePools = Pools de ressources de contrôle d'E/S réseau - {0} + Tags = Étiquettes '@ # Get-AbrVSpherevSAN @@ -1137,6 +1186,10 @@ GetAbrVSpherevSAN = ConvertFrom-StringData @' DiskGroupError = Erreur lors de la collecte des informations sur les groupes de disques vSAN pour '{0}'. {1} iSCSITargetError = Erreur lors de la collecte des informations sur les cibles iSCSI vSAN pour '{0}'. {1} iSCSILUNError = Erreur lors de la collecte des informations sur les LUN iSCSI vSAN pour '{0}'. {1} + ServicesSection = Services vSAN + Service = Service + TableVSANServices = Services vSAN - {0} + ServicesError = Erreur lors de la collecte des informations sur les services vSAN pour '{0}'. {1} '@ # Get-AbrVSphereDatastore @@ -1189,6 +1242,7 @@ GetAbrVSphereDatastore = ConvertFrom-StringData @' TableDatastoreSummary = Récapitulatif du magasin de données - {0} TableDatastoreConfig = Configuration du magasin de données - {0} TableSCSILUN = Informations LUN SCSI - {0} + Tags = Étiquettes '@ # Get-AbrVSphereDSCluster @@ -1230,6 +1284,26 @@ GetAbrVSphereDSCluster = ConvertFrom-StringData @' VirtualMachine = Machine virtuelle KeepVMDKsTogether = Garder les VMDK ensemble DefaultBehavior = Défaut ({0}) + RuleType = Type + RuleAffinity = Affinité + RuleAntiAffinity = Anti-Affinité + TableSDRSRules = Règles SDRS - {0} + SpaceLoadBalanceConfig = Configuration de l'équilibrage de charge d'espace + SpaceMinDiff = Différence minimale d'utilisation de l'espace (%) + SpaceThresholdMode = Mode de seuil d'espace + UtilizationMode = Seuil d'utilisation + FreeSpaceMode = Seuil d'espace libre + UtilizationThreshold = Seuil d'utilisation de l'espace (%) + FreeSpaceThreshold = Seuil d'espace libre (Go) + IOLoadBalanceConfig = Configuration de l'équilibrage de charge d'E/S + IOCongestionThreshold = Seuil de congestion d'E/S (ms) + IOReservationMode = Mode de seuil de réservation + IOPSThresholdMode = Nombre d'opérations d'E/S + ReservationMbpsMode = Réservation en Mbps + ReservationIopsMode = Réservation en IOPS + TableSpaceLoadBalance = Configuration de l'équilibrage de charge d'espace - {0} + TableIOLoadBalance = Configuration de l'équilibrage de charge d'E/S - {0} + Tags = Étiquettes '@ # Get-AbrVSphereVM @@ -1400,7 +1474,16 @@ GetAbrVSphereVM = ConvertFrom-StringData @' TableVMHardDiskConfig = Configuration du disque dur - {0} TableVMHardDisk = {0} - {1} TableVMGuestVolumes = Volumes invités - {0} - TableVMSnapshots = Snapshots des MV - {0} + TableVMSnapshots = Snapshots des MV - {0} + VUMCompliance = Conformité Update Manager de la VM + VUMBaselineName = Ligne de base + VUMStatus = État + NotCompliant = Non conforme + Incompatible = Incompatible + VUMComplianceError = Unable to retrieve VUM compliance information for virtual machines. + InsufficientPrivVUMCompliance = Insufficient privileges to collect VUM compliance information for virtual machines. + TableVUMCompliance = Conformité de la ligne de base VUM - {0} + Tags = Étiquettes '@ # Get-AbrVSphereVUM @@ -1426,6 +1509,43 @@ GetAbrVSphereVUM = ConvertFrom-StringData @' PatchVendorID = ID fournisseur TableVUMBaselines = Résumé des lignes de base VMware Update Manager - {0} TableVUMPatches = Informations sur les correctifs VMware Update Manager - {0} + SoftwareDepots = Dépôts de logiciels + OnlineDepots = Dépôts en ligne + OfflineDepots = Dépôts hors ligne + DepotUrl = URL + SystemDefined = Défini par le système + DepotEnabled = Activé + DepotLocation = Emplacement + DepotError = Impossible de récupérer les informations des dépôts de logiciels. {0} + TableOnlineDepots = Dépôts de logiciels en ligne - {0} + TableOfflineDepots = Dépôts de logiciels hors ligne - {0} +'@ + +# Get-AbrVSphereClusterLCM +GetAbrVSphereClusterLCM = ConvertFrom-StringData @' + Collecting = Collecte des informations du Lifecycle Manager. + ImageComposition = Composition de l'image + BaseImage = Image de base + VendorAddOn = Module complémentaire du fournisseur + None = Aucun + Components = Composants + ComponentName = Composant + ComponentVersion = Version + HardwareSupportManager = Gestionnaire de support matériel + HsmName = Nom + HsmVersion = Version + HsmPackages = Packages de support matériel + ImageCompliance = Conformité de l'image + Cluster = Cluster + VMHost = VMHost + ComplianceStatus = Statut de conformité + LcmError = Impossible de récupérer les informations du Lifecycle Manager pour le cluster {0}. {1} + ComplianceError = Impossible de récupérer les informations de conformité pour le cluster {0}. {1} + TableImageComposition = Composition de l'image - {0} + TableComponents = Composants de l'image - {0} + TableHardwareSupportManager = Gestionnaire de support matériel - {0} + TableImageCompliance = Conformité de l'image - {0} + TableHostCompliance = Conformité de l'image de l'hôte - {0} '@ } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 index 274a005..bd92292 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereCluster.ps1 @@ -133,10 +133,12 @@ function Get-AbrVSphereCluster { $ClusterDetail | Table @TableParams #endregion Cluster Configuration - # Call sub-functions for HA, Proactive HA, and DRS + # Call sub-functions for HA, Proactive HA, DRS, VUM, and LCM Get-AbrVSphereClusterHA Get-AbrVSphereClusterProactiveHA Get-AbrVSphereClusterDRS + Get-AbrVSphereClusterVUM + Get-AbrVSphereClusterLCM } } } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 index 692ad38..5fc31c6 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterDRS.ps1 @@ -5,7 +5,7 @@ Generates a PScriboDocument section detailing the vSphere DRS configuration for a given cluster, including DRS settings, Additional Options, Power Management, Advanced Options, DRS Cluster Groups, DRS VM/Host Rules, DRS Rules, VM Overrides, - Update Manager Baselines, Update Manager Compliance, and Permissions subsections. + and Permissions subsections. .NOTES Version: 2.0.0 Author: Tim Carman @@ -14,7 +14,7 @@ .INPUTS None. Uses variables from the parent scope: $Cluster, $ClusterDrsConfig, $ClusterConfigEx, $VMLookup, $InfoLevel, $Report, - $Healthcheck, $UserPrivileges, $VumServer, $vCenter, $VUMConnection, $reportTranslate + $Healthcheck, $vCenter, $reportTranslate .OUTPUTS None. Writes PScriboDocument content directly. #> @@ -34,7 +34,7 @@ function Get-AbrVSphereClusterDRS { #region vSphere DRS Cluster Specifications $DrsCluster = [PSCustomObject]@{ - ($LocalizedData.DRS) = if ($Cluster.DrsEnabled) { + $LocalizedData.DRS = if ($Cluster.DrsEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled @@ -88,15 +88,15 @@ function Get-AbrVSphereClusterDRS { if ($DrsAdvancedSettings) { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.AdditionalOptions { $DrsAdditionalOptions = [PSCustomObject] @{ - ($LocalizedData.VMDistribution) = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'TryBalanceVmsPerHost' }).Value) { + $LocalizedData.VMDistribution = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'TryBalanceVmsPerHost' }).Value) { '1' { $LocalizedData.Enabled } $null { $LocalizedData.Disabled } } - ($LocalizedData.MemoryMetricForLB) = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'PercentIdleMBInMemDemand' }).Value) { + $LocalizedData.MemoryMetricForLB = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'PercentIdleMBInMemDemand' }).Value) { '100' { $LocalizedData.Enabled } $null { $LocalizedData.Disabled } } - ($LocalizedData.CPUOverCommitment) = if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerCore' }).Value) { + $LocalizedData.CPUOverCommitment = if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerCore' }).Value) { $LocalizedData.Enabled } else { $LocalizedData.Disabled @@ -129,7 +129,7 @@ function Get-AbrVSphereClusterDRS { if ($ClusterConfigEx.DpmConfigInfo.Enabled) { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.PowerManagement { $DpmConfig = [PSCustomObject]@{ - ($LocalizedData.DPM) = if ($ClusterConfigEx.DpmConfigInfo.Enabled) { + $LocalizedData.DPM = if ($ClusterConfigEx.DpmConfigInfo.Enabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled @@ -170,8 +170,8 @@ function Get-AbrVSphereClusterDRS { $DrsAdvancedOptions = @() foreach ($DrsAdvancedSetting in $DrsAdvancedSettings) { $DrsAdvancedOption = [PSCustomObject]@{ - ($LocalizedData.Key) = $DrsAdvancedSetting.Name - ($LocalizedData.Value) = $DrsAdvancedSetting.Value + $LocalizedData.Key = $DrsAdvancedSetting.Name + $LocalizedData.Value = $DrsAdvancedSetting.Value } $DrsAdvancedOptions += $DrsAdvancedOption } @@ -194,13 +194,13 @@ function Get-AbrVSphereClusterDRS { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DRSClusterGroups { $DrsGroups = foreach ($DrsClusterGroup in $DrsClusterGroups) { [PSCustomObject]@{ - ($LocalizedData.GroupName) = $DrsClusterGroup.Name - ($LocalizedData.GroupType) = Switch ($DrsClusterGroup.GroupType) { + $LocalizedData.GroupName = $DrsClusterGroup.Name + $LocalizedData.GroupType = Switch ($DrsClusterGroup.GroupType) { 'VMGroup' { $LocalizedData.VMGroupType } 'VMHostGroup' { $LocalizedData.VMHostGroupType } default { $DrsClusterGroup.GroupType } } - ($LocalizedData.GroupMembers) = if (($DrsClusterGroup.Member).Count -gt 0) { + $LocalizedData.GroupMembers = if (($DrsClusterGroup.Member).Count -gt 0) { ($DrsClusterGroup.Member | Sort-Object) -join ', ' } else { $LocalizedData.None @@ -224,21 +224,21 @@ function Get-AbrVSphereClusterDRS { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DRSVMHostRules { $DrsVMHostRuleDetail = foreach ($DrsVMHostRule in $DrsVMHostRules) { [PSCustomObject]@{ - ($LocalizedData.RuleName) = $DrsVMHostRule.Name - ($LocalizedData.RuleType) = Switch ($DrsVMHostRule.Type) { + $LocalizedData.RuleName = $DrsVMHostRule.Name + $LocalizedData.RuleType = Switch ($DrsVMHostRule.Type) { 'MustRunOn' { $LocalizedData.MustRunOn } 'ShouldRunOn' { $LocalizedData.ShouldRunOn } 'MustNotRunOn' { $LocalizedData.MustNotRunOn } 'ShouldNotRunOn' { $LocalizedData.ShouldNotRunOn } default { $DrsVMHostRule.Type } } - ($LocalizedData.RuleEnabled) = if ($DrsVMHostRule.Enabled) { + $LocalizedData.RuleEnabled = if ($DrsVMHostRule.Enabled) { $LocalizedData.Yes } else { $LocalizedData.No } - ($LocalizedData.VMGroup) = $DrsVMHostRule.VMGroup - ($LocalizedData.HostGroup) = $DrsVMHostRule.VMHostGroup + $LocalizedData.VMGroup = $DrsVMHostRule.VMGroup.Name + $LocalizedData.HostGroup = $DrsVMHostRule.VMHostGroup.Name } } if ($Healthcheck.Cluster.DrsVMHostRules) { @@ -263,18 +263,18 @@ function Get-AbrVSphereClusterDRS { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DRSRules { $DrsRuleDetail = foreach ($DrsRule in $DrsRules) { [PSCustomObject]@{ - ($LocalizedData.RuleName) = $DrsRule.Name - ($LocalizedData.RuleType) = Switch ($DrsRule.Type) { + $LocalizedData.RuleName = $DrsRule.Name + $LocalizedData.RuleType = Switch ($DrsRule.Type) { 'VMAffinity' { $LocalizedData.VMAffinity } 'VMAntiAffinity' { $LocalizedData.VMAntiAffinity } } - ($LocalizedData.RuleEnabled) = if ($DrsRule.Enabled) { + $LocalizedData.RuleEnabled = if ($DrsRule.Enabled) { $LocalizedData.Yes } else { $LocalizedData.No } - ($LocalizedData.Mandatory) = $DrsRule.Mandatory - ($LocalizedData.RuleVMs) = ($DrsRule.VMIds | ForEach-Object { (Get-View -Id $_).name }) -join ', ' + $LocalizedData.Mandatory = $DrsRule.Mandatory + $LocalizedData.RuleVMs = ($DrsRule.VMIds | ForEach-Object { (Get-View -Id $_).name }) -join ', ' } if ($Healthcheck.Cluster.DrsRules) { $DrsRuleDetail | Where-Object { $_.$($LocalizedData.RuleEnabled) -eq $LocalizedData.No } | Set-Style -Style Warning -Property $LocalizedData.RuleEnabled @@ -306,8 +306,8 @@ function Get-AbrVSphereClusterDRS { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.DRS { $DrsVmOverrideDetails = foreach ($DrsVmOverride in $DrsVmOverrides) { [PSCustomObject]@{ - ($LocalizedData.VirtualMachine) = $VMLookup."$($DrsVmOverride.Key.Type)-$($DrsVmOverride.Key.Value)" - ($LocalizedData.DRSAutomationLevel) = if ($DrsVmOverride.Enabled -eq $false) { + $LocalizedData.VirtualMachine = $VMLookup."$($DrsVmOverride.Key.Type)-$($DrsVmOverride.Key.Value)" + $LocalizedData.DRSAutomationLevel = if ($DrsVmOverride.Enabled -eq $false) { $LocalizedData.Disabled } else { Switch ($DrsVmOverride.Behavior) { @@ -336,8 +336,8 @@ function Get-AbrVSphereClusterDRS { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.SectionVSphereHA { $DasVmOverrideDetails = foreach ($DasVmOverride in $DasVmOverrides) { [PSCustomObject]@{ - ($LocalizedData.VirtualMachine) = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" - ($LocalizedData.HARestartPriority) = Switch ($DasVmOverride.DasSettings.RestartPriority) { + $LocalizedData.VirtualMachine = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" + $LocalizedData.HARestartPriority = Switch ($DasVmOverride.DasSettings.RestartPriority) { $null { '--' } 'lowest' { $LocalizedData.Lowest } 'low' { $LocalizedData.Low } @@ -347,12 +347,12 @@ function Get-AbrVSphereClusterDRS { 'disabled' { $LocalizedData.Disabled } 'clusterRestartPriority' { $LocalizedData.ClusterDefault } } - ($LocalizedData.VMDependencyTimeout) = Switch ($DasVmOverride.DasSettings.RestartPriorityTimeout) { + $LocalizedData.VMDependencyTimeout = Switch ($DasVmOverride.DasSettings.RestartPriorityTimeout) { $null { '--' } '-1' { $LocalizedData.Disabled } default { $LocalizedData.Seconds -f $DasVmOverride.DasSettings.RestartPriorityTimeout } } - ($LocalizedData.HAIsolationResponse) = Switch ($DasVmOverride.DasSettings.IsolationResponse) { + $LocalizedData.HAIsolationResponse = Switch ($DasVmOverride.DasSettings.IsolationResponse) { $null { '--' } 'none' { $LocalizedData.Disabled } 'powerOff' { $LocalizedData.PowerOffAndRestart } @@ -375,15 +375,15 @@ function Get-AbrVSphereClusterDRS { $DasVmOverridePdlApd = foreach ($DasVmOverride in $DasVmOverrides) { $DasVmComponentProtection = $DasVmOverride.DasSettings.VmComponentProtectionSettings [PSCustomObject]@{ - ($LocalizedData.VirtualMachine) = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" - ($LocalizedData.PDLFailureResponse) = Switch ($DasVmComponentProtection.VmStorageProtectionForPDL) { + $LocalizedData.VirtualMachine = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" + $LocalizedData.PDLFailureResponse = Switch ($DasVmComponentProtection.VmStorageProtectionForPDL) { $null { '--' } 'clusterDefault' { $LocalizedData.ClusterDefault } 'warning' { $LocalizedData.IssueEvents } 'restartAggressive' { $LocalizedData.PowerOffAndRestart } 'disabled' { $LocalizedData.Disabled } } - ($LocalizedData.APDFailureResponse) = Switch ($DasVmComponentProtection.VmStorageProtectionForAPD) { + $LocalizedData.APDFailureResponse = Switch ($DasVmComponentProtection.VmStorageProtectionForAPD) { $null { '--' } 'clusterDefault' { $LocalizedData.ClusterDefault } 'warning' { $LocalizedData.IssueEvents } @@ -391,12 +391,12 @@ function Get-AbrVSphereClusterDRS { 'restartAggressive' { $LocalizedData.PowerOffRestartAggressive } 'disabled' { $LocalizedData.Disabled } } - ($LocalizedData.VMFailoverDelay) = Switch ($DasVmComponentProtection.VmTerminateDelayForAPDSec) { + $LocalizedData.VMFailoverDelay = Switch ($DasVmComponentProtection.VmTerminateDelayForAPDSec) { $null { '--' } '-1' { $LocalizedData.Disabled } default { $LocalizedData.Minutes -f (($DasVmComponentProtection.VmTerminateDelayForAPDSec)/60) } } - ($LocalizedData.ResponseRecovery) = Switch ($DasVmComponentProtection.VmReactionOnAPDCleared) { + $LocalizedData.ResponseRecovery = Switch ($DasVmComponentProtection.VmReactionOnAPDCleared) { $null { '--' } 'reset' { $LocalizedData.ResetVMs } 'disabled' { $LocalizedData.Disabled } @@ -420,14 +420,14 @@ function Get-AbrVSphereClusterDRS { $DasVmOverrideVmMonitoring = foreach ($DasVmOverride in $DasVmOverrides) { $DasVmMonitoring = $DasVmOverride.DasSettings.VmToolsMonitoringSettings [PSCustomObject]@{ - ($LocalizedData.VirtualMachine) = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" - ($LocalizedData.VMMonitoring) = Switch ($DasVmMonitoring.VmMonitoring) { + $LocalizedData.VirtualMachine = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" + $LocalizedData.VMMonitoring = Switch ($DasVmMonitoring.VmMonitoring) { $null { '--' } 'vmMonitoringDisabled' { $LocalizedData.Disabled } 'vmMonitoringOnly' { $LocalizedData.VMMonitoringOnly } 'vmAndAppMonitoring' { $LocalizedData.VMAndAppMonitoring } } - ($LocalizedData.VMMonitoringFailureInterval) = Switch ($DasVmMonitoring.FailureInterval) { + $LocalizedData.VMMonitoringFailureInterval = Switch ($DasVmMonitoring.FailureInterval) { $null { '--' } default { if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { @@ -437,7 +437,7 @@ function Get-AbrVSphereClusterDRS { } } } - ($LocalizedData.VMMonitoringMinUpTime) = Switch ($DasVmMonitoring.MinUptime) { + $LocalizedData.VMMonitoringMinUpTime = Switch ($DasVmMonitoring.MinUptime) { $null { '--' } default { if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { @@ -447,7 +447,7 @@ function Get-AbrVSphereClusterDRS { } } } - ($LocalizedData.VMMonitoringMaxFailures) = Switch ($DasVmMonitoring.MaxFailures) { + $LocalizedData.VMMonitoringMaxFailures = Switch ($DasVmMonitoring.MaxFailures) { $null { '--' } default { if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { @@ -457,7 +457,7 @@ function Get-AbrVSphereClusterDRS { } } } - ($LocalizedData.VMMonitoringMaxFailureWindow) = Switch ($DasVmMonitoring.MaxFailureWindow) { + $LocalizedData.VMMonitoringMaxFailureWindow = Switch ($DasVmMonitoring.MaxFailureWindow) { $null { '--' } '-1' { $LocalizedData.NoWindow } default { @@ -488,94 +488,6 @@ function Get-AbrVSphereClusterDRS { } #endregion Cluster VM Overrides - #region Cluster VUM Baselines - if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { - if ($VUMConnection) { - Try { - $ClusterPatchBaselines = $Cluster | Get-PatchBaseline - } Catch { - Write-PScriboMessage -Message $LocalizedData.VUMBaselineNotAvailable - } - if ($ClusterPatchBaselines) { - Section -Style Heading4 $LocalizedData.UpdateManagerBaselines { - $ClusterBaselines = foreach ($ClusterBaseline in $ClusterPatchBaselines) { - [PSCustomObject]@{ - ($LocalizedData.Baseline) = $ClusterBaseline.Name - ($LocalizedData.Description) = $ClusterBaseline.Description - ($LocalizedData.Type) = $ClusterBaseline.BaselineType - ($LocalizedData.TargetType) = $ClusterBaseline.TargetType - ($LocalizedData.LastUpdate) = ($ClusterBaseline.LastUpdateTime).ToLocalTime().ToString() - ($LocalizedData.NumPatches) = $ClusterBaseline.CurrentPatches.Count - } - } - $TableParams = @{ - Name = ($LocalizedData.TableVUMBaselines -f $Cluster) - ColumnWidths = 25, 25, 10, 10, 20, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterBaselines | Sort-Object $LocalizedData.Baseline | Table @TableParams - } - } - if ($Healthcheck.Cluster.VUMCompliance) { - $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.Unknown } | Set-Style -Style Warning - $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.NotCompliant -or $_.$($LocalizedData.Status) -eq $LocalizedData.Incompatible } | Set-Style -Style Critical - } - $TableParams = @{ - Name = ($LocalizedData.TableVUMCompliance -f $Cluster) - ColumnWidths = 25, 50, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterComplianceInfo | Sort-Object $LocalizedData.Entity, $LocalizedData.BaselineInfo | Table @TableParams - } - } else { - Write-PScriboMessage -Message $LocalizedData.VUMPrivilegeMsgBaselines - } - #endregion Cluster VUM Baselines - - #region Cluster VUM Compliance (Advanced Detail Information) - if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { - if ($InfoLevel.Cluster -ge 4 -and $VumServer.Name) { - Try { - $ClusterCompliances = $Cluster | Get-Compliance - } Catch { - Write-PScriboMessage -Message $LocalizedData.VUMComplianceNotAvailable - } - if ($ClusterCompliances) { - Section -Style Heading4 $LocalizedData.UpdateManagerCompliance { - $ClusterComplianceInfo = foreach ($ClusterCompliance in $ClusterCompliances) { - [PSCustomObject]@{ - ($LocalizedData.Entity) = $ClusterCompliance.Entity - ($LocalizedData.BaselineInfo) = $ClusterCompliance.Baseline.Name - ($LocalizedData.Status) = Switch ($ClusterCompliance.Status) { - 'NotCompliant' { $LocalizedData.NotCompliant } - default { $ClusterCompliance.Status } - } - } - } - if ($Healthcheck.Cluster.VUMCompliance) { - $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.Unknown } | Set-Style -Style Warning - $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.NotCompliant -or $_.$($LocalizedData.Status) -eq $LocalizedData.Incompatible } | Set-Style -Style Critical - } - $TableParams = @{ - Name = ($LocalizedData.TableVUMCompliance -f $Cluster) - ColumnWidths = 25, 50, 25 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $ClusterComplianceInfo | Sort-Object $LocalizedData.Entity, $LocalizedData.BaselineInfo | Table @TableParams - } - } - } - } else { - Write-PScriboMessage -Message $LocalizedData.VUMPrivilegeMsgCompliance - } - #endregion Cluster VUM Compliance (Advanced Detail Information) - #region Cluster Permissions Section -Style NOTOCHeading4 -ExcludeFromTOC $LocalizedData.Permissions { Paragraph ($LocalizedData.ParagraphPermissions -f $Cluster) @@ -583,15 +495,15 @@ function Get-AbrVSphereClusterDRS { $VIPermissions = $Cluster | Get-VIPermission $ClusterVIPermissions = foreach ($VIPermission in $VIPermissions) { [PSCustomObject]@{ - ($LocalizedData.UserGroup) = $VIPermission.Principal - ($LocalizedData.IsGroup) = if ($VIPermission.IsGroup) { + $LocalizedData.UserGroup = $VIPermission.Principal + $LocalizedData.IsGroup = if ($VIPermission.IsGroup) { $LocalizedData.Yes } else { $LocalizedData.No } - ($LocalizedData.Role) = $VIPermission.Role - ($LocalizedData.DefinedIn) = $VIPermission.Entity - ($LocalizedData.Propagate) = if ($VIPermission.Propagate) { + $LocalizedData.Role = $VIPermission.Role + $LocalizedData.DefinedIn = $VIPermission.Entity.Name + $LocalizedData.Propagate = if ($VIPermission.Propagate) { $LocalizedData.Yes } else { $LocalizedData.No diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterLCM.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterLCM.ps1 new file mode 100644 index 0000000..f275658 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterLCM.ps1 @@ -0,0 +1,183 @@ +function Get-AbrVSphereClusterLCM { + <# + .SYNOPSIS + Used by As Built Report to retrieve VMware vSphere Lifecycle Manager information for a cluster. + .NOTES + Version: 2.0.0 + Author: Tim Carman + #> + [CmdletBinding()] + param () + + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereClusterLCM + } + + process { + try { + # $vcApiUri is only set for vSphere 7.0+; silently skip on older versions + if (-not $vcApiUri) { return } + + $clusterId = $Cluster.Id -replace 'ClusterComputeResource-', '' + $softwareUri = "$vcApiUri/esx/settings/clusters/$clusterId/software" + + try { + $lcmSoftware = Invoke-RestMethod -Uri $softwareUri -Method Get ` + -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + } catch [Microsoft.PowerShell.Commands.HttpResponseException] { + # 404 = cluster is in VUM baseline mode — silently skip + if ($_.Exception.Response.StatusCode -eq 404) { return } + Write-PScriboMessage -IsWarning ($LocalizedData.LcmError -f $Cluster, $_.Exception.Message) + return + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.LcmError -f $Cluster, $_.Exception.Message) + return + } + + #region vLCM Image Composition + BlankLine + Section -Style NOTOCHeading4 -ExcludeFromTOC $LocalizedData.ImageComposition { + $ImageInfo = [PSCustomObject]@{ + $LocalizedData.BaseImage = $lcmSoftware.base_image.version + $LocalizedData.VendorAddOn = if ($lcmSoftware.add_on) { + "$($lcmSoftware.add_on.name) $($lcmSoftware.add_on.version)" + } else { + $LocalizedData.None + } + } + $TableParams = @{ + Name = ($LocalizedData.TableImageComposition -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ImageInfo | Table @TableParams + + # Components sub-table — InfoLevel >= 4 + if ($InfoLevel.Cluster -ge 4 -and $lcmSoftware.components) { + BlankLine + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.Components { + $ComponentInfo = foreach ($name in ($lcmSoftware.components.PSObject.Properties.Name | Sort-Object)) { + [PSCustomObject]@{ + $LocalizedData.ComponentName = $name + $LocalizedData.ComponentVersion = $lcmSoftware.components.$name.version + } + } + $TableParams = @{ + Name = ($LocalizedData.TableComponents -f $Cluster) + ColumnWidths = 60, 40 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ComponentInfo | Table @TableParams + } + } + } + #endregion vLCM Image Composition + + #region Hardware Support Manager + if ($lcmSoftware.hardware_support -and $lcmSoftware.hardware_support.managers) { + BlankLine + Section -Style NOTOCHeading4 -ExcludeFromTOC $LocalizedData.HardwareSupportManager { + $HsmInfo = foreach ($mgrId in $lcmSoftware.hardware_support.managers.PSObject.Properties.Name) { + $mgr = $lcmSoftware.hardware_support.managers.$mgrId + $packages = if ($mgr.packages) { + ($mgr.packages.PSObject.Properties | ForEach-Object { + "$($_.Name) $($_.Value.version)" + }) -join '; ' + } else { + $LocalizedData.None + } + [PSCustomObject]@{ + $LocalizedData.HsmName = $mgr.name + $LocalizedData.HsmVersion = $mgr.version + $LocalizedData.HsmPackages = $packages + } + } + $TableParams = @{ + Name = ($LocalizedData.TableHardwareSupportManager -f $Cluster) + ColumnWidths = 35, 20, 45 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $HsmInfo | Table @TableParams + } + } + #endregion Hardware Support Manager + + #region Image Compliance + $lcmCompliance = $null + try { + $complianceUri = "$vcApiUri/esx/settings/clusters/$clusterId/software/compliance" + $lcmCompliance = Invoke-RestMethod -Uri $complianceUri -Method Get ` + -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.ComplianceError -f $Cluster, $_.Exception.Message) + } + if ($lcmCompliance) { + BlankLine + Section -Style NOTOCHeading4 -ExcludeFromTOC $LocalizedData.ImageCompliance { + $clusterStatus = $TextInfo.ToTitleCase( + $lcmCompliance.status.ToString().ToLower().Replace('_', ' ') + ) + $ClusterComplianceInfo = [PSCustomObject]@{ + $LocalizedData.Cluster = $Cluster.Name + $LocalizedData.ComplianceStatus = $clusterStatus + } + if ($Healthcheck.Cluster.LCMCompliance) { + $ClusterComplianceInfo | Where-Object { + $_.$($LocalizedData.ComplianceStatus) -ne 'Compliant' + } | Set-Style -Style Warning -Property $LocalizedData.ComplianceStatus + } + $TableParams = @{ + Name = ($LocalizedData.TableImageCompliance -f $Cluster) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterComplianceInfo | Table @TableParams + + # Per-host compliance — InfoLevel >= 4 + if ($InfoLevel.Cluster -ge 4 -and $lcmCompliance.hosts) { + BlankLine + $HostComplianceInfo = foreach ($hostId in $lcmCompliance.hosts.PSObject.Properties.Name) { + $hostData = $lcmCompliance.hosts.$hostId + $hostStatus = $TextInfo.ToTitleCase( + $hostData.status.ToString().ToLower().Replace('_', ' ') + ) + [PSCustomObject]@{ + $LocalizedData.VMHost = $hostData.host_info.name + $LocalizedData.ComplianceStatus = $hostStatus + } + } + if ($Healthcheck.Cluster.LCMCompliance) { + $HostComplianceInfo | Where-Object { + $_.$($LocalizedData.ComplianceStatus) -ne 'Compliant' + } | Set-Style -Style Warning -Property $LocalizedData.ComplianceStatus + } + $TableParams = @{ + Name = ($LocalizedData.TableHostCompliance -f $Cluster) + ColumnWidths = 60, 40 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $HostComplianceInfo | Table @TableParams + } + } + } + #endregion Image Compliance + + } catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 index 7f25e78..3af9b71 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterProactiveHA.ps1 @@ -27,13 +27,12 @@ function Get-AbrVSphereClusterProactiveHA { # Proactive HA is only available in vSphere 6.5 and above if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled -and $vCenter.Version -ge 6.5) { Write-PScriboMessage -Message $LocalizedData.Collecting - # TODO: Proactive HA Providers Section -Style Heading4 $LocalizedData.SectionHeading { Paragraph ($LocalizedData.ParagraphSummary -f $Cluster) #region Proactive HA Failures and Responses Section Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.FailuresAndResponses { $ProactiveHa = [PSCustomObject]@{ - ($LocalizedData.ProactiveHA) = if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled) { + $LocalizedData.ProactiveHA = if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled @@ -77,6 +76,36 @@ function Get-AbrVSphereClusterProactiveHA { $ProactiveHa | Table @TableParams } #endregion Proactive HA Failures and Responses Section + + #region Proactive HA Providers Section + $ClusterProviderIds = $ClusterConfigEx.InfraUpdateHaConfig.Providers + if ($ClusterProviderIds) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.Providers { + $ProviderDetails = foreach ($ProviderId in $ClusterProviderIds) { + $HealthUpdateCount = 0 + try { + $SvcContent = (Get-View -Id ServiceInstance -Server $vCenter -ErrorAction Stop).Content + if ($SvcContent.HealthUpdateManager) { + $HealthUpdateMgr = Get-View -Id $SvcContent.HealthUpdateManager -Server $vCenter -ErrorAction Stop + $HealthUpdateCount = ($HealthUpdateMgr.QueryHealthUpdateInfos($ProviderId)).Count + } + } catch {} + [PSCustomObject]@{ + $LocalizedData.Provider = $ProviderId + $LocalizedData.HealthUpdateCount = $HealthUpdateCount + } + } + $TableParams = @{ + Name = ($LocalizedData.TableProactiveHAProviders -f $Cluster) + ColumnWidths = 70, 30 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ProviderDetails | Sort-Object $LocalizedData.Provider | Table @TableParams + } + } + #endregion Proactive HA Providers Section } } } catch { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterVUM.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterVUM.ps1 new file mode 100644 index 0000000..ec4fc86 --- /dev/null +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereClusterVUM.ps1 @@ -0,0 +1,120 @@ +<# +.SYNOPSIS + Private function to report vSphere cluster Update Manager baselines and compliance. +.DESCRIPTION + Generates a PScriboDocument section detailing the VMware Update Manager baselines + and compliance status for a given cluster. +.NOTES + Version: 2.0.0 + Author: Tim Carman + Twitter: @tpcarman + Github: tpcarman +.INPUTS + None. Uses variables from the parent scope: + $Cluster, $InfoLevel, $Report, $Healthcheck, $UserPrivileges, $VumServer, + $vCenter, $VUMConnection, $reportTranslate +.OUTPUTS + None. Writes PScriboDocument content directly. +#> +function Get-AbrVSphereClusterVUM { + [CmdletBinding()] + param () + begin { + $LocalizedData = $reportTranslate.GetAbrVSphereClusterVUM + } + process { + Try { + #region Cluster VUM Baselines + if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { + if ($VUMConnection) { + Try { + $ClusterPatchBaselines = $Cluster | Get-PatchBaseline -ErrorAction Stop + } Catch { + Write-PScriboMessage -Message $LocalizedData.VUMBaselineNotAvailable + } + if ($ClusterPatchBaselines) { + Section -Style Heading3 $LocalizedData.UpdateManagerBaselines { + $ClusterBaselines = foreach ($ClusterBaseline in $ClusterPatchBaselines) { + [PSCustomObject]@{ + $LocalizedData.Baseline = $ClusterBaseline.Name + $LocalizedData.Description = $ClusterBaseline.Description + $LocalizedData.Type = $ClusterBaseline.BaselineType + $LocalizedData.TargetType = $ClusterBaseline.TargetType + $LocalizedData.LastUpdate = ($ClusterBaseline.LastUpdateTime).ToLocalTime().ToString() + $LocalizedData.NumPatches = $ClusterBaseline.CurrentPatches.Count + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMBaselines -f $Cluster) + ColumnWidths = 25, 25, 10, 10, 20, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterBaselines | Sort-Object $LocalizedData.Baseline | Table @TableParams + } + } + if ($Healthcheck.Cluster.VUMCompliance) { + $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.Unknown } | Set-Style -Style Warning + $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.NotCompliant -or $_.$($LocalizedData.Status) -eq $LocalizedData.Incompatible } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMCompliance -f $Cluster) + ColumnWidths = 25, 50, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterComplianceInfo | Sort-Object $LocalizedData.Entity, $LocalizedData.BaselineInfo | Table @TableParams + } + } else { + Write-PScriboMessage -Message $LocalizedData.VUMPrivilegeMsgBaselines + } + #endregion Cluster VUM Baselines + + #region Cluster VUM Compliance (Advanced Detail Information) + if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { + if ($InfoLevel.Cluster -ge 4 -and $VumServer.Name) { + Try { + $ClusterCompliances = $Cluster | Get-Compliance -ErrorAction SilentlyContinue + } Catch { + Write-PScriboMessage -Message $LocalizedData.VUMComplianceNotAvailable + } + if ($ClusterCompliances) { + Section -Style Heading4 $LocalizedData.UpdateManagerCompliance { + $ClusterComplianceInfo = foreach ($ClusterCompliance in $ClusterCompliances) { + [PSCustomObject]@{ + $LocalizedData.Entity = $ClusterCompliance.Entity.Name + $LocalizedData.BaselineInfo = $ClusterCompliance.Baseline.Name + $LocalizedData.Status = Switch ($ClusterCompliance.Status) { + 'NotCompliant' { $LocalizedData.NotCompliant } + default { $ClusterCompliance.Status } + } + } + } + if ($Healthcheck.Cluster.VUMCompliance) { + $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.Unknown } | Set-Style -Style Warning + $ClusterComplianceInfo | Where-Object { $_.$($LocalizedData.Status) -eq $LocalizedData.NotCompliant -or $_.$($LocalizedData.Status) -eq $LocalizedData.Incompatible } | Set-Style -Style Critical + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMCompliance -f $Cluster) + ColumnWidths = 25, 50, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $ClusterComplianceInfo | Sort-Object $LocalizedData.Entity, $LocalizedData.BaselineInfo | Table @TableParams + } + } + } + } else { + Write-PScriboMessage -Message $LocalizedData.VUMPrivilegeMsgCompliance + } + #endregion Cluster VUM Compliance (Advanced Detail Information) + + } Catch { + Write-PScriboMessage -IsWarning $($_.Exception.Message) + } + } + end {} +} diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 index 1e8a040..80bc4a2 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 @@ -62,9 +62,6 @@ function Get-AbrVSphereDSCluster { #region Datastore Cluster Detailed Information if ($InfoLevel.DSCluster -ge 3) { foreach ($DSCluster in $DSClusters) { - # TODO: Space Load Balance Config, IO Load Balance Config, Rules - # TODO: Test Tags - $DSCUsedPercent = if (0 -in @($DSCluster.FreeSpaceGB, $DSCluster.CapacityGB)) { 0 } else { [math]::Round((100 - (($DSCluster.FreeSpaceGB) / ($DSCluster.CapacityGB) * 100)), 2) } $DSCFreePercent = if (0 -in @($DSCluster.FreeSpaceGB, $DSCluster.CapacityGB)) { 0 } else { [math]::Round(($DSCluster.FreeSpaceGB / $DSCluster.CapacityGB) * 100, 2) } $DSCUsedCapacityGB = ($DSCluster.CapacityGB - $DSCluster.FreeSpaceGB) @@ -93,16 +90,13 @@ function Get-AbrVSphereDSCluster { $LocalizedData.FreeCapacity = "{0} ({1}%)" -f (Convert-DataSize $DSCluster.FreeSpaceGB), $DSCFreePercent $LocalizedData.PercentUsed = $DSCUsedPercent } - <# $MemberProps = @{ 'InputObject' = $DSClusterDetail 'MemberType' = 'NoteProperty' } - - if ($TagAssignments | Where-Object {$_.entity -eq $DSCluster}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $DSCluster}).Tag -join ',') + if ($TagAssignments | Where-Object { $_.entity -eq $DSCluster }) { + Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $DSCluster }).Tag -join ', ') } - #> if ($Healthcheck.DSCluster.CapacityUtilization) { $DSClusterDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 90 } | Set-Style -Style Critical -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity $DSClusterDetail | Where-Object { $_.$($LocalizedData.PercentUsed) -ge 75 -and $_.$($LocalizedData.PercentUsed) -lt 90 } | Set-Style -Style Warning -Property $LocalizedData.UsedCapacity, $LocalizedData.FreeCapacity @@ -145,6 +139,55 @@ function Get-AbrVSphereDSCluster { ($null -eq $_.IntraVmAffinity) ) } + $SDRSRules = $PodConfig.Rule + $SpaceConfig = $PodConfig.SpaceLoadBalanceConfig + if ($SpaceConfig) { + Section -Style Heading4 $LocalizedData.SpaceLoadBalanceConfig { + $SpaceLoadBalanceDetail = [PSCustomObject]@{ + $LocalizedData.SpaceMinDiff = "$($SpaceConfig.MinSpaceUtilizationDifference)%" + $LocalizedData.SpaceThresholdMode = switch ($SpaceConfig.SpaceThresholdMode) { + 'utilization' { $LocalizedData.UtilizationMode } + 'freeSpace' { $LocalizedData.FreeSpaceMode } + default { $SpaceConfig.SpaceThresholdMode } + } + $LocalizedData.UtilizationThreshold = "$($SpaceConfig.SpaceUtilizationThreshold)%" + $LocalizedData.FreeSpaceThreshold = "$($SpaceConfig.FreeSpaceThresholdGB) GB" + } + $TableParams = @{ + Name = ($LocalizedData.TableSpaceLoadBalance -f $DSCluster.Name) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $SpaceLoadBalanceDetail | Table @TableParams + } + } + $IOConfig = $PodConfig.IoLoadBalanceConfig + if ($IOConfig) { + Section -Style Heading4 $LocalizedData.IOLoadBalanceConfig { + $IOLoadBalanceDetail = [PSCustomObject]@{ + $LocalizedData.IOCongestionThreshold = "$($IOConfig.CongestionThreshold) ms" + $LocalizedData.IOLatencyThreshold = "$($IOConfig.IoLatencyThreshold) ms" + $LocalizedData.IOReservationMode = switch ($IOConfig.ReservationThresholdMode) { + 'ioOperationsCountThreshold' { $LocalizedData.IOPSThresholdMode } + 'reservationInMbps' { $LocalizedData.ReservationMbpsMode } + 'reservationInIops' { $LocalizedData.ReservationIopsMode } + default { $IOConfig.ReservationThresholdMode } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableIOLoadBalance -f $DSCluster.Name) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $IOLoadBalanceDetail | Table @TableParams + } + } } if ($VMOverrides) { @@ -174,6 +217,33 @@ function Get-AbrVSphereDSCluster { $VMOverrideDetails | Sort-Object $LocalizedData.VirtualMachine | Table @TableParams } } + if ($SDRSRules) { + $SDRSRuleDetails = foreach ($Rule in $SDRSRules) { + [PSCustomObject]@{ + $LocalizedData.RuleName = $Rule.Name + $LocalizedData.RuleEnabled = if ($Rule.Enabled) { $LocalizedData.Yes } else { $LocalizedData.No } + $LocalizedData.RuleType = if ($Rule.GetType().Name -like '*AntiAffinity*') { $LocalizedData.RuleAntiAffinity } else { $LocalizedData.RuleAffinity } + $LocalizedData.RuleVMs = if ($Rule.Vm) { + $RuleVMs = foreach ($Vm in $Rule.Vm) { + $VMLookup."$($Vm.Type)-$($Vm.Value)" + } + ($RuleVMs | Sort-Object) -join ', ' + } else { + '--' + } + } + } + Section -Style Heading4 $LocalizedData.SDRSRules { + $TableParams = @{ + Name = ($LocalizedData.TableSDRSRules -f $DSCluster.Name) + ColumnWidths = 25, 15, 20, 40 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $SDRSRuleDetails | Sort-Object $LocalizedData.RuleName | Table @TableParams + } + } #endregion SDRS VM Overrides } } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 index 51fa3d1..8d7e19a 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 @@ -68,8 +68,6 @@ function Get-AbrVSphereDatastore { #region Datastore Detailed Information if ($InfoLevel.Datastore -ge 3) { foreach ($Datastore in $Datastores) { - # TODO: Test Tags - $DsUsedPercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) { 0 } else { [math]::Round((100 - (($Datastore.FreeSpaceGB) / ($Datastore.CapacityGB) * 100)), 2) } $DSFreePercent = if (0 -in @($Datastore.FreeSpaceGB, $Datastore.CapacityGB)) { 0 } else { [math]::Round(($Datastore.FreeSpaceGB / $Datastore.CapacityGB) * 100, 2) } $UsedCapacityGB = ($Datastore.CapacityGB) - ($Datastore.FreeSpaceGB) @@ -79,7 +77,7 @@ function Get-AbrVSphereDatastore { $DatastoreDetail = [PSCustomObject]@{ $LocalizedData.Datastore = $Datastore.Name $LocalizedData.ID = $Datastore.Id - $LocalizedData.Datacenter = $Datastore.Datacenter + $LocalizedData.Datacenter = $Datastore.Datacenter.Name $LocalizedData.Type = $Datastore.Type $LocalizedData.Version = if ($Datastore.FileSystemVersion) { $Datastore.FileSystemVersion @@ -118,11 +116,9 @@ function Get-AbrVSphereDatastore { 'InputObject' = $DatastoreDetail 'MemberType' = 'NoteProperty' } - <# - if ($TagAssignments | Where-Object {$_.entity -eq $Datastore}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $Datastore}).Tag -join ',') + if ($TagAssignments | Where-Object { $_.entity -eq $Datastore }) { + Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $Datastore }).Tag -join ', ') } - #> #region Datastore Advanced Detailed Information if ($InfoLevel.Datastore -ge 4) { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 index 4483cd7..cd18dbe 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 @@ -53,8 +53,6 @@ function Get-AbrVSphereNetwork { #region Distributed Switch Detailed Information if ($InfoLevel.Network -ge 3) { - # TODO: LACP, NetFlow, NIOC - # TODO: Test Tags foreach ($VDS in ($VDSwitches)) { #region VDS Section Section -Style Heading3 $VDS { @@ -85,15 +83,13 @@ function Get-AbrVSphereNetwork { default { $VDS.LinkDiscoveryProtocolOperation } } } - <# $MemberProps = @{ 'InputObject' = $VDSwitchDetail 'MemberType' = 'NoteProperty' } - if ($TagAssignments | Where-Object {$_.entity -eq $VDS}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VDS}).Tag -join ',') + if ($TagAssignments | Where-Object { $_.entity -eq $VDS }) { + Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $VDS }).Tag -join ', ') } - #> #region Network Advanced Detail Information if ($InfoLevel.Network -ge 4) { $VDSwitchDetail | ForEach-Object { @@ -208,8 +204,85 @@ function Get-AbrVSphereNetwork { } #endregion Distributed Switch Traffic Shaping + #region Distributed Switch LACP + $VDSLacpGroups = $VDS.ExtensionData.Config.LacpGroupConfig + if ($VDSLacpGroups) { + Section -Style Heading4 $LocalizedData.VDSLACP { + $LACPDetail = foreach ($LacpGroup in $VDSLacpGroups) { + [PSCustomObject]@{ + $LocalizedData.VDSwitch = $VDS.Name + $LocalizedData.LACPEnabled = $LocalizedData.Yes + $LocalizedData.LACPMode = switch ($LacpGroup.Mode) { + 'active' { $LocalizedData.LACPActive } + 'passive' { $LocalizedData.LACPPassive } + default { $LacpGroup.Mode } + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSLACP -f $VDS) + ColumnWidths = 34, 33, 33 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $LACPDetail | Table @TableParams + } + } + #endregion Distributed Switch LACP + + #region Distributed Switch NetFlow + $VDSNetFlow = $VDS.ExtensionData.Config.IpfixConfig + if ($VDSNetFlow -and $VDSNetFlow.CollectorIpAddress) { + Section -Style Heading4 $LocalizedData.VDSNetFlow { + $NetFlowDetail = [PSCustomObject]@{ + $LocalizedData.VDSwitch = $VDS.Name + $LocalizedData.CollectorIP = $VDSNetFlow.CollectorIpAddress + $LocalizedData.CollectorPort = $VDSNetFlow.CollectorPort + $LocalizedData.ActiveFlowTimeout = "$($VDSNetFlow.ActiveFlowTimeout) s" + $LocalizedData.IdleFlowTimeout = "$($VDSNetFlow.IdleFlowTimeout) s" + $LocalizedData.SamplingRate = $VDSNetFlow.SamplingRate + $LocalizedData.InternalFlowsOnly = if ($VDSNetFlow.InternalFlowsOnly) { $LocalizedData.Yes } else { $LocalizedData.No } + } + $TableParams = @{ + Name = ($LocalizedData.TableVDSNetFlow -f $VDS) + ColumnWidths = 22, 13, 13, 13, 13, 13, 13 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $NetFlowDetail | Table @TableParams + } + } + #endregion Distributed Switch NetFlow + + #region Distributed Switch NIOC Resource Pools + if ($InfoLevel.Network -ge 4 -and $VDS.ExtensionData.Config.NetworkResourceManagementEnabled) { + $VDSNIOCPools = $VDS | Get-VDNetworkResourcePool | Sort-Object Name + if ($VDSNIOCPools) { + Section -Style Heading4 $LocalizedData.NIOCResourcePools { + $NIOCPoolDetails = foreach ($Pool in $VDSNIOCPools) { + [PSCustomObject]@{ + $LocalizedData.NIOCResourcePool = $Pool.Name + $LocalizedData.NIOCSharesLevel = $Pool.SharesLevel + $LocalizedData.NIOCSharesValue = $Pool.NumShares + $LocalizedData.NIOCLimitMbps = if ($Pool.Limit -eq -1) { $LocalizedData.Unlimited } else { $Pool.Limit } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableNIOCResourcePools -f $VDS) + ColumnWidths = 35, 20, 20, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $NIOCPoolDetails | Table @TableParams + } + } + } + #endregion Distributed Switch NIOC Resource Pools + #region Distributed Switch Port Groups - # TODO: Test Tags $VDSPortgroups = $VDS | Get-VDPortgroup if ($VDSPortgroups) { Section -Style Heading4 $LocalizedData.VDSPortGroups { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 index 46d2f54..934ce71 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 @@ -58,7 +58,6 @@ function Get-AbrVSphereResourcePool { #endregion Resource Pool Advanced Summary #region Resource Pool Detailed Information - # TODO: Test Tags if ($InfoLevel.ResourcePool -ge 3) { foreach ($ResourcePool in $ResourcePools) { Section -Style Heading3 $ResourcePool.Name { @@ -92,16 +91,13 @@ function Get-AbrVSphereResourcePool { } $LocalizedData.NumVMs = $ResourcePool.ExtensionData.VM.Count } - <# $MemberProps = @{ 'InputObject' = $ResourcePoolDetail 'MemberType' = 'NoteProperty' } - - if ($TagAssignments | Where-Object {$_.entity -eq $ResourcePool}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $ResourcePool}).Tag -join ',') + if ($TagAssignments | Where-Object { $_.entity -eq $ResourcePool }) { + Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $ResourcePool }).Tag -join ', ') } - #> #region Resource Pool Advanced Detail Information if ($InfoLevel.ResourcePool -ge 4) { $ResourcePoolDetail | ForEach-Object { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 index 182279b..a04efa5 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 @@ -133,7 +133,6 @@ function Get-AbrVSphereVM { #endregion Virtual Machine Advanced Summary #region Virtual Machine Detailed Information - # TODO: Test Tags if ($InfoLevel.VM -ge 3) { if ($UserPrivileges -contains 'StorageProfile.View') { $VMSpbmConfig = Get-SpbmEntityConfiguration -VM ($VMs) | Where-Object { $null -ne $_.StoragePolicy } @@ -143,6 +142,18 @@ function Get-AbrVSphereVM { if ($InfoLevel.VM -ge 4) { $VMHardDisks = Get-HardDisk -VM ($VMs) -Server $vCenter } + $VMComplianceData = $null + if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { + if ($VUMConnection) { + Try { + $VMComplianceData = $VMs | Get-Compliance -ErrorAction SilentlyContinue + } Catch { + Write-PScriboMessage -Message $LocalizedData.VUMComplianceError + } + } + } else { + Write-PScriboMessage -Message $LocalizedData.InsufficientPrivVUMCompliance + } foreach ($VM in $VMs) { Section -Style Heading3 $VM.name { $VMUptime = Get-Uptime -VM $VM @@ -251,11 +262,9 @@ function Get-AbrVSphereVM { #if ($VMView.Config.CreateDate) { # Add-Member @MemberProps -Name 'Creation Date' -Value ($VMView.Config.CreateDate).ToLocalTime().ToString() #} - <# - if ($TagAssignments | Where-Object {$_.entity -eq $VM}) { - Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VM}).Tag -join ',') + if ($TagAssignments | Where-Object { $_.entity -eq $VM }) { + Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $VM }).Tag -join ', ') } - #> if ($VM.Notes) { Add-Member @MemberProps -Name $LocalizedData.Notes -Value $VM.Notes } @@ -312,11 +321,11 @@ function Get-AbrVSphereVM { Section -Style Heading4 $LocalizedData.NetworkAdapters { $VMnicInfo = foreach ($VMnic in $VMnics) { [PSCustomObject]@{ - $LocalizedData.NICName = $VMnic.Device + $LocalizedData.NICName = $VMnic.Device.DeviceInfo.Label $LocalizedData.NICConnected = if ($VMnic.Connected) { $LocalizedData.Connected } else { $LocalizedData.NotConnected } - $LocalizedData.NetworkName = switch -wildcard ($VMnic.Device.NetworkName) { - 'dvportgroup*' { $VDPortgroupLookup."$($VMnic.Device.NetworkName)" } - default { $VMnic.Device.NetworkName } + $LocalizedData.NetworkName = switch -wildcard ($VMnic.NetworkName) { + 'dvportgroup*' { $VDPortgroupLookup."$($VMnic.NetworkName)" } + default { $VMnic.NetworkName } } $LocalizedData.NICType = $VMnic.Device.Type $LocalizedData.IPAddress = $VMnic.IpAddress -join [Environment]::NewLine @@ -488,6 +497,39 @@ function Get-AbrVSphereVM { $VMSnapshots | Table @TableParams } } + + #region VM VUM Compliance + $VMCompliances = $VMComplianceData | Where-Object { $_.Entity -eq $VM } + if ($VMCompliances) { + BlankLine + Section -Style NOTOCHeading4 -ExcludeFromTOC $LocalizedData.VUMCompliance { + $VMComplianceInfo = foreach ($VMCompliance in $VMCompliances) { + $compStatus = switch ($VMCompliance.Status) { + 'NotCompliant' { $LocalizedData.NotCompliant } + 'Incompatible' { $LocalizedData.Incompatible } + 'Unknown' { $LocalizedData.Unknown } + default { $VMCompliance.Status } + } + [PSCustomObject]@{ + $LocalizedData.VUMBaselineName = $VMCompliance.Baseline.Name + $LocalizedData.VUMStatus = $compStatus + } + } + if ($Healthcheck.VM.VUMCompliance) { + $VMComplianceInfo | Where-Object { $_.$($LocalizedData.VUMStatus) -eq $LocalizedData.Unknown } | Set-Style -Style Warning -Property $LocalizedData.VUMStatus + $VMComplianceInfo | Where-Object { $_.$($LocalizedData.VUMStatus) -eq $LocalizedData.NotCompliant -or $_.$($LocalizedData.VUMStatus) -eq $LocalizedData.Incompatible } | Set-Style -Style Critical -Property $LocalizedData.VUMStatus + } + $TableParams = @{ + Name = ($LocalizedData.TableVUMCompliance -f $VM.Name) + ColumnWidths = 75, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VMComplianceInfo | Sort-Object $LocalizedData.VUMBaselineName | Table @TableParams + } + } + #endregion VM VUM Compliance } } } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 index 37a1606..8f98257 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 @@ -71,8 +71,9 @@ function Get-AbrVSphereVMHost { foreach ($VMHost in ($VMHosts | Where-Object { $_.ConnectionState -eq 'Connected' -or $_.ConnectionState -eq 'Maintenance' })) { #region VMHost Section Section -Style Heading3 $VMHost { - # TODO: Host Certificate, Swap File Location - # TODO: Test Tags + if ($TagAssignments | Where-Object { $_.entity -eq $VMHost }) { + Paragraph ($LocalizedData.Tags + ': ' + (($TagAssignments | Where-Object { $_.entity -eq $VMHost }).Tag -join ', ')) + } Get-AbrVSphereVMHostHardware Get-AbrVSphereVMHostSystem Get-AbrVSphereVMHostStorage diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 index 07a6f4e..53a4417 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostHardware.ps1 @@ -37,7 +37,7 @@ function Get-AbrVSphereVMHostHardware { default { $TextInfo.ToTitleCase($VMHost.ConnectionState) } } $LocalizedData.ID = $VMHost.Id - $LocalizedData.Parent = $VMHost.Parent + $LocalizedData.Parent = $VMHost.Parent.Name $LocalizedData.Manufacturer = $VMHost.Manufacturer $LocalizedData.Model = $VMHost.Model $LocalizedData.SerialNumber = if ($VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber) { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 index c59eb5a..2a43b2b 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostNetwork.ps1 @@ -401,7 +401,7 @@ function Get-AbrVSphereVMHostNetwork { #region ESXi Host Standard Virtual Switch Properties $VSSProperties = foreach ($VSSwitchNicTeam in $VSSwitchNicTeaming) { [PSCustomObject]@{ - $LocalizedData.VirtualSwitch = $VSSwitchNicTeam.VirtualSwitch + $LocalizedData.VirtualSwitch = $VSSwitchNicTeam.VirtualSwitch.Name $LocalizedData.MTU = $VSSwitchNicTeam.VirtualSwitch.Mtu $LocalizedData.NumberOfPorts = $VSSwitchNicTeam.VirtualSwitch.NumPorts $LocalizedData.NumberOfPortsAvailable = $VSSwitchNicTeam.VirtualSwitch.NumPortsAvailable @@ -424,7 +424,7 @@ function Get-AbrVSphereVMHostNetwork { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSSecurity { $VssSecurity = foreach ($VssSec in $VssSecurity) { [PSCustomObject]@{ - $LocalizedData.VirtualSwitch = $VssSec.VirtualSwitch + $LocalizedData.VirtualSwitch = $VssSec.VirtualSwitch.Name $LocalizedData.PromiscuousMode = if ($VssSec.AllowPromiscuous) { $LocalizedData.Accept } else { @@ -488,7 +488,7 @@ function Get-AbrVSphereVMHostNetwork { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSTeamingFailover { $VssNicTeaming = foreach ($VssNicTeam in $VssNicTeamingPolicy) { [PSCustomObject]@{ - $LocalizedData.VirtualSwitch = $VssNicTeam.VirtualSwitch + $LocalizedData.VirtualSwitch = $VssNicTeam.VirtualSwitch.Name $LocalizedData.LoadBalancing = switch ($VssNicTeam.LoadBalancingPolicy) { 'LoadbalanceSrcId' { $LocalizedData.LBSrcId } 'LoadbalanceSrcMac' { $LocalizedData.LBSrcMac } @@ -559,7 +559,7 @@ function Get-AbrVSphereVMHostNetwork { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSPGSecurity { $VssPortgroupSecurity = foreach ($VssPortgroupSec in $VssPortgroupSecurity) { [PSCustomObject]@{ - $LocalizedData.PortGroup = $VssPortgroupSec.VirtualPortGroup + $LocalizedData.PortGroup = $VssPortgroupSec.VirtualPortGroup.Name $LocalizedData.VirtualSwitch = $VssPortgroupSec.virtualportgroup.virtualswitchname $LocalizedData.PromiscuousMode = if ($VssPortgroupSec.AllowPromiscuous) { $LocalizedData.Accept @@ -625,7 +625,7 @@ function Get-AbrVSphereVMHostNetwork { Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.VSPGTeamingFailover { $VssPortgroupNicTeaming = foreach ($VssPortgroupNicTeam in $VssPortgroupNicTeaming) { [PSCustomObject]@{ - $LocalizedData.PortGroup = $VssPortgroupNicTeam.VirtualPortGroup + $LocalizedData.PortGroup = $VssPortgroupNicTeam.VirtualPortGroup.Name $LocalizedData.VirtualSwitch = $VssPortgroupNicTeam.virtualportgroup.virtualswitchname $LocalizedData.LoadBalancing = switch ($VssPortgroupNicTeam.LoadBalancingPolicy) { 'LoadbalanceSrcId' { $LocalizedData.LBSrcId } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 index 125d009..6c7d039 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHostSystem.ps1 @@ -99,25 +99,96 @@ function Get-AbrVSphereVMHostSystem { } #endregion ESXi Host Time Configuration - #region ESXi Host Syslog Configuration - $SyslogConfig = $VMHost | Get-VMHostSysLogServer - if ($SyslogConfig) { - try { - Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.Syslog { - # TODO: Syslog Rotate & Size, Log Directory (Adv Settings) - $SyslogConfig = $SyslogConfig | Select-Object @{L = $LocalizedData.SyslogHost; E = { $_.Host } }, @{L = $LocalizedData.SyslogPort; E = { $_.Port } } + #region ESXi Host VM Swap File Location + try { + $VMHostSwapView = Get-View $VMHost -Property 'config.vmSwapPlacement', 'config.localSwapDatastore' -ErrorAction SilentlyContinue + $SwapPlacement = $VMHostSwapView.Config.VmSwapPlacement + if ($SwapPlacement) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.SwapFileLocation { + $SwapDatastoreRef = $VMHostSwapView.Config.LocalSwapDatastore + $SwapDatastoreName = if ($SwapDatastoreRef -and $SwapPlacement -eq 'hostLocal') { + ($Datastores | Where-Object { $_.ExtensionData.MoRef.Value -eq $SwapDatastoreRef.Value }).Name + } else { + '--' + } + $SwapFileDetail = [PSCustomObject]@{ + $LocalizedData.SwapFilePlacement = switch ($SwapPlacement) { + 'vmDirectory' { $LocalizedData.WithVM } + 'hostLocal' { $LocalizedData.HostLocal } + default { $SwapPlacement } + } + $LocalizedData.SwapDatastore = $SwapDatastoreName + } $TableParams = @{ - Name = ($LocalizedData.TableSyslog -f $VMHost) + Name = ($LocalizedData.TableSwapFileLocation -f $VMHost) ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } - $SyslogConfig | Table @TableParams + $SwapFileDetail | Table @TableParams } - } catch { - Write-PScriboMessage -Message ($LocalizedData.SyslogError -f $VMHost.Name, $_.Exception.Message) } + } catch { + Write-PScriboMessage -Message ($LocalizedData.SwapFileLocationError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host VM Swap File Location + + #region ESXi Host Certificate Information + try { + $CertBytes = $VMHost.ExtensionData.Config.Certificate + if ($CertBytes) { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.HostCertificate { + $Cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]]$CertBytes) + $ThumbprintBytes = $Cert.GetCertHash([System.Security.Cryptography.HashAlgorithmName]::SHA256) + $HostCertDetail = [PSCustomObject]@{ + $LocalizedData.CertSubject = $Cert.Subject + $LocalizedData.CertIssuer = $Cert.Issuer + $LocalizedData.CertValidFrom = $Cert.NotBefore.ToLocalTime().ToString() + $LocalizedData.CertValidTo = $Cert.NotAfter.ToLocalTime().ToString() + $LocalizedData.CertThumbprint = [System.BitConverter]::ToString($ThumbprintBytes) -replace '-', ':' + } + $TableParams = @{ + Name = ($LocalizedData.TableHostCertificate -f $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $HostCertDetail | Table @TableParams + } + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.HostCertificateError -f $VMHost.Name, $_.Exception.Message) + } + #endregion ESXi Host Certificate Information + + #region ESXi Host Syslog Configuration + try { + Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.Syslog { + $SyslogConfig = $VMHost | Get-VMHostSysLogServer + $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter + $SyslogGlobalConfig = $esxcli.system.syslog.config.get.Invoke() + $SyslogDetail = [PSCustomObject]@{ + $LocalizedData.SyslogHost = if ($SyslogConfig) { ($SyslogConfig.Host | Sort-Object) -join ', ' } else { '--' } + $LocalizedData.SyslogPort = if ($SyslogConfig) { ($SyslogConfig.Port | Select-Object -Unique) -join ', ' } else { '--' } + $LocalizedData.LogDir = $SyslogGlobalConfig.LogDir + $LocalizedData.LogRotations = $SyslogGlobalConfig.DefaultRotation + $LocalizedData.LogSize = $SyslogGlobalConfig.DefaultSize + } + $TableParams = @{ + Name = ($LocalizedData.TableSyslog -f $VMHost) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $SyslogDetail | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.SyslogError -f $VMHost.Name, $_.Exception.Message) } #endregion ESXi Host Syslog Configuration @@ -165,7 +236,7 @@ function Get-AbrVSphereVMHostSystem { if ($UserPrivileges -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { if ($VumServer.Name) { try { - $VMHostCompliances = $VMHost | Get-Compliance + $VMHostCompliances = $VMHost | Get-Compliance -ErrorAction SilentlyContinue } catch { Write-PScriboMessage -Message $LocalizedData.VUMComplianceNotAvailable } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 index e80f10f..2c4f82c 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 @@ -25,29 +25,50 @@ function Get-AbrVSphereVUM { } catch { Write-PScriboMessage -Message $LocalizedData.NotAvailable } - if ($VUMBaselines) { + + # Query software depots (vSphere 7.0+ REST API) + $OnlineDepots = $null + $OfflineDepots = $null + if ($vcApiUri) { + try { + $OnlineDepots = Invoke-RestMethod -Uri "$vcApiUri/esx/settings/depots/online" ` + -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.DepotError -f $_.Exception.Message) + } + try { + $OfflineDepots = Invoke-RestMethod -Uri "$vcApiUri/esx/settings/depots/offline" ` + -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.DepotError -f $_.Exception.Message) + } + } + + if ($VUMBaselines -or $OnlineDepots -or $OfflineDepots) { Section -Style Heading2 $LocalizedData.SectionHeading { Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) #region VUM Baseline Detailed Information - Section -Style Heading3 $LocalizedData.Baselines { - $VUMBaselineInfo = foreach ($VUMBaseline in $VUMBaselines) { - [PSCustomObject]@{ - $LocalizedData.BaselineName = $VUMBaseline.Name - $LocalizedData.Description = $VUMBaseline.Description - $LocalizedData.Type = $VUMBaseline.BaselineType - $LocalizedData.TargetType = $VUMBaseline.TargetType - $LocalizedData.LastUpdate = ($VUMBaseline.LastUpdateTime).ToLocalTime().ToString() - $LocalizedData.NumPatches = $VUMBaseline.CurrentPatches.Count + if ($VUMBaselines) { + Section -Style Heading3 $LocalizedData.Baselines { + $VUMBaselineInfo = foreach ($VUMBaseline in $VUMBaselines) { + [PSCustomObject]@{ + $LocalizedData.BaselineName = $VUMBaseline.Name + $LocalizedData.Description = $VUMBaseline.Description + $LocalizedData.Type = $VUMBaseline.BaselineType + $LocalizedData.TargetType = $VUMBaseline.TargetType + $LocalizedData.LastUpdate = ($VUMBaseline.LastUpdateTime).ToLocalTime().ToString() + $LocalizedData.NumPatches = $VUMBaseline.CurrentPatches.Count + } } + $TableParams = @{ + Name = ($LocalizedData.TableVUMBaselines -f $vCenterServerName) + ColumnWidths = 25, 25, 10, 10, 20, 10 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VUMBaselineInfo | Sort-Object $LocalizedData.BaselineName | Table @TableParams } - $TableParams = @{ - Name = ($LocalizedData.TableVUMBaselines -f $vCenterServerName) - ColumnWidths = 25, 25, 10, 10, 20, 10 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $VUMBaselineInfo | Sort-Object $LocalizedData.BaselineName | Table @TableParams } #endregion VUM Baseline Detailed Information @@ -80,6 +101,63 @@ function Get-AbrVSphereVUM { } } #endregion VUM Comprehensive Information + + #region Software Depots + if ($OnlineDepots -or $OfflineDepots) { + BlankLine + Section -Style Heading3 $LocalizedData.SoftwareDepots { + if ($OnlineDepots) { + Section -Style Heading4 $LocalizedData.OnlineDepots { + $OnlineDepotInfo = foreach ($id in $OnlineDepots.PSObject.Properties.Name) { + $depot = $OnlineDepots.$id + # vSphere 7.x uses 'depot_url'; vSphere 8.x uses 'url' + $depotUrl = if ($depot.url) { $depot.url } ` + elseif ($depot.depot_url) { $depot.depot_url } ` + else { '--' } + [PSCustomObject]@{ + $LocalizedData.Description = $depot.description + $LocalizedData.DepotUrl = $depotUrl + $LocalizedData.SystemDefined = $depot.system_defined + $LocalizedData.DepotEnabled = $depot.enabled + } + } + $TableParams = @{ + Name = ($LocalizedData.TableOnlineDepots -f $vCenterServerName) + ColumnWidths = 30, 40, 15, 15 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OnlineDepotInfo | Sort-Object $LocalizedData.Description | Table @TableParams + } + } + if ($OfflineDepots) { + $OfflineDepotInfo = foreach ($id in $OfflineDepots.PSObject.Properties.Name) { + $depot = $OfflineDepots.$id + # Skip system-generated bundles (HA, WCP, etc.) which have no location + if (-not $depot.location) { continue } + [PSCustomObject]@{ + $LocalizedData.Description = $depot.description + $LocalizedData.DepotLocation = $depot.location + } + } + if ($OfflineDepotInfo) { + if ($OnlineDepots) { BlankLine } + Section -Style Heading4 $LocalizedData.OfflineDepots { + $TableParams = @{ + Name = ($LocalizedData.TableOfflineDepots -f $vCenterServerName) + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $OfflineDepotInfo | Sort-Object $LocalizedData.Description | Table @TableParams + } + } + } + } + } + #endregion Software Depots } } } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 index 2a3fa97..275b84c 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 @@ -226,8 +226,8 @@ function Get-AbrVSpherevCenter { $VcenterCertMgmt = [PSCustomObject]@{ $LocalizedData.Subject = $VIMachineCert.Subject $LocalizedData.Issuer = $VIMachineCert.Issuer - $LocalizedData.ValidFrom = $VIMachineCert.NotBefore - $LocalizedData.ValidTo = $VIMachineCert.NotAfter + $LocalizedData.ValidFrom = $VIMachineCert.NotBefore.ToString() + $LocalizedData.ValidTo = $VIMachineCert.NotAfter.ToString() $LocalizedData.Thumbprint = $VIMachineCert.Thumbprint $LocalizedData.CertStatus = $CertificateStatus $LocalizedData.Mode = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.mode' }).Value diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 index abd29f7..58bb966 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 @@ -164,6 +164,32 @@ function Get-AbrVSpherevSAN { Write-PScriboMessage -Message ($LocalizedData.ESAError -f $VsanCluster.Name, $_.Exception.Message) } + #region vSAN Services + try { + Section -Style Heading4 $LocalizedData.ServicesSection { + $VsanServices = @( + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.PerformanceService; $LocalizedData.Status = if ($VsanCluster.PerformanceServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.FileService; $LocalizedData.Status = if ($VsanCluster.FileServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.iSCSITargetService; $LocalizedData.Status = if ($VsanCluster.IscsiTargetServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Deduplication; $LocalizedData.Status = if ($VsanCluster.SpaceEfficiencyEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Encryption; $LocalizedData.Status = if ($VsanCluster.EncryptionEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HistoricalHealthService; $LocalizedData.Status = if ($VsanCluster.HistoricalHealthEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HealthCheck; $LocalizedData.Status = if ($VsanCluster.HealthCheckEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + ) + $TableParams = @{ + Name = ($LocalizedData.TableVSANServices -f $VsanCluster.Name) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanServices | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.ServicesError -f $VsanCluster.Name, $_.Exception.Message) + } + #endregion vSAN Services + if ($VsanStoragePoolDisk) { Write-PScriboMessage -Message ($LocalizedData.CollectingDisks -f $VsanCluster.Name) try { @@ -330,7 +356,32 @@ function Get-AbrVSpherevSAN { Write-PScriboMessage -Message ($LocalizedData.OSAError -f $VsanCluster.Name, $_.Exception.Message) } - # TODO: vSAN Services + #region vSAN Services + try { + Section -Style Heading4 $LocalizedData.ServicesSection { + $VsanServices = @( + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.PerformanceService; $LocalizedData.Status = if ($VsanCluster.PerformanceServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.FileService; $LocalizedData.Status = if ($VsanCluster.FileServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.iSCSITargetService; $LocalizedData.Status = if ($VsanCluster.IscsiTargetServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Deduplication; $LocalizedData.Status = if ($VsanCluster.SpaceEfficiencyEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Encryption; $LocalizedData.Status = if ($VsanCluster.EncryptionEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HistoricalHealthService; $LocalizedData.Status = if ($VsanCluster.HistoricalHealthEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HealthCheck; $LocalizedData.Status = if ($VsanCluster.HealthCheckEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + ) + $TableParams = @{ + Name = ($LocalizedData.TableVSANServices -f $VsanCluster.Name) + ColumnWidths = 50, 50 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $VsanServices | Table @TableParams + } + } catch { + Write-PScriboMessage -Message ($LocalizedData.ServicesError -f $VsanCluster.Name, $_.Exception.Message) + } + #endregion vSAN Services + if ($VsanDiskGroup) { Write-PScriboMessage -Message ($LocalizedData.CollectingDiskGroups -f $VsanCluster.Name) try { @@ -339,7 +390,7 @@ function Get-AbrVSpherevSAN { $Disks = $DiskGroup | Get-VsanDisk [PSCustomObject]@{ $LocalizedData.DiskGroup = $DiskGroup.Uuid - $LocalizedData.Host = $Diskgroup.VMHost + $LocalizedData.Host = $Diskgroup.VMHost.Name $LocalizedData.NumDisks = $Disks.Count $LocalizedData.State = if ($DiskGroup.IsMounted) { $LocalizedData.Mounted @@ -445,7 +496,7 @@ function Get-AbrVSpherevSAN { $LocalizedData.Alias = $VsanIscsiTarget.Name $LocalizedData.LUNsCount = $VsanIscsiTarget.NumLuns $LocalizedData.NetworkInterface = $VsanIscsiTarget.NetworkInterface - $LocalizedData.IOOwnerHost = $VsanIscsiTarget.IoOwnerVMHost + $LocalizedData.IOOwnerHost = $VsanIscsiTarget.IoOwnerVMHost.Name $LocalizedData.TCPPort = $VsanIscsiTarget.TcpPort $LocalizedData.Health = $TextInfo.ToTitleCase($VsanIscsiTarget.VsanHealth) $LocalizedData.StoragePolicy = if ($VsanIscsiTarget.StoragePolicy.Name) { diff --git a/AsBuiltReport.VMware.vSphere/Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 b/AsBuiltReport.VMware.vSphere/Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 index f31ef34..c99e28c 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1 @@ -113,6 +113,23 @@ function Invoke-AsBuiltReport.VMware.vSphere { } #endregion VMware Update Manager Server Name + #region vCenter REST API + $vcApiUri = $null + $vcApiHeaders = $null + if ([version]$vCenter.Version -ge [version]'7.0') { + $vcApiBaseUri = "https://$($vCenter.Name)/api" + try { + $restToken = Invoke-RestMethod -Uri "$vcApiBaseUri/session" -Method Post -Credential $Credential -SkipCertificateCheck -ErrorAction Stop + $vcApiUri = $vcApiBaseUri + $vcApiHeaders = @{ + 'vmware-api-session-id' = $restToken + } + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.RestApiSessionError -f $_.Exception.Message) + } + } + #endregion vCenter REST API + #region VxRail Manager Server Name Write-PScriboMessage -Message $LocalizedData.CheckVxRail $VxRailMgr = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vxrail' } | diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f03f9..244d9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,20 +9,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fix PCI Drivers & Firmware section not reporting on vSphere 8; `VMkernelName` is no longer populated in `esxcli hardware.pci.list` on ESXi 8.x so a PCI address to VMkernel name map is now built via the PowerCLI API as fallback. Also fixes per-device defaults not being reset between loop iterations ([#111](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/111), [#127](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/127)) +- Fix PCI Drivers & Firmware section not reporting on vSphere 8; `VMkernelName` is no longer populated in `esxcli hardware.pci.list` on ESXi 8.x so a PCI address to VMkernel name map is now built via the PowerCLI API as fallback. Also fixes per-device defaults not being reset between loop iterations ([#105]([#111](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/111)), [#111](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/111), [#127](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/127)) - Fix vCenter Server Certificate section reporting VMCA template defaults instead of the actual deployed TLS certificate; now reads the live certificate directly from port 443 ([#88](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/88)) - Fix null disk group crash in OSA vSAN clusters where disk groups have not yet been claimed ([#113](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/113)) -- Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130)) +- ~~Fix `An item with the same key has already been added. Key: LinkedView` error when generating TEXT format reports; raw VMOMI/PowerCLI objects stored directly as PSCustomObject property values cause PScribo's serializer to encounter duplicate `LinkedView` members — all affected properties now store string primitives instead ([#130](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/130))~~ - Issues still evident. Still working on this fix. - Fix "Index operation failed; the array index evaluated to null" crash and `Global.Licenses` privilege errors when querying ESXi host/vCenter licensing on vCenter 8.0.2 ([#123](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/123)) ### Added - -- Add TPM attestation state and host encryption settings to VMHost Security section; includes recovery key reporting (gated behind `ShowEncryptionKeys` option) and `TpmAttestation` healthcheck ([#101](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/101)) -- Add I/O Device Identifiers subsection to VMHost Hardware report, displaying VID/DID/SVID/SSID in lowercase hex for HCL validation ([#126](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/126)) - Modular architecture: each report section is now a dedicated private function (`Get-AbrVSphere*`) - Internationalization (i18n) support via `Language/` `.psd1` files (`en-US`, `en-GB`, `es-ES`, `fr-FR`, `de-DE`) - Pester test suite (`AsBuiltReport.VMware.vSphere.Tests.ps1`, `LocalizationData.Tests.ps1`, `Invoke-Tests.ps1`) - GitHub Actions Pester workflow (`.github/workflows/Pester.yml`) +- Add TPM attestation state and host encryption settings to VMHost Security section; includes recovery key reporting (gated behind `ShowEncryptionKeys` option) and `TpmAttestation` healthcheck ([#101](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/101)) +- Add I/O Device Identifiers subsection to VMHost Hardware report, displaying VID/DID/SVID/SSID in lowercase hex for HCL validation ([#126](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/126)) +- Add vSphere Lifecycle Manager (vLCM) cluster reporting: image composition (base image, vendor add-on), component list (InfoLevel 4+), hardware support manager, and image compliance with per-host breakdown (InfoLevel 4+); clusters in VUM baseline mode are silently skipped +- Add Software Depots section to VMware Update Manager report; online depots show description, URL, system-defined flag, and enabled state; offline depots show description and location; system-generated bundles (HA, WCP) with no location are excluded from the offline table +- Add VM Update Manager baseline compliance reporting per VM, with `VUMCompliance` healthcheck (Warning: Unknown, Critical: Not Compliant / Incompatible) +- Add `LCMCompliance` healthcheck for cluster/host vLCM image compliance +- Add SDRS rules reporting to Datastore Cluster section; shows rule name, enabled state, type (Affinity / Anti-Affinity), and member VMs +- Add Space Load Balance Configuration and I/O Load Balance Configuration subsections to Datastore Cluster section +- Add Distributed Switch LACP reporting (enabled state, mode) at InfoLevel 3+ +- Add Distributed Switch NetFlow reporting (collector IP/port, flow timeouts, sampling rate) at InfoLevel 3+ +- Add Network I/O Control resource pool reporting (shares level, shares, limit) at InfoLevel 4+ when NIOC is enabled +- Add Proactive HA Providers subsection to Cluster Proactive HA section +- Add VM Swap File Location subsection to VMHost System section; shows placement policy and local swap datastore +- Add ESXi host certificate reporting to VMHost System section; shows subject, issuer, validity period, and SHA-256 thumbprint +- Add syslog log directory, rotation count, and log size to VMHost Syslog section; section now always renders regardless of whether a remote syslog server is configured +- Add vSAN Services section to vSAN cluster reporting (both OSA and ESA); shows enabled/disabled state of Performance Service, File Service, iSCSI Target Service, Deduplication, Encryption, Historical Health Service, and Health Check +- Add tag reporting to Datastore, Datastore Cluster, Distributed Switch, Resource Pool, Virtual Machine, and VMHost sections +- Extract Cluster VUM Baselines and Compliance into dedicated `Get-AbrVSphereClusterVUM` sub-function ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index f1e6c92..3ea1f28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,6 +34,8 @@ AsBuiltReport.VMware.vSphere/ ← repo root │ ├── Get-AbrVSphereClusterHA.ps1 │ ├── Get-AbrVSphereClusterProactiveHA.ps1 │ ├── Get-AbrVSphereClusterDRS.ps1 +│ ├── Get-AbrVSphereClusterVUM.ps1 +│ ├── Get-AbrVSphereClusterLCM.ps1 │ ├── Get-AbrVSphereResourcePool.ps1 │ ├── Get-AbrVSphereVMHost.ps1 │ ├── Get-AbrVSphereVMHostHardware.ps1 @@ -107,10 +109,11 @@ function Get-AbrVSphere{Name} { ``` **Key rules:** -- Functions use variables from parent scope (not parameters) — `$vCenter`, `$InfoLevel`, `$Report`, `$Healthcheck`, `$TextInfo`, `$vCenterServerName`, `$reportTranslate`, etc. +- Functions use variables from parent scope (not parameters) — `$vCenter`, `$InfoLevel`, `$Report`, `$Healthcheck`, `$TextInfo`, `$vCenterServerName`, `$reportTranslate`, `$vcApiUri`, `$vcApiHeaders`, etc. - `$LocalizedData` is always set in `begin {}` from `$reportTranslate.{KeyName}` - All PSCustomObject property names (column headers), section headings, table names, and user-visible strings use `$LocalizedData` keys - Table names (`Name =`) in `$TableParams` also use `$LocalizedData` keys for localization +- **PSCustomObject keys must NOT use parentheses around `$LocalizedData` expressions.** Use `$LocalizedData.Key = value`, never `($LocalizedData.Key) = value`. The brackets are unnecessary — PowerShell evaluates `$LocalizedData.Key` as a key name directly in hashtable/PSCustomObject literals. ## Language / i18n Structure @@ -228,6 +231,47 @@ BeforeDiscovery { ### psm1 Module Exports The `.psm1` only exports public functions (`Export-ModuleMember -Function $Public.BaseName`). Private section functions (`Get-AbrVSphere*`) are dot-sourced and available in module scope without being exported. The `FunctionsToExport` in the `.psd1` manifest acts as the final authoritative allow-list. +### vCenter REST API Authentication +The `$vcApiUri` and `$vcApiHeaders` variables are set in `Invoke-AsBuiltReport.VMware.vSphere.ps1` only when the connected vCenter is vSphere 8.0+. A REST API session is established via `POST /api/session` using the report `$Credential`: + +```powershell +$restToken = Invoke-RestMethod -Uri "$vcApiBaseUri/session" -Method Post -Credential $Credential -SkipCertificateCheck +$vcApiHeaders = @{ 'vmware-api-session-id' = $restToken } +``` + +**Do not** use `$vCenter.SessionId` as the session token — that is the PowerCLI SOAP/VMOMI session ID and is rejected by the REST API with HTTP 401. + +Functions that call the REST API check `if (-not $vcApiUri) { return }` to silently skip on unsupported versions or when session establishment failed. + +### `Get-Compliance` Non-Terminating Warning +The PowerCLI `Get-Compliance` cmdlet emits a non-terminating warning alongside its return value. Use `-ErrorAction SilentlyContinue` (not `-ErrorAction Stop`) to suppress the warning without discarding the compliance data. The null check on the result variable handles genuine failures: + +```powershell +$Compliances = $Entity | Get-Compliance -ErrorAction SilentlyContinue +if ($Compliances) { ... } +``` + +Using `-ErrorAction Stop` converts the warning to a terminating error and the catch block fires, losing the data even though the cmdlet succeeded. + +### LinkedView Error — Never Store Raw VMOMI Objects in PSCustomObject +PScribo serializes every `PSCustomObject` when rendering TEXT/Word/HTML output. VMOMI/PowerCLI objects (e.g., `ClusterComputeResource`, `VIPermission`, `VirtualMachine`) expose a duplicate `LinkedView` property internally, which causes: + +``` +An item with the same key has already been added. Key: LinkedView +``` + +**Rule:** PSCustomObject property values must always be **string primitives**, never raw PowerCLI objects. + +```powershell +# WRONG — stores raw VMOMI object +[PSCustomObject]@{ ($LocalizedData.Entity) = $ClusterCompliance.Entity } + +# CORRECT — store the string name +[PSCustomObject]@{ ($LocalizedData.Entity) = $ClusterCompliance.Entity.Name } +``` + +This applies to any property that holds a PowerCLI object: use `.Name`, `.Id`, `.ToString()`, or any other string extraction. Using the object in a `Where-Object` comparison (not as a stored value) is safe. + ### Locale Key Consistency The `en-US` locale file is the authoritative template. All other locales must have **identical key sets** — the `LocalizationData.Tests.ps1` Pester test enforces this. To quickly check parity without running Pester, compare key counts: ```powershell diff --git a/README.md b/README.md index 0c18e4f..20dede0 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,9 @@ For a complete report, the following role assigned privileges are required; * Profile-driven Storage > Profile-driven storage view * VMware vSphere Update Manager > View Compliance Status +> [!NOTE] +> The vSphere Lifecycle Manager (vLCM) image composition, compliance, and Software Depot sections use the vCenter REST API. The credentials supplied to `New-AsBuiltReport` are used to authenticate automatically — no additional configuration is required. + ## :package: Module Installation ### PowerShell @@ -258,6 +261,7 @@ The **Cluster** schema is used to configure health checks for vSphere Clusters. | vSANEnabled | true / false | true | Highlights vSphere Clusters which do not have Virtual SAN enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Virtual SAN disabled | | EVCEnabled | true / false | true | Highlights vSphere Clusters which do not have Enhanced vMotion Compatibility (EVC) enabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) vSphere EVC disabled | | VUMCompliance | true / false | true | Highlights vSphere Clusters which do not comply with VMware Update Manager baselines | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Unknown
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Not Compliant | +| LCMCompliance | true / false | true | Highlights clusters and hosts where the vLCM image compliance status is not Compliant | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Non-compliant | #### VMHost The **VMHost** schema is used to configure health checks for VMHosts. @@ -315,6 +319,7 @@ The **VM** schema is used to configure health checks for virtual machines. | SpbmPolicyCompliance | true / false | true | Highlights VMs which do not comply with storage based policies | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) VM storage based policy compliance is unknown
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) VM does not comply with storage based policies | | VMToolsStatus | true / false | true | Highlights Virtual Machines which do not have VM Tools installed, are out of date or are not running | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) VM Tools not installed, out of date or not running | | VMSnapshots | true / false | true | Highlights Virtual Machines which have snapshots older than 7 days | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) VM Snapshot age >= 7 days
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) VM Snapshot age >= 14 days | +| VUMCompliance | true / false | true | Highlights VMs which do not comply with VMware Update Manager baselines | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Unknown
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Not Compliant / Incompatible | ## :computer: Examples From b100de08e06cd43959792e46eb240e0c7e434593 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Fri, 13 Mar 2026 17:31:40 +1100 Subject: [PATCH 17/24] Add vCenter Server Backup Settings section - Backup Schedule table shows status (Activated/Deactivated), recurrence with appliance timezone, backup location, data components (mapped from API part names to human-readable labels), and retention count - Backup Job History table shows 10 most recent jobs via GET /api/appliance/recovery/backup/job/details; columns: location, type, status, data transferred, duration (HH:MM:SS), end time - Healthcheck: Warning if schedule deactivated, Critical if job failed - Requires vSphere 7.0+ REST API; renders informational paragraph on older versions - Add Backup healthcheck key to AsBuiltReport.VMware.vSphere.json - Update all 5 locale files with new backup keys Co-Authored-By: Claude Sonnet 4.6 --- .../AsBuiltReport.VMware.vSphere.json | Bin 5424 -> 4769 bytes .../Language/de-DE/VMwarevSphere.psd1 | 31 +++++ .../Language/en-GB/VMwarevSphere.psd1 | 31 +++++ .../Language/en-US/VMwarevSphere.psd1 | 31 +++++ .../Language/es-ES/VMwarevSphere.psd1 | 31 +++++ .../Language/fr-FR/VMwarevSphere.psd1 | 31 +++++ .../Src/Private/Get-AbrVSpherevCenter.ps1 | 128 ++++++++++++++++++ CHANGELOG.md | 1 + 8 files changed, 284 insertions(+) diff --git a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json index ff01ba847d1415c31c803f953e128a884f2b9b8d..f7896825f30ea7a78b5260200864da600e4981cc 100644 GIT binary patch literal 4769 zcmb_g!E&205WVLs7@gBLZQ33>(*wcDI8!@eEXUJZM}jn)S#?xLhpc-DP+6_U*#2A3wjJczBTom%OIGtWQ{YuJPXK@Y z!n-JWs=cj&Q){lJX_jOKs^C&s1Fv76k!~rfnJx<}w15;?0dbGyjERSmJ=7k7cyUL2 z1Zg7IfviA5Zd~4SYZk*26ZK+24%cIEN#sPEZ3Nj z#`xb^d#c?dw|lY9bMc6YaKNsvHfW9x&-pC{8VWk{xdkvW3GIN4i@ZRuuB>O$h;VT7)qGLVP!4_N34%K#imkjML3y_NJh(mA5KOXK&>Uu;I_2X;23|E z9T)wAj)%cyLH$z8jG4}6JHS<$rW><2A`PK}m`55}8|@nX16{2J|7J;0t!CIonZlU5 zL<3`04I{S_>FWCBDrjjtLIByG%M|!-C$B8NG1Xh0#caC&^;D1 z-Q=K_iNg783>y#{DW;=jCL@b!XB3m`uzK&Vb(b3gn6P@9b2|PbIvAmJ>h9|${mf|s zse892H_Oodc@&u)r+7cc0s5I~yP<;Cf@}w>(fWvm$5^h#T*e~+d0cIhWuEz3LxI(R zMX^iXklH*nFzm+1frUx%-GxP5Z|*ct`8P*b4mr}aP7~QcZ|vC~=aMyX(69Db{J;Df zK40MwOb7JVAuzyD4+UBe_JblMpKHm7ye&b4BLkU1R?Af7O4{;>drRD_ z$W|g)8RNVDU6U2=ewB?(WD3ibC+oaUU~p?(Dg7067BY`lI+C~Y9>216B3K{b+F(VC zjXq6r4RCKHH@UhkNX+F9x&!=mzKwAAjaQA%1{|eva>X?@w4I3)M4uZ7Ia2zgsl}< zN5514Ri&Gum0ed6s)mtlVU3~km^u4~YZrTd3lvk8%i<`WB3?uIy-TcK&JwFTf;D=- zQV5^tA>aFRXx_rB7QA3}OilE&BXAAO3seoei}}7b(p6M_MZVx_AT}#t(G_xoSXbo8 zJfqLK;Bx)sth0I#99G*&F+0$pR~&*+7z&)x-BioQu2_nE58; zxJEIS4s5Pbx#Z6KJ+kD-nsoL^H4t~ko@>*pWu!c;4Ok6{2z}E&9p}XN3an>kP7)mb zw7)gWklDvTz&sD*7-Uy@R+h6QU9Q`v3N>QOIav(gbdoGGLG9fcfBm#;s1Ekb3>iqT z>U!O-m5zwLTg}Wzv7b#;M!JjBYZrS=ZFcocafIDF*@fhiCH`CX@r!}2{WBANcF#!d z-Yax9t&*R^5bADfEA5G??eOe8?~X3e^uUg-AUxT2YRXcGUs|J&xRbfN;2Fypev#fn z@9>yhfCI``PXn1HGZ8y^oBX5qdbf9-;pr5gT`U(8rJkyv8(1AG+uRTJs@h8N-tPKJ z>m=FvLg6CEhv3n6%IR7b&x*INAE%g{>jz{^Gql}hr-&>AXd6{1BqjF~8<~2TKLr##E#nWi~yV)|G zUnesY19S`b_8lfju1-$*`2t9u@7DhVrtGta?IKPD0Xd6AA#5r4+!!brD@ia5S zJGCjS@U+!eY{+kA_PPhWO_+g$c|PE5w)2<;(T6=+T*aAp@8$erPAmJ~yzIll QGin+a diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 index 1121cf3..cee0808 100644 --- a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -124,6 +124,37 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} RestApiSessionError = Unable to establish vCenter REST API session. {0} + BackupSettings = Sicherungseinstellungen + BackupSchedule = Sicherungszeitplan + BackupJobHistory = Sicherungsauftragsverlauf + BackupNotConfigured = Es ist kein Sicherungszeitplan konfiguriert. + BackupNoJobs = Keine Sicherungsaufträge gefunden. + BackupApiNotAvailable = Der vCenter-Sicherungsstatus erfordert vSphere 7.0 oder höher. + BackupApiError = Sicherungsinformationen konnten nicht abgerufen werden. {0} + BackupScheduleID = Zeitplan-ID + BackupLocation = Speicherort + BackupLocationUser = Speicherortbenutzer + BackupEnabled = Status + BackupActivated = Aktiviert + BackupDeactivated = Deaktiviert + BackupParts = Sicherungsdaten + BackupPartSeat = Supervisors Control Plane + BackupPartCommon = Bestand und Konfiguration + BackupPartStats = Statistiken, Ereignisse und Aufgaben + BackupRecurrence = Zeitplan + BackupRetentionCount = Anzahl der aufzubewahrenden Sicherungen + BackupDaily = Täglich + BackupSendEmail = E-Mail-Benachrichtigung + BackupJobLocation = Sicherungsort + BackupJobType = Typ + BackupJobStatus = Status + BackupJobComplete = Abgeschlossen + BackupJobScheduled = Geplant + BackupJobDataTransferred = Übertragene Daten + BackupJobDuration = Dauer + BackupJobEndTime = Endzeit + TableBackupSchedule = Sicherungszeitplan - {0} + TableBackupJobHistory = Sicherungsauftragsverlauf - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 index 92957c0..5c3caeb 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -124,6 +124,37 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} RestApiSessionError = Unable to establish vCenter REST API session. {0} + BackupSettings = Backup Settings + BackupSchedule = Backup Schedule + BackupJobHistory = Backup Job History + BackupNotConfigured = No backup schedule is configured. + BackupNoJobs = No backup jobs found. + BackupApiNotAvailable = vCenter backup status requires vSphere 7.0 or later. + BackupApiError = Unable to retrieve backup information. {0} + BackupScheduleID = Schedule ID + BackupLocation = Location + BackupLocationUser = Location User + BackupEnabled = Status + BackupActivated = Activated + BackupDeactivated = Deactivated + BackupParts = Backup Data + BackupPartSeat = Supervisors Control Plane + BackupPartCommon = Inventory and Configuration + BackupPartStats = Stats, Events, and Tasks + BackupRecurrence = Schedule + BackupRetentionCount = Number of Backups to Retain + BackupDaily = Daily + BackupSendEmail = Email Notification + BackupJobLocation = Backup Location + BackupJobType = Type + BackupJobStatus = Status + BackupJobComplete = Complete + BackupJobScheduled = Scheduled + BackupJobDataTransferred = Data Transferred + BackupJobDuration = Duration + BackupJobEndTime = End Time + TableBackupSchedule = Backup Schedule - {0} + TableBackupJobHistory = Backup Job History - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 index 52c4c2c..25a1e30 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -124,6 +124,37 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} RestApiSessionError = Unable to establish vCenter REST API session. {0} + BackupSettings = Backup Settings + BackupSchedule = Backup Schedule + BackupJobHistory = Backup Job History + BackupNotConfigured = No backup schedule is configured. + BackupNoJobs = No backup jobs found. + BackupApiNotAvailable = vCenter backup status requires vSphere 7.0 or later. + BackupApiError = Unable to retrieve backup information. {0} + BackupScheduleID = Schedule ID + BackupLocation = Location + BackupLocationUser = Location User + BackupEnabled = Status + BackupActivated = Activated + BackupDeactivated = Deactivated + BackupParts = Backup Data + BackupPartSeat = Supervisors Control Plane + BackupPartCommon = Inventory and Configuration + BackupPartStats = Stats, Events, and Tasks + BackupRecurrence = Schedule + BackupRetentionCount = Number of Backups to Retain + BackupDaily = Daily + BackupSendEmail = Email Notification + BackupJobLocation = Backup Location + BackupJobType = Type + BackupJobStatus = Status + BackupJobComplete = Complete + BackupJobScheduled = Scheduled + BackupJobDataTransferred = Data Transferred + BackupJobDuration = Duration + BackupJobEndTime = End Time + TableBackupSchedule = Backup Schedule - {0} + TableBackupJobHistory = Backup Job History - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 index 45913ae..79b5a9e 100644 --- a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -124,6 +124,37 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} RestApiSessionError = Unable to establish vCenter REST API session. {0} + BackupSettings = Configuración de copia de seguridad + BackupSchedule = Programación de copia de seguridad + BackupJobHistory = Historial de trabajos de copia de seguridad + BackupNotConfigured = No hay ninguna programación de copia de seguridad configurada. + BackupNoJobs = No se encontraron trabajos de copia de seguridad. + BackupApiNotAvailable = El estado de copia de seguridad de vCenter requiere vSphere 7.0 o posterior. + BackupApiError = No se puede recuperar la información de copia de seguridad. {0} + BackupScheduleID = ID de programación + BackupLocation = Ubicación + BackupLocationUser = Usuario de ubicación + BackupEnabled = Estado + BackupActivated = Activado + BackupDeactivated = Desactivado + BackupParts = Datos de copia de seguridad + BackupPartSeat = Plano de control de supervisores + BackupPartCommon = Inventario y configuración + BackupPartStats = Estadísticas, eventos y tareas + BackupRecurrence = Programación + BackupRetentionCount = Número de copias de seguridad a conservar + BackupDaily = Diario + BackupSendEmail = Notificación por correo electrónico + BackupJobLocation = Ubicación de copia de seguridad + BackupJobType = Tipo + BackupJobStatus = Estado + BackupJobComplete = Completado + BackupJobScheduled = Programado + BackupJobDataTransferred = Datos transferidos + BackupJobDuration = Duración + BackupJobEndTime = Hora de finalización + TableBackupSchedule = Programación de copia de seguridad - {0} + TableBackupJobHistory = Historial de trabajos de copia de seguridad - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 index 11430bb..5677cd2 100644 --- a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -124,6 +124,37 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' TableAlarms = Alarms - {0} TableAdvancedSystemSettings = vCenter Advanced System Settings - {0} RestApiSessionError = Unable to establish vCenter REST API session. {0} + BackupSettings = Paramètres de sauvegarde + BackupSchedule = Calendrier de sauvegarde + BackupJobHistory = Historique des tâches de sauvegarde + BackupNotConfigured = Aucun calendrier de sauvegarde n'est configuré. + BackupNoJobs = Aucune tâche de sauvegarde trouvée. + BackupApiNotAvailable = L'état de sauvegarde vCenter nécessite vSphere 7.0 ou version ultérieure. + BackupApiError = Impossible de récupérer les informations de sauvegarde. {0} + BackupScheduleID = ID de calendrier + BackupLocation = Emplacement + BackupLocationUser = Utilisateur de l'emplacement + BackupEnabled = État + BackupActivated = Activé + BackupDeactivated = Désactivé + BackupParts = Données de sauvegarde + BackupPartSeat = Plan de contrôle des superviseurs + BackupPartCommon = Inventaire et configuration + BackupPartStats = Statistiques, événements et tâches + BackupRecurrence = Planification + BackupRetentionCount = Nombre de sauvegardes à conserver + BackupDaily = Quotidien + BackupSendEmail = Notification par e-mail + BackupJobLocation = Emplacement de sauvegarde + BackupJobType = Type + BackupJobStatus = État + BackupJobComplete = Terminé + BackupJobScheduled = Planifié + BackupJobDataTransferred = Données transférées + BackupJobDuration = Durée + BackupJobEndTime = Heure de fin + TableBackupSchedule = Calendrier de sauvegarde - {0} + TableBackupJobHistory = Historique des tâches de sauvegarde - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 index 275b84c..292ecd9 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 @@ -380,6 +380,134 @@ function Get-AbrVSpherevCenter { Write-PScriboMessage -Message $LocalizedData.InsufficientPrivStoragePolicy } #endregion VM Storage Policies + + #region vCenter Server Backup + Section -Style Heading3 $LocalizedData.BackupSettings { + if (-not $vcApiUri) { + Paragraph $LocalizedData.BackupApiNotAvailable + } else { + #region Backup Schedule + $BackupSchedules = $null + try { + $BackupSchedules = Invoke-RestMethod -Uri "$vcApiUri/appliance/recovery/backup/schedules" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.BackupApiError -f $_.Exception.Message) + } + Section -Style Heading4 $LocalizedData.BackupSchedule { + if ($BackupSchedules -and $BackupSchedules.PSObject.Properties.Name.Count -gt 0) { + $ApplianceTimezone = $null + try { + $ApplianceTimezone = Invoke-RestMethod -Uri "$vcApiUri/appliance/system/time/timezone" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + } catch {} + $BackupScheduleInfo = foreach ($schedId in $BackupSchedules.PSObject.Properties.Name) { + $sched = $BackupSchedules.$schedId + $recurrence = if ($sched.recurrence_info) { + $h = [int]$sched.recurrence_info.hour % 12 + if ($h -eq 0) { $h = 12 } + $ap = if ([int]$sched.recurrence_info.hour -ge 12) { 'P.M.' } else { 'A.M.' } + $timeStr = '{0}:{1:D2} {2}' -f $h, [int]$sched.recurrence_info.minute, $ap + $dayStr = if ($sched.recurrence_info.days -and $sched.recurrence_info.days.Count -gt 0) { + ($sched.recurrence_info.days | ForEach-Object { $TextInfo.ToTitleCase($_.ToLower()) }) -join ', ' + } else { $LocalizedData.BackupDaily } + $tz = if ($ApplianceTimezone) { " $ApplianceTimezone" } else { '' } + "$dayStr, $timeStr$tz" + } else { '--' } + $partsFormatted = ($sched.parts | ForEach-Object { + switch ($_) { + 'supervisors' { $LocalizedData.BackupPartSeat } + 'seat' { $LocalizedData.BackupPartSeat } + 'common' { $LocalizedData.BackupPartCommon } + 'stats' { $LocalizedData.BackupPartStats } + default { $_ } + } + }) -join ', ' + [PSCustomObject]@{ + $LocalizedData.BackupEnabled = if ($sched.enable) { $LocalizedData.BackupActivated } else { $LocalizedData.BackupDeactivated } + $LocalizedData.BackupRecurrence = $recurrence + $LocalizedData.BackupLocation = $sched.location + $LocalizedData.BackupParts = $partsFormatted + $LocalizedData.BackupRetentionCount = $sched.retention_info.max_count + } + } + if ($Healthcheck.vCenter.Backup) { + $BackupScheduleInfo | Where-Object { $_.$($LocalizedData.BackupEnabled) -eq $LocalizedData.BackupDeactivated } | Set-Style -Style Warning -Property $LocalizedData.BackupEnabled + } + $TableParams = @{ + Name = ($LocalizedData.TableBackupSchedule -f $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $BackupScheduleInfo | Table @TableParams + } else { + Paragraph $LocalizedData.BackupNotConfigured + } + } + #endregion Backup Schedule + + #region Backup Job History + $BackupJobRecords = $null + try { + $jobDetailsResponse = Invoke-RestMethod -Uri "$vcApiUri/appliance/recovery/backup/job/details" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + if ($jobDetailsResponse -and $jobDetailsResponse.PSObject.Properties.Name.Count -gt 0) { + $BackupJobRecords = $jobDetailsResponse.PSObject.Properties | + Sort-Object Name -Descending | Select-Object -First 10 | ForEach-Object { + $job = $_.Value + $jobDuration = if ($job.start_time -and $job.end_time) { ([datetime]$job.end_time - [datetime]$job.start_time).TotalSeconds } elseif ($job.duration) { $job.duration } else { $null } + [PSCustomObject]@{ + Location = $job.location + Type = $job.type + State = $job.state ?? $job.status + Size = $job.size + Duration = $jobDuration + Timestamp = $job.end_time + } + } + } + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.BackupApiError -f $_.Exception.Message) + } + Section -Style Heading4 $LocalizedData.BackupJobHistory { + if ($BackupJobRecords -and $BackupJobRecords.Count -gt 0) { + $BackupJobInfo = foreach ($record in $BackupJobRecords) { + $duration = if ($record.Duration) { $ts = [timespan]::FromSeconds($record.Duration); '{0:D2}:{1:D2}:{2:D2}' -f $ts.Hours, $ts.Minutes, $ts.Seconds } else { '--' } + $dataTransferred = if ($record.Size -gt 0) { '{0:N2} GB' -f ($record.Size / 1GB) } else { '--' } + [PSCustomObject]@{ + $LocalizedData.BackupJobLocation = if ($record.Location) { $record.Location } else { '--' } + $LocalizedData.BackupJobType = switch ($record.Type) { + 'SCHEDULED' { $LocalizedData.BackupJobScheduled } + default { if ($record.Type) { $TextInfo.ToTitleCase($record.Type.ToString().ToLower()) } else { '--' } } + } + $LocalizedData.BackupJobStatus = switch ($record.State) { + 'SUCCEEDED' { $LocalizedData.BackupJobComplete } + default { if ($record.State) { $TextInfo.ToTitleCase($record.State.ToString().ToLower()) } else { '--' } } + } + $LocalizedData.BackupJobDataTransferred = $dataTransferred + $LocalizedData.BackupJobDuration = $duration + $LocalizedData.BackupJobEndTime = if ($record.Timestamp) { ([datetime]$record.Timestamp).ToLocalTime().ToString() } else { '--' } + } + } + if ($Healthcheck.vCenter.Backup) { + $BackupJobInfo | Where-Object { $_.$($LocalizedData.BackupJobStatus) -eq 'Failed' } | Set-Style -Style Critical -Property $LocalizedData.BackupJobStatus + } + $TableParams = @{ + Name = ($LocalizedData.TableBackupJobHistory -f $vCenterServerName) + ColumnWidths = 28, 13, 13, 13, 13, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $BackupJobInfo | Table @TableParams + } else { + Paragraph $LocalizedData.BackupNoJobs + } + } + #endregion Backup Job History + } + } + #endregion vCenter Server Backup } #endregion vCenter Server Detailed Information diff --git a/CHANGELOG.md b/CHANGELOG.md index 244d9be..8887078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add vSAN Services section to vSAN cluster reporting (both OSA and ESA); shows enabled/disabled state of Performance Service, File Service, iSCSI Target Service, Deduplication, Encryption, Historical Health Service, and Health Check - Add tag reporting to Datastore, Datastore Cluster, Distributed Switch, Resource Pool, Virtual Machine, and VMHost sections - Extract Cluster VUM Baselines and Compliance into dedicated `Get-AbrVSphereClusterVUM` sub-function +- Add vCenter Server Backup Settings section (InfoLevel 3+); Backup Schedule table shows status (Activated/Deactivated), schedule recurrence with appliance timezone, backup location, data components, and retention count; Backup Job History table shows the 10 most recent jobs (location, type, status, data transferred, duration, end time) via `GET /api/appliance/recovery/backup/job/details`; requires vSphere 7.0+ REST API; includes `Backup` healthcheck (Warning: schedule deactivated, Critical: job failed) ### Changed From fcdaca321145c091c02d2a4843cbfe194fe2fed8 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Fri, 13 Mar 2026 17:46:47 +1100 Subject: [PATCH 18/24] Add Certificate and Backup healthchecks; add Backup Settings section to vCenter report - Add vCenter Server Backup Settings section (InfoLevel 3+) with Backup Schedule and Backup Job History subsections via REST API - Add Certificate healthcheck (Critical: EXPIRED/EXPIRING, Warning: EXPIRING_SOON) - Add Backup healthcheck (Warning: schedule deactivated, Critical: job failed) - Update report JSON with Certificate and Backup healthcheck defaults - Update README vCenter healthcheck table with Certificate and Backup rows - Update CHANGELOG with Certificate healthcheck entry - Add pre-release support to Release.yml (manifest Prerelease string, conditional tweet/bsky jobs) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/Release.yml | 39 +++ .../AsBuiltReport.VMware.vSphere.json | 3 +- .../Src/Private/Get-AbrVSpherevCenter.ps1 | 260 +++++++++--------- .../Src/Private/Get-AbrVSpherevSAN.ps1 | 28 +- CHANGELOG.md | 1 + README.md | 8 +- 6 files changed, 193 insertions(+), 146 deletions(-) diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 333dfa8..75925ce 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -17,6 +17,17 @@ jobs: shell: pwsh run: | Install-Module -Name AsBuiltReport.Core -Repository PSGallery -Force + - name: Set Prerelease string in manifest + if: ${{ github.event.release.prerelease }} + shell: pwsh + run: | + $tag = '${{ github.event.release.tag_name }}' + if ($tag -match '-(.+)$') { + $prerelease = $Matches[1] + Update-ModuleManifest ` + -Path .\AsBuiltReport.VMware.vSphere\AsBuiltReport.VMware.vSphere.psd1 ` + -Prerelease $prerelease + } - name: Test Module Manifest shell: pwsh run: | @@ -27,6 +38,7 @@ jobs: Publish-Module -Path .\AsBuiltReport.VMware.vSphere -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose tweet: needs: publish-to-psgallery + if: ${{ !github.event.release.prerelease }} runs-on: ubuntu-latest steps: - uses: Eomm/why-don-t-you-tweet@v2 @@ -41,13 +53,40 @@ jobs: 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 }} + tweet-prerelease: + needs: publish-to-psgallery + if: ${{ github.event.release.prerelease }} + 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: + tweet-message: "[Pre-release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} is now available for testing! Install with -AllowPrerelease ${{ github.event.release.html_url }} #VMware #vSphere #AsBuiltReport #vExpert" + 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-psgallery + if: ${{ !github.event.release.prerelease }} runs-on: ubuntu-latest steps: - uses: zentered/bluesky-post-action@v0.2.0 with: post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #VMware #vSphere #AsBuiltReport #vExpert" + env: + BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }} + BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }} + bsky-post-prerelease: + needs: publish-to-psgallery + if: ${{ github.event.release.prerelease }} + runs-on: ubuntu-latest + steps: + - uses: zentered/bluesky-post-action@v0.2.0 + with: + post: "[Pre-release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} is now available for testing! Install with -AllowPrerelease ${{ github.event.release.html_url }} #VMware #vSphere #AsBuiltReport #vExpert" env: BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }} BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }} \ No newline at end of file diff --git a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json index f789682..18771a9 100644 --- a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json +++ b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json @@ -32,7 +32,8 @@ "Mail": true, "Licensing": true, "Alarms": true, - "Backup": true + "Backup": true, + "Certificate": true }, "Cluster": { "HAEnabled": true, diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 index 292ecd9..2b61690 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 @@ -148,6 +148,134 @@ function Get-AbrVSpherevCenter { } #endregion vCenter Server Mail Settings + #region vCenter Server Backup + Section -Style Heading3 $LocalizedData.BackupSettings { + if (-not $vcApiUri) { + Paragraph $LocalizedData.BackupApiNotAvailable + } else { + #region Backup Schedule + $BackupSchedules = $null + try { + $BackupSchedules = Invoke-RestMethod -Uri "$vcApiUri/appliance/recovery/backup/schedules" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.BackupApiError -f $_.Exception.Message) + } + Section -Style Heading4 $LocalizedData.BackupSchedule { + if ($BackupSchedules -and $BackupSchedules.PSObject.Properties.Name.Count -gt 0) { + $ApplianceTimezone = $null + try { + $ApplianceTimezone = Invoke-RestMethod -Uri "$vcApiUri/appliance/system/time/timezone" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + } catch {} + $BackupScheduleInfo = foreach ($schedId in $BackupSchedules.PSObject.Properties.Name) { + $sched = $BackupSchedules.$schedId + $recurrence = if ($sched.recurrence_info) { + $h = [int]$sched.recurrence_info.hour % 12 + if ($h -eq 0) { $h = 12 } + $ap = if ([int]$sched.recurrence_info.hour -ge 12) { 'P.M.' } else { 'A.M.' } + $timeStr = '{0}:{1:D2} {2}' -f $h, [int]$sched.recurrence_info.minute, $ap + $dayStr = if ($sched.recurrence_info.days -and $sched.recurrence_info.days.Count -gt 0) { + ($sched.recurrence_info.days | ForEach-Object { $TextInfo.ToTitleCase($_.ToLower()) }) -join ', ' + } else { $LocalizedData.BackupDaily } + $tz = if ($ApplianceTimezone) { " $ApplianceTimezone" } else { '' } + "$dayStr, $timeStr$tz" + } else { '--' } + $partsFormatted = ($sched.parts | ForEach-Object { + switch ($_) { + 'supervisors' { $LocalizedData.BackupPartSeat } + 'seat' { $LocalizedData.BackupPartSeat } + 'common' { $LocalizedData.BackupPartCommon } + 'stats' { $LocalizedData.BackupPartStats } + default { $_ } + } + }) -join ', ' + [PSCustomObject]@{ + $LocalizedData.BackupEnabled = if ($sched.enable) { $LocalizedData.BackupActivated } else { $LocalizedData.BackupDeactivated } + $LocalizedData.BackupRecurrence = $recurrence + $LocalizedData.BackupLocation = $sched.location + $LocalizedData.BackupParts = $partsFormatted + $LocalizedData.BackupRetentionCount = $sched.retention_info.max_count + } + } + if ($Healthcheck.vCenter.Backup) { + $BackupScheduleInfo | Where-Object { $_.$($LocalizedData.BackupEnabled) -eq $LocalizedData.BackupDeactivated } | Set-Style -Style Warning -Property $LocalizedData.BackupEnabled + } + $TableParams = @{ + Name = ($LocalizedData.TableBackupSchedule -f $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $BackupScheduleInfo | Table @TableParams + } else { + Paragraph $LocalizedData.BackupNotConfigured + } + } + #endregion Backup Schedule + + #region Backup Job History + $BackupJobRecords = $null + try { + $jobDetailsResponse = Invoke-RestMethod -Uri "$vcApiUri/appliance/recovery/backup/job/details" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop + if ($jobDetailsResponse -and $jobDetailsResponse.PSObject.Properties.Name.Count -gt 0) { + $BackupJobRecords = $jobDetailsResponse.PSObject.Properties | + Sort-Object Name -Descending | Select-Object -First 10 | ForEach-Object { + $job = $_.Value + $jobDuration = if ($job.start_time -and $job.end_time) { ([datetime]$job.end_time - [datetime]$job.start_time).TotalSeconds } elseif ($job.duration) { $job.duration } else { $null } + [PSCustomObject]@{ + Location = $job.location + Type = $job.type + State = $job.state ?? $job.status + Size = $job.size + Duration = $jobDuration + Timestamp = $job.end_time + } + } + } + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.BackupApiError -f $_.Exception.Message) + } + Section -Style Heading4 $LocalizedData.BackupJobHistory { + if ($BackupJobRecords -and $BackupJobRecords.Count -gt 0) { + $BackupJobInfo = foreach ($record in $BackupJobRecords) { + $duration = if ($record.Duration) { $ts = [timespan]::FromSeconds($record.Duration); '{0:D2}:{1:D2}:{2:D2}' -f $ts.Hours, $ts.Minutes, $ts.Seconds } else { '--' } + $dataTransferred = if ($record.Size -gt 0) { '{0:N2} GB' -f ($record.Size / 1GB) } else { '--' } + [PSCustomObject]@{ + $LocalizedData.BackupJobLocation = if ($record.Location) { $record.Location } else { '--' } + $LocalizedData.BackupJobType = switch ($record.Type) { + 'SCHEDULED' { $LocalizedData.BackupJobScheduled } + default { if ($record.Type) { $TextInfo.ToTitleCase($record.Type.ToString().ToLower()) } else { '--' } } + } + $LocalizedData.BackupJobStatus = switch ($record.State) { + 'SUCCEEDED' { $LocalizedData.BackupJobComplete } + default { if ($record.State) { $TextInfo.ToTitleCase($record.State.ToString().ToLower()) } else { '--' } } + } + $LocalizedData.BackupJobDataTransferred = $dataTransferred + $LocalizedData.BackupJobDuration = $duration + $LocalizedData.BackupJobEndTime = if ($record.Timestamp) { ([datetime]$record.Timestamp).ToLocalTime().ToString() } else { '--' } + } + } + if ($Healthcheck.vCenter.Backup) { + $BackupJobInfo | Where-Object { $_.$($LocalizedData.BackupJobStatus) -eq 'Failed' } | Set-Style -Style Critical -Property $LocalizedData.BackupJobStatus + } + $TableParams = @{ + Name = ($LocalizedData.TableBackupJobHistory -f $vCenterServerName) + ColumnWidths = 28, 13, 13, 13, 13, 20 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $BackupJobInfo | Table @TableParams + } else { + Paragraph $LocalizedData.BackupNoJobs + } + } + #endregion Backup Job History + } + } + #endregion vCenter Server Backup + #region vCenter Server Historical Statistics Section -Style Heading3 $LocalizedData.HistoricalStatistics { $vCenterHistoricalStats = Get-vCenterStats | Select-Object @{L = $LocalizedData.IntervalDuration; E = { $_.IntervalDuration } }, @@ -236,6 +364,10 @@ function Get-AbrVSpherevCenter { $LocalizedData.MinutesBefore = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.minutesBefore' }).Value $LocalizedData.PollInterval = "$(($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.pollIntervalDays' }).Value) days" } + if ($Healthcheck.vCenter.Certificate) { + $VcenterCertMgmt | Where-Object { $_.$($LocalizedData.CertStatus) -in @('EXPIRED', 'EXPIRING') } | Set-Style -Style Critical -Property $LocalizedData.CertStatus + $VcenterCertMgmt | Where-Object { $_.$($LocalizedData.CertStatus) -eq 'EXPIRING_SOON' } | Set-Style -Style Warning -Property $LocalizedData.CertStatus + } $TableParams = @{ Name = ($LocalizedData.TableCertificate -f $vCenterServerName) List = $true @@ -380,134 +512,6 @@ function Get-AbrVSpherevCenter { Write-PScriboMessage -Message $LocalizedData.InsufficientPrivStoragePolicy } #endregion VM Storage Policies - - #region vCenter Server Backup - Section -Style Heading3 $LocalizedData.BackupSettings { - if (-not $vcApiUri) { - Paragraph $LocalizedData.BackupApiNotAvailable - } else { - #region Backup Schedule - $BackupSchedules = $null - try { - $BackupSchedules = Invoke-RestMethod -Uri "$vcApiUri/appliance/recovery/backup/schedules" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop - } catch { - Write-PScriboMessage -IsWarning ($LocalizedData.BackupApiError -f $_.Exception.Message) - } - Section -Style Heading4 $LocalizedData.BackupSchedule { - if ($BackupSchedules -and $BackupSchedules.PSObject.Properties.Name.Count -gt 0) { - $ApplianceTimezone = $null - try { - $ApplianceTimezone = Invoke-RestMethod -Uri "$vcApiUri/appliance/system/time/timezone" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop - } catch {} - $BackupScheduleInfo = foreach ($schedId in $BackupSchedules.PSObject.Properties.Name) { - $sched = $BackupSchedules.$schedId - $recurrence = if ($sched.recurrence_info) { - $h = [int]$sched.recurrence_info.hour % 12 - if ($h -eq 0) { $h = 12 } - $ap = if ([int]$sched.recurrence_info.hour -ge 12) { 'P.M.' } else { 'A.M.' } - $timeStr = '{0}:{1:D2} {2}' -f $h, [int]$sched.recurrence_info.minute, $ap - $dayStr = if ($sched.recurrence_info.days -and $sched.recurrence_info.days.Count -gt 0) { - ($sched.recurrence_info.days | ForEach-Object { $TextInfo.ToTitleCase($_.ToLower()) }) -join ', ' - } else { $LocalizedData.BackupDaily } - $tz = if ($ApplianceTimezone) { " $ApplianceTimezone" } else { '' } - "$dayStr, $timeStr$tz" - } else { '--' } - $partsFormatted = ($sched.parts | ForEach-Object { - switch ($_) { - 'supervisors' { $LocalizedData.BackupPartSeat } - 'seat' { $LocalizedData.BackupPartSeat } - 'common' { $LocalizedData.BackupPartCommon } - 'stats' { $LocalizedData.BackupPartStats } - default { $_ } - } - }) -join ', ' - [PSCustomObject]@{ - $LocalizedData.BackupEnabled = if ($sched.enable) { $LocalizedData.BackupActivated } else { $LocalizedData.BackupDeactivated } - $LocalizedData.BackupRecurrence = $recurrence - $LocalizedData.BackupLocation = $sched.location - $LocalizedData.BackupParts = $partsFormatted - $LocalizedData.BackupRetentionCount = $sched.retention_info.max_count - } - } - if ($Healthcheck.vCenter.Backup) { - $BackupScheduleInfo | Where-Object { $_.$($LocalizedData.BackupEnabled) -eq $LocalizedData.BackupDeactivated } | Set-Style -Style Warning -Property $LocalizedData.BackupEnabled - } - $TableParams = @{ - Name = ($LocalizedData.TableBackupSchedule -f $vCenterServerName) - List = $true - ColumnWidths = 40, 60 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $BackupScheduleInfo | Table @TableParams - } else { - Paragraph $LocalizedData.BackupNotConfigured - } - } - #endregion Backup Schedule - - #region Backup Job History - $BackupJobRecords = $null - try { - $jobDetailsResponse = Invoke-RestMethod -Uri "$vcApiUri/appliance/recovery/backup/job/details" -Method Get -Headers $vcApiHeaders -SkipCertificateCheck -ErrorAction Stop - if ($jobDetailsResponse -and $jobDetailsResponse.PSObject.Properties.Name.Count -gt 0) { - $BackupJobRecords = $jobDetailsResponse.PSObject.Properties | - Sort-Object Name -Descending | Select-Object -First 10 | ForEach-Object { - $job = $_.Value - $jobDuration = if ($job.start_time -and $job.end_time) { ([datetime]$job.end_time - [datetime]$job.start_time).TotalSeconds } elseif ($job.duration) { $job.duration } else { $null } - [PSCustomObject]@{ - Location = $job.location - Type = $job.type - State = $job.state ?? $job.status - Size = $job.size - Duration = $jobDuration - Timestamp = $job.end_time - } - } - } - } catch { - Write-PScriboMessage -IsWarning ($LocalizedData.BackupApiError -f $_.Exception.Message) - } - Section -Style Heading4 $LocalizedData.BackupJobHistory { - if ($BackupJobRecords -and $BackupJobRecords.Count -gt 0) { - $BackupJobInfo = foreach ($record in $BackupJobRecords) { - $duration = if ($record.Duration) { $ts = [timespan]::FromSeconds($record.Duration); '{0:D2}:{1:D2}:{2:D2}' -f $ts.Hours, $ts.Minutes, $ts.Seconds } else { '--' } - $dataTransferred = if ($record.Size -gt 0) { '{0:N2} GB' -f ($record.Size / 1GB) } else { '--' } - [PSCustomObject]@{ - $LocalizedData.BackupJobLocation = if ($record.Location) { $record.Location } else { '--' } - $LocalizedData.BackupJobType = switch ($record.Type) { - 'SCHEDULED' { $LocalizedData.BackupJobScheduled } - default { if ($record.Type) { $TextInfo.ToTitleCase($record.Type.ToString().ToLower()) } else { '--' } } - } - $LocalizedData.BackupJobStatus = switch ($record.State) { - 'SUCCEEDED' { $LocalizedData.BackupJobComplete } - default { if ($record.State) { $TextInfo.ToTitleCase($record.State.ToString().ToLower()) } else { '--' } } - } - $LocalizedData.BackupJobDataTransferred = $dataTransferred - $LocalizedData.BackupJobDuration = $duration - $LocalizedData.BackupJobEndTime = if ($record.Timestamp) { ([datetime]$record.Timestamp).ToLocalTime().ToString() } else { '--' } - } - } - if ($Healthcheck.vCenter.Backup) { - $BackupJobInfo | Where-Object { $_.$($LocalizedData.BackupJobStatus) -eq 'Failed' } | Set-Style -Style Critical -Property $LocalizedData.BackupJobStatus - } - $TableParams = @{ - Name = ($LocalizedData.TableBackupJobHistory -f $vCenterServerName) - ColumnWidths = 28, 13, 13, 13, 13, 20 - } - if ($Report.ShowTableCaptions) { - $TableParams['Caption'] = "- $($TableParams.Name)" - } - $BackupJobInfo | Table @TableParams - } else { - Paragraph $LocalizedData.BackupNoJobs - } - } - #endregion Backup Job History - } - } - #endregion vCenter Server Backup } #endregion vCenter Server Detailed Information diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 index 58bb966..f26c3d3 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevSAN.ps1 @@ -168,16 +168,16 @@ function Get-AbrVSpherevSAN { try { Section -Style Heading4 $LocalizedData.ServicesSection { $VsanServices = @( - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.PerformanceService; $LocalizedData.Status = if ($VsanCluster.PerformanceServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.FileService; $LocalizedData.Status = if ($VsanCluster.FileServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.iSCSITargetService; $LocalizedData.Status = if ($VsanCluster.IscsiTargetServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Deduplication; $LocalizedData.Status = if ($VsanCluster.SpaceEfficiencyEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Encryption; $LocalizedData.Status = if ($VsanCluster.EncryptionEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.PerformanceService; $LocalizedData.Status = if ($VsanCluster.PerformanceServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.FileService; $LocalizedData.Status = if ($VsanCluster.FileServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.iSCSITargetService; $LocalizedData.Status = if ($VsanCluster.IscsiTargetServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Deduplication; $LocalizedData.Status = if ($VsanCluster.SpaceEfficiencyEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Encryption; $LocalizedData.Status = if ($VsanCluster.EncryptionEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HistoricalHealthService; $LocalizedData.Status = if ($VsanCluster.HistoricalHealthEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HealthCheck; $LocalizedData.Status = if ($VsanCluster.HealthCheckEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HealthCheck; $LocalizedData.Status = if ($VsanCluster.HealthCheckEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } ) $TableParams = @{ - Name = ($LocalizedData.TableVSANServices -f $VsanCluster.Name) + Name = ($LocalizedData.TableVSANServices -f $VsanCluster.Name) ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { @@ -360,16 +360,16 @@ function Get-AbrVSpherevSAN { try { Section -Style Heading4 $LocalizedData.ServicesSection { $VsanServices = @( - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.PerformanceService; $LocalizedData.Status = if ($VsanCluster.PerformanceServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.FileService; $LocalizedData.Status = if ($VsanCluster.FileServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.iSCSITargetService; $LocalizedData.Status = if ($VsanCluster.IscsiTargetServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Deduplication; $LocalizedData.Status = if ($VsanCluster.SpaceEfficiencyEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Encryption; $LocalizedData.Status = if ($VsanCluster.EncryptionEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.PerformanceService; $LocalizedData.Status = if ($VsanCluster.PerformanceServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.FileService; $LocalizedData.Status = if ($VsanCluster.FileServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.iSCSITargetService; $LocalizedData.Status = if ($VsanCluster.IscsiTargetServiceEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Deduplication; $LocalizedData.Status = if ($VsanCluster.SpaceEfficiencyEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.Encryption; $LocalizedData.Status = if ($VsanCluster.EncryptionEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HistoricalHealthService; $LocalizedData.Status = if ($VsanCluster.HistoricalHealthEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } - [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HealthCheck; $LocalizedData.Status = if ($VsanCluster.HealthCheckEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } + [PSCustomObject]@{ $LocalizedData.Service = $LocalizedData.HealthCheck; $LocalizedData.Status = if ($VsanCluster.HealthCheckEnabled) { $LocalizedData.Enabled } else { $LocalizedData.Disabled } } ) $TableParams = @{ - Name = ($LocalizedData.TableVSANServices -f $VsanCluster.Name) + Name = ($LocalizedData.TableVSANServices -f $VsanCluster.Name) ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 8887078..6a84894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add tag reporting to Datastore, Datastore Cluster, Distributed Switch, Resource Pool, Virtual Machine, and VMHost sections - Extract Cluster VUM Baselines and Compliance into dedicated `Get-AbrVSphereClusterVUM` sub-function - Add vCenter Server Backup Settings section (InfoLevel 3+); Backup Schedule table shows status (Activated/Deactivated), schedule recurrence with appliance timezone, backup location, data components, and retention count; Backup Job History table shows the 10 most recent jobs (location, type, status, data transferred, duration, end time) via `GET /api/appliance/recovery/backup/job/details`; requires vSphere 7.0+ REST API; includes `Backup` healthcheck (Warning: schedule deactivated, Critical: job failed) +- Add `Certificate` healthcheck to vCenter Server Certificate section (Critical: EXPIRED/EXPIRING, Warning: EXPIRING_SOON) ### Changed diff --git a/README.md b/README.md index 20dede0..0730099 100644 --- a/README.md +++ b/README.md @@ -236,9 +236,11 @@ The **vCenter** schema is used to configure health checks for vCenter Server. | Sub-Schema | Setting | Default | Description | Highlight | |------------|--------------|---------|-----------------------------------------------------|-------------------------------------------------------------------------------------------| -| Mail | true / false | true | Highlights mail settings which are not configured | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Not Configured | -| Licensing | true / false | true | Highlights product evaluation licenses | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Product evaluation license in use | -| Alarms | true / false | true | Highlights vCenter Server alarms which are disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Alarm disabled | +| Mail | true / false | true | Highlights mail settings which are not configured | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Not Configured | +| Licensing | true / false | true | Highlights product evaluation licenses | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Product evaluation license in use | +| Alarms | true / false | true | Highlights vCenter Server alarms which are disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Alarm disabled | +| Certificate | true / false | true | Highlights vCenter Server certificates which are expired or approaching expiry | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Expired / Expiring
![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Expiring Soon | +| Backup | true / false | true | Highlights vCenter Server backup schedules which are deactivated and backup jobs which have failed (requires vSphere 7.0 or later REST API access) | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Backup schedule deactivated
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Backup job failed | #### Cluster The **Cluster** schema is used to configure health checks for vSphere Clusters. From 5fc84531b76bb05673a39cc807f6ec244708c512 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Fri, 13 Mar 2026 19:05:33 +1100 Subject: [PATCH 19/24] Add vCenter summary tables, Content Libraries, ShowRoles/ShowAlarms/ShowTags options - Add vCenter resource/inventory summary tables at InfoLevel 1+ (#30) - Add Content Libraries section to vCenter report (InfoLevel 3+) - Add ShowRoles, ShowAlarms (#122), and ShowTags options - Gate tag reporting across all sections with ShowTags option - Use Convert-DataSize for memory/storage and library item sizes Co-Authored-By: Claude Sonnet 4.6 --- .../AsBuiltReport.VMware.vSphere.json | 8 +- .../Language/de-DE/VMwarevSphere.psd1 | 43 ++++ .../Language/en-GB/VMwarevSphere.psd1 | 43 ++++ .../Language/en-US/VMwarevSphere.psd1 | 43 ++++ .../Language/es-ES/VMwarevSphere.psd1 | 43 ++++ .../Language/fr-FR/VMwarevSphere.psd1 | 43 ++++ .../Src/Private/Get-AbrVSphereDSCluster.ps1 | 2 +- .../Src/Private/Get-AbrVSphereDatastore.ps1 | 2 +- .../Src/Private/Get-AbrVSphereNetwork.ps1 | 2 +- .../Private/Get-AbrVSphereResourcePool.ps1 | 2 +- .../Src/Private/Get-AbrVSphereVM.ps1 | 2 +- .../Src/Private/Get-AbrVSphereVMHost.ps1 | 2 +- .../Src/Private/Get-AbrVSpherevCenter.ps1 | 211 +++++++++++++++++- CHANGELOG.md | 4 + README.md | 6 +- 15 files changed, 443 insertions(+), 13 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json index 18771a9..2006636 100644 --- a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json +++ b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.json @@ -12,7 +12,10 @@ "Options": { "ShowLicenseKeys": false, "ShowEncryptionKeys": false, - "ShowVMSnapshots": true + "ShowVMSnapshots": true, + "ShowRoles": true, + "ShowAlarms": true, + "ShowTags": true }, "InfoLevel": { "_comment_": "0 = Disabled, 1 = Enabled / Summary, 2 = Adv Summary, 3 = Detailed, 4 = Adv Detailed, 5 = Comprehensive", @@ -33,7 +36,8 @@ "Licensing": true, "Alarms": true, "Backup": true, - "Certificate": true + "Certificate": true, + "ContentLibrary": true }, "Cluster": { "HAEnabled": true, diff --git a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 index cee0808..23192c3 100644 --- a/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/de-DE/VMwarevSphere.psd1 @@ -25,6 +25,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' InfoLevel = vCenter-Informationsebene auf {0} gesetzt. Collecting = vCenter Server-Informationen werden gesammelt. SectionHeading = vCenter Server + ParagraphSummaryBrief = Die folgenden Abschnitte fassen die Konfiguration des vCenter Servers {0} zusammen. ParagraphSummary = Die folgenden Abschnitte beschreiben die Konfiguration des vCenter Servers {0}. InsufficientPrivLicense = Unzureichende Benutzerberechtigungen für den Bericht über vCenter Server-Lizenzierung. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'Global > Lizenzen' zugewiesen ist. InsufficientPrivStoragePolicy = Unzureichende Benutzerberechtigungen für den Bericht über VM-Speicherrichtlinien. Stellen Sie sicher, dass dem Benutzerkonto das Privileg 'Speicherprofil > Anzeigen' zugewiesen ist. @@ -155,6 +156,48 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' BackupJobEndTime = Endzeit TableBackupSchedule = Sicherungszeitplan - {0} TableBackupJobHistory = Sicherungsauftragsverlauf - {0} + ResourceSummary = Ressourcen + SummaryResource = Ressource + Free = Frei + CPU = CPU + Memory = Arbeitsspeicher + Storage = Speicher + VirtualMachines = Virtuelle Maschinen + Hosts = Hosts + PoweredOn = Eingeschaltet + PoweredOff = Ausgeschaltet + Suspended = Angehalten + Connected = Verbunden + Disconnected = Getrennt + Maintenance = Wartungsmodus + ContentLibraries = Inhaltsbibliotheken + ContentLibrary = Inhaltsbibliothek + LibraryType = Typ + Datastore = Datenspeicher + LibraryLocal = Lokal + LibrarySubscribed = Abonniert + ItemCount = Elemente + SubscriptionUrl = Abonnement-URL + AutomaticSync = Automatische Synchronisierung + OnDemandSync = Bedarfsgesteuerte Synchronisierung + LibraryItems = Bibliothekselemente + ItemName = Element + ContentType = Inhaltstyp + ItemSize = Größe + CreationTime = Erstellungszeit + LastModified = Zuletzt geändert + ContentLibraryNoItems = Keine Elemente in dieser Inhaltsbibliothek gefunden. + ContentLibraryNone = Keine Inhaltsbibliotheken gefunden. + CollectingContentLibrary = Informationen zur Inhaltsbibliothek werden gesammelt. + ContentLibraryError = Inhaltsbibliotheksinformationen konnten nicht abgerufen werden. {0} + ContentLibraryItemError = Elemente für Inhaltsbibliothek '{0}' konnten nicht abgerufen werden. {1} + TableContentLibraries = Content Libraries - {0} + TableContentLibrary = Content Library {0} - {1} + TableLibraryItems = Library Items - {0} + TableLibraryItem = {0} - {1} + TablevCenterResourceSummary = Ressourcenübersicht - {0} + TablevCenterVMSummary = Übersicht Virtuelle Maschinen - {0} + TablevCenterHostSummary = Host-Übersicht - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 index 5c3caeb..cb3611b 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-GB/VMwarevSphere.psd1 @@ -25,6 +25,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' InfoLevel = vCenter InfoLevel set to {0}. Collecting = Collecting vCenter Server information. SectionHeading = vCenter Server + ParagraphSummaryBrief = The following sections summarise the configuration of vCenter Server {0}. ParagraphSummary = The following sections detail the configuration of vCenter Server {0}. InsufficientPrivLicense = Insufficient user privileges to report vCenter Server licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned. InsufficientPrivStoragePolicy = Insufficient user privileges to report VM storage policies. Please ensure the user account has the 'Storage Profile > View' privilege assigned. @@ -155,6 +156,48 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' BackupJobEndTime = End Time TableBackupSchedule = Backup Schedule - {0} TableBackupJobHistory = Backup Job History - {0} + ResourceSummary = Resources + SummaryResource = Resource + Free = Free + CPU = CPU + Memory = Memory + Storage = Storage + VirtualMachines = Virtual Machines + Hosts = Hosts + PoweredOn = Powered On + PoweredOff = Powered Off + Suspended = Suspended + Connected = Connected + Disconnected = Disconnected + Maintenance = Maintenance + ContentLibraries = Content Libraries + ContentLibrary = Content Library + LibraryType = Type + Datastore = Datastore + LibraryLocal = Local + LibrarySubscribed = Subscribed + ItemCount = Items + SubscriptionUrl = Subscription URL + AutomaticSync = Automatic Synchronisation + OnDemandSync = On-Demand Synchronisation + LibraryItems = Library Items + ItemName = Item + ContentType = Content Type + ItemSize = Size + CreationTime = Creation Time + LastModified = Last Modified + ContentLibraryNoItems = No items found in this content library. + ContentLibraryNone = No content libraries found. + CollectingContentLibrary = Collecting Content Library information. + ContentLibraryError = Unable to retrieve Content Library information. {0} + ContentLibraryItemError = Unable to retrieve items for Content Library '{0}'. {1} + TableContentLibraries = Content Libraries - {0} + TableContentLibrary = Content Library {0} - {1} + TableLibraryItems = Library Items - {0} + TableLibraryItem = {0} - {1} + TablevCenterResourceSummary = Resource Summary - {0} + TablevCenterVMSummary = Virtual Machine Summary - {0} + TablevCenterHostSummary = Host Summary - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 index 25a1e30..6a63612 100644 --- a/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/en-US/VMwarevSphere.psd1 @@ -25,6 +25,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' InfoLevel = vCenter InfoLevel set to {0}. Collecting = Collecting vCenter Server information. SectionHeading = vCenter Server + ParagraphSummaryBrief = The following sections summarise the configuration of vCenter Server {0}. ParagraphSummary = The following sections detail the configuration of vCenter Server {0}. InsufficientPrivLicense = Insufficient user privileges to report vCenter Server licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned. InsufficientPrivStoragePolicy = Insufficient user privileges to report VM storage policies. Please ensure the user account has the 'Storage Profile > View' privilege assigned. @@ -155,6 +156,48 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' BackupJobEndTime = End Time TableBackupSchedule = Backup Schedule - {0} TableBackupJobHistory = Backup Job History - {0} + ResourceSummary = Resources + SummaryResource = Resource + Free = Free + CPU = CPU + Memory = Memory + Storage = Storage + VirtualMachines = Virtual Machines + Hosts = Hosts + PoweredOn = Powered On + PoweredOff = Powered Off + Suspended = Suspended + Connected = Connected + Disconnected = Disconnected + Maintenance = Maintenance + ContentLibraries = Content Libraries + ContentLibrary = Content Library + LibraryType = Type + Datastore = Datastore + LibraryLocal = Local + LibrarySubscribed = Subscribed + ItemCount = Items + SubscriptionUrl = Subscription URL + AutomaticSync = Automatic Synchronisation + OnDemandSync = On-Demand Synchronisation + LibraryItems = Library Items + ItemName = Item + ContentType = Content Type + ItemSize = Size + CreationTime = Creation Time + LastModified = Last Modified + ContentLibraryNoItems = No items found in this content library. + ContentLibraryNone = No content libraries found. + CollectingContentLibrary = Collecting Content Library information. + ContentLibraryError = Unable to retrieve Content Library information. {0} + ContentLibraryItemError = Unable to retrieve items for Content Library '{0}'. {1} + TableContentLibraries = Content Libraries - {0} + TableContentLibrary = Content Library {0} - {1} + TableLibraryItems = Library Items - {0} + TableLibraryItem = {0} - {1} + TablevCenterResourceSummary = Resource Summary - {0} + TablevCenterVMSummary = Virtual Machine Summary - {0} + TablevCenterHostSummary = Host Summary - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 index 79b5a9e..7ab172f 100644 --- a/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/es-ES/VMwarevSphere.psd1 @@ -25,6 +25,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' InfoLevel = Nivel de información de vCenter establecido en {0}. Collecting = Recopilando información del servidor vCenter. SectionHeading = Servidor vCenter + ParagraphSummaryBrief = Las siguientes secciones resumen la configuración del servidor vCenter {0}. ParagraphSummary = Las siguientes secciones detallan la configuración del servidor vCenter {0}. InsufficientPrivLicense = Privilegios de usuario insuficientes para informar sobre las licencias del servidor vCenter. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'Global > Licencias'. InsufficientPrivStoragePolicy = Privilegios de usuario insuficientes para informar sobre las políticas de almacenamiento de máquinas virtuales. Asegúrese de que la cuenta de usuario tenga asignado el privilegio 'Perfil de almacenamiento > Ver'. @@ -155,6 +156,48 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' BackupJobEndTime = Hora de finalización TableBackupSchedule = Programación de copia de seguridad - {0} TableBackupJobHistory = Historial de trabajos de copia de seguridad - {0} + ResourceSummary = Recursos + SummaryResource = Recurso + Free = Libre + CPU = CPU + Memory = Memoria + Storage = Almacenamiento + VirtualMachines = Máquinas Virtuales + Hosts = Hosts + PoweredOn = Encendido + PoweredOff = Apagado + Suspended = Suspendido + Connected = Conectado + Disconnected = Desconectado + Maintenance = Mantenimiento + ContentLibraries = Bibliotecas de Contenido + ContentLibrary = Biblioteca de Contenido + LibraryType = Tipo + Datastore = Almacén de Datos + LibraryLocal = Local + LibrarySubscribed = Suscrita + ItemCount = Elementos + SubscriptionUrl = URL de Suscripción + AutomaticSync = Sincronización Automática + OnDemandSync = Sincronización bajo Demanda + LibraryItems = Elementos de Biblioteca + ItemName = Elemento + ContentType = Tipo de Contenido + ItemSize = Tamaño + CreationTime = Fecha de Creación + LastModified = Última Modificación + ContentLibraryNoItems = No se encontraron elementos en esta biblioteca de contenido. + ContentLibraryNone = No se encontraron bibliotecas de contenido. + CollectingContentLibrary = Recopilando información de la biblioteca de contenido. + ContentLibraryError = No se puede recuperar la información de la biblioteca de contenido. {0} + ContentLibraryItemError = No se pueden recuperar los elementos de la biblioteca de contenido '{0}'. {1} + TableContentLibraries = Content Libraries - {0} + TableContentLibrary = Content Library {0} - {1} + TableLibraryItems = Library Items - {0} + TableLibraryItem = {0} - {1} + TablevCenterResourceSummary = Resumen de Recursos - {0} + TablevCenterVMSummary = Resumen de Máquinas Virtuales - {0} + TablevCenterHostSummary = Resumen de Hosts - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 index 5677cd2..f520d96 100644 --- a/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/Language/fr-FR/VMwarevSphere.psd1 @@ -25,6 +25,7 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' InfoLevel = Niveau d'information vCenter défini sur {0}. Collecting = Collecte des informations du serveur vCenter. SectionHeading = Serveur vCenter + ParagraphSummaryBrief = Les sections suivantes résument la configuration du serveur vCenter {0}. ParagraphSummary = Les sections suivantes détaillent la configuration du serveur vCenter {0}. InsufficientPrivLicense = Privilèges utilisateur insuffisants pour rapporter les licences du serveur vCenter. Veuillez vous assurer que le compte utilisateur dispose du privilège 'Global > Licences'. InsufficientPrivStoragePolicy = Privilèges utilisateur insuffisants pour rapporter les stratégies de stockage des machines virtuelles. Veuillez vous assurer que le compte utilisateur dispose du privilège 'Profil de stockage > Afficher'. @@ -155,6 +156,48 @@ GetAbrVSpherevCenter = ConvertFrom-StringData @' BackupJobEndTime = Heure de fin TableBackupSchedule = Calendrier de sauvegarde - {0} TableBackupJobHistory = Historique des tâches de sauvegarde - {0} + ResourceSummary = Ressources + SummaryResource = Ressource + Free = Libre + CPU = CPU + Memory = Mémoire + Storage = Stockage + VirtualMachines = Machines Virtuelles + Hosts = Hôtes + PoweredOn = Allumé + PoweredOff = Éteint + Suspended = Suspendu + Connected = Connecté + Disconnected = Déconnecté + Maintenance = Maintenance + ContentLibraries = Bibliothèques de Contenu + ContentLibrary = Bibliothèque de Contenu + LibraryType = Type + Datastore = Banque de Données + LibraryLocal = Locale + LibrarySubscribed = Abonnée + ItemCount = Éléments + SubscriptionUrl = URL d'Abonnement + AutomaticSync = Synchronisation Automatique + OnDemandSync = Synchronisation à la Demande + LibraryItems = Éléments de Bibliothèque + ItemName = Élément + ContentType = Type de Contenu + ItemSize = Taille + CreationTime = Date de Création + LastModified = Dernière Modification + ContentLibraryNoItems = Aucun élément trouvé dans cette bibliothèque de contenu. + ContentLibraryNone = Aucune bibliothèque de contenu trouvée. + CollectingContentLibrary = Collecte des informations de la bibliothèque de contenu. + ContentLibraryError = Impossible de récupérer les informations de la bibliothèque de contenu. {0} + ContentLibraryItemError = Impossible de récupérer les éléments de la bibliothèque de contenu '{0}'. {1} + TableContentLibraries = Content Libraries - {0} + TableContentLibrary = Content Library {0} - {1} + TableLibraryItems = Library Items - {0} + TableLibraryItem = {0} - {1} + TablevCenterResourceSummary = Résumé des Ressources - {0} + TablevCenterVMSummary = Résumé des Machines Virtuelles - {0} + TablevCenterHostSummary = Résumé des Hôtes - {0} '@ # Get-AbrVSphereCluster diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 index 80bc4a2..534d3b1 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDSCluster.ps1 @@ -94,7 +94,7 @@ function Get-AbrVSphereDSCluster { 'InputObject' = $DSClusterDetail 'MemberType' = 'NoteProperty' } - if ($TagAssignments | Where-Object { $_.entity -eq $DSCluster }) { + if ($Options.ShowTags -and ($TagAssignments | Where-Object { $_.entity -eq $DSCluster })) { Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $DSCluster }).Tag -join ', ') } if ($Healthcheck.DSCluster.CapacityUtilization) { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 index 8d7e19a..80e3243 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereDatastore.ps1 @@ -116,7 +116,7 @@ function Get-AbrVSphereDatastore { 'InputObject' = $DatastoreDetail 'MemberType' = 'NoteProperty' } - if ($TagAssignments | Where-Object { $_.entity -eq $Datastore }) { + if ($Options.ShowTags -and ($TagAssignments | Where-Object { $_.entity -eq $Datastore })) { Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $Datastore }).Tag -join ', ') } diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 index cd18dbe..12bf4b2 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereNetwork.ps1 @@ -87,7 +87,7 @@ function Get-AbrVSphereNetwork { 'InputObject' = $VDSwitchDetail 'MemberType' = 'NoteProperty' } - if ($TagAssignments | Where-Object { $_.entity -eq $VDS }) { + if ($Options.ShowTags -and ($TagAssignments | Where-Object { $_.entity -eq $VDS })) { Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $VDS }).Tag -join ', ') } #region Network Advanced Detail Information diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 index 934ce71..2a0a90e 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereResourcePool.ps1 @@ -95,7 +95,7 @@ function Get-AbrVSphereResourcePool { 'InputObject' = $ResourcePoolDetail 'MemberType' = 'NoteProperty' } - if ($TagAssignments | Where-Object { $_.entity -eq $ResourcePool }) { + if ($Options.ShowTags -and ($TagAssignments | Where-Object { $_.entity -eq $ResourcePool })) { Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $ResourcePool }).Tag -join ', ') } #region Resource Pool Advanced Detail Information diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 index a04efa5..7852975 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVM.ps1 @@ -262,7 +262,7 @@ function Get-AbrVSphereVM { #if ($VMView.Config.CreateDate) { # Add-Member @MemberProps -Name 'Creation Date' -Value ($VMView.Config.CreateDate).ToLocalTime().ToString() #} - if ($TagAssignments | Where-Object { $_.entity -eq $VM }) { + if ($Options.ShowTags -and ($TagAssignments | Where-Object { $_.entity -eq $VM })) { Add-Member @MemberProps -Name $LocalizedData.Tags -Value $(($TagAssignments | Where-Object { $_.entity -eq $VM }).Tag -join ', ') } if ($VM.Notes) { diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 index 8f98257..c8f4647 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVMHost.ps1 @@ -71,7 +71,7 @@ function Get-AbrVSphereVMHost { foreach ($VMHost in ($VMHosts | Where-Object { $_.ConnectionState -eq 'Connected' -or $_.ConnectionState -eq 'Maintenance' })) { #region VMHost Section Section -Style Heading3 $VMHost { - if ($TagAssignments | Where-Object { $_.entity -eq $VMHost }) { + if ($Options.ShowTags -and ($TagAssignments | Where-Object { $_.entity -eq $VMHost })) { Paragraph ($LocalizedData.Tags + ': ' + (($TagAssignments | Where-Object { $_.entity -eq $VMHost }).Tag -join ', ')) } Get-AbrVSphereVMHostHardware diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 index 2b61690..e148b7a 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSpherevCenter.ps1 @@ -27,7 +27,11 @@ function Get-AbrVSpherevCenter { if ($InfoLevel.vCenter -ge 1) { Write-PScriboMessage -Message $LocalizedData.Collecting Section -Style Heading2 $LocalizedData.SectionHeading { - Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + if ($InfoLevel.vCenter -le 2) { + Paragraph ($LocalizedData.ParagraphSummaryBrief -f $vCenterServerName) + } else { + Paragraph ($LocalizedData.ParagraphSummary -f $vCenterServerName) + } BlankLine # Gather basic vCenter Server Information $vCenterServerInfo = [PSCustomObject]@{ @@ -46,6 +50,100 @@ function Get-AbrVSpherevCenter { $TableParams['Caption'] = "- $($TableParams.Name)" } $vCenterServerInfo | Table @TableParams + + #region Resource Summary + Section -Style Heading3 $LocalizedData.ResourceSummary { + $totalCpuMhz = ($VMHosts | Measure-Object -Property CpuTotalMhz -Sum).Sum + $usedCpuMhz = ($VMHosts | Measure-Object -Property CpuUsageMhz -Sum).Sum + $freeCpuMhz = $totalCpuMhz - $usedCpuMhz + $totalCpuGHz = [Math]::Round($totalCpuMhz / 1000, 2) + $usedCpuGHz = [Math]::Round($usedCpuMhz / 1000, 2) + $freeCpuGHz = [Math]::Round($freeCpuMhz / 1000, 2) + + $totalMemGB = ($VMHosts | Measure-Object -Property MemoryTotalGB -Sum).Sum + $usedMemGB = ($VMHosts | Measure-Object -Property MemoryUsageGB -Sum).Sum + $freeMemGB = $totalMemGB - $usedMemGB + $totalMem = Convert-DataSize -Size $totalMemGB -InputUnit GB + $usedMem = Convert-DataSize -Size $usedMemGB -InputUnit GB + $freeMem = Convert-DataSize -Size $freeMemGB -InputUnit GB + + $totalStorageGB = ($Datastores | Measure-Object -Property CapacityGB -Sum).Sum + $freeStorageGB = ($Datastores | Measure-Object -Property FreeSpaceGB -Sum).Sum + $usedStorageGB = $totalStorageGB - $freeStorageGB + $totalStorage = Convert-DataSize -Size $totalStorageGB -InputUnit GB + $usedStorage = Convert-DataSize -Size $usedStorageGB -InputUnit GB + $freeStorage = Convert-DataSize -Size $freeStorageGB -InputUnit GB + + $vCenterResourceSummary = @( + [PSCustomObject]@{ + $LocalizedData.SummaryResource = $LocalizedData.CPU + $LocalizedData.Free = "$freeCpuGHz GHz" + $LocalizedData.Used = "$usedCpuGHz GHz" + $LocalizedData.Total = "$totalCpuGHz GHz" + } + [PSCustomObject]@{ + $LocalizedData.SummaryResource = $LocalizedData.Memory + $LocalizedData.Free = $freeMem + $LocalizedData.Used = $usedMem + $LocalizedData.Total = $totalMem + } + [PSCustomObject]@{ + $LocalizedData.SummaryResource = $LocalizedData.Storage + $LocalizedData.Free = $freeStorage + $LocalizedData.Used = $usedStorage + $LocalizedData.Total = $totalStorage + } + ) + $TableParams = @{ + Name = ($LocalizedData.TablevCenterResourceSummary -f $vCenterServerName) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterResourceSummary | Table @TableParams + } # end Section ResourceSummary + + Section -Style Heading3 $LocalizedData.VirtualMachines { + $vmPoweredOn = ($VMs | Where-Object { $_.PowerState -eq 'PoweredOn' }).Count + $vmPoweredOff = ($VMs | Where-Object { $_.PowerState -eq 'PoweredOff' }).Count + $vmSuspended = ($VMs | Where-Object { $_.PowerState -eq 'Suspended' }).Count + $vCenterVMSummary = [PSCustomObject]@{ + $LocalizedData.PoweredOn = $vmPoweredOn + $LocalizedData.PoweredOff = $vmPoweredOff + $LocalizedData.Suspended = $vmSuspended + $LocalizedData.Total = $vmPoweredOn + $vmPoweredOff + $vmSuspended + } + $TableParams = @{ + Name = ($LocalizedData.TablevCenterVMSummary -f $vCenterServerName) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterVMSummary | Table @TableParams + } # end Section VirtualMachines + + Section -Style Heading3 $LocalizedData.Hosts { + $hostsConnected = ($VMHosts | Where-Object { $_.ConnectionState -eq 'Connected' }).Count + $hostsDisconnected = ($VMHosts | Where-Object { $_.ConnectionState -eq 'Disconnected' }).Count + $hostsMaintenance = ($VMHosts | Where-Object { $_.ConnectionState -eq 'Maintenance' }).Count + $vCenterHostSummary = [PSCustomObject]@{ + $LocalizedData.Connected = $hostsConnected + $LocalizedData.Disconnected = $hostsDisconnected + $LocalizedData.Maintenance = $hostsMaintenance + $LocalizedData.Total = $hostsConnected + $hostsDisconnected + $hostsMaintenance + } + $TableParams = @{ + Name = ($LocalizedData.TablevCenterHostSummary -f $vCenterServerName) + ColumnWidths = 25, 25, 25, 25 + } + if ($Report.ShowTableCaptions) { + $TableParams['Caption'] = "- $($TableParams.Name)" + } + $vCenterHostSummary | Table @TableParams + } # end Section Hosts + #endregion Resource Summary } #endregion vCenter Server Summary & Advanced Summary @@ -385,6 +483,7 @@ function Get-AbrVSpherevCenter { #endregion vCenter Server Certificate #region vCenter Server Roles + if ($Options.ShowRoles) { Section -Style Heading3 $LocalizedData.Roles { $VIRoles = Get-VIRole -Server $vCenter | Where-Object { $null -ne $_.PrivilegeList } | Sort-Object Name $VIRoleInfo = foreach ($VIRole in $VIRoles) { @@ -419,10 +518,11 @@ function Get-AbrVSpherevCenter { $VIRoleInfo | Table @TableParams } } + } # end if ShowRoles #endregion vCenter Server Roles #region vCenter Server Tags - if ($Tags) { + if ($Options.ShowTags -and $Tags) { Section -Style Heading3 $LocalizedData.Tags { $TagInfo = foreach ($Tag in $Tags) { [PSCustomObject]@{ @@ -444,7 +544,7 @@ function Get-AbrVSpherevCenter { #endregion vCenter Server Tags #region vCenter Server Tag Categories - if ($TagCategories) { + if ($Options.ShowTags -and $TagCategories) { Section -Style Heading3 $LocalizedData.TagCategories { $TagCategoryInfo = foreach ($TagCategory in $TagCategories) { [PSCustomObject]@{ @@ -466,7 +566,7 @@ function Get-AbrVSpherevCenter { #endregion vCenter Server Tag Categories #region vCenter Server Tag Assignments - if ($TagAssignments) { + if ($Options.ShowTags -and $TagAssignments) { Section -Style Heading3 $LocalizedData.TagAssignments { $TagAssignmentInfo = foreach ($TagAssignment in $TagAssignments) { [PSCustomObject]@{ @@ -512,12 +612,114 @@ function Get-AbrVSpherevCenter { Write-PScriboMessage -Message $LocalizedData.InsufficientPrivStoragePolicy } #endregion VM Storage Policies + + #region Content Libraries + $ContentLibraries = $null + try { + $ContentLibraries = Get-ContentLibrary -Server $vCenter -ErrorAction Stop | Sort-Object Name + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.ContentLibraryError -f $_.Exception.Message) + } + if ($ContentLibraries) { + Write-PScriboMessage -Message $LocalizedData.CollectingContentLibrary + Section -Style Heading3 $LocalizedData.ContentLibraries { + if ($InfoLevel.vCenter -eq 3) { + $LibrarySummaryInfo = foreach ($Library in $ContentLibraries) { + $LibItems = $null + try { $LibItems = Get-ContentLibraryItem -ContentLibrary $Library -ErrorAction Stop } catch {} + [PSCustomObject]@{ + $LocalizedData.ContentLibrary = $Library.Name + $LocalizedData.LibraryType = if ($Library.Type -eq 'Local') { $LocalizedData.LibraryLocal } else { $LocalizedData.LibrarySubscribed } + $LocalizedData.Datastore = if ($Library.Datastore) { $Library.Datastore.Name } else { '--' } + $LocalizedData.ItemCount = if ($LibItems) { $LibItems.Count } else { 0 } + $LocalizedData.Description = if ($Library.Description) { $Library.Description } else { $LocalizedData.None } + } + } + if ($Healthcheck.vCenter.ContentLibrary) { + foreach ($Library in $ContentLibraries) { + if ($Library.Type -ne 'Local' -and -not $Library.AutomaticSync) { + $LibrarySummaryInfo | Where-Object { $_.$($LocalizedData.ContentLibrary) -eq $Library.Name } | + Set-Style -Style Warning -Property $LocalizedData.LibraryType + } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableContentLibraries -f $vCenterServerName) + ColumnWidths = 25, 15, 20, 10, 30 + } + if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } + $LibrarySummaryInfo | Table @TableParams + } + if ($InfoLevel.vCenter -ge 4) { + foreach ($Library in $ContentLibraries) { + Section -Style Heading4 $Library.Name { + $LibItems = $null + try { + $LibItems = Get-ContentLibraryItem -ContentLibrary $Library -ErrorAction Stop | Sort-Object Name + } catch { + Write-PScriboMessage -IsWarning ($LocalizedData.ContentLibraryItemError -f $Library.Name, $_.Exception.Message) + } + $LibDetailObj = [PSCustomObject]@{ + $LocalizedData.ContentLibrary = $Library.Name + $LocalizedData.LibraryType = if ($Library.Type -eq 'Local') { $LocalizedData.LibraryLocal } else { $LocalizedData.LibrarySubscribed } + $LocalizedData.Datastore = if ($Library.Datastore) { $Library.Datastore.Name } else { '--' } + $LocalizedData.ItemCount = if ($LibItems) { $LibItems.Count } else { 0 } + $LocalizedData.Description = if ($Library.Description) { $Library.Description } else { $LocalizedData.None } + $LocalizedData.CreationTime = if ($Library.CreateDate) { $Library.CreateDate.ToString() } else { '--' } + $LocalizedData.LastModified = if ($Library.UpdateDate) { $Library.UpdateDate.ToString() } else { '--' } + } + if ($Library.Type -ne 'Local') { + $MemberProps = @{ InputObject = $LibDetailObj; MemberType = 'NoteProperty' } + Add-Member @MemberProps -Name $LocalizedData.SubscriptionUrl -Value $(if ($Library.SubscriptionUri) { $Library.SubscriptionUri } else { '--' }) + Add-Member @MemberProps -Name $LocalizedData.AutomaticSync -Value $(if ($Library.AutomaticSync) { $LocalizedData.Enabled } else { $LocalizedData.Disabled }) + Add-Member @MemberProps -Name $LocalizedData.OnDemandSync -Value $(if ($Library.DownloadContentOnDemand) { $LocalizedData.Enabled } else { $LocalizedData.Disabled }) + } + if ($Healthcheck.vCenter.ContentLibrary) { + $LibDetailObj | Where-Object { $_.$($LocalizedData.AutomaticSync) -eq $LocalizedData.Disabled } | + Set-Style -Style Warning -Property $LocalizedData.AutomaticSync + } + $TableParams = @{ + Name = ($LocalizedData.TableContentLibrary -f $Library.Name, $vCenterServerName) + List = $true + ColumnWidths = 40, 60 + } + if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } + $LibDetailObj | Table @TableParams + + if ($LibItems) { + $ItemsInfo = foreach ($Item in $LibItems) { + $itemSize = if ($Item.SizeGB -gt 0) { Convert-DataSize -Size $Item.SizeGB -InputUnit GB } else { '--' } + [PSCustomObject]@{ + $LocalizedData.ItemName = $Item.Name + $LocalizedData.ContentType = if ($Item.ItemType) { $Item.ItemType } else { '--' } + $LocalizedData.ItemSize = $itemSize + $LocalizedData.Description = if ($Item.Description) { $Item.Description } else { $LocalizedData.None } + $LocalizedData.CreationTime = if ($Item.CreationTime) { $Item.CreationTime.ToString() } else { '--' } + $LocalizedData.LastModified = if ($Item.LastWriteTime) { $Item.LastWriteTime.ToString() } else { '--' } + } + } + $TableParams = @{ + Name = ($LocalizedData.TableLibraryItems -f $Library.Name) + ColumnWidths = 25, 13, 13, 21, 14, 14 + } + if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } + $ItemsInfo | Table @TableParams + } else { + Paragraph $LocalizedData.ContentLibraryNoItems + } + } + } + } + } + } + #endregion Content Libraries } #endregion vCenter Server Detailed Information #region vCenter Server Advanced Detail Information if ($InfoLevel.vCenter -ge 4) { #region vCenter Alarms + if ($Options.ShowAlarms) { Section -Style Heading3 $LocalizedData.Alarms { $Alarms = Get-AlarmDefinition -PipelineVariable alarm | ForEach-Object -Process { Get-AlarmAction -AlarmDefinition $_ -PipelineVariable action | ForEach-Object -Process { @@ -579,6 +781,7 @@ function Get-AbrVSpherevCenter { $Alarms | Table @TableParams } } + } # end if ShowAlarms #endregion vCenter Alarms } #endregion vCenter Server Advanced Detail Information diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a84894..58aeadf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extract Cluster VUM Baselines and Compliance into dedicated `Get-AbrVSphereClusterVUM` sub-function - Add vCenter Server Backup Settings section (InfoLevel 3+); Backup Schedule table shows status (Activated/Deactivated), schedule recurrence with appliance timezone, backup location, data components, and retention count; Backup Job History table shows the 10 most recent jobs (location, type, status, data transferred, duration, end time) via `GET /api/appliance/recovery/backup/job/details`; requires vSphere 7.0+ REST API; includes `Backup` healthcheck (Warning: schedule deactivated, Critical: job failed) - Add `Certificate` healthcheck to vCenter Server Certificate section (Critical: EXPIRED/EXPIRING, Warning: EXPIRING_SOON) +- Add Content Libraries section to vCenter Server report (InfoLevel 3+); summary table shows library name, type, backing datastore, item count, and description; InfoLevel 4+ adds per-library detail (including subscription URL, automatic and on-demand sync settings for subscribed libraries) and a flat items table showing name, content type, size, description, creation time, and last modified; includes `ContentLibrary` healthcheck (Warning: automatic synchronisation disabled) +- Add `ShowRoles` and `ShowAlarms` options to suppress vCenter Server Roles and Alarm Definitions sections; both default to `true`; useful for reducing report size in large environments ([#122](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/122)) +- Add `ShowTags` option to gate all tag reporting across the report (vCenter Tags, Categories, Assignments, VMHost, VM, Datastore, Datastore Cluster, Resource Pool, Distributed Switch); defaults to `true` +- Add vCenter Server resource and inventory summary tables at InfoLevel 1+; resource table shows CPU (GHz), Memory (GB/TB), and Storage (GB/TB/PB) with free/used/total columns (auto-scaled to most relevant unit); separate Virtual Machine table shows Powered On/Off/Suspended/Total counts; separate Host table shows Connected/Disconnected/Maintenance/Total counts ([#30](https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues/30)) ### Changed diff --git a/README.md b/README.md index 0730099..b99c1ab 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,9 @@ The **Options** schema allows certain options within the report to be toggled on | ShowLicenseKeys | true / false | false | Toggle to mask/unmask vSphere license keys

**Masked License Key**
\*\*\*\*\*-\*\*\*\*\*-\*\*\*\*\*-\*\*\*\*\*-AS12K

**Unmasked License Key**
AKLU4-PFG8M-W2D8J-56YDM-AS12K | | ShowEncryptionKeys | true / false | false | Toggle to show/hide ESXi host encryption recovery keys in the report. When disabled, the recovery keys table is suppressed even at InfoLevel 3+ | | ShowVMSnapshots | true / false | true | Toggle to enable/disable reporting of VM snapshots | +| ShowRoles | true / false | true | Toggle to enable/disable reporting of vCenter Server roles and privileges. Disable to reduce report size in environments with many custom roles | +| ShowAlarms | true / false | true | Toggle to enable/disable reporting of vCenter Server alarm definitions. Disable to reduce report size in environments with many alarm definitions | +| ShowTags | true / false | true | Toggle to enable/disable tag reporting across all sections (vCenter Tags/Categories/Assignments, VMHost, VM, Datastore, Datastore Cluster, Resource Pool, Distributed Switch) | ### InfoLevel @@ -240,7 +243,8 @@ The **vCenter** schema is used to configure health checks for vCenter Server. | Licensing | true / false | true | Highlights product evaluation licenses | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Product evaluation license in use | | Alarms | true / false | true | Highlights vCenter Server alarms which are disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Alarm disabled | | Certificate | true / false | true | Highlights vCenter Server certificates which are expired or approaching expiry | ![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Expired / Expiring
![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Expiring Soon | -| Backup | true / false | true | Highlights vCenter Server backup schedules which are deactivated and backup jobs which have failed (requires vSphere 7.0 or later REST API access) | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Backup schedule deactivated
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Backup job failed | +| Backup | true / false | true | Highlights vCenter Server backup schedules which are deactivated and backup jobs which have failed (requires vSphere 7.0 or later REST API access) | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Backup schedule deactivated
![Critical](https://placehold.co/15x15/FEDDD7/FEDDD7) Backup job failed | +| ContentLibrary | true / false | true | Highlights subscribed content libraries with automatic synchronisation disabled | ![Warning](https://placehold.co/15x15/FFF4C7/FFF4C7) Automatic synchronisation disabled | #### Cluster The **Cluster** schema is used to configure health checks for vSphere Clusters. From d3c1e9b1bde6293f08dbbcc6092d50a1159a1b37 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Sat, 14 Mar 2026 16:22:17 +1100 Subject: [PATCH 20/24] Remove blanklines --- AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 index 2c4f82c..4e6f526 100644 --- a/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 +++ b/AsBuiltReport.VMware.vSphere/Src/Private/Get-AbrVSphereVUM.ps1 @@ -79,7 +79,6 @@ function Get-AbrVSphereVUM { Write-PScriboMessage -Message $LocalizedData.PatchNotAvailable } if ($VUMPatches -and $InfoLevel.VUM -ge 5) { - BlankLine Section -Style Heading3 $LocalizedData.Patches { $VUMPatchInfo = foreach ($VUMPatch in $VUMPatches) { [PSCustomObject]@{ @@ -104,7 +103,6 @@ function Get-AbrVSphereVUM { #region Software Depots if ($OnlineDepots -or $OfflineDepots) { - BlankLine Section -Style Heading3 $LocalizedData.SoftwareDepots { if ($OnlineDepots) { Section -Style Heading4 $LocalizedData.OnlineDepots { From dbcca1e56996cbdd7fba58519d6650f690833e34 Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Sat, 14 Mar 2026 16:25:50 +1100 Subject: [PATCH 21/24] Update module manifest --- AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 index 738a0de..c2b86ba 100644 --- a/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 +++ b/AsBuiltReport.VMware.vSphere/AsBuiltReport.VMware.vSphere.psd1 @@ -105,7 +105,7 @@ ReleaseNotes = 'https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/main/CHANGELOG.md' # Prerelease string of this module - Prerelease = 'alpha' + Prerelease = 'beta1' # Flag to indicate whether the module requires explicit user acceptance for install/update/save # RequireLicenseAcceptance = $false From bc8be1406936f2bd6e34f96e61bb4adfa8ab487e Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Sat, 14 Mar 2026 16:49:41 +1100 Subject: [PATCH 22/24] Fix Pester test reporter format mismatch Change dorny/test-reporter from java-junit to dotnet-nunit to match the NUnitXml format generated by Invoke-Tests.ps1 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/Pester.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Pester.yml b/.github/workflows/Pester.yml index 471c6f9..2827a8f 100644 --- a/.github/workflows/Pester.yml +++ b/.github/workflows/Pester.yml @@ -40,5 +40,5 @@ jobs: with: name: Pester Tests path: Tests/testResults.xml - reporter: java-junit + reporter: dotnet-nunit fail-on-error: true From 68eba38e38f3d022f2f09f9e4deea43eec5a58bc Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Sat, 14 Mar 2026 16:51:13 +1100 Subject: [PATCH 23/24] Fix Pester workflow: switch to JUnitXml output for dorny/test-reporter dorny/test-reporter@v1 only supports java-junit (JUnit XML) format; switch Invoke-Tests.ps1 output from NUnitXml to JUnitXml to match Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/Pester.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Pester.yml b/.github/workflows/Pester.yml index 2827a8f..af9a1c2 100644 --- a/.github/workflows/Pester.yml +++ b/.github/workflows/Pester.yml @@ -32,7 +32,7 @@ jobs: - name: Run Pester tests shell: pwsh run: | - .\Tests\Invoke-Tests.ps1 -OutputFormat NUnitXml + .\Tests\Invoke-Tests.ps1 -OutputFormat JUnitXml - name: Publish test results uses: dorny/test-reporter@v1 @@ -40,5 +40,5 @@ jobs: with: name: Pester Tests path: Tests/testResults.xml - reporter: dotnet-nunit + reporter: java-junit fail-on-error: true From e48521186c5f76a844575aefe2344c6685982d4f Mon Sep 17 00:00:00 2001 From: Tim Carman Date: Sat, 14 Mar 2026 16:54:37 +1100 Subject: [PATCH 24/24] Rewrite Pester workflow based on AsBuiltReport.Microsoft.Azure template - Add matrix strategy across windows-latest, ubuntu-latest, macos-latest - Replace dorny/test-reporter with actions/upload-artifact (avoids XML format parsing issues; test results stored as downloadable artifacts) - Add Codecov code coverage upload (Windows only) - Add workflow_dispatch for manual runs - Revert output format to NUnitXml (JUnitXml change reverted) - Split module installs into separate named steps - Always use pwsh shell (PS5.1 not supported by this module) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/Pester.yml | 71 +++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/.github/workflows/Pester.yml b/.github/workflows/Pester.yml index af9a1c2..33843da 100644 --- a/.github/workflows/Pester.yml +++ b/.github/workflows/Pester.yml @@ -9,36 +9,71 @@ on: branches: - master - dev + workflow_dispatch: jobs: - pester-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + test: + name: Pester Tests - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] - - name: Set PSRepository to Trusted for PowerShell Gallery + defaults: + run: shell: pwsh + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PowerShell Gallery run: | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted - - name: Install required modules - shell: pwsh + - name: Install Pester run: | - Install-Module -Name AsBuiltReport.Core -Repository PSGallery -Force -AllowClobber -Scope CurrentUser - Install-Module -Name PScribo -Repository PSGallery -Force -AllowClobber -Scope CurrentUser Install-Module -Name Pester -MinimumVersion 5.0.0 -Repository PSGallery -Force -AllowClobber -Scope CurrentUser - Install-Module -Name PSScriptAnalyzer -Repository PSGallery -Force -AllowClobber -Scope CurrentUser - - name: Run Pester tests - shell: pwsh + - name: Install PScribo + run: | + Install-Module -Name PScribo -MinimumVersion 0.11.1 -Repository PSGallery -Force -AllowClobber -Scope CurrentUser + + - name: Install PSScriptAnalyzer run: | - .\Tests\Invoke-Tests.ps1 -OutputFormat JUnitXml + Install-Module -Name PSScriptAnalyzer -MinimumVersion 1.0.0 -Repository PSGallery -Force -AllowClobber -Scope CurrentUser - - name: Publish test results - uses: dorny/test-reporter@v1 + - name: Install AsBuiltReport.Core + run: | + Install-Module -Name AsBuiltReport.Core -MinimumVersion 1.6.2 -Repository PSGallery -Force -AllowClobber -Scope CurrentUser + + - name: Run Pester Tests + run: | + $CodeCoverageParam = @{} + $OutputFormatParam = @{ OutputFormat = 'NUnitXml' } + + # Only enable code coverage for Windows + if ('${{ matrix.os }}' -eq 'windows-latest') { + $CodeCoverageParam = @{ CodeCoverage = $true } + } + + .\Tests\Invoke-Tests.ps1 @CodeCoverageParam @OutputFormatParam + + - name: Upload test results if: always() + uses: actions/upload-artifact@v4 with: - name: Pester Tests + name: test-results-${{ matrix.os }} path: Tests/testResults.xml - reporter: java-junit - fail-on-error: true + retention-days: 30 + + - name: Upload code coverage to Codecov + if: matrix.os == 'windows-latest' + uses: codecov/codecov-action@v5 + with: + files: ./Tests/coverage.xml + flags: unit + name: codecov-${{ matrix.os }} + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false