From 8503b5f933776e0008155349fb35fbe35cc007ef Mon Sep 17 00:00:00 2001
From: Jonathan Colon
Date: Fri, 5 Sep 2025 00:17:41 -0400
Subject: [PATCH 1/4] Add initial project files and icons
- Created a Todo.md file with a list of tasks to be completed, including adding servers and configuration settings.
- Added various icon files for the project, including logos and server icons.
---
.github/workflows/Codeql.yml | 47 +
.github/workflows/PSScriptAnalyzer.yml | 2 +-
.../workflows/PSScriptAnalyzerSettings.psd1 | 20 +-
.github/workflows/Release.yml | 62 ++
.github/workflows/Stale.yml | 18 +
AsBuiltReport.Microsoft.SCVMM.json | 33 +-
AsBuiltReport.Microsoft.SCVMM.psd1 | 145 +--
CHANGELOG.md | 8 +-
Src/Private/Export-AbrDiagram.ps1 | 132 +++
Src/Private/Get-AbrVmmAutoNetSetting.ps1 | 81 ++
Src/Private/Get-AbrVmmCluster.ps1 | 80 ++
Src/Private/Get-AbrVmmClusterSummary.ps1 | 58 ++
Src/Private/Get-AbrVmmDBSetting.ps1 | 81 ++
Src/Private/Get-AbrVmmFOCluster.ps1 | 62 ++
.../Get-AbrVmmFOClusterAvailableDisk.ps1 | 64 ++
.../Get-AbrVmmFOClusterFaultDomain.ps1 | 74 ++
Src/Private/Get-AbrVmmFOClusterNetwork.ps1 | 72 ++
.../Get-AbrVmmFOClusterNetworkInterface.ps1 | 70 ++
Src/Private/Get-AbrVmmFOClusterNode.ps1 | 91 ++
Src/Private/Get-AbrVmmFOClusterPermission.ps1 | 64 ++
Src/Private/Get-AbrVmmFOClusterQuorum.ps1 | 66 ++
Src/Private/Get-AbrVmmFOClusterResource.ps1 | 70 ++
.../Get-AbrVmmFOClusterSharedVolume.ps1 | 100 ++
.../Get-AbrVmmFOClusterSharedVolumeState.ps1 | 70 ++
Src/Private/Get-AbrVmmGuestOSProfile.ps1 | 94 ++
Src/Private/Get-AbrVmmHardwareProfile.ps1 | 85 ++
Src/Private/Get-AbrVmmHost.ps1 | 43 +
Src/Private/Get-AbrVmmHostSummary.ps1 | 58 ++
.../Get-AbrVmmInfrastructureDiagram.ps1 | 95 ++
Src/Private/Get-AbrVmmLibraryServer.ps1 | 88 ++
Src/Private/Get-AbrVmmLibraryShare.ps1 | 82 ++
Src/Private/Get-AbrVmmLibraryTemplate.ps1 | 44 +
Src/Private/Get-AbrVmmLogicalNetwork.ps1 | 84 ++
Src/Private/Get-AbrVmmLogicalSwitch.ps1 | 83 ++
.../Get-AbrVmmNetworkAdapterPortProfile.ps1 | 92 ++
Src/Private/Get-AbrVmmNetworking.ps1 | 45 +
Src/Private/Get-AbrVmmPortClassification.ps1 | 62 ++
Src/Private/Get-AbrVmmRequiredModule.ps1 | 75 ++
Src/Private/Get-AbrVmmServerConnection.ps1 | 39 +
Src/Private/Get-AbrVmmServerSetting.ps1 | 84 ++
Src/Private/Get-AbrVmmUplinkPortProfile.ps1 | 83 ++
Src/Private/Get-AbrVmmVMTemplate.ps1 | 100 ++
Src/Private/Get-AbrVmmVlanSubnet.ps1 | 82 ++
Src/Private/Get-AbrVmmVmNetwork.ps1 | 86 ++
Src/Private/Get-AbrVmmVmSubnet.ps1 | 85 ++
Src/Private/Get-AbrVmmVmVlanSubnet.ps1 | 65 ++
Src/Private/SharedUtilsFunctions.ps1 | 512 ++++++++++
.../Invoke-AsBuiltReport.Microsoft.SCVMM.ps1 | 918 ++----------------
Todo.md | 8 +
icons/AsBuiltReport_Logo.png | Bin 0 -> 5154 bytes
icons/AsBuiltReport_Signature.png | Bin 0 -> 12338 bytes
icons/DB_Server.png | Bin 0 -> 5390 bytes
icons/Microsoft_Logo.png | Bin 0 -> 13194 bytes
icons/no_icon.png | Bin 0 -> 8183 bytes
icons/server.png | Bin 0 -> 4411 bytes
55 files changed, 3747 insertions(+), 915 deletions(-)
create mode 100644 .github/workflows/Codeql.yml
create mode 100644 .github/workflows/Release.yml
create mode 100644 .github/workflows/Stale.yml
create mode 100644 Src/Private/Export-AbrDiagram.ps1
create mode 100644 Src/Private/Get-AbrVmmAutoNetSetting.ps1
create mode 100644 Src/Private/Get-AbrVmmCluster.ps1
create mode 100644 Src/Private/Get-AbrVmmClusterSummary.ps1
create mode 100644 Src/Private/Get-AbrVmmDBSetting.ps1
create mode 100644 Src/Private/Get-AbrVmmFOCluster.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterAvailableDisk.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterFaultDomain.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterNetwork.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterNetworkInterface.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterNode.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterPermission.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterQuorum.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterResource.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterSharedVolume.ps1
create mode 100644 Src/Private/Get-AbrVmmFOClusterSharedVolumeState.ps1
create mode 100644 Src/Private/Get-AbrVmmGuestOSProfile.ps1
create mode 100644 Src/Private/Get-AbrVmmHardwareProfile.ps1
create mode 100644 Src/Private/Get-AbrVmmHost.ps1
create mode 100644 Src/Private/Get-AbrVmmHostSummary.ps1
create mode 100644 Src/Private/Get-AbrVmmInfrastructureDiagram.ps1
create mode 100644 Src/Private/Get-AbrVmmLibraryServer.ps1
create mode 100644 Src/Private/Get-AbrVmmLibraryShare.ps1
create mode 100644 Src/Private/Get-AbrVmmLibraryTemplate.ps1
create mode 100644 Src/Private/Get-AbrVmmLogicalNetwork.ps1
create mode 100644 Src/Private/Get-AbrVmmLogicalSwitch.ps1
create mode 100644 Src/Private/Get-AbrVmmNetworkAdapterPortProfile.ps1
create mode 100644 Src/Private/Get-AbrVmmNetworking.ps1
create mode 100644 Src/Private/Get-AbrVmmPortClassification.ps1
create mode 100644 Src/Private/Get-AbrVmmRequiredModule.ps1
create mode 100644 Src/Private/Get-AbrVmmServerConnection.ps1
create mode 100644 Src/Private/Get-AbrVmmServerSetting.ps1
create mode 100644 Src/Private/Get-AbrVmmUplinkPortProfile.ps1
create mode 100644 Src/Private/Get-AbrVmmVMTemplate.ps1
create mode 100644 Src/Private/Get-AbrVmmVlanSubnet.ps1
create mode 100644 Src/Private/Get-AbrVmmVmNetwork.ps1
create mode 100644 Src/Private/Get-AbrVmmVmSubnet.ps1
create mode 100644 Src/Private/Get-AbrVmmVmVlanSubnet.ps1
create mode 100644 Src/Private/SharedUtilsFunctions.ps1
create mode 100644 Todo.md
create mode 100644 icons/AsBuiltReport_Logo.png
create mode 100644 icons/AsBuiltReport_Signature.png
create mode 100644 icons/DB_Server.png
create mode 100644 icons/Microsoft_Logo.png
create mode 100644 icons/no_icon.png
create mode 100644 icons/server.png
diff --git a/.github/workflows/Codeql.yml b/.github/workflows/Codeql.yml
new file mode 100644
index 0000000..832aa3e
--- /dev/null
+++ b/.github/workflows/Codeql.yml
@@ -0,0 +1,47 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+#
+# https://github.com/microsoft/action-psscriptanalyzer
+# For more information on PSScriptAnalyzer in general, see
+# https://github.com/PowerShell/PSScriptAnalyzer
+
+name: CodeQL
+
+on:
+ push:
+ branches: [ "dev" ]
+ pull_request:
+ branches: [ "dev" ]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ permissions:
+ contents: read # for actions/checkout to fetch code
+ security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
+ actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
+ name: PSScriptAnalyzer
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Run PSScriptAnalyzer
+ uses: microsoft/psscriptanalyzer-action@v1.1
+ with:
+ # Check https://github.com/microsoft/action-psscriptanalyzer for more info about the options.
+ # The below set up runs PSScriptAnalyzer to your entire repository and runs some basic security rules.
+ path: .\
+ recurse: true
+ # Include your own basic security rules. Removing this option will run all the rules
+ excludeRule: '"PSAvoidUsingPlainTextForPassword", "PSAvoidUsingUsernameAndPasswordParams", "PSAvoidUsingConvertToSecureStringWithPlainText"'
+ output: results.sarif
+
+ # Upload the SARIF file generated in the previous step
+ - name: Upload SARIF results file
+ uses: github/codeql-action/upload-sarif@v2
+ with:
+ sarif_file: results.sarif
diff --git a/.github/workflows/PSScriptAnalyzer.yml b/.github/workflows/PSScriptAnalyzer.yml
index fe69d8f..a73694f 100644
--- a/.github/workflows/PSScriptAnalyzer.yml
+++ b/.github/workflows/PSScriptAnalyzer.yml
@@ -5,7 +5,7 @@ jobs:
name: Run PSScriptAnalyzer
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v5
- name: lint
uses: devblackops/github-action-psscriptanalyzer@master
with:
diff --git a/.github/workflows/PSScriptAnalyzerSettings.psd1 b/.github/workflows/PSScriptAnalyzerSettings.psd1
index c053761..3fe6424 100644
--- a/.github/workflows/PSScriptAnalyzerSettings.psd1
+++ b/.github/workflows/PSScriptAnalyzerSettings.psd1
@@ -3,6 +3,24 @@
'PSUseToExportFieldsInManifest',
'PSReviewUnusedParameter',
'PSUseDeclaredVarsMoreThanAssignments',
- 'PSAvoidGlobalVars'
+ 'PSAvoidGlobalVars',
+ 'PSAvoidUsingWriteHost'
)
+ Rules = @{
+ PSAvoidExclaimOperator = @{
+ Enable = $true
+ }
+ AvoidUsingDoubleQuotesForConstantString = @{
+ Enable = $true
+ }
+ UseCorrectCasing = @{
+ Enable = $true
+ }
+ PSAvoidUsingCmdletAliases = @{
+ Enable = $true
+ }
+ PSUseConsistentWhitespace = @{
+ Enable = $true
+ }
+ }
}
\ No newline at end of file
diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml
new file mode 100644
index 0000000..dd8ce4f
--- /dev/null
+++ b/.github/workflows/Release.yml
@@ -0,0 +1,62 @@
+name: Publish PowerShell Module
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ publish-to-gallery:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v5
+ - name: Set PSRepository to Trusted for PowerShell Gallery
+ shell: pwsh
+ run: |
+ Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
+ - name: Install AsBuiltReport.Core module
+ shell: pwsh
+ run: |
+ Install-Module -Name AsBuiltReport.Core -Repository PSGallery -Force
+ - name: Install PScriboCharts module
+ shell: pwsh
+ run: |
+ Install-Module -Name PScriboCharts -Repository PSGallery -Force
+ - name: Install Diagrammer.Core module
+ shell: pwsh
+ run: |
+ Install-Module -Name Diagrammer.Core -Repository PSGallery -Force
+ - name: Test Module Manifest
+ shell: pwsh
+ run: |
+ Test-ModuleManifest .\AsBuiltReport.Veeam.VBR.psd1
+ - name: Publish module to PowerShell Gallery
+ shell: pwsh
+ run: |
+ Publish-Module -Path ./ -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose
+
+ tweet:
+ needs: publish-to-gallery
+ runs-on: ubuntu-latest
+ steps:
+ - uses: Eomm/why-don-t-you-tweet@v2
+ # We don't want to tweet if the repository is not a public one
+ if: ${{ !github.event.repository.private }}
+ with:
+ # GitHub event payload
+ # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release
+ tweet-message: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #Microsoft #SCVMM #AsBuiltReport #PowerShell #MicrosoftMVP #MVPBuzz"
+ env:
+ TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
+ TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
+ TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
+ TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
+ bsky-post:
+ needs: publish-to-gallery
+ runs-on: ubuntu-latest
+ steps:
+ - uses: zentered/bluesky-post-action@v0.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 }} #Microsoft #SCVMM #AsBuiltReport #PowerShell #MicrosoftMVP #MVPBuzz"
+ env:
+ BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }}
+ BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }}
diff --git a/.github/workflows/Stale.yml b/.github/workflows/Stale.yml
new file mode 100644
index 0000000..afebe72
--- /dev/null
+++ b/.github/workflows/Stale.yml
@@ -0,0 +1,18 @@
+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: 30
+ 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/AsBuiltReport.Microsoft.SCVMM.json b/AsBuiltReport.Microsoft.SCVMM.json
index 14426c2..38bbfb0 100644
--- a/AsBuiltReport.Microsoft.SCVMM.json
+++ b/AsBuiltReport.Microsoft.SCVMM.json
@@ -9,13 +9,36 @@
"ShowTableCaptions": true
},
"Options": {
-
+ "VmmServerPort": 8100,
+ "EnableDiagrams": true,
+ "EnableDiagramDebug": false,
+ "DiagramTheme": "White",
+ "DiagramWaterMark": "Acme Corporation",
+ "ExportDiagrams": true,
+ "ExportDiagramsFormat": [
+ "png"
+ ],
+ "EnableDiagramSignature": false,
+ "DiagramColumnSize": 4,
+ "SignatureAuthorName": "Jonathan Colon",
+ "SignatureCompanyName": "Zen PR Solutions"
},
"InfoLevel": {
- "_comment_": "Please refer to the AsBuiltReport project contributing guide for information about how to define InfoLevels.",
- "_comment_": "0 = Disabled, 1 = Enabled / Summary, 2 = Adv Summary, 3 = Detailed, 4 = Adv Detailed, 5 = Comprehensive"
+ "_comment_": "Please refer to the AsBuiltReport project contributing guide for information about how to define InfoLevels.",
+ "_comment_": "0 = Disabled, 1 = Enabled / Summary, 2 = Adv Summary, 3 = Detailed, 4 = Adv Detailed, 5 = Comprehensive",
+ "ServerSettings": 1,
+ "DBSettings": 1,
+ "AutoNetworkSettings": 1,
+ "Networking": 1,
+ "LibraryTemplates": 1,
+ "Clusters": 1,
+ "Hosts": 1
},
"HealthCheck": {
-
+ "VMMServer": true,
+ "Database": true,
+ "AutoNetwork": true,
+ "Clusters": true,
+ "Hosts": true
}
-}
+}
\ No newline at end of file
diff --git a/AsBuiltReport.Microsoft.SCVMM.psd1 b/AsBuiltReport.Microsoft.SCVMM.psd1
index d78ac62..eaafedb 100644
--- a/AsBuiltReport.Microsoft.SCVMM.psd1
+++ b/AsBuiltReport.Microsoft.SCVMM.psd1
@@ -8,99 +8,108 @@
@{
-# Script module or binary module file associated with this manifest.
-RootModule = 'AsBuiltReport.Microsoft.SCVMM.psm1'
+ # Script module or binary module file associated with this manifest.
+ RootModule = 'AsBuiltReport.Microsoft.SCVMM.psm1'
-# Version number of this module.
-ModuleVersion = '0.1.0'
+ # Version number of this module.
+ ModuleVersion = '0.1.1'
-# Supported PSEditions
-# CompatiblePSEditions = @()
+ # Supported PSEditions
+ # CompatiblePSEditions = @()
-# ID used to uniquely identify this module
-GUID = 'b4a72506-7a52-40c6-8649-050db73f2ab7'
+ # ID used to uniquely identify this module
+ GUID = 'b4a72506-7a52-40c6-8649-050db73f2ab7'
-# Author of this module
-Author = 'Andrew Ramsay'
+ # Author of this module
+ Author = 'Andrew Ramsay'
-# Company or vendor of this module
-# CompanyName = 'Unknown'
+ # Company or vendor of this module
+ # CompanyName = 'Unknown'
-# Copyright statement for this module
-Copyright = '(c) 2021 Andrew Ramsay. All rights reserved.'
+ # Copyright statement for this module
+ Copyright = '(c) 2025 Andrew Ramsay. 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 Microsoft SCVMM.'
+ # Description of the functionality provided by this module
+ Description = 'A PowerShell module to generate an as built report on the configuration of Microsoft SCVMM.'
-# Minimum version of the Windows PowerShell engine required by this module
-PowerShellVersion = '5.1'
+ # Minimum version of the Windows PowerShell engine required by this module
+ PowerShellVersion = '5.1'
-# Name of the Windows PowerShell host required by this module
-# PowerShellHostName = ''
+ # Name of the Windows PowerShell host required by this module
+ # PowerShellHostName = ''
-# Minimum version of the Windows PowerShell host required by this module
-# PowerShellHostVersion = ''
+ # Minimum version of the Windows PowerShell host required by this module
+ # PowerShellHostVersion = ''
-# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
-# DotNetFrameworkVersion = ''
+ # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
+ # DotNetFrameworkVersion = ''
-# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
-# CLRVersion = ''
+ # 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 = ''
+ # Processor architecture (None, X86, Amd64) required by this module
+ # ProcessorArchitecture = ''
-# Modules that must be imported into the global environment prior to importing this module
- RequiredModules = @(
- @{
- ModuleName = 'AsBuiltReport.Core';
- ModuleVersion = '1.1.0'
- }
- )
+ # Modules that must be imported into the global environment prior to importing this module
+ RequiredModules = @(
+ @{
+ ModuleName = 'AsBuiltReport.Core';
+ ModuleVersion = '1.4.3'
+ },
+ @{
+ ModuleName = 'Diagrammer.Core';
+ ModuleVersion = '0.2.29'
+ },
+ @{
+ ModuleName = 'PScriboCharts';
+ ModuleVersion = '0.9.0'
+ }
-# Assemblies that must be loaded prior to importing this module
-# RequiredAssemblies = @()
+ )
-# Script files (.ps1) that are run in the caller's environment prior to importing this module.
-# ScriptsToProcess = @()
+ # Assemblies that must be loaded prior to importing this module
+ # RequiredAssemblies = @()
-# Type files (.ps1xml) to be loaded when importing this module
-# TypesToProcess = @()
+ # Script files (.ps1) that are run in the caller's environment prior to importing this module.
+ # ScriptsToProcess = @()
-# Format files (.ps1xml) to be loaded when importing this module
-# FormatsToProcess = @()
+ # Type files (.ps1xml) to be loaded when importing this module
+ # TypesToProcess = @()
-# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
-# NestedModules = @()
+ # Format files (.ps1xml) to be loaded when importing this module
+ # FormatsToProcess = @()
-# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
-FunctionsToExport = @('Invoke-AsBuiltReport.Microsoft.SCVMM')
+ # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
+ # NestedModules = @()
-# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
-# CmdletsToExport = '*'
+ # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
+ FunctionsToExport = @('Invoke-AsBuiltReport.Microsoft.SCVMM')
-# Variables to export from this module
-# VariablesToExport = '*'
+ # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
+ # CmdletsToExport = '*'
-# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
-# AliasesToExport = '*'
+ # Variables to export from this module
+ # VariablesToExport = '*'
-# DSC resources to export from this module
-# DscResourcesToExport = @()
+ # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
+ # AliasesToExport = '*'
-# List of all modules packaged with this module
-# ModuleList = @()
+ # DSC resources to export from this module
+ # DscResourcesToExport = @()
-# List of all files packaged with this module
-# FileList = @()
+ # List of all modules packaged with this module
+ # ModuleList = @()
-# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
-PrivateData = @{
+ # List of all files packaged with this module
+ # FileList = @()
- PSData = @{
+ # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
+ PrivateData = @{
+
+ PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
- Tags = 'AsBuiltReport', 'Report', 'Microsoft', 'SCVMM', 'Documentation', 'PScribo', 'Windows', 'Linux', 'MacOS', 'PSEdition_Desktop', 'PSEdition_Core'
+ Tags = 'AsBuiltReport', 'Report', 'Microsoft', 'SCVMM', 'Documentation', 'PScribo', 'Windows', 'PSEdition_Desktop', 'PSEdition_Core'
# A URL to the license for this module.
LicenseUri = 'https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.Microsoft.SCVMM/master/LICENSE'
@@ -116,13 +125,13 @@ PrivateData = @{
} # End of PSData hashtable
-} # End of PrivateData hashtable
+ } # End of PrivateData hashtable
-# HelpInfo URI of this module
-# HelpInfoURI = ''
+ # HelpInfo URI of this module
+ # HelpInfoURI = ''
-# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
-# DefaultCommandPrefix = ''
+ # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
+ # DefaultCommandPrefix = ''
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0eb720e..6878fbb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,19 @@
# :arrows_counterclockwise: Microsoft SCVMM As Built Report Changelog
-## [0.1.0] - YYYY-MM-DD
+## [0.1.1] - 2024-06-10
### Added
+- Initial release of Microsoft SCVMM As Built Report module.
+- Basic changelog structure.
+
### Changed
+- N/A
### Fixed
+- N/A
### Removed
+- N/A
diff --git a/Src/Private/Export-AbrDiagram.ps1 b/Src/Private/Export-AbrDiagram.ps1
new file mode 100644
index 0000000..e7be722
--- /dev/null
+++ b/Src/Private/Export-AbrDiagram.ps1
@@ -0,0 +1,132 @@
+function Export-AbrDiagram {
+ <#
+ .SYNOPSIS
+ Function used to build the settings needed to call Export-Diagrammer (Diagrammer.Core)
+
+ .DESCRIPTION
+ The Export-AbrDiagram function build the settings needed to call Export-Diagrammer (Diagrammer.Core)
+ to export a diagram in PDF, PNG, SVG, or base64 formats using PSgraph.
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.SCVMM
+ #>
+
+ # Don't remove this line (Don't touch it!)
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingCmdletAliases", "", Scope = "Function")]
+
+ [CmdletBinding()]
+ param (
+ $DiagramObject,
+ [string] $MainDiagramLabel = 'Change Me',
+ [Parameter(Mandatory = $true)]
+ [string] $FileName
+ )
+
+ begin {
+ Write-PScriboMessage -Message "EnableDiagrams set to $($Options.EnableDiagrams)."
+ }
+
+ process {
+ if ($Options.EnableDiagrams) {
+ Write-PScriboMessage -Message "Loading export diagram settings"
+
+ $RootPath = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
+ [System.IO.FileInfo]$IconPath = Join-Path $RootPath 'icons'
+
+ $DiagramParams = @{
+ 'FileName' = $FileName
+ 'OutputFolderPath' = $OutputFolderPath
+ 'MainDiagramLabel' = $MainDiagramLabel
+ 'MainDiagramLabelFontsize' = 28
+ 'MainDiagramLabelFontcolor' = '#565656'
+ 'MainDiagramLabelFontname' = 'Segoe UI Black'
+ 'IconPath' = $IconPath
+ 'ImagesObj' = $Images
+ 'LogoName' = 'AsBuiltReport_LOGO'
+ 'SignatureLogoName' = 'AsBuiltReport_Signature'
+ 'WaterMarkText' = $Options.DiagramWaterMark
+ 'Direction' = 'top-to-bottom'
+ }
+
+ if ($Options.DiagramTheme -eq 'Black') {
+ $DiagramParams.add('MainGraphBGColor', 'Black')
+ $DiagramParams.add('Edgecolor', 'White')
+ $DiagramParams.add('Fontcolor', 'White')
+ $DiagramParams.add('NodeFontcolor', 'White')
+ $DiagramParams.add('WaterMarkColor', 'White')
+ } elseif ($Options.DiagramTheme -eq 'Neon') {
+ $DiagramParams.add('MainGraphBGColor', 'grey14')
+ $DiagramParams.add('Edgecolor', 'gold2')
+ $DiagramParams.add('Fontcolor', 'gold2')
+ $DiagramParams.add('NodeFontcolor', 'gold2')
+ $DiagramParams.add('WaterMarkColor', '#FFD700')
+ } else {
+ $DiagramParams.add('WaterMarkColor', '#333333')
+ }
+
+ if ($Options.ExportDiagrams) {
+ if (-not $Options.ExportDiagramsFormat) {
+ $DiagramFormat = 'png'
+ } else {
+ $DiagramFormat = $Options.ExportDiagramsFormat
+ }
+ $DiagramParams.Add('Format', $DiagramFormat)
+ } else {
+ $DiagramParams.Add('Format', "base64")
+ }
+
+ if ($Options.EnableDiagramDebug) {
+
+ $DiagramParams.Add('DraftMode', $True)
+
+ }
+
+ if ($Options.EnableDiagramSignature) {
+ $DiagramParams.Add('Signature', $True)
+ $DiagramParams.Add('AuthorName', $Options.SignatureAuthorName)
+ $DiagramParams.Add('CompanyName', $Options.SignatureCompanyName)
+ }
+
+ if ($Options.ExportDiagrams) {
+ try {
+ Write-PScriboMessage -Message "Generating $MainDiagramLabel diagram"
+ $Graph = $DiagramObject
+ if ($Graph) {
+ Write-PScriboMessage -Message "Saving $MainDiagramLabel diagram"
+ $Diagram = New-Diagrammer @DiagramParams -InputObject $Graph
+ if ($Diagram) {
+ foreach ($OutputFormat in $DiagramFormat) {
+ Write-Information -MessageData "Saved '$($FileName).$($OutputFormat)' diagram to '$($OutputFolderPath)'." -InformationAction Continue
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning -Message "Unable to export the $MainDiagramLabel Diagram: $($_.Exception.Message)"
+ }
+ }
+ try {
+ $DiagramParams.Remove('Format')
+ $DiagramParams.Add('Format', "base64")
+
+ $Graph = $DiagramObject
+ $Diagram = New-Diagrammer @DiagramParams -InputObject $Graph
+ if ($Diagram) {
+ if ((Get-DiaImagePercent -GraphObj $Diagram).Width -gt 600) { $ImagePrty = 40 } else { $ImagePrty = 30 }
+ Section -Style Heading2 $MainDiagramLabel {
+ Image -Base64 $Diagram -Text "$MainDiagramLabel" -Percent $ImagePrty -Align Center
+ Paragraph "Image preview: Opens the image in a new tab to view it at full resolution." -Tabs 2
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning -Message "Unable to generate the $MainDiagramLabel Diagram: $($_.Exception.Message)"
+ }
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmAutoNetSetting.ps1 b/Src/Private/Get-AbrVmmAutoNetSetting.ps1
new file mode 100644
index 0000000..a03f20a
--- /dev/null
+++ b/Src/Private/Get-AbrVmmAutoNetSetting.ps1
@@ -0,0 +1,81 @@
+function Get-AbrVmmAutoNetSetting {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft VMM Auto Network information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "AutoNetworkSettings InfoLevel set at $($InfoLevel.AutoNetworkSettings)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.AutoNetworkSettings -gt 0) {
+ if ($Vmm) {
+ Write-PScriboMessage "Collecting VMM Auto Network information."
+ Section -Style Heading2 'AutoNetwork Settings' {
+ $VmmServerSettingsInfo = @()
+ foreach ($VmmServerSetting in $Vmm) {
+ $InObj = [Ordered]@{
+ 'Logical Network Creation Enabled' = $VMM.AutomaticLogicalNetworkCreationEnabled
+ 'Virtual Network Creation Enabled' = $VMM.AutomaticVirtualNetworkCreationEnabled
+ 'Logical Network Match' = $VMM.LogicalNetworkMatchOption
+ 'Backup Network Match' = $VMM.BackupLogicalNetworkMatchOption
+ }
+
+ $VmmServerSettingsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.AutoNetworkSettings -ge 2) {
+ Paragraph "The following sections detail the configuration of the VMM Auto Network Settings."
+ foreach ($VmmServerSetting in $VmmServerSettingsInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($Vmm.FQDN)" {
+ $TableParams = @{
+ Name = "Auto Network Settings - $($Vmm.FQDN)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmServerSetting | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the VMM Auto Network Settings."
+ BlankLine
+ $TableParams = @{
+ Name = "Auto Network Settings - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Logical Network Creation Enabled', 'Virtual Network Creation Enabled', 'Logical Network Match', 'Backup Network Match'
+ ColumnWidths = 25, 25, 25, 25
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmServerSettingsInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmCluster.ps1 b/Src/Private/Get-AbrVmmCluster.ps1
new file mode 100644
index 0000000..459e1af
--- /dev/null
+++ b/Src/Private/Get-AbrVmmCluster.ps1
@@ -0,0 +1,80 @@
+function Get-AbrVmmCluster {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Clusters information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Clusters -gt 0) {
+ Write-PScriboMessage "Collecting VMM Cluster information."
+ if ($ScVmmClusters = Get-SCVMHostCluster) {
+ Section -Style Heading1 'Clusters' {
+ Paragraph "The following table summarises the configuration of the clusters."
+ BlankLine
+ Get-AbrVmmClusterSummary
+ Section -Style Heading3 'Clusters Configuration' {
+ foreach ($ScVmmCluster in ($ScVmmClusters | Where-Object { $_.VirtualizationPlatform -eq 'HyperV' })) {
+ try {
+ $script:ClusterTempPssSession = New-PSSession $ScVmmCluster.Name -Credential $Credential
+ $script:ClusterTempCimSession = New-CimSession $ScVmmCluster.Name -Credential $Credential
+ if ($Cluster = Invoke-Command -Session $script:ClusterTempPssSession { Get-Cluster }) {
+ Section -Style Heading4 $Cluster.Name {
+ # Cluster Server Configuration
+ Get-AbrVmmFOCluster
+ # Cluster Access Permission
+ Get-AbrVmmFOClusterPermission
+ # Cluster Nodes
+ Get-AbrVmmFOClusterNode
+ # Cluster Available Disks
+ Get-AbrVmmFOClusterAvailableDisk
+ # Cluster Fault Domain
+ Get-AbrVmmFOClusterFaultDomain
+ # Cluster Networks
+ Get-AbrVmmFOClusterNetwork
+ # Cluster Quorum
+ Get-AbrVmmFOClusterQuorum
+ #Cluster Resources
+ Get-AbrVmmFOClusterResource
+ #Cluster Shared Volume
+ Get-AbrVmmFOClusterSharedVolume
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ if ($ClusterTempPssSession) {
+ Remove-PSSession $ClusterTempPssSession
+ }
+ if ($ClusterTempCimSession) {
+ Remove-CimSession $ClusterTempCimSession
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmClusterSummary.ps1 b/Src/Private/Get-AbrVmmClusterSummary.ps1
new file mode 100644
index 0000000..3955235
--- /dev/null
+++ b/Src/Private/Get-AbrVmmClusterSummary.ps1
@@ -0,0 +1,58 @@
+function Get-AbrVmmClusterSummary {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Cluster Summary information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Clusters -gt 0) {
+ if ($ScVmmClusters = Get-SCVMHostCluster | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Cluster information."
+ $VmmClusterInfo = @()
+ foreach ($ScVmmCluster in $ScVmmClusters) {
+ $InObj = [Ordered]@{
+ 'Name' = $ScVmmCluster.Name
+ 'Host Group' = $ScVmmCluster.HostGroup
+ 'Cluster Nodes' = $ScVmmCluster.Nodes
+ }
+
+ $VmmClusterInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ $TableParams = @{
+ Name = "Cluster Summary - $($Vmm.FQDN)"
+ List = $false
+ ColumnWidths = 33, 33, 34
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmClusterInfo | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmDBSetting.ps1 b/Src/Private/Get-AbrVmmDBSetting.ps1
new file mode 100644
index 0000000..de548f7
--- /dev/null
+++ b/Src/Private/Get-AbrVmmDBSetting.ps1
@@ -0,0 +1,81 @@
+function Get-AbrVmmDBSetting {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft VMM Database information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "DBSettings InfoLevel set at $($InfoLevel.DBSettings)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.DBSettings -gt 0) {
+ if ($Vmm) {
+ Write-PScriboMessage "Collecting VMM Database Settings information."
+ Section -Style Heading2 'Database Settings' {
+ $VmmServerSettingsInfo = @()
+ foreach ($VmmServerSetting in $Vmm) {
+ $InObj = [Ordered]@{
+ 'Server Name' = $VMM.DatabaseServerName
+ 'Instance Name' = $VMM.DatabaseInstanceName
+ 'DataBase Name' = $VMM.DatabaseName
+ 'Version' = $VMM.DatabaseVersion
+ }
+
+ $VmmServerSettingsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.DBSettings -ge 2) {
+ Paragraph "The following sections detail the configuration of the VMM Database Settings."
+ foreach ($VmmServerSetting in $VmmServerSettingsInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($Vmm.FQDN)" {
+ $TableParams = @{
+ Name = "Database Settings - $($Vmm.FQDN)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmServerSetting | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the VMM Database Settings."
+ BlankLine
+ $TableParams = @{
+ Name = "Database Settings - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Server Name', 'Instance Name', 'DataBase Name', 'Version'
+ ColumnWidths = 30, 30, 20, 20
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmServerSettingsInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOCluster.ps1 b/Src/Private/Get-AbrVmmFOCluster.ps1
new file mode 100644
index 0000000..042a4b9
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOCluster.ps1
@@ -0,0 +1,62 @@
+function Get-AbrVmmFOCluster {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster configuration
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Cluster Server information."
+ }
+
+ process {
+ try {
+ $Clusters = Invoke-Command -Session $ClusterTempPssSession { Get-Cluster | Select-Object -Property * }
+ if ($Clusters) {
+ $OutObj = @()
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $Clusters.Name
+ 'Domain' = $Clusters.Domain
+ 'Shared Volumes Root' = $Clusters.SharedVolumesRoot
+ 'Administrative Access Point' = $Clusters.AdministrativeAccessPoint
+ 'Description' = $Clusters.Description
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+
+ $TableParams = @{
+ Name = "Cluster Servers Settings - $($Clusters.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterAvailableDisk.ps1 b/Src/Private/Get-AbrVmmFOClusterAvailableDisk.ps1
new file mode 100644
index 0000000..125ff00
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterAvailableDisk.ps1
@@ -0,0 +1,64 @@
+function Get-AbrVmmFOClusterAvailableDisk {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Available Disk
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Clusters Available Disk information."
+ }
+
+ process {
+ try {
+ $ClusterAvailableDisks = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterAvailableDisk } | Sort-Object -Property Name
+ if ($ClusterAvailableDisks) {
+ Section -Style Heading3 "Available Disk" {
+ $OutObj = @()
+ foreach ($ClusterAvailableDisk in $ClusterAvailableDisks) {
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterAvailableDisk.Name
+ 'Number' = $ClusterAvailableDisk.Number
+ 'Size' = ConvertTo-FileSizeString $ClusterAvailableDisk.Size
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ $TableParams = @{
+ Name = "Available Disk - $($Cluster)"
+ List = $false
+ ColumnWidths = 40, 30, 30
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterFaultDomain.ps1 b/Src/Private/Get-AbrVmmFOClusterFaultDomain.ps1
new file mode 100644
index 0000000..766483a
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterFaultDomain.ps1
@@ -0,0 +1,74 @@
+function Get-AbrVmmFOClusterFaultDomain {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Fault Domain
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Clusters Fault Domain information."
+ }
+
+ process {
+ try {
+ $ClusterFaultDomains = Get-ClusterFaultDomain -CimSession $ClusterTempCimSession | Sort-Object -Property Name
+ if ($ClusterFaultDomains) {
+ Section -Style Heading3 "Fault Domain" {
+ $OutObj = @()
+ foreach ($ClusterFaultDomain in $ClusterFaultDomains) {
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterFaultDomain.Name
+ 'Type' = $ClusterFaultDomain.Type
+ 'Parent Name' = Switch ([string]::IsNullOrEmpty($ClusterFaultDomain.ParentName)) {
+ $true { "--" }
+ $false { $ClusterFaultDomain.ParentName }
+ default { 'Unknown' }
+ }
+ 'Children Names' = Switch ([string]::IsNullOrEmpty($ClusterFaultDomain.ChildrenNames)) {
+ $true { "--" }
+ $false { $ClusterFaultDomain.ChildrenNames }
+ default { 'Unknown' }
+ }
+ 'Location' = $ClusterFaultDomain.Location
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ $TableParams = @{
+ Name = "Fault Domain - $($Cluster)"
+ List = $false
+ ColumnWidths = 20, 20, 20, 20, 20
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterNetwork.ps1 b/Src/Private/Get-AbrVmmFOClusterNetwork.ps1
new file mode 100644
index 0000000..f8676c2
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterNetwork.ps1
@@ -0,0 +1,72 @@
+function Get-AbrVmmFOClusterNetwork {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Networks
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Cluster Networks information."
+ }
+
+ process {
+ try {
+ $ClusterNetworks = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterNetwork } | Sort-Object -Property Name
+ if ($ClusterNetworks) {
+ Section -Style Heading3 "Networks" {
+ $OutObj = @()
+ foreach ($ClusterNetwork in $ClusterNetworks) {
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterNetwork.Name
+ 'State' = $ClusterNetwork.State
+ 'Role' = $ClusterNetwork.Role
+ 'Address' = "$($ClusterNetwork.Address)/$($ClusterNetwork.AddressMask)"
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+
+ if ($HealthCheck.Clusters) {
+ $OutObj | Where-Object { $_.'State' -ne 'UP' } | Set-Style -Style Warning -Property 'State'
+ }
+
+ $TableParams = @{
+ Name = "Networks - $($Cluster)"
+ List = $false
+ ColumnWidths = 30, 15, 20, 35
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ # Cluster Network Interfaces
+ Get-AbrVmmFOClusterNetworkInterface
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterNetworkInterface.ps1 b/Src/Private/Get-AbrVmmFOClusterNetworkInterface.ps1
new file mode 100644
index 0000000..fbe3bd0
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterNetworkInterface.ps1
@@ -0,0 +1,70 @@
+function Get-AbrVmmFOClusterNetworkInterface {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Network Interfaces
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Cluster Network Interface information."
+ }
+
+ process {
+ try {
+ $ClusterNetworkInterfaces = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterNetworkInterface } | Sort-Object -Property Name
+ if ($ClusterNetworkInterfaces) {
+ Section -Style Heading3 "Interfaces" {
+ $OutObj = @()
+ foreach ($ClusterNetworkInterface in $ClusterNetworkInterfaces) {
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterNetworkInterface.Name
+ 'Node' = $ClusterNetworkInterface.Node
+ 'Network' = $ClusterNetworkInterface.Network
+ 'State' = $ClusterNetworkInterface.State
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+
+ if ($HealthCheck.Clusters) {
+ $OutObj | Where-Object { $_.'State' -ne 'UP' } | Set-Style -Style Warning -Property 'State'
+ }
+
+ $TableParams = @{
+ Name = "Interfaces - $($Cluster)"
+ List = $false
+ ColumnWidths = 30, 25, 30, 15
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterNode.ps1 b/Src/Private/Get-AbrVmmFOClusterNode.ps1
new file mode 100644
index 0000000..052d054
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterNode.ps1
@@ -0,0 +1,91 @@
+function Get-AbrVmmFOClusterNode {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Nodes
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ }
+
+ process {
+ try {
+ $ClusterNodes = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterNode } | Sort-Object -Property Identity
+ if ($ClusterNodes) {
+ Write-PScriboMessage "Collecting Host Cluster Settings information."
+ Section -Style Heading3 'Nodes' {
+ $OutObj = @()
+ foreach ($ClusterNode in $ClusterNodes) {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterNode.Name
+ 'State' = $ClusterNode.State
+ 'Type' = $ClusterNode.Type
+ 'Cluster' = $ClusterNode.Cluster
+ 'Fault Domain' = $ClusterNode.FaultDomain
+ 'Node Weight' = $ClusterNode.NodeWeight
+ 'Model' = $ClusterNode.Model
+ 'Manufacturer' = $ClusterNode.Manufacturer
+ 'Serial Number' = $ClusterNode.SerialNumber
+ 'Status Information' = $ClusterNode.StatusInformation
+ 'Description' = $ClusterNode.Description
+
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ }
+
+ if ($HealthCheck.Clusters) {
+ $OutObj | Where-Object { $_.'State' -ne 'UP' } | Set-Style -Style Warning -Property 'State'
+ }
+
+ if ($InfoLevel.Clusters -ge 2) {
+ Paragraph "The following sections detail the configuration of the Cluster Nodes."
+ foreach ($ClusterNode in $OutObj) {
+ Section -ExcludeFromTOC -Style NOTOCHeading4 "$($ClusterNode.Name)" {
+ $TableParams = @{
+ Name = "Node - $($ClusterNode.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $ClusterNode | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarizes the configuration of the Cluster Nodes."
+ BlankLine
+ $TableParams = @{
+ Name = "Nodes - $($Cluster)"
+ List = $false
+ Columns = 'Name', 'State', 'Type'
+ ColumnWidths = 40, 30, 30
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning "Cluster Nodes Section: $($_.Exception.Message)"
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterPermission.ps1 b/Src/Private/Get-AbrVmmFOClusterPermission.ps1
new file mode 100644
index 0000000..6aaa3b5
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterPermission.ps1
@@ -0,0 +1,64 @@
+function Get-AbrVmmFOClusterPermission {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Permissions
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Cluster Permissions Settings information."
+ }
+
+ process {
+ try {
+ $ClusterAccess = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterAccess } | Sort-Object -Property Identity
+ if ($ClusterAccess) {
+ Section -Style Heading3 "Access Permissions" {
+ $OutObj = @()
+ foreach ($Permission in $ClusterAccess) {
+ try {
+ $inObj = [ordered] @{
+ 'Identity' = $Permission.IdentityReference
+ 'Access Control Type' = $Permission.AccessControlType
+ 'Rights' = $Permission.ClusterRights
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ $TableParams = @{
+ Name = "Access Permission - $($Cluster)"
+ List = $false
+ ColumnWidths = 60, 20, 20
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterQuorum.ps1 b/Src/Private/Get-AbrVmmFOClusterQuorum.ps1
new file mode 100644
index 0000000..5b84b82
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterQuorum.ps1
@@ -0,0 +1,66 @@
+function Get-AbrVmmFOClusterQuorum {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Quorum
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Cluster Quorum information."
+ }
+
+ process {
+ try {
+ $ClusterQuorums = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterQuorum | Select-Object -Property * } | Sort-Object -Property Name
+ if ($ClusterQuorums) {
+ Section -Style Heading3 "Quorum" {
+ $OutObj = @()
+ foreach ($ClusterQuorum in $ClusterQuorums) {
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterQuorum.QuorumResource.Name
+ 'In State' = $ClusterQuorum.QuorumType
+ 'Status' = $ClusterQuorum.QuorumResource.State
+ 'Owner Node' = $ClusterQuorum.QuorumResource.OwnerNode
+ 'Resource Type' = $ClusterQuorum.QuorumResource.ResourceType
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ $TableParams = @{
+ Name = "Quorum - $($Cluster)"
+ List = $false
+ ColumnWidths = 20, 20, 20, 20, 20
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterResource.ps1 b/Src/Private/Get-AbrVmmFOClusterResource.ps1
new file mode 100644
index 0000000..82931b1
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterResource.ps1
@@ -0,0 +1,70 @@
+function Get-AbrVmmFOClusterResource {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Resource
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Cluster Resource information."
+ }
+
+ process {
+ try {
+ $ClusterResources = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterResource | Select-Object -Property * } | Sort-Object -Property Name
+ if ($ClusterResources) {
+ Section -Style Heading3 "Resource" {
+ $OutObj = @()
+ foreach ($ClusterResource in $ClusterResources) {
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterResource.Name
+ 'Owner Group' = $ClusterResource.OwnerGroup
+ 'Resource Type' = $ClusterResource.ResourceType
+ 'State' = $ClusterResource.State
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+
+ if ($HealthCheck.Clusters) {
+ $OutObj | Where-Object { $_.'State' -notlike 'Online' } | Set-Style -Style Warning -Property 'State'
+ }
+
+ $TableParams = @{
+ Name = "Resource - $($Cluster)"
+ List = $false
+ ColumnWidths = 25, 25, 35, 15
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterSharedVolume.ps1 b/Src/Private/Get-AbrVmmFOClusterSharedVolume.ps1
new file mode 100644
index 0000000..6c4c139
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterSharedVolume.ps1
@@ -0,0 +1,100 @@
+function Get-AbrVmmFOClusterSharedVolume {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Shared Volume
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.5
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Cluster Shared Volume information."
+ }
+
+ process {
+ try {
+ # Todo: Add more properties if needed
+ $ClusterSharedVolumes = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterSharedVolume | Select-Object -Property * } | Sort-Object -Property Name
+ if ($ClusterSharedVolumes) {
+ Section -Style Heading3 "Cluster Shared Volume" {
+ $OutObj = @()
+ foreach ($ClusterSharedVolume in $ClusterSharedVolumes) {
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterSharedVolume.Name
+ 'Owner Node' = $ClusterSharedVolume.OwnerNode
+ 'Shared Volume' = switch ([string]::IsNullOrEmpty($ClusterSharedVolume.SharedVolumeInfo.FriendlyVolumeName)) {
+ $true { "Unknown" }
+ $false { $ClusterSharedVolume.SharedVolumeInfo.FriendlyVolumeName }
+ default { "--" }
+ }
+ 'File System Type' = $ClusterSharedVolume.SharedVolumeInfo.Partition.FileSystem
+ 'Volume Capacity(GB)' = [Math]::Round(($ClusterSharedVolume.SharedVolumeInfo.Partition.Size) / 1gb)
+ 'Free Capacity(GB)' = [Math]::Round(($ClusterSharedVolume.SharedVolumeInfo.Partition.FreeSpace) / 1gb)
+ 'State' = $ClusterSharedVolume.State
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+
+ if ($HealthCheck.Clusters) {
+ $OutObj | Where-Object { $_.'State' -notlike 'Online' } | Set-Style -Style Warning -Property 'State'
+ }
+
+ if ($InfoLevel.Clusters -ge 2) {
+ Paragraph "The following sections detail the configuration of the Cluster Shared Volume."
+ foreach ($ClusterSharedVolume in $OutObj) {
+ Section -ExcludeFromTOC -Style NOTOCHeading4 "$($ClusterSharedVolume.Name)" {
+ $TableParams = @{
+ Name = "Cluster Shared Volume - $($ClusterSharedVolume.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $ClusterSharedVolume | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarizes the configuration of the Cluster Shared Volume."
+ BlankLine
+ $TableParams = @{
+ Name = "Cluster Shared Volumes - $($Cluster)"
+ List = $false
+ Columns = 'Name', 'Owner Node', 'Shared Volume', 'State'
+ ColumnWidths = 25, 25, 35, 15
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ #Cluster Shared Volume State
+ Get-AbrVmmFOClusterSharedVolumeState
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmFOClusterSharedVolumeState.ps1 b/Src/Private/Get-AbrVmmFOClusterSharedVolumeState.ps1
new file mode 100644
index 0000000..eb0392d
--- /dev/null
+++ b/Src/Private/Get-AbrVmmFOClusterSharedVolumeState.ps1
@@ -0,0 +1,70 @@
+function Get-AbrVmmFOClusterSharedVolumeState {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft Cluster Shared Volume State
+ .DESCRIPTION
+ Documents the configuration of Microsoft Windows Server in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.5.2
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Windows
+ #>
+
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Clusters InfoLevel set at $($InfoLevel.Clusters)."
+ Write-PScriboMessage "Collecting Host Cluster Shared Volume State information."
+ }
+
+ process {
+ try {
+ $ClusterSharedVolumeStates = Invoke-Command -Session $ClusterTempPssSession { Get-ClusterSharedVolumeState | Select-Object -Property * } | Sort-Object -Property Name
+ if ($ClusterSharedVolumeStates) {
+ Section -Style Heading4 "Cluster Shared Volume State" {
+ $OutObj = @()
+ foreach ($ClusterSharedVolumeState in $ClusterSharedVolumeStates) {
+ try {
+ $inObj = [ordered] @{
+ 'Name' = $ClusterSharedVolumeState.Name
+ 'Node' = $ClusterSharedVolumeState.Node
+ 'State' = $ClusterSharedVolumeState.StateInfo
+ 'Volume Name' = $ClusterSharedVolumeState.VolumeFriendlyName
+ 'Volume Path' = $ClusterSharedVolumeState.VolumeName
+ }
+ $OutObj += [pscustomobject](ConvertTo-HashToYN $inObj)
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ if ($HealthCheck.Clusters) {
+ $OutObj | Where-Object { $_.State.Value -eq 'Unavailable' } | Set-Style -Style Warning -Property 'State'
+ }
+
+ $TableParams = @{
+ Name = "Cluster Shared Volume State - $($Cluster)"
+ List = $false
+ ColumnWidths = 20, 20, 20, 20, 20
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $OutObj | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmGuestOSProfile.ps1 b/Src/Private/Get-AbrVmmGuestOSProfile.ps1
new file mode 100644
index 0000000..1201aae
--- /dev/null
+++ b/Src/Private/Get-AbrVmmGuestOSProfile.ps1
@@ -0,0 +1,94 @@
+function Get-AbrVmmGuestOSProfile {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Guest OS Profile information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "LibraryTemplates InfoLevel set at $($InfoLevel.LibraryTemplates)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.LibraryTemplates -gt 0) {
+ if ($GuestOSProfiles = Get-SCGuestOSProfile | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Guest OS Profiles information."
+ Section -Style Heading3 'Guest OS Profiles' {
+ $VmmGuestOSProfilesInfo = @()
+ foreach ($GuestOSProfile in $GuestOSProfiles) {
+ $InObj = [Ordered]@{
+ 'Name' = $GuestOSProfile.Name
+ 'Full Name' = $GuestOSProfile.FullName
+ 'Operating System' = $GuestOSProfile.OperatingSystem
+ 'Operating System Type' = $GuestOSProfile.OSType
+ 'Computer Name' = $GuestOSProfile.ComputerName
+ 'Product Key' = $GuestOSProfile.ProductKey
+ 'Join Workgroup' = $GuestOSProfile.JoinWorkgroup
+ 'Join Domain RunAs Account' = $GuestOSProfile.JoinDomainRunAsAccount
+ 'Join Domain' = $GuestOSProfile.JoinDomain
+ 'Admin Password RunAsAccount' = $GuestOSProfile.AdminPasswordRunAsAccount
+ 'Org Name' = $GuestOSProfile.OrgName
+ 'Domain Admin' = $GuestOSProfile.DomainAdmin
+ 'DNS Domain Name' = $GuestOSProfile.DNSDomainName
+ 'Sysprep Script' = $GuestOSProfile.SysprepScript
+ 'Domain Join Organizational Unit' = $GuestOSProfile.DomainJoinOrganizationalUnit
+ 'Server Features' = $GuestOSProfile.ServerFeatures.DisplayName
+ 'Description' = $GuestOSProfile.Description
+ }
+
+ $VmmGuestOSProfilesInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.LibraryTemplates -ge 2) {
+ Paragraph "The following sections detail the configuration of the guest os profiles."
+ foreach ($GuestOSProfile in $VmmGuestOSProfilesInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($GuestOSProfile.Name)" {
+ $TableParams = @{
+ Name = "Guest OS Profiles - $($GuestOSProfile.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $GuestOSProfile | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the guest os profiles."
+ BlankLine
+ $TableParams = @{
+ Name = "Guest OS Profiles - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Join Domain', 'Operating System Type', 'Server Features'
+ ColumnWidths = 25, 20, 20, 35
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmGuestOSProfilesInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmHardwareProfile.ps1 b/Src/Private/Get-AbrVmmHardwareProfile.ps1
new file mode 100644
index 0000000..6bf4610
--- /dev/null
+++ b/Src/Private/Get-AbrVmmHardwareProfile.ps1
@@ -0,0 +1,85 @@
+function Get-AbrVmmHardwareProfile {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Hardware Profile information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "LibraryTemplates InfoLevel set at $($InfoLevel.LibraryTemplates)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.LibraryTemplates -gt 0) {
+ if ($HardwareProfiles = Get-SCHardwareProfile | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Hardware Profile information."
+ Section -Style Heading3 'Hardware Profiles' {
+ $VmmHardwareProfilesInfo = @()
+ foreach ($HardwareProfile in $HardwareProfiles) {
+ $InObj = [Ordered]@{
+ 'Name' = $HardwareProfile.Name
+ 'CPU Count' = $HardwareProfile.CPUCount
+ 'Memory MB' = $HardwareProfile.Memory
+ 'Secure Boot Enabled' = $HardwareProfile.SecureBootEnabled
+ 'Generation' = $HardwareProfile.Generation
+ 'Checkpoint Type' = $HardwareProfile.CheckpointType
+ 'Capability Profile' = $HardwareProfile.CapabilityProfile
+ 'Monitor Maximum Count' = $HardwareProfile.MonitorMaximumCount
+ }
+
+ $VmmHardwareProfilesInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.LibraryTemplates -ge 2) {
+ Paragraph "The following sections detail the configuration of the hardware profiles."
+ foreach ($HardwareProfile in $VmmHardwareProfilesInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($HardwareProfile.Name)" {
+ $TableParams = @{
+ Name = "Hardware Profiles - $($HardwareProfile.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $HardwareProfile | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the hardware profiles."
+ BlankLine
+ $TableParams = @{
+ Name = "Hardware Profiles - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'CPU Count', 'Memory MB', 'Generation'
+ ColumnWidths = 25, 25, 25, 25
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmHardwareProfilesInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmHost.ps1 b/Src/Private/Get-AbrVmmHost.ps1
new file mode 100644
index 0000000..f18a686
--- /dev/null
+++ b/Src/Private/Get-AbrVmmHost.ps1
@@ -0,0 +1,43 @@
+function Get-AbrVmmHost {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Hosts information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Hosts)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Hosts -gt 0) {
+ Write-PScriboMessage "Collecting VMM Host information."
+ if ($ScVmmHosts = Get-SCVMHost) {
+ Section -Style Heading1 'Hosts' {
+ Paragraph "The following table summarises the configuration of the hosts."
+ BlankLine
+ Get-AbrVmmHostSummary
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmHostSummary.ps1 b/Src/Private/Get-AbrVmmHostSummary.ps1
new file mode 100644
index 0000000..aec437c
--- /dev/null
+++ b/Src/Private/Get-AbrVmmHostSummary.ps1
@@ -0,0 +1,58 @@
+function Get-AbrVmmHostSummary {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Host Summary information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Hosts InfoLevel set at $($InfoLevel.Hosts)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Hosts -gt 0) {
+ if ($VMHosts = Get-SCVMHost -VMMServer $ConnectVmmServer | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Host information."
+ $VmmHostInfo = @()
+ foreach ($VMHost in $VMHosts) {
+ $InObj = [Ordered]@{
+ 'Name' = $VMHost.ComputerName
+ 'Host Group' = $VMHost.OperatingSystem
+ 'Cluster Nodes' = $VMHost.VMHostGroup
+ }
+
+ $VmmHostInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ $TableParams = @{
+ Name = "Host Summary - $($Vmm.FQDN)"
+ List = $false
+ ColumnWidths = 33, 33, 34
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmHostInfo | Table @TableParams
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmInfrastructureDiagram.ps1 b/Src/Private/Get-AbrVmmInfrastructureDiagram.ps1
new file mode 100644
index 0000000..37161dc
--- /dev/null
+++ b/Src/Private/Get-AbrVmmInfrastructureDiagram.ps1
@@ -0,0 +1,95 @@
+function Get-AbrVmmInfrastructureDiagram {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to built VMM infrastructure diagram
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Generating Infrastructure Diagram for VMM."
+ # Used for DraftMode (Don't touch it!)
+ if ($Options.EnableDiagramDebug) {
+ $EdgeDebug = @{style = 'filled'; color = 'red' }
+ $SubGraphDebug = @{style = 'dashed'; color = 'red' }
+ $NodeDebug = @{color = 'black'; style = 'red'; shape = 'plain' }
+ $NodeDebugEdge = @{color = 'black'; style = 'red'; shape = 'plain' }
+ $IconDebug = $true
+ } else {
+ $EdgeDebug = @{style = 'invis'; color = 'red' }
+ $SubGraphDebug = @{style = 'invis'; color = 'gray' }
+ $NodeDebug = @{color = 'transparent'; style = 'transparent'; shape = 'point' }
+ $NodeDebugEdge = @{color = 'transparent'; style = 'transparent'; shape = 'none' }
+ $IconDebug = $false
+ }
+
+ # Used for setting diagram Theme (Can be change to fits your needs!)
+ if ($Options.DiagramTheme -eq 'Black') {
+ $Edgecolor = 'White'
+ $Fontcolor = 'White'
+ } elseif ($Options.DiagramTheme -eq 'Neon') {
+ $Edgecolor = 'gold2'
+ $Fontcolor = 'gold2'
+ } else {
+ $Edgecolor = '#71797E'
+ $Fontcolor = '#565656'
+ }
+ }
+
+ process {
+ try {
+ if ($VMM) {
+ $UpdateServer = Get-SCUpdateServer
+ $VMMServerAdditionalInfo = [pscustomobject][Ordered]@{
+ 'IP Address' = (Get-NetIPAddress -CimSession $VMMCimSession -AddressFamily IPv4 | Where-Object { $_.IPAddress -notlike "127.0.0.1" })[0].IPAddress
+ 'Server Port' = $VMM.Port
+ 'Version' = $VMM.ProductVersion
+ 'Role' = "VMM Server"
+
+ }
+ $VMMDBServerAdditionalInfo = [pscustomobject][Ordered]@{
+ 'Server' = $VMM.DatabaseServerName
+ 'Instance' = $VMM.DatabaseInstanceName
+ 'Server Port' = '1431'
+ 'Version' = $VMM.DatabaseVersion
+ }
+ $VMMUDServerAdditionalInfo = [pscustomobject][Ordered]@{
+ 'IP Address' = Get-NodeIP -Hostname $UpdateServer.Name
+ 'Server Port' = $UpdateServer.Port
+ 'Type' = $UpdateServer.ServerType
+ 'Role' = "Update Server"
+
+ }
+
+ Node VMMserver @{Label = Add-DiaNodeIcon -Name $VMM.FQDN.split(".")[0].ToUpper() -AditionalInfo $VMMServerAdditionalInfo -ImagesObj $Images -IconType "Server" -Align "Center" -IconDebug $IconDebug -FontSize 18; shape = 'plain'; fillColor = 'transparent'; fontsize = 14 }
+
+ Node DBServer @{Label = Add-DiaNodeIcon -Name $VMM.DatabaseName -AditionalInfo $VMMDBServerAdditionalInfo -ImagesObj $Images -IconType "DB_Server" -Align "Center" -IconDebug $IconDebug -FontSize 18; shape = 'plain'; fillColor = 'transparent'; fontsize = 14 }
+
+ Node UpdateServer @{Label = Add-DiaNodeIcon -Name $UpdateServer.Name.split(".")[0].ToUpper() -AditionalInfo $VMMUDServerAdditionalInfo -ImagesObj $Images -IconType "Server" -Align "Center" -IconDebug $IconDebug -FontSize 18; shape = 'plain'; fillColor = 'transparent'; fontsize = 14 }
+
+ Edge -From VMMserver -To DBServer -Attributes @{minlen = 2; label = "DB Connection 1431"; color = $Edgecolor; fontcolor = $Fontcolor; fontsize = 16; style = 'dashed'; penwidth = 2; arrowhead = 'normal'; arrowtail = 'none' }
+
+ Edge -From VMMserver -To UpdateServer -Attributes @{minlen = 2; color = $Edgecolor; fontcolor = $Fontcolor; fontsize = 16; style = 'dashed'; penwidth = 2; arrowhead = 'normal'; arrowtail = 'none' }
+
+ Rank VMMserver, DBServer
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
+ end {}
+
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmLibraryServer.ps1 b/Src/Private/Get-AbrVmmLibraryServer.ps1
new file mode 100644
index 0000000..0c12c7b
--- /dev/null
+++ b/Src/Private/Get-AbrVmmLibraryServer.ps1
@@ -0,0 +1,88 @@
+function Get-AbrVmmLibraryServer {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Library Servers information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "LibraryTemplates InfoLevel set at $($InfoLevel.LibraryTemplates)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.LibraryTemplates -gt 0) {
+ if ($VMLibraries = Get-SCLibraryServer | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Library Servers information."
+ Section -Style Heading3 'Library Servers' {
+ $VmmLibraryServersInfo = @()
+ foreach ($VMLibrary in $VMLibraries) {
+ $InObj = [Ordered]@{
+ 'Name' = $VMLibrary.Name
+ 'FQDN' = $VMLibrary.FQDN
+ 'Status' = $VMLibrary.Status
+ 'Domain Name' = $VMLibrary.DomainName
+ 'Library Group' = $VMLibrary.LibraryGroup
+ 'Is Cluster Node?' = $VMLibrary.IsClusterNode
+ 'Is Virtual Cluster Name?' = $VMLibrary.IsVirtualClusterName
+ 'VM Networks' = ($VMLibrary.VMNetworks | ForEach-Object { $_.Name }) -join ', '
+ 'Is Unencrypted File Transfer Enabled?' = $VMLibrary.IsUnencryptedFileTransferEnabled
+ 'Library Server Management Credential' = $VMLibrary.LibraryServerManagementCredential.Name
+ 'Description' = $VMLibrary.Description
+ }
+
+ $VmmLibraryServersInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.LibraryTemplates -ge 2) {
+ Paragraph "The following sections detail the configuration of the library servers."
+ foreach ($LibraryServer in $VmmLibraryServersInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($LibraryServer.Name)" {
+ $TableParams = @{
+ Name = "Library Servers - $($LibraryServer.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $LibraryServer | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the library servers."
+ BlankLine
+ $TableParams = @{
+ Name = "Library Servers - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Description', 'Status'
+ ColumnWidths = 42, 42, 16
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmLibraryServersInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmLibraryShare.ps1 b/Src/Private/Get-AbrVmmLibraryShare.ps1
new file mode 100644
index 0000000..40cf6ff
--- /dev/null
+++ b/Src/Private/Get-AbrVmmLibraryShare.ps1
@@ -0,0 +1,82 @@
+function Get-AbrVmmLibraryShare {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Library Shares information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "LibraryTemplates InfoLevel set at $($InfoLevel.LibraryTemplates)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.LibraryTemplates -gt 0) {
+ if ($LibraryShares = Get-SCLibraryShare | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Library Shares information."
+ Section -Style Heading3 'Library Shares' {
+ $VmmLibrarySharesInfo = @()
+ foreach ($LibraryShare in $LibraryShares) {
+ $InObj = [Ordered]@{
+ 'Name' = $LibraryShare.Name
+ 'Library Server' = $LibraryShare.LibraryServer
+ 'Path' = $LibraryShare.Path
+ 'Use Alternate DataStream?' = $LibraryShare.UseAlternateDataStream
+ 'Description' = $LibraryShare.Description
+ }
+
+ $VmmLibrarySharesInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.LibraryTemplates -ge 2) {
+ Paragraph "The following sections detail the configuration of the library shares."
+ foreach ($LibraryShare in $VmmLibrarySharesInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($LibraryShare.Name)" {
+ $TableParams = @{
+ Name = "Library Shares - $($LibraryShare.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $LibraryShare | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the library shares."
+ BlankLine
+ $TableParams = @{
+ Name = "Library Shares - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Library Server', 'Path', 'Use Alternate DataStream?'
+ ColumnWidths = 25, 30, 30, 15
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmLibrarySharesInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmLibraryTemplate.ps1 b/Src/Private/Get-AbrVmmLibraryTemplate.ps1
new file mode 100644
index 0000000..692fb8b
--- /dev/null
+++ b/Src/Private/Get-AbrVmmLibraryTemplate.ps1
@@ -0,0 +1,44 @@
+function Get-AbrVmmLibraryTemplate {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Library and Templates information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.LibraryTemplates)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.LibraryTemplates -gt 0) {
+ Write-PScriboMessage "Collecting VMM Library and Template information."
+ Section -Style Heading1 'Library and Templates' {
+ Paragraph 'The following section details the library and vm templates configured'
+ Get-AbrVmmLibraryServer
+ Get-AbrVmmLibraryShare
+ Get-AbrVmmVMTemplate
+ Get-AbrVmmGuestOSProfile
+ Get-AbrVmmHardwareProfile
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmLogicalNetwork.ps1 b/Src/Private/Get-AbrVmmLogicalNetwork.ps1
new file mode 100644
index 0000000..ec76ca4
--- /dev/null
+++ b/Src/Private/Get-AbrVmmLogicalNetwork.ps1
@@ -0,0 +1,84 @@
+function Get-AbrVmmLogicalNetwork {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Logical Network information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($LogicalNetworks = Get-SCLogicalNetworkDefinition | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Networking information."
+ Section -Style Heading2 'Logical Networks' {
+ $VmmLogicalNetworksInfo = @()
+ foreach ($LogicalNetwork in $LogicalNetworks) {
+ $InObj = [Ordered]@{
+ 'Name' = $LogicalNetwork.LogicalNetwork
+ 'ID' = $LogicalNetwork.ID
+ 'Isolation Type' = $LogicalNetwork.IsolationType
+ 'Host Groups' = $LogicalNetwork.HostGroups -join ', '
+ }
+
+ $VmmLogicalNetworksInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.Networking -ge 2) {
+ Paragraph "The following sections detail the configuration of the logical networks."
+ foreach ($LogicalNetwork in $VmmLogicalNetworksInfo) {
+ Section -Style Heading3 "$($LogicalNetwork.Name)" {
+ $TableParams = @{
+ Name = "Logical Networks - $($LogicalNetwork.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $LogicalNetwork | Table @TableParams
+ Get-AbrVmmVlanSubnet -SubnetVLans ($LogicalNetworks | Where-Object { $_.ID -eq $LogicalNetwork.ID }).SubnetVLans
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the logical networks."
+ BlankLine
+ $TableParams = @{
+ Name = "Logical Networks - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Isolation Type', 'Host Groups'
+ ColumnWidths = 40, 30, 30
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmLogicalNetworksInfo | Table @TableParams
+ Get-AbrVmmVlanSubnet -SubnetVLans $LogicalNetworks.SubnetVLans
+
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmLogicalSwitch.ps1 b/Src/Private/Get-AbrVmmLogicalSwitch.ps1
new file mode 100644
index 0000000..9b767c4
--- /dev/null
+++ b/Src/Private/Get-AbrVmmLogicalSwitch.ps1
@@ -0,0 +1,83 @@
+function Get-AbrVmmLogicalSwitch {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Logical Switch information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($LogicalSwitches = Get-SCLogicalSwitch | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Logical Switches information."
+ Section -Style Heading3 'Logical Switches' {
+ $VmmLogicalSwitchesInfo = @()
+ foreach ($LogicalSwitch in $LogicalSwitches) {
+ $InObj = [Ordered]@{
+ 'Name' = $LogicalSwitch.Name
+ 'ID' = $LogicalSwitch.ID
+ 'Uplink Mode' = $LogicalSwitch.UplinkMode
+ 'Minimum Bandwidth Mode' = $LogicalSwitch.MinimumBandwidthMode
+ 'Enable SR-IOV' = $LogicalSwitch.EnableSriov
+ 'Description' = $LogicalSwitch.Description
+ }
+
+ $VmmLogicalSwitchesInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.Networking -ge 2) {
+ Paragraph "The following sections detail the configuration of the logical switches."
+ foreach ($LogicalSwitch in $VmmLogicalSwitchesInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($LogicalSwitch.Name)" {
+ $TableParams = @{
+ Name = "Logical Switches - $($LogicalSwitch.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $LogicalSwitch | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the logical switches."
+ BlankLine
+ $TableParams = @{
+ Name = "Logical Switches - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Uplink Mode', 'Minimum Bandwidth Mode', 'Enable SR-IOV'
+ ColumnWidths = 25, 25, 25, 25
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmLogicalSwitchesInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmNetworkAdapterPortProfile.ps1 b/Src/Private/Get-AbrVmmNetworkAdapterPortProfile.ps1
new file mode 100644
index 0000000..330cd1c
--- /dev/null
+++ b/Src/Private/Get-AbrVmmNetworkAdapterPortProfile.ps1
@@ -0,0 +1,92 @@
+function Get-AbrVmmNetworkAdapterPortProfile {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Network Adapter Port Profiles information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($VirtualNetworkAdapterPortProfiles = Get-SCVirtualNetworkAdapterNativePortProfile | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Network Adapter Port Profiles information."
+ Section -Style Heading3 'Network Adapter Port Profiles' {
+ $VmmNetworkAdapterPortProfileInfo = @()
+ foreach ($VirtualNetworkAdapterPortProfile in $VirtualNetworkAdapterPortProfiles) {
+ $InObj = [Ordered]@{
+ 'Name' = $VirtualNetworkAdapterPortProfile.Name
+ 'Teaming' = $VirtualNetworkAdapterPortProfile.AllowTeaming
+ 'Mac Address Spoofing' = $VirtualNetworkAdapterPortProfile.AllowMacAddressSpoofing
+ 'Ieee Priority Tagging' = $VirtualNetworkAdapterPortProfile.AllowIeeePriorityTagging
+ 'DHCP Guard' = $VirtualNetworkAdapterPortProfile.EnableDHCPGuard
+ 'Guest IP Network Virtualization Updates' = $VirtualNetworkAdapterPortProfile.EnableGuestIPNetworkVirtualizationUpdates
+ 'Router Guard' = $VirtualNetworkAdapterPortProfile.EnableRouterGuard
+ 'Minimum Bandwidth Weight' = $VirtualNetworkAdapterPortProfile.MinimumBandwidthWeight
+ 'Minimum Bandwidth Absolute In Mbps' = $VirtualNetworkAdapterPortProfile.MinimumBandwidthAbsoluteInMbps
+ 'Maximum Bandwidth Absolute In Mbps' = $VirtualNetworkAdapterPortProfile.MaximumBandwidthAbsoluteInMbps
+ 'Enable Vmq' = $VirtualNetworkAdapterPortProfile.EnableVmq
+ 'Enable IPsec Offload' = $VirtualNetworkAdapterPortProfile.EnableIPsecOffload
+ 'Enable Iov' = $VirtualNetworkAdapterPortProfile.EnableIov
+ 'Enable Vrss' = $VirtualNetworkAdapterPortProfile.EnableVrss
+ 'Enable Rdma' = $VirtualNetworkAdapterPortProfile.EnableRdma
+ }
+
+ $VmmNetworkAdapterPortProfileInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.Networking -ge 2) {
+ Paragraph "The following sections detail the configuration of the network adapter port profiles."
+ foreach ($VirtualNetworkAdapterPortProfile in $VmmNetworkAdapterPortProfileInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($VirtualNetworkAdapterPortProfile.Name)" {
+ $TableParams = @{
+ Name = "Network Adapter Port Profiles - $($VirtualNetworkAdapterPortProfile.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VirtualNetworkAdapterPortProfile | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the network adapter port profiles."
+ BlankLine
+ $TableParams = @{
+ Name = "Network Adapter Port Profiles - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Teaming', 'Mac Address Spoofing', 'DHCP Guard', 'Router Guard'
+ ColumnWidths = 20, 20, 20, 20, 20
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmNetworkAdapterPortProfileInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmNetworking.ps1 b/Src/Private/Get-AbrVmmNetworking.ps1
new file mode 100644
index 0000000..e73be13
--- /dev/null
+++ b/Src/Private/Get-AbrVmmNetworking.ps1
@@ -0,0 +1,45 @@
+function Get-AbrVmmNetworking {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Networking information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ Write-PScriboMessage "Collecting VMM Networking information."
+ Section -Style Heading1 'Networking' {
+ Paragraph 'The following section contains as built for Logical Networks, Logical Switches, Port Profiles and VM Networks'
+ Get-AbrVmmLogicalNetwork
+ Get-AbrVmmVmNetwork
+ Get-AbrVmmLogicalSwitch
+ Get-AbrVmmUplinkPortProfile
+ Get-AbrVmmNetworkAdapterPortProfile
+ Get-AbrVmmPortClassification
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmPortClassification.ps1 b/Src/Private/Get-AbrVmmPortClassification.ps1
new file mode 100644
index 0000000..2d2448f
--- /dev/null
+++ b/Src/Private/Get-AbrVmmPortClassification.ps1
@@ -0,0 +1,62 @@
+function Get-AbrVmmPortClassification {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Port Classification information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($PortClassifications = Get-SCPortClassification | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Port Classification information."
+ Section -Style Heading3 'Port Classifications' {
+ $VmmPortClassificationInfo = @()
+ foreach ($PortClassification in $PortClassifications) {
+ $InObj = [Ordered]@{
+ 'Name' = $PortClassification.Name
+ 'Description' = $PortClassification.Description
+ }
+
+ $VmmPortClassificationInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ Paragraph "The following table summarises the configuration of the port classification."
+ BlankLine
+ $TableParams = @{
+ Name = "Port Classification - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Description'
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmPortClassificationInfo | Sort-Object -Property Name | Table @TableParams
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmRequiredModule.ps1 b/Src/Private/Get-AbrVmmRequiredModule.ps1
new file mode 100644
index 0000000..b65d10b
--- /dev/null
+++ b/Src/Private/Get-AbrVmmRequiredModule.ps1
@@ -0,0 +1,75 @@
+function Get-AbrVmmRequiredModule {
+ <#
+ .SYNOPSIS
+ Function to check if the required version of VirtualMachineManager is installed
+ .DESCRIPTION
+ Documents the configuration of Veeam VMM in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.1.1
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Veeam.VMM
+ #>
+ [CmdletBinding()]
+
+ param
+ (
+ [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Name,
+
+ [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $Version
+ )
+ process {
+ #region: Start Load VirtualMachineManager Module
+ # Loading Module
+ # Make sure PSModulePath includes SCVMM Console
+ #Code taken from @vMarkus_K
+ if (Test-Path "C:\Program Files\Microsoft System Center\Virtual Machine Manager\bin\psModules" ) {
+ $MyModulePath = "C:\Program Files\Microsoft System Center\Virtual Machine Manager\bin\psModules"
+ $env:PSModulePath = $env:PSModulePath + "$([System.IO.Path]::PathSeparator)$MyModulePath"
+ } elseif (Test-Path "D:\Program Files\Microsoft System Center\Virtual Machine Manager\bin\psModules" ) {
+ $MyModulePath = "D:\Program Files\Microsoft System Center\Virtual Machine Manager\bin\psModules"
+ $env:PSModulePath = $env:PSModulePath + "$([System.IO.Path]::PathSeparator)$MyModulePath"
+ } elseif (Test-Path "E:\Program Files\Microsoft System Center\Virtual Machine Manager\bin\psModules" ) {
+ $MyModulePath = "E:\Program Files\Microsoft System Center\Virtual Machine Manager\bin\psModules"
+ $env:PSModulePath = $env:PSModulePath + "$([System.IO.Path]::PathSeparator)$MyModulePath"
+ }
+ if ($Modules = Get-Module -ListAvailable -Name VirtualMachineManager) {
+ try {
+ Write-PScriboMessage "Trying to import Microsoft SCVMM modules."
+ $Modules | Import-Module -WarningAction SilentlyContinue
+ } catch {
+ Write-PScriboMessage -IsWarning "Failed to load Microsoft SCVMM modules"
+ }
+ }
+ Write-PScriboMessage "Identifying Microsoft SCVMM module version."
+ if ($Module = Get-Module -ListAvailable -Name virtualmachinemanager) {
+ try {
+ $script:SCVmmVersion = $Module.Version.ToString()
+ Write-PScriboMessage "Using Microsoft SCVMM powershell module version $($SCVmmVersion)."
+ } catch {
+ Write-PScriboMessage -IsWarning "Failed to get Version from Module"
+ }
+ }
+ # Check if the required version of VMware PowerCLI is installed
+ $RequiredModule = Get-Module -ListAvailable -Name $Name
+ $ModuleVersion = "{0}.{1}" -f $RequiredModule.Version.Major, $RequiredModule.Version.Minor
+ if ($ModuleVersion -eq ".") {
+ throw "$Name $Version or higher is required to run the Microsoft SCVMM As Built Report. Install the Microsoft SCVMM console that provide the required modules."
+ }
+
+ if ($ModuleVersion -lt $Version) {
+ throw "$Name $Version or higher is required to run the Microsoft SCVMM As Built Report. Update the Microsoft SCVMM console that provide the required modules."
+ }
+ }
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmServerConnection.ps1 b/Src/Private/Get-AbrVmmServerConnection.ps1
new file mode 100644
index 0000000..a3da55c
--- /dev/null
+++ b/Src/Private/Get-AbrVmmServerConnection.ps1
@@ -0,0 +1,39 @@
+function Get-AbrVmmServerConnection {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to establish connection to Microsoft SCVMM Server.
+ .DESCRIPTION
+ Documents the configuration of Microsoft SCVMM in Word/HTML/Text formats using PScribo.
+ .NOTES
+ Version: 0.1.1
+ Author: Jonathan Colon
+ Twitter: @jcolonfzenpr
+ Github: rebelinux
+ Credits: Iain Brighton (@iainbrighton) - PScribo module
+
+ .LINK
+ https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.SCVMM
+ #>
+ [CmdletBinding()]
+ param (
+
+ )
+
+ begin {
+ Write-PScriboMessage "Establishing initial connection to VMM Server: $($System)."
+ }
+
+ process {
+ Write-PScriboMessage "Looking for VMM existing server connection."
+ $script:ConnectVmmServer = Get-SCVMMServer -ComputerName $Server -Credential $Credential -TCPPort $Options.VmmServerPort
+ if ($ConnectVmmServer) {
+ Write-PScriboMessage "Successfully connected to $($System):$($Options.VmmServerPort) Vmm Server."
+ $script:VMM = $ConnectVmmServer
+ $script:VMMCimSession = New-CimSession -ComputerName ($VMM.FQDN) -Credential $Credential
+ } else {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ throw "Failed to connect to Vmm Server Host $($System):$($Options.VmmServerPort) with username $($Credential.USERNAME)"
+ }
+ }
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmServerSetting.ps1 b/Src/Private/Get-AbrVmmServerSetting.ps1
new file mode 100644
index 0000000..f45bed6
--- /dev/null
+++ b/Src/Private/Get-AbrVmmServerSetting.ps1
@@ -0,0 +1,84 @@
+function Get-AbrVmmServerSetting {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft VMM Server information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "ServerSettings InfoLevel set at $($InfoLevel.ServerSettings)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.ServerSettings -gt 0) {
+ if ($Vmm) {
+ Write-PScriboMessage "Collecting VMM Server Settings information."
+ Section -Style Heading2 'Virtual Machine Manager Server' {
+ $VmmServerSettingsInfo = @()
+ foreach ($VmmServerSetting in $Vmm) {
+ $InObj = [Ordered]@{
+ 'Server FQDN' = $VmmServerSetting.FQDN
+ 'IP Address' = (Get-NetIPAddress -CimSession $VMMCimSession -AddressFamily IPv4 | Where-Object { $_.IPAddress -notlike "127.0.0.1" }).IPAddress
+ 'Product Version' = $VmmServerSetting.ProductVersion
+ 'Server Port' = $VmmServerSetting.Port
+ 'VM Connect Port' = $VmmServerSetting.VMConnectDefaultPort
+ 'VMM Service Account' = $VmmServerSetting.VMMServiceAccount
+ 'VMM High Availability' = $VmmServerSetting.IsHighlyAvailable
+ }
+
+ $VmmServerSettingsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.ServerSettings -ge 2) {
+ Paragraph "The following sections detail the configuration of the VMM Server Settings."
+ foreach ($VmmServerSetting in $VmmServerSettingsInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($VmmServerSetting.'Server FQDN')" {
+ $TableParams = @{
+ Name = "Server Settings - $($VmmServerSetting.'Server FQDN')"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmServerSetting | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the VMM Server Settings."
+ BlankLine
+ $TableParams = @{
+ Name = "Server Settings - $($VmmServerSetting.FQDN)"
+ List = $false
+ Columns = 'Server FQDN', 'IP Address', 'Product Version', 'Server Port'
+ ColumnWidths = 45, 20, 20, 15
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmServerSettingsInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmUplinkPortProfile.ps1 b/Src/Private/Get-AbrVmmUplinkPortProfile.ps1
new file mode 100644
index 0000000..19e39e5
--- /dev/null
+++ b/Src/Private/Get-AbrVmmUplinkPortProfile.ps1
@@ -0,0 +1,83 @@
+function Get-AbrVmmUplinkPortProfile {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Uplink Port Profile information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($UplinkPortProfiles = Get-SCNativeUplinkPortProfile | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Uplink Port Profile information."
+ Section -Style Heading3 'Uplink Port Profiles' {
+ $VmmUplinkPortProfileInfo = @()
+ foreach ($UplinkPortProfile in $UplinkPortProfiles) {
+ $InObj = [Ordered]@{
+ 'Name' = $UplinkPortProfile.Name
+ 'Team Mode' = $UplinkPortProfile.LBFOTeamMode
+ 'Load Balance' = $UplinkPortProfile.LBFOLoadBalancingAlgorithm
+ 'Enabled Network Virtualization' = $UplinkPortProfile.EnableNetworkVirtualization
+ 'Logical Network Definitions' = $UplinkPortProfile.LogicalNetworkDefinitions -join ', '
+ 'Description' = $UplinkPortProfile.Description
+ }
+
+ $VmmUplinkPortProfileInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.Networking -ge 2) {
+ Paragraph "The following sections detail the configuration of the uplink port profile."
+ foreach ($UplinkPortProfile in $VmmUplinkPortProfileInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($UplinkPortProfile.Name)" {
+ $TableParams = @{
+ Name = "Uplink Port Profile - $($UplinkPortProfile.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $UplinkPortProfile | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the uplink port profile."
+ BlankLine
+ $TableParams = @{
+ Name = "Uplink Port Profile - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Team Mode', 'Load Balance', 'Enabled Network Virtualization'
+ ColumnWidths = 25, 25, 25, 25
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmUplinkPortProfileInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmVMTemplate.ps1 b/Src/Private/Get-AbrVmmVMTemplate.ps1
new file mode 100644
index 0000000..2aebfba
--- /dev/null
+++ b/Src/Private/Get-AbrVmmVMTemplate.ps1
@@ -0,0 +1,100 @@
+function Get-AbrVmmVMTemplate {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM VM Templates information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "LibraryTemplates InfoLevel set at $($InfoLevel.LibraryTemplates)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.LibraryTemplates -gt 0) {
+ if ($VMTemplates = Get-SCVMTemplate | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM VM Templates information."
+ Section -Style Heading3 'VM Templates' {
+ $VmmVMTemplatesInfo = @()
+ foreach ($VMTemplate in $VMTemplates) {
+ $InObj = [Ordered]@{
+ 'Name' = $VMTemplate.Name
+ 'Operating System' = $VMTemplate.OperatingSystem
+ 'CPU Count' = $VMTemplate.CPUCount
+ 'Product Key' = $VMTemplate.ProductKey
+ 'Memory (MB)' = $VMTemplate.Memory
+ 'JoinWorkgroup' = $VMTemplate.JoinWorkgroup
+ 'OrgName' = $VMTemplate.OrgName
+ 'DomainAdmin' = $VMTemplate.DomainAdmin
+ 'ComputerName' = $VMTemplate.ComputerName
+ 'FullName' = $VMTemplate.FullName
+ 'DNSDomainName' = $VMTemplate.DNSDomainName
+ 'SysprepScript' = $VMTemplate.SysprepScript
+ 'DynamicMemoryEnabled' = $VMTemplate.DynamicMemoryEnabled
+ 'VirtualVideoAdapterEnabled' = $VMTemplate.VirtualVideoAdapterEnabled
+ 'MonitorMaximumCount' = $VMTemplate.MonitorMaximumCount
+ 'MonitorResolutionMaximum' = $VMTemplate.MonitorResolutionMaximum
+ 'UseHardwareAssistedVirtualization' = $VMTemplate.UseHardwareAssistedVirtualization
+ 'Tags' = ($VMTemplate.Tags -join ', ')
+ 'CapabilityProfile' = $VMTemplate.CapabilityProfile
+ 'VirtualizationPlatform' = $VMTemplate.VirtualizationPlatform
+ 'DomainJoinOrganizationalUnit' = $VMTemplate.DomainJoinOrganizationalUnit
+ 'Generation' = $VMTemplate.Generation
+ 'Description' = $VMTemplate.Description
+ }
+
+ $VmmVMTemplatesInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.LibraryTemplates -ge 2) {
+ Paragraph "The following sections detail the configuration of the vm templates."
+ foreach ($VMTemplate in $VmmVMTemplatesInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($VMTemplate.Name)" {
+ $TableParams = @{
+ Name = "VM Templates - $($VMTemplate.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VMTemplate | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the vm templates."
+ BlankLine
+ $TableParams = @{
+ Name = "VM Templates - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Operating System', 'CPU Count', 'Memory (MB)', 'Generation'
+ ColumnWidths = 27, 28, 15, 15, 15
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmVMTemplatesInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmVlanSubnet.ps1 b/Src/Private/Get-AbrVmmVlanSubnet.ps1
new file mode 100644
index 0000000..582a9d6
--- /dev/null
+++ b/Src/Private/Get-AbrVmmVlanSubnet.ps1
@@ -0,0 +1,82 @@
+function Get-AbrVmmVlanSubnet {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM VLANs and Subnets information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory)]
+ $SubnetVLans
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($SubnetVLans) {
+ Write-PScriboMessage "Collecting VMM VLANs and Subnets information."
+ Section -Style Heading2 'Subnets & VLANs' {
+ $VmmVlansSubnetsInfo = @()
+ foreach ($VlansSubnets in ($SubnetVLans | Sort-Object -Property Name)) {
+ $InObj = [Ordered]@{
+ 'Name' = $VlansSubnets.Subnet
+ 'VLAN ID' = $VlansSubnets.VLanID
+ 'Secondary VLan ID' = $VlansSubnets.SecondaryVLanID -join ', '
+ 'Supports DHCP' = $VlansSubnets.SupportsDHCP
+ 'Is Assigned To VM Subnet?' = $VlansSubnets.IsAssignedToVMSubnet
+ 'Enabled' = $VlansSubnets.IsVLanEnabled
+ }
+
+ $VmmVlansSubnetsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.Networking -ge 2) {
+ foreach ($VlansSubnets in $VmmVlansSubnetsInfo) {
+ Section -Style NOTOCHeading6 -ExcludeFromTOC "$($VlansSubnets.Name)" {
+ $TableParams = @{
+ Name = "Subnets & VLANs - $($VlansSubnets.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VlansSubnets | Table @TableParams
+ }
+ }
+ } else {
+ $TableParams = @{
+ Name = "Subnets & VLANs - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'VLAN ID'
+ ColumnWidths = 50, 50
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmVlansSubnetsInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmVmNetwork.ps1 b/Src/Private/Get-AbrVmmVmNetwork.ps1
new file mode 100644
index 0000000..1072d70
--- /dev/null
+++ b/Src/Private/Get-AbrVmmVmNetwork.ps1
@@ -0,0 +1,86 @@
+function Get-AbrVmmVmNetwork {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM VM Network information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($VMNetworks = Get-SCVMNetwork | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM VM Networks information."
+ Section -Style Heading3 'VM Networks' {
+ $VmmVMNetworksInfo = @()
+ foreach ($VMNetwork in $VMNetworks) {
+ $InObj = [Ordered]@{
+ 'Name' = $VMNetwork.Name
+ 'ID' = $VMNetwork.ID
+ 'Logical Network' = $VMNetwork.LogicalNetwork
+ 'VM Subnets' = $VMNetwork.VMSubnet.Name -join ', '
+ 'Isolation Type' = $VMNetwork.IsolationType
+ 'Enabled' = $VMNetwork.Enabled
+ 'Is Assigned?' = $VMNetwork.IsAssigned
+ 'Description' = $VMNetwork.Description
+ }
+
+ $VmmVMNetworksInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.Networking -ge 2) {
+ Paragraph "The following sections detail the configuration of the vm networks."
+ foreach ($VMNetwork in $VmmVMNetworksInfo) {
+ Section -Style Heading4 "$($VMNetwork.Name)" {
+ $TableParams = @{
+ Name = "VM Networks - $($VMNetwork.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VMNetwork | Table @TableParams
+ Get-AbrVmmVmSubnet -VMSubnet ($VMNetworks | Where-Object { $_.ID -eq $VMNetwork.ID }).VMSubnet
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the vm networks."
+ BlankLine
+ $TableParams = @{
+ Name = "VM Networks - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Logical Network', 'VM Subnets', 'Isolation Type', 'Enabled'
+ ColumnWidths = 20, 20, 20, 25, 15
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmVMNetworksInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmVmSubnet.ps1 b/Src/Private/Get-AbrVmmVmSubnet.ps1
new file mode 100644
index 0000000..597d08b
--- /dev/null
+++ b/Src/Private/Get-AbrVmmVmSubnet.ps1
@@ -0,0 +1,85 @@
+function Get-AbrVmmVmSubnet {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM VM Subnets information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory)]
+ $VMSubnet
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($VMSubnet) {
+ Write-PScriboMessage "Collecting VMM VM Subnets information."
+ Section -Style Heading5 'VM Subnets' {
+ $VmmVlansSubnetsInfo = @()
+ foreach ($VlansSubnets in ($VMSubnet | Sort-Object -Property Name)) {
+ $InObj = [Ordered]@{
+ 'Name' = $VlansSubnets.Name
+ 'ID' = $VlansSubnets.ID
+ 'VM Network' = $VlansSubnets.VMNetwork
+ 'Encrypted' = $VlansSubnets.EncryptionEnabled
+ 'Allows Intra Port Communication' = $VlansSubnets.AllowsIntraPortCommunication
+ }
+
+ $VmmVlansSubnetsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.Networking -ge 2) {
+ Paragraph "The following sections detail the configuration of the vm subnets."
+ foreach ($VlansSubnets in $VmmVlansSubnetsInfo) {
+ Section -Style Heading6 "$($VlansSubnets.Name)" {
+ $TableParams = @{
+ Name = "VM Subnets - $($VlansSubnets.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VlansSubnets | Table @TableParams
+ Get-AbrVmmVmVlanSubnet -Name $VlansSubnets.Name -SubnetVLans ($VMSubnet | Where-Object { $_.ID -eq $VlansSubnets.ID }).SubnetVLans
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the vm subnets."
+ BlankLine
+ $TableParams = @{
+ Name = "VM Subnets - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'VM Network', 'Encrypted', 'Allows Intra Port Communication'
+ ColumnWidths = 25, 25, 25, 25
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmVlansSubnetsInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmVmVlanSubnet.ps1 b/Src/Private/Get-AbrVmmVmVlanSubnet.ps1
new file mode 100644
index 0000000..682b9f8
--- /dev/null
+++ b/Src/Private/Get-AbrVmmVmVlanSubnet.ps1
@@ -0,0 +1,65 @@
+function Get-AbrVmmVmVlanSubnet {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM VM VLANs and Subnets information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory)]
+ $SubnetVLans,
+ [string] $Name
+ )
+
+ begin {
+ Write-PScriboMessage "Networking InfoLevel set at $($InfoLevel.Networking)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Networking -gt 0) {
+ if ($SubnetVLans) {
+ Write-PScriboMessage "Collecting VMM VM VLANs and Subnets information."
+ Section -Style NOTOCHeading5 -ExcludeFromTOC 'Subnets & VLANs' {
+ $VmmVlansSubnetsInfo = @()
+ foreach ($VlansSubnets in ($SubnetVLans | Sort-Object -Property Name)) {
+ $InObj = [Ordered]@{
+ 'Name' = $VlansSubnets.Subnet
+ 'VLAN ID' = $VlansSubnets.VLanID
+ 'Supports DHCP' = $VlansSubnets.SupportsDHCP
+ 'Is Assigned To VM Subnet?' = $VlansSubnets.IsAssignedToVMSubnet
+ 'Enabled' = $VlansSubnets.IsVLanEnabled
+ }
+
+ $VmmVlansSubnetsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+ BlankLine
+ $TableParams = @{
+ Name = "Subnets & VLANs - $($Name)"
+ List = $false
+ ColumnWidths = 20, 20, 20, 20, 20
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmVlansSubnetsInfo | Table @TableParams
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/SharedUtilsFunctions.ps1 b/Src/Private/SharedUtilsFunctions.ps1
new file mode 100644
index 0000000..5bce849
--- /dev/null
+++ b/Src/Private/SharedUtilsFunctions.ps1
@@ -0,0 +1,512 @@
+function ConvertTo-TextYN {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to convert true or false automatically to Yes or No.
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.3.0
+ Author: LEE DAILEY
+
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ [OutputType([String])]
+ param (
+ [Parameter (
+ Position = 0,
+ Mandatory)]
+ [AllowEmptyString()]
+ [string] $TEXT
+ )
+
+ switch ($TEXT) {
+ "" { "--"; break }
+ " " { "--"; break }
+ $Null { "--"; break }
+ "True" { "Yes"; break }
+ "False" { "No"; break }
+ default { $TEXT }
+ }
+} # end
+
+
+function ConvertTo-FileSizeString {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to convert bytes automatically to GB or TB based on size.
+ .DESCRIPTION
+ .NOTES
+ Version: 0.1.0
+ Author: Jonathan Colon
+ .EXAMPLE
+ .LINK
+ #>
+ [CmdletBinding()]
+ [OutputType([String])]
+ param
+ (
+ [Parameter (
+ Position = 0,
+ Mandatory)]
+ [int64] $Size,
+ [Parameter(
+ Position = 1,
+ Mandatory = $false,
+ HelpMessage = 'Please provide the source space unit'
+ )]
+ [ValidateSet('MB', 'GB', 'TB', 'PB')]
+ [string] $SourceSpaceUnit,
+ [Parameter(
+ Position = 2,
+ Mandatory = $false,
+ HelpMessage = 'Please provide the space unit to output'
+ )]
+ [ValidateSet('MB', 'GB', 'TB', 'PB')]
+ [string] $TargetSpaceUnit,
+ [Parameter(
+ Position = 3,
+ Mandatory = $false,
+ HelpMessage = 'Please provide the value to round the storage unit'
+ )]
+ [int] $RoundUnits = 0
+ )
+
+ if ($SourceSpaceUnit) {
+ return "$([math]::Round(($Size * $("1" + $SourceSpaceUnit) / $("1" + $TargetSpaceUnit)), $RoundUnits)) $TargetSpaceUnit"
+ } else {
+ $Unit = switch ($Size) {
+ { $Size -gt 1PB } { 'PB' ; break }
+ { $Size -gt 1TB } { 'TB' ; break }
+ { $Size -gt 1GB } { 'GB' ; break }
+ { $Size -gt 1Mb } { 'MB' ; break }
+ Default { 'KB' }
+ }
+ return "$([math]::Round(($Size / $("1" + $Unit)), $RoundUnits)) $Unit"
+ }
+} # end
+function Convert-Size {
+ [cmdletbinding()]
+ param(
+ [validateset("Bytes", "KB", "MB", "GB", "TB")]
+ [string]$From,
+ [validateset("Bytes", "KB", "MB", "GB", "TB")]
+ [string]$To,
+ [Parameter(Mandatory = $true)]
+ [double]$Value,
+ [int]$Precision = 4
+ )
+ switch ($From) {
+ "Bytes" { $value = $Value }
+ "KB" { $value = $Value * 1024 }
+ "MB" { $value = $Value * 1024 * 1024 }
+ "GB" { $value = $Value * 1024 * 1024 * 1024 }
+ "TB" { $value = $Value * 1024 * 1024 * 1024 * 1024 }
+ }
+
+ switch ($To) {
+ "Bytes" { return $value }
+ "KB" { $Value = $Value / 1KB }
+ "MB" { $Value = $Value / 1MB }
+ "GB" { $Value = $Value / 1GB }
+ "TB" { $Value = $Value / 1TB }
+
+ }
+
+ return [Math]::Round($value, $Precision, [MidPointRounding]::AwayFromZero)
+}
+
+function Get-PieChart {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to generate PScriboChart pie charts.
+ .DESCRIPTION
+ .NOTES
+ Version: 0.1.0
+ Author: Jonathan Colon
+ .EXAMPLE
+ .LINK
+ #>
+ [CmdletBinding()]
+ [OutputType([System.String])]
+ param
+ (
+ [Parameter (
+ Position = 0,
+ Mandatory)]
+ [System.Array]
+ $SampleData,
+ [String]
+ $ChartName,
+ [String]
+ $XField,
+ [String]
+ $YField,
+ [String]
+ $ChartLegendName,
+ [String]
+ $ChartLegendAlignment = 'Center',
+ [String]
+ $ChartTitleName = ' ',
+ [String]
+ $ChartTitleText = ' ',
+ [int]
+ $Width = 600,
+ [int]
+ $Height = 400,
+ [Switch]
+ $Status,
+ [bool]
+ $ReversePalette = $false
+ )
+
+ $StatusCustomPalette = @(
+ [System.Drawing.ColorTranslator]::FromHtml('#DFF0D0')
+ [System.Drawing.ColorTranslator]::FromHtml('#FFF4C7')
+ [System.Drawing.ColorTranslator]::FromHtml('#FEDDD7')
+ [System.Drawing.ColorTranslator]::FromHtml('#878787')
+ )
+
+ $AbrCustomPalette = @(
+ [System.Drawing.ColorTranslator]::FromHtml('#d5e2ff')
+ [System.Drawing.ColorTranslator]::FromHtml('#bbc9e9')
+ [System.Drawing.ColorTranslator]::FromHtml('#a2b1d3')
+ [System.Drawing.ColorTranslator]::FromHtml('#8999bd')
+ [System.Drawing.ColorTranslator]::FromHtml('#7082a8')
+ [System.Drawing.ColorTranslator]::FromHtml('#586c93')
+ [System.Drawing.ColorTranslator]::FromHtml('#40567f')
+ [System.Drawing.ColorTranslator]::FromHtml('#27416b')
+ [System.Drawing.ColorTranslator]::FromHtml('#072e58')
+ )
+
+ $VeeamCustomPalette = @(
+ [System.Drawing.ColorTranslator]::FromHtml('#ddf6ed')
+ [System.Drawing.ColorTranslator]::FromHtml('#c3e2d7')
+ [System.Drawing.ColorTranslator]::FromHtml('#aacec2')
+ [System.Drawing.ColorTranslator]::FromHtml('#90bbad')
+ [System.Drawing.ColorTranslator]::FromHtml('#77a898')
+ [System.Drawing.ColorTranslator]::FromHtml('#5e9584')
+ [System.Drawing.ColorTranslator]::FromHtml('#458370')
+ [System.Drawing.ColorTranslator]::FromHtml('#2a715d')
+ [System.Drawing.ColorTranslator]::FromHtml('#005f4b')
+ )
+
+ if ($Options.ReportStyle -eq "Veeam") {
+ $BorderColor = 'DarkGreen'
+ } else {
+ $BorderColor = 'DarkBlue'
+ }
+
+ $exampleChart = New-Chart -Name $ChartName -Width $Width -Height $Height -BorderStyle Dash -BorderWidth 1 -BorderColor $BorderColor
+
+ $addChartAreaParams = @{
+ Chart = $exampleChart
+ Name = 'exampleChartArea'
+ AxisXInterval = 1
+ }
+ $exampleChartArea = Add-ChartArea @addChartAreaParams -PassThru
+
+ if ($Status) {
+ $CustomPalette = $StatusCustomPalette
+ } elseif ($Options.ReportStyle -eq 'Veeam') {
+ $CustomPalette = $VeeamCustomPalette
+
+ } else {
+ $CustomPalette = $AbrCustomPalette
+ }
+
+ $addChartSeriesParams = @{
+ Chart = $exampleChart
+ ChartArea = $exampleChartArea
+ Name = 'exampleChartSeries'
+ XField = $XField
+ YField = $YField
+ CustomPalette = $CustomPalette
+ ColorPerDataPoint = $true
+ ReversePalette = $ReversePalette
+ }
+
+ $sampleData | Add-PieChartSeries @addChartSeriesParams
+
+ $addChartLegendParams = @{
+ Chart = $exampleChart
+ Name = $ChartLegendName
+ TitleAlignment = $ChartLegendAlignment
+ }
+ Add-ChartLegend @addChartLegendParams
+
+ $addChartTitleParams = @{
+ Chart = $exampleChart
+ ChartArea = $exampleChartArea
+ Name = $ChartTitleName
+ Text = $ChartTitleText
+ Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList @('Segoe Ui', '12', [System.Drawing.FontStyle]::Bold)
+ }
+ Add-ChartTitle @addChartTitleParams
+
+ $TempPath = Resolve-Path ([System.IO.Path]::GetTempPath())
+
+ $ChartImage = Export-Chart -Chart $exampleChart -Path $TempPath.Path -Format "PNG" -PassThru
+
+ $Base64Image = [convert]::ToBase64String((Get-Content $ChartImage -Encoding byte))
+
+ Remove-Item -Path $ChartImage.FullName
+
+ return $Base64Image
+
+} # end
+
+function Get-ColumnChart {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to generate PScriboChart column charts.
+ .DESCRIPTION
+ .NOTES
+ Version: 0.1.0
+ Author: Jonathan Colon
+ .EXAMPLE
+ .LINK
+ #>
+ [CmdletBinding()]
+ [OutputType([System.String])]
+ param
+ (
+ [Parameter (
+ Position = 0,
+ Mandatory)]
+ [System.Array]
+ $SampleData,
+ [String]
+ $ChartName,
+ [String]
+ $AxisXTitle,
+ [String]
+ $AxisYTitle,
+ [String]
+ $XField,
+ [String]
+ $YField,
+ [String]
+ $ChartAreaName,
+ [String]
+ $ChartTitleName = ' ',
+ [String]
+ $ChartTitleText = ' ',
+ [int]
+ $Width = 600,
+ [int]
+ $Height = 400,
+ [Switch]
+ $Status,
+ [bool]
+ $ReversePalette = $false
+ )
+
+ $StatusCustomPalette = @(
+ [System.Drawing.ColorTranslator]::FromHtml('#DFF0D0')
+ [System.Drawing.ColorTranslator]::FromHtml('#FFF4C7')
+ [System.Drawing.ColorTranslator]::FromHtml('#FEDDD7')
+ [System.Drawing.ColorTranslator]::FromHtml('#878787')
+ )
+
+ $AbrCustomPalette = @(
+ [System.Drawing.ColorTranslator]::FromHtml('#d5e2ff')
+ [System.Drawing.ColorTranslator]::FromHtml('#bbc9e9')
+ [System.Drawing.ColorTranslator]::FromHtml('#a2b1d3')
+ [System.Drawing.ColorTranslator]::FromHtml('#8999bd')
+ [System.Drawing.ColorTranslator]::FromHtml('#7082a8')
+ [System.Drawing.ColorTranslator]::FromHtml('#586c93')
+ [System.Drawing.ColorTranslator]::FromHtml('#40567f')
+ [System.Drawing.ColorTranslator]::FromHtml('#27416b')
+ [System.Drawing.ColorTranslator]::FromHtml('#072e58')
+ )
+
+ $VeeamCustomPalette = @(
+ [System.Drawing.ColorTranslator]::FromHtml('#ddf6ed')
+ [System.Drawing.ColorTranslator]::FromHtml('#c3e2d7')
+ [System.Drawing.ColorTranslator]::FromHtml('#aacec2')
+ [System.Drawing.ColorTranslator]::FromHtml('#90bbad')
+ [System.Drawing.ColorTranslator]::FromHtml('#77a898')
+ [System.Drawing.ColorTranslator]::FromHtml('#5e9584')
+ [System.Drawing.ColorTranslator]::FromHtml('#458370')
+ [System.Drawing.ColorTranslator]::FromHtml('#2a715d')
+ [System.Drawing.ColorTranslator]::FromHtml('#005f4b')
+ )
+
+ if ($Options.ReportStyle -eq "Veeam") {
+ $BorderColor = 'DarkGreen'
+ } else {
+ $BorderColor = 'DarkBlue'
+ }
+
+ $exampleChart = New-Chart -Name $ChartName -Width $Width -Height $Height -BorderStyle Dash -BorderWidth 1 -BorderColor $BorderColor
+
+ $addChartAreaParams = @{
+ Chart = $exampleChart
+ Name = $ChartAreaName
+ AxisXTitle = $AxisXTitle
+ AxisYTitle = $AxisYTitle
+ NoAxisXMajorGridLines = $true
+ NoAxisYMajorGridLines = $true
+ AxisXInterval = 1
+ }
+ $exampleChartArea = Add-ChartArea @addChartAreaParams -PassThru
+
+ if ($Status) {
+ $CustomPalette = $StatusCustomPalette
+ } elseif ($Options.ReportStyle -eq 'Veeam') {
+ $CustomPalette = $VeeamCustomPalette
+
+ } else {
+ $CustomPalette = $AbrCustomPalette
+ }
+
+ $addChartSeriesParams = @{
+ Chart = $exampleChart
+ ChartArea = $exampleChartArea
+ Name = 'exampleChartSeries'
+ XField = $XField
+ YField = $YField
+ CustomPalette = $CustomPalette
+ ColorPerDataPoint = $true
+ ReversePalette = $ReversePalette
+ }
+
+ $sampleData | Add-ColumnChartSeries @addChartSeriesParams
+
+ $addChartTitleParams = @{
+ Chart = $exampleChart
+ ChartArea = $exampleChartArea
+ Name = $ChartTitleName
+ Text = $ChartTitleText
+ Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList @('Segoe Ui', '12', [System.Drawing.FontStyle]::Bold)
+ }
+ Add-ChartTitle @addChartTitleParams
+
+ $TempPath = Resolve-Path ([System.IO.Path]::GetTempPath())
+
+ $ChartImage = Export-Chart -Chart $exampleChart -Path $TempPath.Path -Format "PNG" -PassThru
+
+ if ($PassThru) {
+ Write-Output -InputObject $chartFileItem
+ }
+
+ $Base64Image = [convert]::ToBase64String((Get-Content $ChartImage -Encoding byte))
+
+ Remove-Item -Path $ChartImage.FullName
+
+ return $Base64Image
+
+} # end
+
+function Get-WindowsTimePeriod {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to generate time period table.
+ .DESCRIPTION
+ .NOTES
+ Version: 0.1.0
+ Author: Jonathan Colon
+ .EXAMPLE
+ .LINK
+ #>
+ [CmdletBinding()]
+ param
+ (
+ [Parameter (
+ Position = 0,
+ Mandatory)]
+ [System.Array]
+ $InputTimePeriod
+ )
+
+ $OutObj = @()
+ $Hours24 = [ordered]@{
+ 0 = 12
+ 1 = 1
+ 2 = 2
+ 3 = 3
+ 4 = 4
+ 5 = 5
+ 6 = 6
+ 7 = 7
+ 8 = 8
+ 9 = 9
+ 10 = 10
+ 11 = 11
+ 12 = 12
+ 13 = 1
+ 14 = 2
+ 15 = 3
+ 16 = 4
+ 17 = 5
+ 18 = 6
+ 19 = 7
+ 20 = 8
+ 21 = 9
+ 22 = 10
+ 23 = 11
+ }
+ $ScheduleTimePeriod = $InputTimePeriod -split '(.{48})' | Where-Object { $_ }
+
+ foreach ($OBJ in $Hours24.GetEnumerator()) {
+
+ $inObj = [ordered] @{
+ 'H' = $OBJ.Value
+ 'Sun' = $ScheduleTimePeriod[0].Split(',')[$OBJ.Key]
+ 'Mon' = $ScheduleTimePeriod[1].Split(',')[$OBJ.Key]
+ 'Tue' = $ScheduleTimePeriod[2].Split(',')[$OBJ.Key]
+ 'Wed' = $ScheduleTimePeriod[3].Split(',')[$OBJ.Key]
+ 'Thu' = $ScheduleTimePeriod[4].Split(',')[$OBJ.Key]
+ 'Fri' = $ScheduleTimePeriod[5].Split(',')[$OBJ.Key]
+ 'Sat' = $ScheduleTimePeriod[6].Split(',')[$OBJ.Key]
+ }
+ $OutObj += $inobj
+ }
+
+ return $OutObj
+
+} # end
+
+# Variable translating Icon to Image Path ($IconPath)
+$script:Images = @{
+}
+
+function ConvertTo-HashToYN {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to convert array content true or false automatically to Yes or No.
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.2.0
+ Author: Jonathan Colon
+
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
+ param (
+ [Parameter (Position = 0, Mandatory)]
+ [AllowEmptyString()]
+ [System.Collections.Specialized.OrderedDictionary] $TEXT
+ )
+
+ $result = [ordered] @{}
+ foreach ($i in $TEXT.GetEnumerator()) {
+ try {
+ $result.add($i.Key, (ConvertTo-TextYN $i.Value))
+ } catch {
+ $result.add($i.Key, ($i.Value))
+ }
+ }
+ if ($result) {
+ return $result
+ } else { return $TEXT }
+} # end
\ No newline at end of file
diff --git a/Src/Public/Invoke-AsBuiltReport.Microsoft.SCVMM.ps1 b/Src/Public/Invoke-AsBuiltReport.Microsoft.SCVMM.ps1
index 52bb047..2b304d5 100644
--- a/Src/Public/Invoke-AsBuiltReport.Microsoft.SCVMM.ps1
+++ b/Src/Public/Invoke-AsBuiltReport.Microsoft.SCVMM.ps1
@@ -5,22 +5,65 @@ function Invoke-AsBuiltReport.Microsoft.SCVMM {
.DESCRIPTION
Documents the configuration of Microsoft SCVMM in Word/HTML/Text formats using PScribo.
.NOTES
- Version: 0.1.0
- Author: Andrew Ramsay
- Twitter:
- Github:
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: https://github.com/AsBuiltReport
Credits: Iain Brighton (@iainbrighton) - PScribo module
.LINK
https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.SCVMM
#>
- # Do not remove or add to these parameters
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Scope = "Function")]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "", Scope = "Function")]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Scope = "Function")]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope = "Function")]
+
+ # Do not remove or add to these parameters
param (
[String[]] $Target,
[PSCredential] $Credential
)
+ #Requires -Version 5.1
+ #Requires -PSEdition Desktop
+ #Requires -RunAsAdministrator
+
+ if ($psISE) {
+ Write-Error -Message "You cannot run this script inside the PowerShell ISE. Please execute it from the PowerShell Command Window."
+ break
+ }
+
+ Get-AbrVmmRequiredModule -Name 'VirtualMachineManager' -Version '1.0'
+
+ Write-Host "- Please refer to the AsBuiltReport.Microsoft.SCVMM github website for more detailed information about this project."
+ Write-Host "- Do not forget to update your report configuration file after each new version release."
+ Write-Host "- Documentation: https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.SCVMM"
+ Write-Host "- Issues or bug reporting: https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.SCVMM/issues"
+ Write-Host "- This project is community maintained and has no sponsorship from Microsoft, its employees or any of its affiliates."
+
+
+ # Check the version of the dependency modules
+ $ModuleArray = @('AsBuiltReport.Microsoft.SCVMM', 'Diagrammer.Core')
+
+ foreach ($Module in $ModuleArray) {
+ try {
+ $InstalledVersion = Get-Module -ListAvailable -Name $Module -ErrorAction SilentlyContinue | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version
+
+ if ($InstalledVersion) {
+ Write-Host "- $Module module v$($InstalledVersion.ToString()) is currently installed."
+ $LatestVersion = Find-Module -Name $Module -Repository PSGallery -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Version
+ if ($InstalledVersion -lt $LatestVersion) {
+ Write-Host " - $Module module v$($LatestVersion.ToString()) is available." -ForegroundColor Red
+ Write-Host " - Run 'Update-Module -Name $Module -Force' to install the latest version." -ForegroundColor Red
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $_.Exception.Message
+ }
+ }
+
# Import Report Configuration
$Report = $ReportConfig.Report
$InfoLevel = $ReportConfig.InfoLevel
@@ -29,844 +72,41 @@ function Invoke-AsBuiltReport.Microsoft.SCVMM {
# Used to set values to TitleCase where required
$TextInfo = (Get-Culture).TextInfo
- # Update/rename the $VmmServer variable and build out your code within the ForEach loop. The ForEach loop enables AsBuiltReport to generate an as built configuration against multiple defined targets.
-
#region foreach loop
foreach ($Server in $Target) {
- $ConnectVmmServer = Get-SCVMMServer -ComputerName $Server -Credential $Credential
- Write-Verbose "`VMM Server [$($ConnectVmmServer.name)] connection status is [$($ConnectVmmServer.IsConnected)]"
- Section -Style Heading1 'Virtual Machine Manager Server' {
- $VMM = $ConnectVmmServer
- $vmmCim = New-CimSession -ComputerName ($VMM.FQDN) -Credential $Credential
- $VMMFQDN = $VMM.FQDN
+ # Establish initial connection to VMM server
+ Get-AbrVmmServerConnection
- Paragraph "The following section details the configuration of SCVMM server $VMMFQDN."
-
- Section -Style Heading2 $VMMFQDN {
- $VMMServerSettingsReport = [PSCustomObject]@{
- 'Server FQDN' = $VMMFQDN
- 'IP Address' = (Get-NetIPAddress -CimSession $vmmCim -AddressFamily IPv4 | Where-Object {$_.IPAddress -notlike "127.0.0.1"}).IPAddress
- 'Product Version' = $VMM.ProductVersion
- 'Server Port' = $VMM.Port
- 'VM Connect Port' = $VMM.VMConnectDefaultPort
- 'VMM Service Account' = $VMM.VMMServiceAccount
- 'VMM High Availability' = $VMM.IsHighlyAvailable
- }
- $TableParams = @{
- Name = 'VMM Server Settings'
- List = $true
- ColumnWidths = 50,50
- }
- $VMMServerSettingsReport | Table $TableParams
- }
- Section -Style Heading3 'VMM Database Settings' {
- $VMMDBSettingsReport = [PSCustomObject]@{
- 'DB Server Name' = $VMM.DatabaseServerName
- 'DB Instance Name' = $VMM.DatabaseInstanceName
- 'DB Name' = $VMM.DatabaseName
- 'DB Version' = $VMM.DatabaseVersion
- }
- $VMMDBSettingsReport | Table -Name 'VMM DB Settings' -List -ColumnWidths 50,50
- }
- Section -Style Heading3 'VMM AutoNetwork Settings' {
- $VmmAutoNetworkSettingsReport = [PSCustomObject]@{
- 'Logical Network Creation Enabled' = $VMM.AutomaticLogicalNetworkCreationEnabled
- 'Virtual Network Creation Enabled' = $VMM.AutomaticVirtualNetworkCreationEnabled
- 'Logical Network Match' = $VMM.LogicalNetworkMatchOption
- 'Backup Network Match' = $VMM.BackupLogicalNetworkMatchOption
- }
- $VMMAutoNetworkSettingsReport | Table -Name 'VMM Server Settings' -List -ColumnWidths 50,50
- }
+ # Variable translating Icon to Image Path ($IconPath)
+ $script:Images = @{
+ "AsBuiltReport_LOGO" = "AsBuiltReport_Logo.png"
+ "AsBuiltReport_Signature" = "AsBuiltReport_Signature.png"
+ "Abr_LOGO_Footer" = "AsBuiltReport_Signature.png"
+ "Microsoft_Logo" = "Microsoft_Logo.png"
+ "Server" = "server.png"
+ "DB_Server" = "DB_Server.png"
}
- Section -Style Heading1 'VMM Networking' {
- Paragraph 'The following section contains as built for Logical Networks, Logical Switches, Port Profiles and VM Networks'
- Section -Style Heading2 'Logical Networks' {
- $LogicalNetworks = Get-SCLogicalNetworkDefinition
- Paragraph 'The summary for logical networks is as follws'
- $LogicalNetworks | Select-Object Name,IsolationType | Table -Name 'Logical Network Summary'
- foreach($network in $LogicalNetworks){
- Section -Style Heading3 $network.Name {
- $network | Select-Object Name,IsolationType,@{L="HostGroups";E={$_.HostGroups -Join ","}}| Table -Name $network.Name
- }
- Section -Style Heading3 'Vlans and Subnets' {
- $network.SubnetVLans | Select-Object VLanID,Subnet | Table -Name ($network.Name + "VLANs")
- }
- }
- }
- Section -Style Heading2 'VM Networks' {
- Paragraph 'The following section details VM Networks'
- BlankLine
- $VMNetworks = Get-SCVMNetwork
- $VMNetworkReport = @()
- ForEach($VMNetwork in $VMNetworks) {
- $TempVMNetworkReport = [PSCustomObject]@{
- 'Name' = $VMNetwork.Name
- 'Logical Network' = $VMNetwork.LogicalNetwork
- 'VLAN' = $VMNetwork.VMSubnet.SubnetVLans.VLanID
- 'Subnet' = $VMNetwork.VMSubnet.SubnetVLans.Subnet
- 'Isolation Type' = $VMNetwork.IsolationType
- }
- $VMNetworkReport += $TempVMNetworkReport
- }
- $VMNetworkReport | Table -Name 'VM Networks'
- }
- Section -Style Heading2 'Logical Switches'{
- Paragraph 'The following section contains as-built for Logical Switches'
- $LogicalSwitches = Get-SCLogicalSwitch
- if($LogicalSwitches){
- $LogicalSwitchesReport = @()
- ForEach($TempPssSessionwitch in $LogicalSwitches){
- $TempLogicalSwitchesReport = [PSCustomObject]@{
- 'Name' = $TempPssSessionwitch.Name
- 'Uplink Mode' = $TempPssSessionwitch.UplinkMode
- 'Minimum Bandwidth Mode' = $TempPssSessionwitch.MinimumMandwidthMode
- }
- $LogicalSwitchesReport += $TempLogicalSwitchesReport
- }
- $LogicalSwitchesReport | Table -Name 'LogicalSwitches'
- }
- }
- Section -Style Heading2 'Uplink Port Profiles'{
- Paragraph 'The following section contains as-built for Uplink Port Profiles'
- $UplinkPortProfile = Get-SCNativeUplinkPortProfile
- if($UplinkPortProfle){
- $UplinkReport = $UplinkPortProfile | Select-Object Name, `
- @{L='Team Mode'; E={$_.LBFOTeamMode}}, `
- @{L='Load Balance'; E={$_LBFOLoadBalancingAlgorithm}}
- $UplinkReport | Table -Name 'Uplink Port Profiles'
- }
- }
- Section -Style Heading2 'Network Adapter Port Profiles' {
- Paragraph 'The following section details Virtual Network Adapter Port Profiles'
- $VirtualNetworkAdapterPortProfiles = Get-SCVirtualNetworkAdapterNativePortProfile
- if($VirtualNetworkAdapterPortProfiles){
- foreach($adapter in $VirtualNetworkAdapterPortProfiles){
- Section -Style Heading3 ($adapter.Name) {
- Paragraph ($adapter.Description)
- $AdapterReport = [PSCustomObject]@{
- 'Teaming' = $adapter.AllowTeaming
- 'Mac Address Spoofing' = $adapter.AllowMacAddressSpoofing
- 'Ieee Priority Tagging' = $adapter.AllowIeeePriorityTagging
- 'DHCP Guard' = $adapter.EnableDHCPGuard
- 'Guest IP Network Virtualization Updates' = $adapter.EnableGuestIPNetworkVirtualizationUpdates
- 'Router Guard' = $adapter.EnableRouterGuard
- 'Minimum Bandwidth Weight' = $adapter.MinimumBandwidthWeight
- 'Minimum Bandwidth Absolute In Mbps' = $adapter.MinimumBandwidthAbsoluteInMbps
- 'Maximum Bandwidth Absolute In Mbps' = $adapter.MaximumBandwidthAbsoluteInMbps
- 'Enable Vmq' = $adapter.EnableVmq
- 'Enable IPsec Offload' = $adapter.EnableIPsecOffload
- 'Enable Iov' = $adapter.EnableIov
- 'Enable Vrss' = $adapter.EnableVrss
- 'Enable Rdma' = $adapter.EnableRdma
- }
- $AdapterReport | Table -Name ($adapter.'Name') -List -ColumnWidths 50,50
- }
- }
- }
- }
- Section -Style Heading2 'Port Classifications' {
- Paragraph 'The following section details Port Classifications Configured'
- $PortClassifications = Get-SCPortClassification
- $PortClassifications | Select-Object Name, Description | Table -Name 'Port Classifications'
- }
- }
- Section -Style Heading1 'VMM Library and Templates'{
- Paragraph 'The following section details the Library and VM Templates configured'
- Section -Style Heading2 'VMM Library Servers'{
- Paragraph 'The following table is a summary of the VMM Library servers deployed'
- $VMLibrary = Get-SCLibraryServer
- $VMLibrary | Select-Object ComputerName,Description,Status | Table -Name 'VMM Library Servers'
- }
- Section -Style Heading2 'VMM Library Shares'{
- Paragraph 'The following table details the Library Shares Configured'
- $VMLibraryShares = Get-SCLibraryShare
- $VMLibraryShares | Select-Object Name,Description,LibraryServer | Table -Name 'VMM Library Shares'
- }
- Section -Style Heading2 'VMM Templates'{
- Paragraph 'The following table is a summary of VM Templates Deployed'
- $VMTemplates = Get-SCVMTemplate
- if($VMTemplates){
- $VMTemplates | Select-Object Name,OperatingSystem,Description | Table -Name 'VMM Templates' -ColumnWidths 20,20,60
- }
- }
- Section -Style Heading2 'Guest OS Profiles'{
- Paragraph 'The following table is a summary of the Guest OS Profiles Deployed'
- $GuestOSProfiles = Get-SCGuestOsProfile
- $GuestOSProfiles | Select-Object Name,JoinDomain,OSTYpe | Table -Name 'Guest OS Profiles'
- }
- Section -Style Heading2 'Hardware Profiles'{
- Paragraph 'The following table is a summary of deployed Hardware Profiles'
- $HardwareProfiles = Get-SCHardwareProfile
- if($HardwareProfiles){
- $HardwareProfiles | Select-Object Name,CPUCount,Memory,IsHighlyAvailable,SecureBootEnabled | Table -Name 'Hardware Profiles'
- }
- }
- }
- Section -Style Heading1 'Clusters' {
- Paragraph 'The following section details Hyper-V Clusters'
- $ScVmmClusters = Get-SCVMHostCluster
- #$Clusters = Get-Cluster -Name $TempPssSessioncVmmClusters.Name
- Paragraph 'The following table is the summary of the clusters managed by SCVMM'
- $ClusterSummaryReport = @()
- ForEach($ScVmmCluster in $ScVmmClusters){
- $TempReport = [PSCustomObject]@{
- 'Cluster Name' = $ScVmmCluster.Name
- 'Cluster IP' = $ScVmmCluster.IPAddresses -Join ","
- 'Host Group' = $ScVmmCluster.HostGroup
- 'Cluster Nodes' = $ScVmmCluster.Nodes -Join ","
- }
- $ClusterSummaryReport += $TempReport
- }
- $ClusterSummaryReport | Table -Name 'Cluster Summary' -ColumnWidths 20,10,30,40
- Section -Style Heading2 'Cluster Details' {
- ForEach($ScVmmCluster in $ScVmmClusters){
- $Cluster = Get-Cluster -Name $ScVmmCluster.Name
- Section -Style Heading3 $Cluster.Name {
- #region Cluster Settings
- Section -Style Heading4 'Cluster Settings' {
- $ClusterSettingsTable = [PSCustomObject] @{
- 'Add Evict Delay' = $Cluster.AddEvictDelay
- 'Administrative Access Point' = $Cluster.AdministrativeAccessPoint
- 'Auto Assign Node Site' = $Cluster.AutoAssignNodeSite
- 'Auto Balancer Mode' = $Cluster.AutoBalancerMode
- 'Auto Balancer Level' = $Cluster.AutoBalancerLevel
- 'Backup In Progress' = $Cluster.BackupInProgress
- 'Block Cache Size' = $Cluster.BlockCacheSize
- 'Cluster Service Hang Timeout' = $Cluster.ClusSvcHangTimeout
- 'Cluster Service Regroup Stage Timeout' = $Cluster.ClusSvcRegroupStageTimeout
- 'Cluster Service Regroup Tick In Milliseconds' = $Cluster.ClusSvcRegroupTickInMilliseconds
- 'Cluster Enforced AntiAffinity' = $Cluster.ClusterEnforcedAntiAffinity
- 'Cluster Functional Level' = $Cluster.ClusterFunctionalLevel
- 'Cluster Upgrade Version' = $Cluster.ClusterUpgradeVersion
- 'Cluster Group Wait Delay' = $Cluster.ClusterGroupWaitDelay
- 'Cluster Log Level' = $Cluster.ClusterLogLevel
- 'Cluster Log Size' = $Cluster.ClusterLogSize
- 'Cross Site Delay' = $Cluster.CrossSiteDelay
- 'Cross Site Threshold' = $Cluster.CrossSiteThreshold
- 'Cross Subnet Delay' = $Cluster.CCrossSubnetDelay
- 'Cross Subnet Threshold' = $Cluster.CrossSubnetThreshold
- 'Csv Balancer' = $Cluster.CsvBalancer
- 'Database Read Write Mode' = $Cluster.DatabaseReadWriteMode
- 'Default Network Role' = $Cluster.DefaultNetworkRole
- 'Description' = $Cluster.Description
- 'Domain' = $Cluster.Domain
- 'Drain On Shutdown' = $Cluster.DrainOnShutdown
- 'Dump Policy' = $Cluster.DumpPolicy
- 'Dynamic Quorum' = $Cluster.DynamicQuorum
- 'Enable Shared Volumes' = $Cluster.EnableSharedVolumes
- 'Fix Quorum' = $Cluster.FixQuorum
- 'Group Dependency Timeout' = $Cluster.GroupDependencyTimeout
- 'Hang Recovery Action' = $Cluster.HangRecoveryAction
- 'Ignore Persistent State On Startup' = $Cluster.IgnorePersistentStateOnStartup
- 'Log Resource Controls' = $Cluster.LogResourceControls
- 'Lower Quorum Priority Node Id' = $Cluster.LowerQuorumPriorityNodeId
- 'Message Buffer Length' = $Cluster.MessageBufferLength
- 'Minimum Never Preempt Priority' = $Cluster.MinimumNeverPreemptPriority
- 'Minimum Preemptor Priority' = $Cluster.MinimumPreemptorPriority
- 'Name' = $Cluster.Name
- 'Net ft IPSec Enabled' = $Cluster.NetftIPSecEnabled
- 'Placement Options' = $Cluster.PlacementOptions
- 'Plumb All Cross Subnet Routes' = $Cluster.PlumbAllCrossSubnetRoutes
- 'Preferred Site' = $Cluster.PreferredSite
- 'Prevent Quorum' = $Cluster.PreventQuorum
- 'Quarantine Duration' = $Cluster.QuarantineDuration
- 'Quarantine Threshold' = $Cluster.QuarantineThreshold
- 'Quorum Arbitration Time Max' = $Cluster.QuorumArbitrationTimeMax
- 'Recent Events Reset Time' = $Cluster.RecentEventsResetTime
- 'Request Reply Timeout' = $Cluster.RequestReplyTimeout
- 'Resiliency Default Period' = $Cluster.ResiliencyDefaultPeriod
- 'Resiliency Level' = $Cluster.ResiliencyLevel
- 'Route History Length' = $Cluster.RouteHistoryLength
- 'Same Subnet Delay' = $Cluster.SameSubnetDelay
- 'Same Subnet Threshold' = $Cluster.SameSubnetThreshold
- 'Security Level' = $Cluster.SecurityLevel
- 'Shared Volume Compatible Filters' = $Cluster.SharedVolumeCompatibleFilters
- 'Shared Volume Incompatible Filters' = $Cluster.SharedVolumeIncompatibleFilters
- 'Shared Volume Security Descriptor' = $Cluster.SharedVolumeSecurityDescriptor
- 'Shared Volumes Root' = $Cluster.SharedVolumesRoot
- 'Shared Volume VssWriter Operation Timeout' = $Cluster.SharedVolumeVssWriterOperationTimeout
- 'Shutdown Timeout In Minutes' = $Cluster.ShutdownTimeoutInMinutes
- 'Use Client Access Networks For Shared Volumes' = $Cluster.UseClientAccessNetworksForSharedVolumes
- 'Witness Database Write Timeout' = $Cluster.WitnessDatabaseWriteTimeout
- 'Witness Dynamic Weight' = $Cluster.WitnessDynamicWeight
- 'Witness Restart Interval' = $Cluster.WitnessRestartInterval
- }
- $ClusterSettingsTable | Table -Name 'Cluster Settings' -List -ColumnWidths 50,50
- }
- #end region Cluster Settings
- #Cluster Nodes
- Section -Style Heading4 'Cluster Nodes' {
- Paragraph 'The following Nodes are members of the cluster'
- $ClusterNodes = $Cluster | Get-ClusterNode
- $ClusterNodeReport = @()
- foreach($ClusterNode in $ClusterNodes){
- $Temp = [PSCustomObject] @{
- 'Name' = $ClusterNode.Name
- 'Status Information' = $ClusterNode.StatusInformation
- 'Node Weight' = $ClusterNode.NodeWeight
- 'Model' = $ClusterNode.Model
- 'Manufacturer' = $ClusterNode.Manufacturer
- 'Serial Number' = $ClusterNode.SerialNumber
- }
- $ClusterNodeReport += $Temp
- }
- $ClusterNodeReport | Table -Name 'Cluster Nodes'
- }
- #Cluster Quorum
- Section -Style Heading4 'Cluster Quorum' {
- Paragraph 'The following Cluster Quorum Settings are applied'
- $ClusterQuorum = $Cluster | Get-ClusterQuorum
- $QuorumReport = [PSCustomObject] @{
- 'Name' = $ClusterQuorum.QuorumResource.Name
- 'State' = $ClusterQuorum.QuorumResource.State
- 'Owner Node' = $ClusterQuorum.QuorumResource.OwnerNode
- 'ResourceType' = $ClusterQuorum.QuorumResource.ResourceType
- }
- $QuorumReport | Table -Name 'Cluster Quorum Settings' -List -ColumnWidths 50,50
- }
- #Cluster Networks
- Section -Style Heading4 'Cluster Networks'{
- Paragraph 'The following Cluster Networks are configured'
- $ClusterNetworks = $Cluster | Get-ClusterNetwork
- $ClusterNetworkReport = @()
- foreach($ClusterNetwork in $ClusterNetworks){
- $TempClusterNetwork = [PSCustomObject]@{
- 'Name' = $ClusterNetwork.Name
- 'Description' = $ClusterNetwork.Description
- 'Role' = $ClusterNetwork.Role
- 'Network Address' = $ClusterNetwork.Address
- 'State' = $ClusterNetwork.State
- }
- $ClusterNetworkReport += $TempClusterNetwork
- }
- $ClusterNetworkReport | Table -Name 'Cluster Networks'
- #Cluster Network Interfaces
- Section -Style Heading5 'Cluster Network Interfaces'{
- Paragraph 'The following table details the network interfaces nodes use in the cluster'
- $ClusterNetworkInterfaces = $Cluster | Get-ClusterNetworkInterface
- $ClusterInterfaceReport = @()
- foreach($Interface in $ClusterNetworkInterfaces){
- $TempInterfaceReport = [PSCustomObject]@{
- 'Node' = $Interface.Node
- 'Address' = $Interface.Address
- 'Network' = $Interface.Network
- 'Interface Name' = $Interface.Name
- }
- $ClusterInterfaceReport += $TempInterfaceReport
- }
- $ClusterInterfaceReport | Table -Name 'Cluster Interfaces'
- }
- }
- #Cluster Storage
- Section -Style Heading4 'Cluster Shared Volumes'{
- Paragraph 'The following Cluster Shared Volumes are Configure'
- $ClusterVolumes = $Cluster | Get-ClusterSharedVolume
- if($ClusterVolumes){
- $ClusterVolumeReport = @()
- foreach($ClusterVolume in $ClusterVolumes){
- $TempClusterVolume = [PSCustomObject] @{
- 'Name' = $ClusterVolume.Name
- 'State' = $ClusterVolume.State
- 'File System Type' = $ClusterVolume.SharedVolumeInfo.Partition.FileSystem
- 'Volume Capacity(GB)' = [Math]::Round(($ClusterVolume.SharedVolumeInfo.Partition.Size)/1gb)
- 'Free Fapacity(GB)' = [Math]::Round(($ClusterVolume.SharedVolumeInfo.Partition.FreeSpace)/1gb)
- }
- $ClusterVolumeReport += $TempClusterVolume
- }
- $ClusterVolumeReport | Table -Name 'Cluster Volumes'
- }
- }
- #Cluster Hyper-V Replica Broker
- }
- }
- }
- }
- Section -Style Heading1 'Hyper-V Hosts'{
- Paragraph 'The following table details the Hyper-V hosts'
- $VMHosts = Get-SCVMHost -VMMServer $ConnectVmmServer | Sort-Object Name
- $VMHostSummary = $VMHosts | Select-Object ComputerName,OperatingSystem,VMHostGroup
- $VMHostSummary | Table -Name 'VM Host Summary'
- #Host Summary
- ForEach($VMHost in $VMHosts){
- #Create Remote Sessions
- $TempPssSession = New-PSSession $VMHost.Name -Credential $Credential
- $TempCimSession = New-CimSession $VMHost.Name -Credential $Credential
- #Get Server Data using WinRM
- $HostInfo = Invoke-Command -Session $TempPssSession {Get-ComputerInfo}
- $HostCPU = Get-CimInstance -CimSession $TempCimSession -ClassName Win32_Processor
- $HostComputer = Get-CimInstance -CimSession $TempCimSession -ClassName Win32_ComputerSystem
- $HostBIOS = Get-CimInstance -CimSession $TempCimSession -ClassName Win32_Bios
- $HostLicense = Get-CimInstance -CimSession $TempCimSession -query 'Select * from SoftwareLicensingProduct'| Where-Object {$_.LicenseStatus -eq 1}
- $HotFixes = Get-CimInstance -CimSession $TempCimSession -ClassName Win32_QuickFixEngineering
- Section -Style Heading2 ($VMHost.Name){
- #Host Hardware
- Section -Style Heading3 'Host Hardware Settings'{
- Paragraph 'The following section details hardware settings for the host'
- $HostHardware = [PSCustomObject] @{
- 'Manufacturer' = $HostComputer.Manufacturer
- 'Model' = $HostComputer.Model
- 'Product ID' = $HostComputer.SystemSKUNumber
- 'Serial Number' = $HostBIOS.SerialNumber
- 'BIOS Version' = $HostBIOS.Version
- 'Processor Manufacturer' = $HostCPU[0].Manufacturer
- 'Processor Model' = $HostCPU[0].Name
- 'Number of Processors' = $HostCPU.Length
- 'Number of CPU Cores' = $HostCPU[0].NumberOfCores
- 'Number of Logical Cores' = $HostCPU[0].NumberOfLogicalProcessors
- 'Physical Memory (GB)' = [Math]::Round($HostComputer.TotalPhysicalMemory/1Gb)
- }
- $HostHardware | Table -Name 'Host Hardware Specifications' -List -ColumnWidths 50,50
- }
- #Host OS
- Section -Style Heading3 'Host OS' {
- Paragraph 'The following settings details host OS Settings'
- Section -Style Heading4 'OS Configuration'{
- Paragraph 'The following section details hos OS configuration'
- $HostOSReport = [PSCustomObject] @{
- 'Windows Product Name' = $HostInfo.WindowsProductName
- 'Windows Version' = $HostInfo.WindowsCurrentVersion
- 'Windows Build Number' = $HostInfo.OsVersion
- 'Windows Install Type' = $HostInfo.WindowsInstallationType
- 'AD Domain' = $HostInfo.CsDomain
- 'Windows Installation Date' = $HostInfo.OsInstallDate
- 'Time Zone' = $HostInfo.TimeZone
- 'License Type' = $HostLicense.ProductKeyChannel
- 'Partial Product Key' = $HostLicense.PartialProductKey
- }
- $HostOSReport | Table -Name 'Host OS Settings' -List -ColumnWidths 50,50
- }
- Section -Style Heading4 'Host Hotfixes'{
- Paragraph 'The following table details the OS Hotfixes installed'
- $HotFixReport = @()
- Foreach($HotFix in $HotFixes){
- $TempHotFix = [PSCustomObject] @{
- 'Hotfix ID' = $HotFix.HotFixID
- 'Description' = $HotFix.Description
- 'Installation Date' = $HotFix.InstalledOn
- }
- $HotFixReport += $TempHotFix
- }
- $HotFixReport | Table -Name 'HostFixes Installed' -ColumnWidths 10,70,20
- }
- Section -Style Heading4 'Host Drivers'{
- Paragraph 'The following section details host drivers'
- Invoke-Command -Session $TempPssSession {Import-Module DISM}
- $HostDriversList = Invoke-Command -Session $TempPssSession {Get-WindowsDriver -Online}
- $HostDriverReport = @()
- ForEach($HostDriver in $HostDriversList){
- $TempDriver = [PSCustomObject] @{
- 'Class Description' = $HostDriver.ClassDescription
- 'Provider Name' = $HostDriver.ProviderName
- 'Driver Version' = $HostDriver.Version
- 'Version Date' = $HostDriver.Date
- }
- $HostDriverReport += $TempDriver
- }
- $HostDriverReport | Table -Name 'Host Drivers' -ColumnWidths 30,30,20,20
- }
- #Host Roles and Features
- Section -Style Heading4 'Roles and Features' {
- Paragraph 'The following settings details host roles and features installed'
- $HostRolesAndFeatures = Get-WindowsFeature -ComputerName $VMHost.Name -Credential $Credential | Where-Object {$_.Installed -eq $True}
- [array]$HostRolesAndFeaturesReport = @()
- ForEach($HostRoleAndFeature in $HostRolesAndFeatures){
- $TempHostRolesAndFeaturesReport = [PSCustomObject] @{
- 'Feature Name' = $HostRoleAndFeature.DisplayName
- 'Feature Type' = $HostRoleAndFeature.FeatureType
- 'Description' = $HostRoleAndFeature.Description
- }
- $HostRolesAndFeaturesReport += $TempHostRolesAndFeaturesReport
- }
- $HostRolesAndFeaturesReport | Table -Name 'Roles and Features' -ColumnWidths 20,10,70
- }
- #Host 3rd Party Applications
- Section -Style Heading4 'Installed Applications' {
- Paragraph 'The following settings details applications listed in Add/Remove Programs'
- [array]$AddRemove = @()
- $AddRemove += Invoke-Command -Session $TempPssSession {Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*}
- $AddRemove += Invoke-Command -Session $TempPssSession {Get-ItemProperty HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*}
- [array]$AddRemoveReport = @()
- ForEach($App in $AddRemove){
- $TempAddRemoveReport = [PSCustomObject]@{
- 'Application Name' = $App.DisplayName
- 'Publisher' = $App.Publisher
- 'Version' = $App.Version
- 'Install Date' = $App.InstallDate
- }
- $AddRemoveReport += $TempAddRemoveReport
- }
- $AddRemoveReport | Where-Object {$_.'Application Name' -notlike $null} | Sort-Object 'Application Name' | Table -Name 'Installed Applications'
- }
- }
- #Local Users and Groups
- Section -Style Heading3 'Local Users and Groups'{
- Paragraph 'The following section details local users and groups configured'
- Section -Style Heading4 'Local Users'{
- Paragraph 'The following table details local users'
- $LocalUsers = Invoke-Command -Session $TempPssSession {Get-LocalUser}
- $LocalUsersReport = @()
- ForEach($LocalUser in $LocalUsers){
- $TempLocalUsersReport = [PSCustomObject]@{
- 'User Name' = $LocalUser.Name
- 'Description' = $LocalUser.Description
- 'Account Enabled' = $LocalUser.Enabled
- 'Last Logon Date' = $LocalUser.LastLogon
- }
- $LocalUsersReport += $TempLocalUsersReport
- }
- $LocalUsersReport | Table -Name 'Local Users' -ColumnWidths 20,40,10,30
- }
- Section -Style Heading4 'Local Groups'{
- Paragraph 'The following table details local groups configured'
- $LocalGroups = Invoke-Command -Session $TempPssSession {Get-LocalGroup}
- $LocalGroupsReport = @()
- ForEach($LocalGroup in $LocalGroups){
- $TempLocalGroupsReport = [PSCustomObject]@{
- 'Group Name' = $LocalGroup.Name
- 'Description' = $LocalGroup.Description
- }
- $LocalGroupsReport += $TempLocalGroupsReport
- }
- $LocalGroupsReport | Table -Name 'Local Group Summary'
- }
- Section -Style Heading4 'Local Administrators'{
- Paragraph 'The following table lists Local Administrators'
- $LocalAdmins = Invoke-Command -Session $TempPssSession {Get-LocalGroupMember -Name 'Administrators'}
- $LocalAdminsReport = @()
- ForEach($LocalAdmin in $LocalAdmins){
- $TempLocalAdminsReport = [PSCustomObject]@{
- 'Account Name' = $LocalAdmin.Name
- 'Account Type' = $LocalAdmin.ObjectClass
- 'Account Source' = $LocalAdmin.PrincipalSource
- }
- $LocalAdminsReport += $TempLocalAdminsReport
- }
- $LocalAdminsReport | Table -Name 'Local Administrators'
- }
- }
- #Host Firewall
- Section -Style Heading3 'Windows Firewall'{
- Paragraph 'The Following table is a the Windowss Firewall Summary'
- $NetFirewallProfile = Get-NetFirewallProfile -CimSession $TempCimSession
- $NetFirewallProfileReport = @()
- Foreach($FirewallProfile in $NetFireWallProfile){
- $TempNetFirewallProfileReport = [PSCustomObject]@{
- 'Profile' = $FirewallProfile.Name
- 'Profile Enabled' = $FirewallProfile.Enabled
- 'Inbound Action' = $FirewallProfile.DefaultInboundAction
- 'Outbound Action' = $FirewallProfile.DefaultOutboundAction
- }
- $NetFirewallProfileReport += $TempNetFirewallProfileReport
- }
- $NetFirewallProfileReport | Table -Name 'Windows Firewall Profiles'
- }
- #Host Networking
- Section -Style Heading3 'Host Networking'{
- Paragraph 'The following section details Host Network Configuration'
- Section -Style Heading4 'Network Adapters'{
- Paragraph 'The Following table details host network adapters'
- $HostAdapters = Invoke-Command -Session $TempPssSession {Get-NetAdapter}
- $HostAdaptersReport = @()
- ForEach($HostAdapter in $HostAdapters){
- $TempHostAdaptersReport = [PSCustomObject]@{
- 'Adapter Name' = $HostAdapter.Name
- 'Adapter Description' = $HostAdapter.InterfaceDescription
- 'Mac Address' = $HostAdapter.MacAddress
- 'Link Speed' = $HostAdapter.LinkSpeed
- }
- $HostAdaptersReport += $TempHostAdaptersReport
- }
- $HostAdaptersReport | Table -Name 'Network Adapters' -ColumnWidths 20,40,20,20
- }
- Section -Style Heading4 'IP Addresses'{
- Paragraph 'The following table details IP Addresses assigned to hosts'
- $NetIPs = Invoke-Command -Session $TempPssSession {Get-NetIPConfiguration | Where-Object -FilterScript {($_.NetAdapter.Status -Eq "Up")}}
- $NetIpsReport = @()
- ForEach($NetIp in $NetIps){
- $TempNetIpsReport = [PSCustomObject]@{
- 'Interface Name' = $NetIp.InterfaceAlias
- 'Interface Description' = $NetIp.InterfaceDescription
- 'IPv4 Addresses' = $NetIp.IPv4Address -Join ","
- 'Subnet Mask' = $NetIp.IPv4Address[0].PrefixLength
- 'IPv4 Gateway' = $NetIp.IPv4DefaultGateway.NextHop
- }
- $NetIpsReport += $TempNetIpsReport
- }
- $NetIpsReport | Table -Name 'Net IP Addresses'
- }
- Section -Style Heading4 'DNS Client'{
- Paragraph 'The following table details the DNS Seach Domains'
- $DnsClient = Invoke-Command -Session $TempPssSession {Get-DnsClientGlobalSetting}
- $DnsClientReport = [PSCustomObject]@{
- 'DNS Suffix' = $DnsClient.SuffixSearchList -Join ","
- }
- $DnsClientReport | Table -Name "DNS Seach Domain"
- }
- Section -Style Heading4 'DNS Servers'{
- Paragraph 'The following table details the DNS Server Addresses Configured'
- $DnsServers = Invoke-Command -Session $TempPssSession {Get-DnsClientServerAddress -AddressFamily IPv4 | `
- Where-Object {$_.ServerAddresses -notlike $null -and $_.InterfaceAlias -notlike "*isatap*"}}
- $DnsServerReport = @()
- ForEach($DnsServer in $DnsServers){
- $TempDnsServerReport = [PSCustomObject]@{
- 'Interface' = $DnsServer.InterfaceAlias
- 'Server Address' = $DnsServer.ServerAddresses -Join ","
- }
- $DnsServerReport += $TempDnsServerReport
- }
- $DnsServerReport | Table -Name 'DNS Server Addresses' -ColumnWidths 40,60
- }
- $NetworkTeamCheck = Invoke-Command -Session $TempPssSession {Get-NetLbfoTeam}
- if($NetworkTeamCheck){
- Section -Style Heading4 'Network Team Interfaces'{
- Paragraph 'The following table details Network Team Interfaces'
- $NetTeams = Invoke-Command -Session $TempPssSession {Get-NetLbfoTeam}
- $NetTeamReport = @()
- ForEach($NetTeam in $NetTeams){
- $TempNetTeamReport = [PSCustomObject]@{
- 'Team Name' = $NetTeam.Name
- 'Team Mode' = $NetTeam.tm
- 'Load Balancing' = $NetTeam.lba
- 'Network Adapters' = $NetTeam.Members -Join ","
- }
- $NetTeamReport += $TempNetTeamReport
- }
- $NetTeamReport | Table -Name 'Network Team Interfaces'
- }
- }
- Section -Style Heading4 'Network Adapter MTU'{
- Paragraph 'The following table lists Network Adapter MTU settings'
- $NetMtus = Invoke-Command -Session $TempPssSession {Get-NetAdapterAdvancedProperty | Where-Object {$_.DisplayName -eq 'Jumbo Packet'}}
- $NetMtuReport = @()
- ForEach($NetMtu in $NetMtus){
- $TempNetMtuReport = [PSCustomObject]@{
- 'Adapter Name' = $NetMtu.Name
- 'MTU Size' = $NetMtu.DisplayValue
- }
- $NetMtuReport += $TempNetMtuReport
- }
- $NetMtuReport | Table -Name 'Network Adapter MTU' -ColumnWidths 50,50
- }
- }
- #Host Storage
- Section -Style Heading3 'Host Storage'{
- Paragraph 'The following section details the storage configuration of the host'
- #Local Disks
- Section -Style Heading4 'Local Disks'{
- Paragraph 'The following table details physical disks installed in the host'
- $HostDisks = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-Disk | Where-Object -FilterScript {$_.BusType -Eq "RAID" -or $_.BusType -eq "File Backed Virtual" -or $_.BusType -eq "SATA" -or $_.BusType -eq "USB"}}
- $LocalDiskReport = @()
- ForEach($Disk in $HostDisks){
- $TempLocalDiskReport = [PSCustomObject]@{
- 'Disk Number' = $Disk.Number
- 'Model' = $Disk.Model
- 'Serial Number' = $Disk.SerialNumber
- 'Partition Style' = $Disk.PartitionStyle
- 'Disk Size(GB)' = [Math]::Round($Disk.Size/1Gb)
- }
- $LocalDiskReport += $TempLocalDiskReport
- }
- $LocalDiskReport | Sort-Object -Property 'Disk Number' | Table -Name 'Local Disks'
- }
- #SAN Disks
- $SanDisks = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-Disk | Where-Object {$_.BusType -Eq "iSCSI"}}
- if($SanDisks){
- Section -Style Heading4 'SAN Disks'{
- Paragraph 'The following section details SAN disks connected to the host'
- $SanDiskReport = @()
- ForEach($Disk in $SanDisks){
- $TempSanDiskReport = [PSCustomObject]@{
- 'Disk Number' = $Disk.Number
- 'Model' = $Disk.Model
- 'Serial Number' = $Disk.SerialNumber
- 'Partition Style' = $Disk.PartitionStyle
- 'Disk Size(GB)' = [Math]::Round($Disk.Size/1Gb)
- }
- $SanDiskReport += $TempSanDiskReport
- }
- $SanDiskReport | Sort-Object -Property 'Disk Number' | Table -Name 'Local Disks'
- }
- }
- #Local Volumes
- Section -Style Heading4 'Host Volumes'{
- Paragraph 'The following section details local volumes on the host'
- $HostVolumes = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-Volume}
- $HostVolumeReport = @()
- ForEach($HostVolume in $HostVolumes){
- $TempHostVolumeReport = [PSCustomObject]@{
- 'Drive Letter' = $HostVolume.DriveLetter
- 'File System Label' = $HostVolume.FileSystemLabel
- 'File System' = $HostVolume.FileSystem
- 'Size (GB)' = [Math]::Round($HostVolume.Size/1gb)
- 'Free Space(GB)' = [Math]::Round($HostVolume.SizeRemaining/1gb)
- }
- $HostVolumeReport += $TempHostVolumeReport
- }
- $HostVolumeReport | Sort-Object 'Drive Letter' | Table -Name 'Host Volumes'
- }
- #iSCSI Configuration
- $iSCSICheck = Invoke-Command -Session $TempPssSession {Get-Service -Name 'MSiSCSI'}
- if($iSCSICheck.Status -eq 'Running'){
- Section -Style Heading4 'Host iSCSI Settings'{
- Paragraph 'The following section details the iSCSI configuration for the host'
- $HostInitiator = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-InitiatorPort}
- Paragraph 'The following table details the hosts iSCI IQN'
- $HostInitiator | Select-Object NodeAddress | Table -Name 'Host IQN'
- Section -Style Heading5 'iSCSI Target Server'{
- Paragraph 'The following table details iSCSI Target Server details'
- $HostIscsiTargetServer = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-IscsiTargetPortal}
- $HostIscsiTargetServer | Select-Object TargetPortalAddress,TargetPortalPortNumber | Table -Name 'iSCSI Target Servers' -ColumnWidths 50,50
- }
- Section -Style Heading5 'iSCIS Target Volumes'{
- Paragraph 'The following table details iSCSI target volumes'
- $HostIscsiTargetVolumes = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-IscsiTarget}
- $HostIscsiTargetVolumeReport = @()
- ForEach($HostIscsiTargetVolume in $HostIscsiTargetVolumes){
- $TempHostIscsiTargetVolumeReport = [PSCustomObject]@{
- 'Node Address' = $HostIscsiTargetVolume.NodeAddress
- 'Node Connected' = $HostIscsiTargetVolume.IsConnected
- }
- $HostIscsiTargetVolumeReport += $TempHostIscsiTargetVolumeReport
- }
- $HostIscsiTargetVolumeReport | Table -Name 'iSCSI Target Volumes' -ColumnWidths 80,20
- }
- Section -Style Heading5 'iSCSI Connections'{
- Paragraph 'The following table details iSCSI Connections'
- $HostIscsiConnections = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-IscsiConnection}
- $HostIscsiConnections | Select-Object ConnectionIdentifier,InitiatorAddress,TargetAddress | Table -Name 'iSCSI Connections'
- }
- }
- }
- #MPIO Configuration
- $MPIOInstalledCheck = Invoke-Command -Session $TempPssSession {Get-WindowsFeature | Where-Object {$_.Name -like "Multipath*"}}
- if($MPIOInstalledCheck.InstallState -eq "Installed"){
- Section -Style Heading4 'Host MPIO Settings'{
- Paragraph 'The following section details host MPIO Settings'
- [string]$MpioLoadBalance = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-MSDSMGlobalDefaultLoadBalancePolicy}
- Paragraph "The default load balancing policy is: $MpioLoadBalance"
- Section -Style Heading5 'Multipath I/O AutoClaim'{
- Paragraph 'The Following table details the BUS types MPIO will automatically claim for'
- $MpioAutoClaim = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-MSDSMAutomaticClaimSettings | Select-Object -ExpandProperty Keys}
- $MpioAutoClaimReport = @()
- foreach($key in $MpioAutoClaim){
- $Temp = "" | Select-Object BusType,State
- $Temp.BusType = $key
- $Temp.State = 'Enabled'
- $MpioAutoClaimReport += $Temp
- }
- $MpioAutoClaimReport | Table -Name 'Multipath I/O Auto Claim Settings'
- }
- Section -Style Heading5 'MPIO Detected Hardware'{
- Paragraph 'The following table details the hardware detected and claimed by MPIO'
- $MpioAvailableHw = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-MPIOAvailableHw}
- $MpioAvailableHw | Select-Object VendorId,ProductId,BusType,IsMultipathed | Table -Name 'MPIO Available Hardware'
- }
- }
- }
- }
- #HyperV Configuration
- $HyperVInstalledCheck = Invoke-Command -Session $TempPssSession { Get-WindowsFeature | Where-Object { $_.Name -like "*Hyper-V*" } }
- if ($HyperVInstalledCheck.InstallState -eq "Installed") {
- Section -Style Heading4 "Hyper-V Configuration Settings" {
- Paragraph 'The following table details the Hyper-V Server Settings'
- $VmHost = Invoke-Command -Session $TempPssSession { Get-VMHost }
- $VmHostReport = [PSCustomObject]@{
- 'Logical Processor Count' = $VmHost.LogicalProcessorCount
- 'Memory Capacity (GB)' = [Math]::Round($VmHost.MemoryCapacity / 1gb)
- 'VM Default Path' = $VmHost.VirtualMachinePath
- 'VM Disk Default Path' = $VmHost.VirtualHardDiskPath
- 'Supported VM Versions' = $VmHost.SupportedVmVersions -Join ","
- 'Numa Spannning Enabled' = $VmHost.NumaSpanningEnabled
- 'Iov Support' = $VmHost.IovSupport
- 'VM Migrations Enabled' = $VmHost.VirtualMachineMigrationEnabled
- 'Allow any network for Migrations' = $VmHost.UseAnyNetworkForMigrations
- 'VM Migration Authentication Type' = $VmHost.VirtualMachineMigrationAuthenticationType
- 'Max Concurrent Storage Migrations' = $VmHost.MaximumStorageMigrations
- 'Max Concurrent VM Migrations' = $VmHost.MaximumStorageMigrations
- }
- $VmHostReport | Table -Name 'Hyper-V Host Settings' -List -ColumnWidths 50, 50
- Section -Style Heading5 "Hyper-V NUMA Boundaries" {
- Paragraph 'The following table details the NUMA nodes on the host'
- $VmHostNumaNodes = Get-VMHostNumaNode -CimSession $TempCimSession
- [array]$VmHostNumaReport = @()
- foreach ($Node in $VmHostNumaNodes) {
- $TempVmHostNumaReport = [PSCustomObject]@{
- 'Numa Node Id' = $Node.NodeId
- 'Memory Available(GB)' = ($Node.MemoryAvailable)/1024
- 'Memory Total(GB)' = ($Node.MemoryTotal)/1024
- }
- $VmHostNumaReport += $TempVmHostNumaReport
- }
- $VmHostNumaReport | Table -Name 'Host NUMA Nodes'
- }
- Section -Style Heading5 "Hyper-V MAC Pool settings" {
- 'The following table details the Hyper-V MAC Pool'
- $VmHostMacPool = [PSCustomObject]@{
- 'Mac Address Minimum' = $VmHost.MacAddressMinimum
- 'Mac Address Maximum' = $VmHost.MacAddressMaximum
- }
- $VmHostMacPool | Table -Name 'MAC Address Pool' -ColumnWidths 50, 50
- }
- Section -Style Heading5 "Hyper-V Management OS Adapters" {
- Paragraph 'The following table details the Management OS Virtual Adapters created on Virtual Switches'
- $VmOsAdapters = Get-VMNetworkAdapter -CimSession $TempCimSession -ManagementOS
- $VmOsAdapterReport = @()
- Foreach ($VmOsAdapter in $VmOsAdapters) {
- $AdapterVlan = Get-VMNetworkAdapterVlan -CimSession $TempCimSession -ManagementOS -VMNetworkAdapterName $VmOsAdapter.Name
- $TempVmOsAdapterReport = [PSCustomObject]@{
- 'Name' = $VmOsAdapter.Name
- 'Switch Name' = $VmOsAdapter.SwitchName
- 'Mac Address' = $VmOsAdapter.MacAddress
- 'IPv4 Address' = $VmOsAdapter.IPAddresses -Join ","
- 'Adapter Mode' = $AdapterVlan.OperationMode
- 'Vlan ID' = $AdapterVlan.AccessVlanId
- }
- $VmOsAdapterReport += $TempVmOsAdapterReport
- }
- $VmOsAdapterReport | Table -Name 'VM Management OS Adapters'
- }
- Section -Style Heading5 "Hyper-V vSwitch Settings" {
- Paragraph 'The following table details the Hyper-V vSwitches configured'
- $VmSwitches = Invoke-Command -Session $TempPssSession { Get-VMSwitch }
- $VmSwitchesReport = @()
- ForEach ($VmSwitch in $VmSwitches) {
- $TempVmSwitchesReport = [PSCustomObject]@{
- 'Switch Name' = $VmSwitch.Name
- 'Switch Type' = $VmSwitch.SwitchType
- 'Embedded Team' = $VmSwitch.EmbeddedTeamingEnabled
- 'Interface Description' = $VmSwitch.NetAdapterInterfaceDescription
- }
- $VmSwitchesReport += $TempVmSwitchesReport
- }
- $VmSwitchesReport | Table -Name 'Virtual Switch Summary' -ColumnWidths 40, 10, 10, 40
- Foreach ($VmSwitch in $VmSwitches) {
- Section -Style Heading6 ($VmSwitch.Name) {
- Paragraph 'The following table details the Hyper-V vSwitch'
- $VmSwitchReport = [PSCustomObject]@{
- 'Switch Name' = $VmSwitch.Name
- 'Switch Type' = $VmSwitch.SwitchType
- 'Switch Embedded Teaming Status' = $VmSwitch.EmbeddedTeamingEnabled
- 'Bandwidth Reservation Mode' = $VmSwitch.BandwidthReservationMode
- 'Bandwidth Reservation Percentage' = $VmSwitch.Percentage
- 'Management OS Allowed' = $VmSwitch.AllowManagementOS
- 'Physical Adapters' = $VmSwitch.NetAdapterInterfaceDescriptions -Join ","
- 'IOV Support' = $VmSwitch.IovSupport
- 'IOV Support Reasons' = $VmSwitch.IovSupportReasons
- 'Available VM Queues' = $VmSwitch.AvailableVMQueues
- 'Packet Direct Enabled' = $VmSwitch.PacketDirectinUse
- }
- $VmSwitchReport | Table -Name 'VM Switch Details' -List -ColumnWidths 50, 50
- }
- }
- }
- }
- }
- }
+ $script:ColumnSize = $Options.DiagramColumnSize
+
+ Write-Verbose "`VMM Server [$($VMM.name)] connection status is [$($VMM.IsConnected)]"
+ Section -Style Heading1 $($VMM.FQDN) {
+ Paragraph "The following section details the configuration of SCVMM server $($VMM.FQDN)."
+
+ $VMMDiagram = Get-AbrVmmInfrastructureDiagram
+ if ($VMMDiagram) {
+ Export-AbrDiagram -DiagramObject $VMMDiagram -MainDiagramLabel "Infrastructure Diagram" -FileName "AsBuiltReport.Microsoft.SCVMM.Infrastructure"
+ } else {
+ Write-PScriboMessage -IsWarning "Unable to generate the Infrastructure Diagram."
}
- Remove-PSSession $TempPssSession
- Remove-CimSession $TempCimSession
- }
- }
- #endregion foreach loop
+ Get-AbrVmmServerSetting
+ Get-AbrVmmDBSetting
+ Get-AbrVmmAutoNetSetting
+ Get-AbrVmmNetworking
+ Get-AbrVmmLibraryTemplate
+ Get-AbrVmmCluster
+ Get-AbrVmmHost
+ }
+ }
+ #endregion foreach loop
}
diff --git a/Todo.md b/Todo.md
new file mode 100644
index 0000000..4bfd21b
--- /dev/null
+++ b/Todo.md
@@ -0,0 +1,8 @@
+[] Add Update Server
+[] Add PXE Server
+[] Add vCenter Server
+[] Add Network Service
+[] Add configuration settings
+ [] Get-SCRunAsAccount
+ [] Get-SCUserRole
+[] Add Cloud (Get-SCCloud)
\ No newline at end of file
diff --git a/icons/AsBuiltReport_Logo.png b/icons/AsBuiltReport_Logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..ff8a542f746566838cd4d05a958711baf99690e8
GIT binary patch
literal 5154
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
Q;{X5v07*qoM6N<$g2mI!4gdfE
literal 0
HcmV?d00001
diff --git a/icons/AsBuiltReport_Signature.png b/icons/AsBuiltReport_Signature.png
new file mode 100644
index 0000000000000000000000000000000000000000..000cde6004bdcc3974a853ec31a95436e795ff9b
GIT binary patch
literal 12338
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
A&Hw-a
literal 0
HcmV?d00001
diff --git a/icons/DB_Server.png b/icons/DB_Server.png
new file mode 100644
index 0000000000000000000000000000000000000000..315e9044abc6c16f39a25af6bdad0d28bf4a5413
GIT binary patch
literal 5390
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
zℜD`_-fg(>`6fYMn=WYZ#v`SidAi>DamCklZ7IyX
z$E9IPwU%n&n>#Z-ZQU7oDohC;0zl5}PZxeyFOda)yYvf#)`fjX&P=bDseBBa;)V9`
z9h{?<)v2>^gPvCN<$t+R)d%-A&d)7q7K0Jv%B+|J$mXuA%cm~G&cwr}rl@2y+4
z{Hme*qN1Wyo42ITOKg_;BaJ{8qMpZ{x1v$HQ3
z0D}Bu5k7SMcOo=qM5*40UO9EnR}313>^MB3zZC#T0068wEJP$#7#Orf+LY;YVz+Hx
zU)vfPk7Q;=N)>9yXOBDES?)GQ#uH5c_#+~A$k;DY6iLEy;;DiO^-}rB_96srYa7&)
zb*QRFgy=E6699M{`jg64&VqV5ntSC&gLx(-(DyomAgX8EeOSWndh34a-=oG)S(ZF`
zhIfnQK@o#iXh%EiEiJh}`_#euPA>LmpFJ*?DqKGJFxBggAp}7HEZEGKE^R>&1Q3Aq
zdh-9u@0Ln{Aa)wL+@XwH_hnHz|Ix59lkz4`nUym0lLgO>JLcAko2(sH9G1Y<#o=yC
zWfX{|O_T*4hNCWy_CBwK5Ddqk>vCe!sj2`Uw-Fz{KisXWkJ}J334N)s&I(@b*y*$9
zHOMjaKM{+~%@~!>Des4JYs8^<3M;nXxLZ^h(l2q&lCQrrNK8n8SCb8(NUA9Na?$)2
z7et8e6Qn{6Y~C(jyY2Z(2qBY5fS|^;aXV}C-*=^N%y@rXl6cp)ji)_b>|d&if=Z+7
zF0Pl4X)tOoULX$c>J#hgVEtEfJ6fF{a|D2O*KXdszWA$UO)S$wv7||{91^K@lLCj$
zzF{csQlrzqnte!MV1P;@Q|24HK_*u{bq|JNFKg36s_R5+Kl}X4hHu>JAC?s+#6|Vx
zu;^24E!oXRxC{D6$f_D);>!vlS85#ZJ}MjVlomE6MScFvpQPNlpno-NCNUE^n*9bQ~tqfE((WK0nG}h5$87&?rI}e?*CJgN8BSQRDuWV75
z%47!u{X7=|fW|<`4_&3v9pcs17s-^GqgoxtYJ*HDy_cKgDMRNX`h@F`?BAAiDeK6L
zxV}AIE!d2y6f&vA(6yB{!nlMH6B>5p=4Q+$IF_=Hg%zZm83)6+Fso
znc~t0gdEuYGm)OQc4a3QyI3lPY%t%jEz6ACSzBC4`)O5m^Y*d@((F?Q)0uQi4;qzx
zsp)h?umZbyhcyb|J8z(t3<u#!tG1O;g8dvZeE*6
z%B6C}=gE_2S~T~Z$3wi+=cu_EgV#nC$^EdS0>4f^nqF2A;{%m~ry5z;Et$*Ps#@&RBo0Bk;T
z>^SZBKQ7vG`GRmubF*N9SZYT?k+52kjBvKI^ySuy$&DAmCzC1Cs1s2X0cU%wjKll>
zb-{YM*w0m{)N`%kMs2g=u#P9hMHQre{E_+@e}Cx7?7aDgRG*%Ko$uu~+FN_NIelGG
zEr`?V^iRbFlvdS7A_#IRq+jBaA+cdszxirW9RR?rxeL3NR`60D6jjVpYxR_8+qbu|
zxHf1&-;=9XHs4hed6h#04ArSYu$35)pOcyWMg?1o>ZKpN#LqCTeNa)C4u(^_F$6(A@^*9B{Qm;XtpbZd6Zw0&
z3^7JVhmC#@=v!~M$2945U7par2g~-#{cKW><>AcE07rH*Z
zS+f$T)!N(cE)EyHJY4?#eE!^4?V5!G0kUe{rhvci7saYH+F+SNJ>l8Du(4#Xh>z*B
zYR>G``W7akx&lDZOT6}#{G3d|JCP>#JgncChGt*9{+L%Qe%8OiwX!pfhQ%YMqWBl0B7y-m2CtHiH&1qp|GU>F5kXL{k
z7c%uHkL>yw0SI)kHa}p=VM!3==^Y({AQ+D0XH09zYry8dOt4Z`H@H{
s8tCWYxb?qlQcS&Lf(a&=0003017NhjMTbZzp#T5?07*qoM6N<$g2N(3M*si-
literal 0
HcmV?d00001
diff --git a/icons/Microsoft_Logo.png b/icons/Microsoft_Logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..e263d99d461634d127889c257d8a1ae96fbd1291
GIT binary patch
literal 13194
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
literal 0
HcmV?d00001
diff --git a/icons/server.png b/icons/server.png
new file mode 100644
index 0000000000000000000000000000000000000000..40b8e580cee3913e15dcf11f9c94ac9a5545a530
GIT binary patch
literal 4411
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
literal 0
HcmV?d00001
From f0e95d38b9999afcf6be4ee3e6061b285cba07bd Mon Sep 17 00:00:00 2001
From: Jonathan Colon
Date: Fri, 5 Sep 2025 00:18:11 -0400
Subject: [PATCH 2/4] Update Todo list with ReadMe file addition
---
Todo.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Todo.md b/Todo.md
index 4bfd21b..8506e3a 100644
--- a/Todo.md
+++ b/Todo.md
@@ -5,4 +5,5 @@
[] Add configuration settings
[] Get-SCRunAsAccount
[] Get-SCUserRole
-[] Add Cloud (Get-SCCloud)
\ No newline at end of file
+[] Add Cloud (Get-SCCloud)
+[] Update ReadMe file
\ No newline at end of file
From 8cdb087e4b5c86c0e5c73bd9553d3eabd7846bcf Mon Sep 17 00:00:00 2001
From: Jonathan Colon
Date: Fri, 5 Sep 2025 20:59:07 -0400
Subject: [PATCH 3/4] Improve report content
---
AsBuiltReport.Microsoft.SCVMM.json | 14 +-
README.md | 171 +++++++++++++++---
Src/Private/Get-AbrVmmAutoNetSetting.ps1 | 8 +-
Src/Private/Get-AbrVmmDBSetting.ps1 | 8 +-
Src/Private/Get-AbrVmmInfrastructure.ps1 | 45 +++++
Src/Private/Get-AbrVmmLibraryTemplate.ps1 | 1 -
...ServerSetting.ps1 => Get-AbrVmmServer.ps1} | 14 +-
Src/Private/Get-AbrVmmUpdateServer.ps1 | 84 +++++++++
.../Invoke-AsBuiltReport.Microsoft.SCVMM.ps1 | 4 +-
Todo.md | 5 +-
10 files changed, 299 insertions(+), 55 deletions(-)
create mode 100644 Src/Private/Get-AbrVmmInfrastructure.ps1
rename Src/Private/{Get-AbrVmmServerSetting.ps1 => Get-AbrVmmServer.ps1} (86%)
create mode 100644 Src/Private/Get-AbrVmmUpdateServer.ps1
diff --git a/AsBuiltReport.Microsoft.SCVMM.json b/AsBuiltReport.Microsoft.SCVMM.json
index 38bbfb0..04390bc 100644
--- a/AsBuiltReport.Microsoft.SCVMM.json
+++ b/AsBuiltReport.Microsoft.SCVMM.json
@@ -11,7 +11,7 @@
"Options": {
"VmmServerPort": 8100,
"EnableDiagrams": true,
- "EnableDiagramDebug": false,
+ "EnableDiagramDebug": true,
"DiagramTheme": "White",
"DiagramWaterMark": "Acme Corporation",
"ExportDiagrams": true,
@@ -26,13 +26,11 @@
"InfoLevel": {
"_comment_": "Please refer to the AsBuiltReport project contributing guide for information about how to define InfoLevels.",
"_comment_": "0 = Disabled, 1 = Enabled / Summary, 2 = Adv Summary, 3 = Detailed, 4 = Adv Detailed, 5 = Comprehensive",
- "ServerSettings": 1,
- "DBSettings": 1,
- "AutoNetworkSettings": 1,
- "Networking": 1,
- "LibraryTemplates": 1,
- "Clusters": 1,
- "Hosts": 1
+ "Infrastructure": 2,
+ "Networking": 2,
+ "LibraryTemplates": 2,
+ "Clusters": 2,
+ "Hosts": 2
},
"HealthCheck": {
"VMMServer": true,
diff --git a/README.md b/README.md
index 0939e0f..f007cbd 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
- 
+
+ 
@@ -22,90 +22,209 @@
+
+
+
# Microsoft SCVMM As Built Report
+Microsoft SCVMM As Built Report is a PowerShell module which works in conjunction with [AsBuiltReport.Core](https://github.com/AsBuiltReport/AsBuiltReport.Core).
+
+[AsBuiltReport](https://github.com/AsBuiltReport/AsBuiltReport) is an open-sourced community project which utilizes PowerShell to produce as-built documentation in multiple document formats for multiple vendors and technologies.
+
+Please refer to the AsBuiltReport [website](https://www.asbuiltreport.com) for more detailed information about this project.
+
+# :books: Sample Reports
+
+## Sample Report - Custom Style 1
+
+Sample Microsoft SCVMM As Built report HTML file: [Sample Microsoft SCVMM As-Built Report.html](https://htmlpreview.github.io/?https://raw.githubusercontent.com/AsBuiltReport/AsBuiltReport.Microsoft.SCVMM/dev/Samples/Sample%20Microsoft%20Windows%20As%20Built%20Report.html "Sample Microsoft SCVMM As-Built Report")
+
# :beginner: Getting Started
+Below are the instructions on how to install, configure and generate a Microsoft SCVMM As Built report.
+
## :floppy_disk: Supported Versions
+
+The Microsoft SCVMM As Built Report supports the following SCVMM Server versions;
+
+- 2016, 2019, 2022 & 2025
+
+### PowerShell
+This report is compatible with the following PowerShell versions;
+
+
+| Windows PowerShell 5.1 | PowerShell 7 |
+| :--------------------: | :----------: |
+| :white_check_mark: | :white_check_mark: |
## :wrench: System Requirements
+
+PowerShell 5.1/7 and the following PowerShell modules are required for generating a Microsoft SCVMM As Built report.
+
+- [AsBuiltReport.Microsoft.SCVMM Module](https://www.powershellgallery.com/packages/AsBuiltReport.Microsoft.SCVMM/)
+- [Hyper-V Module](https://docs.microsoft.com/en-us/powershell/module/hyper-v/?view=windowsserver2022-ps)
+- [FailoverClusters Module](https://learn.microsoft.com/en-us/powershell/module/failoverclusters/?view=windowsserver2022-ps)
+- [Diagrammer.Core Module](https://www.powershellgallery.com/packages/Diagrammer.Core/)
+
+### Linux & macOS
+
+This report does not support Linux or Mac due to the fact that the SCVMM modules are dependent on the .NET Framework. Until Microsoft migrates these modules to native PowerShell Core, only PowerShell >= 5.1.x will be supported on SCVMM.
### :closed_lock_with_key: Required Privileges
+A Microsoft SCVMM As Built Report can be generated with Administrator level role. Since this report relies extensively on the WinRM component, you should make sure that it is enabled and configured
## :package: Module Installation
-### PowerShell
+The installation of the modules will depend on the roles that are being served on the server to be documented.
+
+### PowerShell v5.x running on a Windows server (Target)
```powershell
-install-module AsBuiltReport.Microsoft.SCVMM
+Install-Module AsBuiltReport.Microsoft.SCVMM
+
+# Hyper-V Server powershell modules
+Install-WindowsFeature -Name Hyper-V-PowerShell
+
+# FailOver Cluster powershell modules
+Install-WindowsFeature -Name RSAT-Clustering-PowerShell
+```
+
+### PowerShell v5.x running on Windows client computer (Target)
+
+```powershell
+Install-Module AsBuiltReport.Microsoft.SCVMM
+
+#FailOver Cluster powershell modules
+Add-WindowsCapability -Online -Name 'Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0'
+
+# Hyper-V Server powershell modules
+Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell
```
### 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.Microsoft.SCVMM#wrench-system-requirements) also.
1. Download the code package / [latest release](https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.SCVMM/releases/latest) zip from GitHub
2. Extract the zip file
3. Copy the folder `AsBuiltReport.Microsoft.SCVMM` 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.Microsoft.SCVMM -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 Microsoft SCVMM As Built Report utilises a JSON file to allow configuration of report information, options, detail and healthchecks.
+The Microsoft SCVMM As Built Report utilizes a JSON file to allow configuration of report information, options, detail and healthchecks.
A Microsoft SCVMM report configuration file can be generated by executing the following command;
+
```powershell
-New-AsBuiltReportConfig -Report AsBuiltReport.Microsoft.SCVMM -FolderPath -Filename
+New-AsBuiltReportConfig -Report Microsoft.SCVMM -FolderPath -Filename
```
-Executing this command will copy the default Microsoft SCVMM report JSON configuration to a user specified folder.
+Executing this command will copy the default Microsoft Windows report JSON configuration to a user specified folder.
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 Microsoft SCVMM report information.
-| Sub-Schema | Setting | Default | Description |
-|---------------------|--------------|--------------------------------|--------------------------------------------------------------|
+| Sub-Schema | Setting | Default | Description |
+| ------------------- | ------------ | --------------------------------- | ------------------------------------------------------------ |
| Name | User defined | Microsoft SCVMM 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 |
-| 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 |
-| ShowTableCaptions | true / false | true | Toggle to enable/disable table captions/numbering |
+| Version | User defined | 1.0 | The report version |
+| Status | User defined | Released | The report release status |
+| 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 |
+| ShowTableCaptions | true / false | true | Toggle to enable/disable table captions/numbering |
### Options
+
The **Options** schema allows certain options within the report to be toggled on or off.
+| Sub-Schema | Setting | Default | Description |
+| ----------------- | ------------ | ------- | ---------------------------------------- |
+| SQLLogin | true / false | false | Enable sql server login authentication . |
+| SQLUserName | User defined | empty | Sql server login Username . |
+| SQLSecurePassword | User defined | empty | Sql server login SecureString Password . |
+
+#### Generating a SecureString
+
+```powershell
+PS C:\> "SecurePassword" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
+01000000d08c9ddf0115d1118c7a00c04fc297eb01000000b3605317d738c346801fbff6596b0d130000
+PS C:\>
+```
+Copy/Paste the output text to the variable SQLSecurePassword
+
+##### Note: Storing any credential in a file can pose a security risk. Use this option at your own risk!
+
### InfoLevel
-The **InfoLevel** schema allows configuration of each section of the report at a granular level. The following sections can be set.
-There are 6 levels (0-5) of detail granularity for each section as follows;
+The **InfoLevel** schema allows configuration of each section of the report at a granular level. The following sections can be set.
-| Setting | InfoLevel | Description |
-|:-------:|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
-| 0 | Disabled | Does not collect or display any information |
-| 1 | Enabled / Summary | Provides summarised information for a collection of objects |
-| 2 | Adv Summary | Provides condensed, detailed information for a collection of objects |
-| 3 | Detailed | Provides detailed information for individual objects |
-| 4 | Adv Detailed | Provides detailed information for individual objects, as well as information for associated objects |
-| 5 | Comprehensive | Provides comprehensive information for individual objects, such as advanced configuration settings |
+There are 3 levels (0-2) of detail granularity for each section as follows;
+
+| Setting | InfoLevel | Description |
+| :-----: | ----------- | -------------------------------------------------------------------- |
+| 0 | Disabled | Does not collect or display any information |
+| 1 | Enabled | Provides summarized information for a collection of objects |
+| 2 | Adv Summary | Provides condensed, detailed information for a collection of objects |
+
+The table below outlines the default and maximum **InfoLevel** settings for each section.
+
+| Sub-Schema | Default Setting | Maximum Setting |
+| --------------- | :-------------: | :-------------: |
+| Hardware | 1 | 1 |
+| OperatingSystem | 1 | 2 |
+| Storage | 1 | 1 |
+| Networking | 1 | 1 |
+| IIS | 1 | 1 |
+| HyperV | 1 | 1 |
+| DHCP | 1 | 2 |
+| DNS | 1 | 2 |
+| FailOverCluster | 1 | 2 |
+| SQLServer | 1 | 2 |
### Healthcheck
+
The **Healthcheck** schema is used to toggle health checks on or off.
-## :computer: Examples
+## :computer: Examples
+
+There are a few examples listed below on running the AsBuiltReport script against a Microsoft Windows server target. Refer to the `README.md` file in the main AsBuiltReport project repository for more examples.
+
+```powershell
+
+# Generate a Microsoft SCVMM As Built Report for Server 'win-server-01v.contoso.local' using specified credentials. Export report to HTML & DOCX formats. Use default report style. Append timestamp to report filename. Save reports to 'C:\Users\Jon\Documents'
+PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'win-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -Timestamp
+
+# Generate a Microsoft SCVMM As Built Report for Server 'win-server-01v.contoso.local' using specified credentials and report configuration file. Export report to Text, HTML & DOCX formats. Use default report style. Save reports to 'C:\Users\Jon\Documents'. Display verbose messages to the console.
+PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'win-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Text,Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -ReportConfigFilePath 'C:\Users\Jon\AsBuiltReport\AsBuiltReport.Microsoft.SCVMM.json' -Verbose
+
+# Generate a Microsoft SCVMM As Built Report for Server 'win-server-01v.contoso.local' using stored credentials. Export report to HTML & Text formats. Use default report style. Highlight environment issues within the report. Save reports to 'C:\Users\Jon\Documents'.
+PS C:\> $Creds = Get-Credential
+PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'win-server-01v.contoso.local' -Credential $Creds -Format Html,Text -OutputFolderPath 'C:\Users\Jon\Documents' -EnableHealthCheck
+
+# Generate a Microsoft SCVMM As Built Report for Server 'win-server-01v.contoso.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 Microsoft.SCVMM -Target 'win-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -SendEmail
+```
+## :x: Known Issues
+- Issues with WinRM when using the IP address instead of the "Fully Qualified Domain Name".
+- The report provides the ability to extract the configuration of the DNS/DHCP/Hyper-V/IIS/FailOver-Cluster services. In order to obtain this information it is required that the servers running these services have the corresponding powershell modules installed.
diff --git a/Src/Private/Get-AbrVmmAutoNetSetting.ps1 b/Src/Private/Get-AbrVmmAutoNetSetting.ps1
index a03f20a..6f4e1b4 100644
--- a/Src/Private/Get-AbrVmmAutoNetSetting.ps1
+++ b/Src/Private/Get-AbrVmmAutoNetSetting.ps1
@@ -19,15 +19,15 @@ function Get-AbrVmmAutoNetSetting {
)
begin {
- Write-PScriboMessage "AutoNetworkSettings InfoLevel set at $($InfoLevel.AutoNetworkSettings)."
+ Write-PScriboMessage "Infrastructure InfoLevel set at $($InfoLevel.Infrastructure)."
}
process {
try {
- if ($InfoLevel.AutoNetworkSettings -gt 0) {
+ if ($InfoLevel.Infrastructure -gt 0) {
if ($Vmm) {
Write-PScriboMessage "Collecting VMM Auto Network information."
- Section -Style Heading2 'AutoNetwork Settings' {
+ Section -Style Heading3 'AutoNetwork Settings' {
$VmmServerSettingsInfo = @()
foreach ($VmmServerSetting in $Vmm) {
$InObj = [Ordered]@{
@@ -40,7 +40,7 @@ function Get-AbrVmmAutoNetSetting {
$VmmServerSettingsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
}
- if ($InfoLevel.AutoNetworkSettings -ge 2) {
+ if ($InfoLevel.Infrastructure -ge 2) {
Paragraph "The following sections detail the configuration of the VMM Auto Network Settings."
foreach ($VmmServerSetting in $VmmServerSettingsInfo) {
Section -Style NOTOCHeading4 -ExcludeFromTOC "$($Vmm.FQDN)" {
diff --git a/Src/Private/Get-AbrVmmDBSetting.ps1 b/Src/Private/Get-AbrVmmDBSetting.ps1
index de548f7..faa881a 100644
--- a/Src/Private/Get-AbrVmmDBSetting.ps1
+++ b/Src/Private/Get-AbrVmmDBSetting.ps1
@@ -19,15 +19,15 @@ function Get-AbrVmmDBSetting {
)
begin {
- Write-PScriboMessage "DBSettings InfoLevel set at $($InfoLevel.DBSettings)."
+ Write-PScriboMessage "Infrastructure InfoLevel set at $($InfoLevel.Infrastructure)."
}
process {
try {
- if ($InfoLevel.DBSettings -gt 0) {
+ if ($InfoLevel.Infrastructure -gt 0) {
if ($Vmm) {
Write-PScriboMessage "Collecting VMM Database Settings information."
- Section -Style Heading2 'Database Settings' {
+ Section -Style Heading3 'Database Settings' {
$VmmServerSettingsInfo = @()
foreach ($VmmServerSetting in $Vmm) {
$InObj = [Ordered]@{
@@ -40,7 +40,7 @@ function Get-AbrVmmDBSetting {
$VmmServerSettingsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
}
- if ($InfoLevel.DBSettings -ge 2) {
+ if ($InfoLevel.Infrastructure -ge 2) {
Paragraph "The following sections detail the configuration of the VMM Database Settings."
foreach ($VmmServerSetting in $VmmServerSettingsInfo) {
Section -Style NOTOCHeading4 -ExcludeFromTOC "$($Vmm.FQDN)" {
diff --git a/Src/Private/Get-AbrVmmInfrastructure.ps1 b/Src/Private/Get-AbrVmmInfrastructure.ps1
new file mode 100644
index 0000000..9bafb51
--- /dev/null
+++ b/Src/Private/Get-AbrVmmInfrastructure.ps1
@@ -0,0 +1,45 @@
+function Get-AbrVmmInfrastructure {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft VMM Server information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Infrastructure InfoLevel set at $($InfoLevel.Infrastructure)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Infrastructure -gt 0) {
+ if ($Vmm) {
+ Write-PScriboMessage "Collecting VMM Server Settings information."
+ Section -Style Heading2 'Infrastructure' {
+ Get-AbrVmmServer
+ Get-AbrVmmDBSetting
+ Get-AbrVmmAutoNetSetting
+ Get-AbrVmmUpdateServer
+ Get-AbrVmmLibraryServer
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Private/Get-AbrVmmLibraryTemplate.ps1 b/Src/Private/Get-AbrVmmLibraryTemplate.ps1
index 692fb8b..18eb85e 100644
--- a/Src/Private/Get-AbrVmmLibraryTemplate.ps1
+++ b/Src/Private/Get-AbrVmmLibraryTemplate.ps1
@@ -28,7 +28,6 @@ function Get-AbrVmmLibraryTemplate {
Write-PScriboMessage "Collecting VMM Library and Template information."
Section -Style Heading1 'Library and Templates' {
Paragraph 'The following section details the library and vm templates configured'
- Get-AbrVmmLibraryServer
Get-AbrVmmLibraryShare
Get-AbrVmmVMTemplate
Get-AbrVmmGuestOSProfile
diff --git a/Src/Private/Get-AbrVmmServerSetting.ps1 b/Src/Private/Get-AbrVmmServer.ps1
similarity index 86%
rename from Src/Private/Get-AbrVmmServerSetting.ps1
rename to Src/Private/Get-AbrVmmServer.ps1
index f45bed6..99ef1f6 100644
--- a/Src/Private/Get-AbrVmmServerSetting.ps1
+++ b/Src/Private/Get-AbrVmmServer.ps1
@@ -1,4 +1,4 @@
-function Get-AbrVmmServerSetting {
+function Get-AbrVmmServer {
<#
.SYNOPSIS
Used by As Built Report to retrieve Microsoft VMM Server information
@@ -19,15 +19,15 @@ function Get-AbrVmmServerSetting {
)
begin {
- Write-PScriboMessage "ServerSettings InfoLevel set at $($InfoLevel.ServerSettings)."
+ Write-PScriboMessage "Infrastructure InfoLevel set at $($InfoLevel.Infrastructure)."
}
process {
try {
- if ($InfoLevel.ServerSettings -gt 0) {
+ if ($InfoLevel.Infrastructure -gt 0) {
if ($Vmm) {
Write-PScriboMessage "Collecting VMM Server Settings information."
- Section -Style Heading2 'Virtual Machine Manager Server' {
+ Section -Style Heading2 'VMM Server' {
$VmmServerSettingsInfo = @()
foreach ($VmmServerSetting in $Vmm) {
$InObj = [Ordered]@{
@@ -43,12 +43,12 @@ function Get-AbrVmmServerSetting {
$VmmServerSettingsInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
}
- if ($InfoLevel.ServerSettings -ge 2) {
+ if ($InfoLevel.Infrastructure -ge 2) {
Paragraph "The following sections detail the configuration of the VMM Server Settings."
foreach ($VmmServerSetting in $VmmServerSettingsInfo) {
Section -Style NOTOCHeading4 -ExcludeFromTOC "$($VmmServerSetting.'Server FQDN')" {
$TableParams = @{
- Name = "Server Settings - $($VmmServerSetting.'Server FQDN')"
+ Name = "VMM Server Settings - $($VmmServerSetting.'Server FQDN')"
List = $true
ColumnWidths = 40, 60
}
@@ -62,7 +62,7 @@ function Get-AbrVmmServerSetting {
Paragraph "The following table summarises the configuration of the VMM Server Settings."
BlankLine
$TableParams = @{
- Name = "Server Settings - $($VmmServerSetting.FQDN)"
+ Name = "VMM Server Settings - $($VmmServerSetting.FQDN)"
List = $false
Columns = 'Server FQDN', 'IP Address', 'Product Version', 'Server Port'
ColumnWidths = 45, 20, 20, 15
diff --git a/Src/Private/Get-AbrVmmUpdateServer.ps1 b/Src/Private/Get-AbrVmmUpdateServer.ps1
new file mode 100644
index 0000000..7cdbac1
--- /dev/null
+++ b/Src/Private/Get-AbrVmmUpdateServer.ps1
@@ -0,0 +1,84 @@
+function Get-AbrVmmUpdateServer {
+ <#
+ .SYNOPSIS
+ Used by As Built Report to retrieve Microsoft SCVMM Update Servers information
+ .DESCRIPTION
+
+ .NOTES
+ Version: 0.1.1
+ Author: AsBuiltReport Organization
+ Twitter: @AsBuiltReport
+ Github: AsBuiltReport
+ .EXAMPLE
+
+ .LINK
+
+ #>
+ [CmdletBinding()]
+ param (
+ )
+
+ begin {
+ Write-PScriboMessage "Infrastructure InfoLevel set at $($InfoLevel.Infrastructure)."
+ }
+
+ process {
+ try {
+ if ($InfoLevel.Infrastructure -gt 0) {
+ if ($VMMUpdateServers = Get-SCUpdateServer | Sort-Object -Property Name) {
+ Write-PScriboMessage "Collecting VMM Update Servers information."
+ Section -Style Heading3 'Update Servers' {
+ $VmmUpdateServersInfo = @()
+ foreach ($VMMUpdateServer in $VMMUpdateServers) {
+ $InObj = [Ordered]@{
+ 'Name' = $VMMUpdateServer.Name
+ 'Port' = $VMMUpdateServer.Port
+ 'Is Connection Secure' = $VMMUpdateServer.IsConnectionSecure
+ 'Server Type' = $VMMUpdateServer.ServerType
+ 'Version' = $VMMUpdateServer.Version
+ 'Synchronization Type' = $VMMUpdateServer.SynchronizationType
+ 'UsesProxy' = $VMMUpdateServer.UsesProxy
+ }
+
+ $VmmUpdateServersInfo += [pscustomobject](ConvertTo-HashToYN $InObj)
+ }
+
+ if ($InfoLevel.Infrastructure -ge 2) {
+ Paragraph "The following sections detail the configuration of the update server."
+ foreach ($VMMUpdateServer in $VmmUpdateServersInfo) {
+ Section -Style NOTOCHeading4 -ExcludeFromTOC "$($VMMUpdateServer.Name)" {
+ $TableParams = @{
+ Name = "Update Servers - $($VMMUpdateServer.Name)"
+ List = $true
+ ColumnWidths = 40, 60
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VMMUpdateServer | Table @TableParams
+ }
+ }
+ } else {
+ Paragraph "The following table summarises the configuration of the update servers."
+ BlankLine
+ $TableParams = @{
+ Name = "Update Servers - $($Vmm.FQDN)"
+ List = $false
+ Columns = 'Name', 'Port', 'Is Connection Secure', 'Server Type'
+ ColumnWidths = 45, 15, 20, 20
+ }
+ if ($Report.ShowTableCaptions) {
+ $TableParams['Caption'] = "- $($TableParams.Name)"
+ }
+ $VmmUpdateServersInfo | Table @TableParams
+ }
+ }
+ }
+ }
+ } catch {
+ Write-PScriboMessage -IsWarning $($_.Exception.Message)
+ }
+ }
+
+ end {}
+}
\ No newline at end of file
diff --git a/Src/Public/Invoke-AsBuiltReport.Microsoft.SCVMM.ps1 b/Src/Public/Invoke-AsBuiltReport.Microsoft.SCVMM.ps1
index 2b304d5..f022637 100644
--- a/Src/Public/Invoke-AsBuiltReport.Microsoft.SCVMM.ps1
+++ b/Src/Public/Invoke-AsBuiltReport.Microsoft.SCVMM.ps1
@@ -99,9 +99,7 @@ function Invoke-AsBuiltReport.Microsoft.SCVMM {
Write-PScriboMessage -IsWarning "Unable to generate the Infrastructure Diagram."
}
- Get-AbrVmmServerSetting
- Get-AbrVmmDBSetting
- Get-AbrVmmAutoNetSetting
+ Get-AbrVmmInfrastructure
Get-AbrVmmNetworking
Get-AbrVmmLibraryTemplate
Get-AbrVmmCluster
diff --git a/Todo.md b/Todo.md
index 8506e3a..1e6a224 100644
--- a/Todo.md
+++ b/Todo.md
@@ -1,4 +1,4 @@
-[] Add Update Server
+[x] Add Update Server
[] Add PXE Server
[] Add vCenter Server
[] Add Network Service
@@ -6,4 +6,5 @@
[] Get-SCRunAsAccount
[] Get-SCUserRole
[] Add Cloud (Get-SCCloud)
-[] Update ReadMe file
\ No newline at end of file
+[] Update ReadMe file
+[] Add HostGroup (Get-SCVMHostGroup)
\ No newline at end of file
From f8f2c3b2b03077a1953fec697ffd6a095512b6ac Mon Sep 17 00:00:00 2001
From: Jonathan Colon
Date: Fri, 5 Sep 2025 21:07:48 -0400
Subject: [PATCH 4/4] Update Readme file
---
README.md | 83 +++++++++++++++++++++++++------------------------------
1 file changed, 37 insertions(+), 46 deletions(-)
diff --git a/README.md b/README.md
index f007cbd..3b9a7c6 100644
--- a/README.md
+++ b/README.md
@@ -55,9 +55,9 @@ The Microsoft SCVMM As Built Report supports the following SCVMM Server versions
This report is compatible with the following PowerShell versions;
-| Windows PowerShell 5.1 | PowerShell 7 |
-| :--------------------: | :----------: |
-| :white_check_mark: | :white_check_mark: |
+| Windows PowerShell 5.1 | PowerShell 7 |
+| :--------------------: | :----------------: |
+| :white_check_mark: | :white_check_mark: |
## :wrench: System Requirements
@@ -142,36 +142,33 @@ The following provides information of how to configure each schema within the re
The **Report** schema provides configuration of the Microsoft SCVMM report information.
-| Sub-Schema | Setting | Default | Description |
-| ------------------- | ------------ | --------------------------------- | ------------------------------------------------------------ |
+| Sub-Schema | Setting | Default | Description |
+| ------------------- | ------------ | ------------------------------- | ------------------------------------------------------------ |
| Name | User defined | Microsoft SCVMM 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 |
-| 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 |
-| ShowTableCaptions | true / false | true | Toggle to enable/disable table captions/numbering |
+| Version | User defined | 1.0 | The report version |
+| Status | User defined | Released | The report release status |
+| 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 |
+| ShowTableCaptions | true / false | true | Toggle to enable/disable table captions/numbering |
### Options
The **Options** schema allows certain options within the report to be toggled on or off.
-| Sub-Schema | Setting | Default | Description |
-| ----------------- | ------------ | ------- | ---------------------------------------- |
-| SQLLogin | true / false | false | Enable sql server login authentication . |
-| SQLUserName | User defined | empty | Sql server login Username . |
-| SQLSecurePassword | User defined | empty | Sql server login SecureString Password . |
-
-#### Generating a SecureString
-
-```powershell
-PS C:\> "SecurePassword" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
-01000000d08c9ddf0115d1118c7a00c04fc297eb01000000b3605317d738c346801fbff6596b0d130000
-PS C:\>
-```
-Copy/Paste the output text to the variable SQLSecurePassword
-
-##### Note: Storing any credential in a file can pose a security risk. Use this option at your own risk!
+| Sub-Schema | Setting | Default | Description |
+| ---------------------- | ------------ | ------- | ----------------------------------------------------------------------------- |
+| DiagramColumnSize | int | 3 | Set the diagram node table size |
+| DiagramTheme | string | White | Set the diagram theme (Black/White/Neon) |
+| DiagramWaterMark | string | empty | Set the diagram watermark |
+| DiagramType | true / false | true | Toggle to enable/disable the export of individual diagram diagrams |
+| EnableDiagrams | true / false | false | Toggle to enable/disable infrastructure diagrams |
+| EnableDiagramsDebug | true / false | false | Toggle to enable/disable diagram debug option |
+| EnableDiagramSignature | true / false | false | Toggle to enable/disable diagram signature (bottom right corner) |
+| ExportDiagrams | true / false | true | Toggle to enable/disable diagram export option |
+| ExportDiagramsFormat | string array | png | Set the format used to export the infrastructure diagram (dot, png, pdf, svg) |
+| SignatureAuthorName | string | empty | Set the signature author name |
+| SignatureCompanyName | string | empty | Set the signature company name |
### InfoLevel
@@ -189,16 +186,11 @@ The table below outlines the default and maximum **InfoLevel** settings for each
| Sub-Schema | Default Setting | Maximum Setting |
| --------------- | :-------------: | :-------------: |
-| Hardware | 1 | 1 |
-| OperatingSystem | 1 | 2 |
-| Storage | 1 | 1 |
+| Cluster | 1 | 1 |
+| Host | 1 | 2 |
+| Infrastructure | 1 | 1 |
+| LibraryTemplate | 1 | 2 |
| Networking | 1 | 1 |
-| IIS | 1 | 1 |
-| HyperV | 1 | 1 |
-| DHCP | 1 | 2 |
-| DNS | 1 | 2 |
-| FailOverCluster | 1 | 2 |
-| SQLServer | 1 | 2 |
### Healthcheck
@@ -210,21 +202,20 @@ There are a few examples listed below on running the AsBuiltReport script agains
```powershell
-# Generate a Microsoft SCVMM As Built Report for Server 'win-server-01v.contoso.local' using specified credentials. Export report to HTML & DOCX formats. Use default report style. Append timestamp to report filename. Save reports to 'C:\Users\Jon\Documents'
-PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'win-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -Timestamp
+# Generate a Microsoft SCVMM As Built Report for Server 'scvmm-server-01v.contoso.local' using specified credentials. Export report to HTML & DOCX formats. Use default report style. Append timestamp to report filename. Save reports to 'C:\Users\Jon\Documents'
+PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'scvmm-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -Timestamp
-# Generate a Microsoft SCVMM As Built Report for Server 'win-server-01v.contoso.local' using specified credentials and report configuration file. Export report to Text, HTML & DOCX formats. Use default report style. Save reports to 'C:\Users\Jon\Documents'. Display verbose messages to the console.
-PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'win-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Text,Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -ReportConfigFilePath 'C:\Users\Jon\AsBuiltReport\AsBuiltReport.Microsoft.SCVMM.json' -Verbose
+# Generate a Microsoft SCVMM As Built Report for Server 'scvmm-server-01v.contoso.local' using specified credentials and report configuration file. Export report to Text, HTML & DOCX formats. Use default report style. Save reports to 'C:\Users\Jon\Documents'. Display verbose messages to the console.
+PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'scvmm-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Text,Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -ReportConfigFilePath 'C:\Users\Jon\AsBuiltReport\AsBuiltReport.Microsoft.SCVMM.json' -Verbose
-# Generate a Microsoft SCVMM As Built Report for Server 'win-server-01v.contoso.local' using stored credentials. Export report to HTML & Text formats. Use default report style. Highlight environment issues within the report. Save reports to 'C:\Users\Jon\Documents'.
+# Generate a Microsoft SCVMM As Built Report for Server 'scvmm-server-01v.contoso.local' using stored credentials. Export report to HTML & Text formats. Use default report style. Highlight environment issues within the report. Save reports to 'C:\Users\Jon\Documents'.
PS C:\> $Creds = Get-Credential
-PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'win-server-01v.contoso.local' -Credential $Creds -Format Html,Text -OutputFolderPath 'C:\Users\Jon\Documents' -EnableHealthCheck
+PS C:\> New-AsBuiltReport -Report Microsoft.SCVMM -Target 'scvmm-server-01v.contoso.local' -Credential $Creds -Format Html,Text -OutputFolderPath 'C:\Users\Jon\Documents' -EnableHealthCheck
-# Generate a Microsoft SCVMM As Built Report for Server 'win-server-01v.contoso.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 Microsoft.SCVMM -Target 'win-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -SendEmail
+# Generate a Microsoft SCVMM As Built Report for Server 'scvmm-server-01v.contoso.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 Microsoft.SCVMM -Target 'scvmm-server-01v.contoso.local' -Username 'administrator@contoso.local' -Password 'P@ssw0rd' -Format Html,Word -OutputFolderPath 'C:\Users\Jon\Documents' -SendEmail
```
## :x: Known Issues
-- Issues with WinRM when using the IP address instead of the "Fully Qualified Domain Name".
-- The report provides the ability to extract the configuration of the DNS/DHCP/Hyper-V/IIS/FailOver-Cluster services. In order to obtain this information it is required that the servers running these services have the corresponding powershell modules installed.
+- Issues with WinRM when using the IP address instead of the "Fully Qualified Domain Name".
\ No newline at end of file