Src/Public/Invoke-AsBuiltReport.VMware.vSphere.ps1
function Invoke-AsBuiltReport.VMware.vSphere { <# .SYNOPSIS PowerShell script to document the configuration of VMware vSphere infrastucture in Word/HTML/Text formats .DESCRIPTION Documents the configuration of VMware vSphere infrastucture in Word/HTML/Text formats using PScribo. .NOTES Version: 1.3.4-RC2 Author: Tim Carman Twitter: @tpcarman Github: tpcarman Credits: Iain Brighton (@iainbrighton) - PScribo module .LINK https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere #> param ( [String[]] $Target, [PSCredential] $Credential ) Write-PScriboMessage -Plugin "Module" -IsWarning "Please refer to www.asbuiltreport.com for more detailed information about this project." Write-PScriboMessage -Plugin "Module" -IsWarning "Do not forget to update your report configuration file after each new version release." Write-PScriboMessage -Plugin "Module" -IsWarning "Documentation: https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere" Write-PScriboMessage -Plugin "Module" -IsWarning "Issues or bug reporting: https://github.com/AsBuiltReport/AsBuiltReport.VMware.vSphere/issues" # Check the current AsBuiltReport.VMware.vSphere module Try { $InstalledVersion = Get-Module -ListAvailable -Name AsBuiltReport.VMware.vSphere -ErrorAction SilentlyContinue | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version if ($InstalledVersion) { Write-PScriboMessage -Plugin "Module" -IsWarning "AsBuiltReport.VMware.vSphere $($InstalledVersion.ToString()) is currently installed." $LatestVersion = Find-Module -Name AsBuiltReport.VMware.vSphere -Repository PSGallery -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Version if ($LatestVersion -gt $InstalledVersion) { Write-PScriboMessage -Plugin "Module" -IsWarning "AsBuiltReport.VMware.vSphere $($LatestVersion.ToString()) is available." Write-PScriboMessage -Plugin "Module" -IsWarning "Run 'Update-Module -Name AsBuiltReport.VMware.vSphere -Force' to install the latest version." } } } Catch { Write-PscriboMessage -Plugin "Module" -IsWarning $_.Exception.Message } # Check if the required version of VMware PowerCLI is installed Get-RequiredModule -Name 'VMware.PowerCLI' -Version '13.2' # Import Report Configuration $Report = $ReportConfig.Report $InfoLevel = $ReportConfig.InfoLevel $Options = $ReportConfig.Options # Used to set values to TitleCase where required $TextInfo = (Get-Culture).TextInfo #region Script Body #---------------------------------------------------------------------------------------------# # SCRIPT BODY # #---------------------------------------------------------------------------------------------# # Connect to vCenter Server using supplied credentials foreach ($VIServer in $Target) { try { Write-PScriboMessage "Connecting to vCenter Server '$VIServer'." $vCenter = Connect-VIServer $VIServer -Credential $Credential -ErrorAction Stop } catch { Write-Error $_ } #region Generate vSphere report if ($vCenter) { # Check logged in user has sufficient privileges to generate an As Built Report Write-PScriboMessage 'Checking vCenter user privileges.' Try { $UserPermission = Get-VIPermission | Where-Object { $_.Principal -eq $vCenter.User } $AuthMgr = Get-View $($vCenter.ExtensionData.Content.AuthorizationManager) $UserRole = $AuthMgr.RoleList | Where-Object { $_.Name -eq $($UserPermission.Role) } } Catch { Write-PScriboMessage 'Unable to obtain vCenter user privileges.' } # Create a lookup hashtable to quickly link VM MoRefs to Names # Exclude VMware Site Recovery Manager placeholder VMs Write-PScriboMessage 'Creating VM lookup hashtable.' $VMs = Get-VM -Server $vCenter | Where-Object { $_.ExtensionData.Config.ManagedBy.ExtensionKey -notlike 'com.vmware.vcDr*' } | Sort-Object Name $VMLookup = @{ } foreach ($VM in $VMs) { $VMLookup.($VM.Id) = $VM.Name } # Create a lookup hashtable to link Host MoRefs to Names # Exclude VMware HCX hosts and ESX/ESXi versions prior to vSphere 5.0 from VMHost lookup Write-PScriboMessage 'Creating VMHost lookup hashtable.' $VMHosts = Get-VMHost -Server $vCenter | Where-Object { $_.Model -notlike "*VMware Mobility Platform" -and $_.Version -gt 5 } | Sort-Object Name $VMHostLookup = @{ } foreach ($VMHost in $VMHosts) { $VMHostLookup.($VMHost.Id) = $VMHost.Name } # Create a lookup hashtable to link Datastore MoRefs to Names Write-PScriboMessage 'Creating Datastore lookup hashtable.' $Datastores = Get-Datastore -Server $vCenter | Where-Object { ($_.State -eq 'Available') -and ($_.CapacityGB -gt 0) } | Sort-Object Name $DatastoreLookup = @{ } foreach ($Datastore in $Datastores) { $DatastoreLookup.($Datastore.Id) = $Datastore.Name } # Create a lookup hashtable to link VDS Portgroups MoRefs to Names Write-PScriboMessage 'Creating VDPortGroup lookup hashtable.' $VDPortGroups = Get-VDPortgroup -Server $vCenter | Sort-Object Name $VDPortGroupLookup = @{ } foreach ($VDPortGroup in $VDPortGroups) { $VDPortGroupLookup.($VDPortGroup.Key) = $VDPortGroup.Name } # Create a lookup hashtable to link EVC Modes to Names Write-PScriboMessage 'Creating EVC lookup hashtable.' $SupportedEvcModes = $vCenter.ExtensionData.Capability.SupportedEVCMode $EvcModeLookup = @{ } foreach ($EvcMode in $SupportedEvcModes) { $EvcModeLookup.($EvcMode.Key) = $EvcMode.Label } $si = Get-View ServiceInstance -Server $vCenter $extMgr = Get-View -Id $si.Content.ExtensionManager -Server $vCenter #region VMware Update Manager Server Name Write-PScriboMessage 'Checking for VMware Update Manager Server.' $VumServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vcIntegrity' } | Select-Object @{ N = 'Name'; E = { ($_.Server | Where-Object { $_.Type -eq 'SOAP' -and $_.Company -eq 'VMware, Inc.' } | Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } } #endregion VMware Update Manager Server Name #region VxRail Manager Server Name Write-PScriboMessage 'Checking for VxRail Manager Server.' $VxRailMgr = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vxrail' } | Select-Object @{ N = 'Name'; E = { ($_.Server | Where-Object { $_.Type -eq 'HTTPS' } | Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } } #endregion VxRail Manager Server Name #region Site Recovery Manager Server Name Write-PScriboMessage 'Checking for VMware Site Recovery Manager Server.' $SrmServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.vcDr' } | Select-Object @{ N = 'Name'; E = { ($_.Server | Where-Object { $_.Company -eq 'VMware, Inc.' } | Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } } #endregion Site Recovery Manager Server Name #region NSX-T Manager Server Name Write-PScriboMessage 'Checking for VMware NSX-T Manager Server.' $NsxtServer = $extMgr.ExtensionList | Where-Object { $_.Key -eq 'com.vmware.nsx.management.nsxt' } | Select-Object @{ N = 'Name'; E = { ($_.Server | Where-Object { ($_.Company -eq 'VMware') -and ($_.Type -eq 'VIP') } | Select-Object -ExpandProperty Url).Split('/')[2].Split(':')[0] } } #endregion NSX-T Manager Server Name #region Tag Information $TagAssignments = Get-TagAssignment -Server $vCenter $Tags = Get-Tag -Server $vCenter | Sort-Object Name, Category $TagCategories = Get-TagCategory -Server $vCenter | Sort-Object Name | Select-Object Name, Description, Cardinality -Unique #endregion Tag Information #region vCenter Advanced Settings Write-PScriboMessage "Collecting $vCenter advanced settings." $vCenterAdvSettings = Get-AdvancedSetting -Entity $vCenter $vCenterServerName = ($vCenterAdvSettings | Where-Object { $_.name -eq 'VirtualCenter.FQDN' }).Value $vCenterServerName = $vCenterServerName.ToString().ToLower() #endregion vCenter Advanced Settings #region vCenter Server Heading1 Section Section -Style Heading1 $vCenterServerName { #region vCenter Server Section Write-PScriboMessage "vCenter InfoLevel set at $($InfoLevel.vCenter)." if ($InfoLevel.vCenter -ge 1) { Section -Style Heading2 'vCenter Server' { Paragraph "The following sections detail the configuration of vCenter Server $vCenterServerName." BlankLine # Gather basic vCenter Server Information $vCenterServerInfo = [PSCustomObject]@{ 'vCenter Server' = $vCenterServerName 'IP Address' = ($vCenterAdvSettings | Where-Object { $_.name -like 'VirtualCenter.AutoManagedIPV4' }).Value 'Version' = $vCenter.Version 'Build' = $vCenter.Build } #region vCenter Server Summary & Advanced Summary if ($InfoLevel.vCenter -le 2) { $TableParams = @{ Name = "vCenter Server Summary - $vCenterServerName" ColumnWidths = 25, 25, 25, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vCenterServerInfo | Table @TableParams } #endregion vCenter Server Summary & Advanced Summary #region vCenter Server Detailed Information if ($InfoLevel.vCenter -ge 3) { $MemberProps = @{ 'InputObject' = $vCenterServerInfo 'MemberType' = 'NoteProperty' } #region vCenter Server Detail if ($UserRole.Privilege -contains 'Global.Licenses') { $vCenterLicense = Get-License -vCenter $vCenter Add-Member @MemberProps -Name 'Product' -Value $vCenterLicense.Product Add-Member @MemberProps -Name 'License Key' -Value $vCenterLicense.LicenseKey Add-Member @MemberProps -Name 'License Expiration' -Value $vCenterLicense.Expiration } else { Write-PScriboMessage "Insufficient user privileges to report vCenter Server licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned." } Add-Member @MemberProps -Name 'Instance ID' -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'instance.id' }).Value if ($vCenter.Version -ge 6) { Add-Member @MemberProps -Name 'HTTP Port' -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.rhttpproxy.httpport' }).Value Add-Member @MemberProps -Name 'HTTPS Port' -Value ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.rhttpproxy.httpsport' }).Value Add-Member @MemberProps -Name 'Platform Services Controller' -Value ((($vCenterAdvSettings).Where{ $_.name -eq 'config.vpxd.sso.admin.uri' }).Value).Split('/')[2] } if ($VumServer.Name) { Add-Member @MemberProps -Name 'Update Manager Server' -Value $VumServer.Name } if ($SrmServer.Name) { Add-Member @MemberProps -Name 'Site Recovery Manager Server' -Value $SrmServer.Name } if ($NsxtServer.Name) { Add-Member @MemberProps -Name 'NSX-T Manager Server' -Value $NsxtServer.Name } if ($VxRailMgr.Name) { Add-Member @MemberProps -Name 'VxRail Manager Server' -Value $VxRailMgr.Name } if ($Healthcheck.vCenter.Licensing) { $vCenterServerInfo | Where-Object { $_.'Product' -like '*Evaluation*' } | Set-Style -Style Warning -Property 'Product' $vCenterServerInfo | Where-Object { $null -eq $_.'Product' } | Set-Style -Style Warning -Property 'Product' $vCenterServerInfo | Where-Object { $_.'License Key' -like '*-00000-00000' } | Set-Style -Style Warning -Property 'License Key' $vCenterServerInfo | Where-Object { $_.'License Expiration' -eq 'Expired' } | Set-Style -Style Critical -Property 'License Expiration' } $TableParams = @{ Name = "vCenter Server Configuration - $vCenterServerName" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vCenterServerInfo | Table @TableParams #endregion vCenter Server Detail #region vCenter Server Database Settings Section -Style Heading3 'Database Settings' { $vCenterDbInfo = [PSCustomObject]@{ 'Database Type' = $TextInfo.ToTitleCase(($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.odbc.dbtype' }).Value) 'Data Source Name' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'config.vpxd.odbc.dsn' }).Value 'Maximum Database Connection' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'VirtualCenter.MaxDBConnection' }).Value } $TableParams = @{ Name = "Database Settings - $vCenterServerName" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vCenterDbInfo | Table @TableParams } #endregion vCenter Server Database Settings #region vCenter Server Mail Settings Section -Style Heading3 'Mail Settings' { $vCenterMailInfo = [PSCustomObject]@{ 'SMTP Server' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.smtp.server' }).Value 'SMTP Port' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.smtp.port' }).Value 'Mail Sender' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'mail.sender' }).Value } if ($Healthcheck.vCenter.Mail) { $vCenterMailInfo | Where-Object { !($_.'SMTP Server') } | Set-Style -Style Critical -Property 'SMTP Server' $vCenterMailInfo | Where-Object { !($_.'SMTP Port') } | Set-Style -Style Critical -Property 'SMTP Port' $vCenterMailInfo | Where-Object { !($_.'Mail Sender') } | Set-Style -Style Critical -Property 'Mail Sender' } $TableParams = @{ Name = "Mail Settings - $vCenterServerName" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vCenterMailInfo | Table @TableParams } #endregion vCenter Server Mail Settings #region vCenter Server Historical Statistics Section -Style Heading3 'Historical Statistics' { $vCenterHistoricalStats = Get-vCenterStats | Select-Object @{L = 'Interval Duration'; E = { $_.IntervalDuration } }, @{L = 'Interval Enabled'; E = { $_.IntervalEnabled } }, @{L = 'Save Duration'; E = { $_.SaveDuration } }, @{L = 'Statistics Level'; E = { $_.StatsLevel } } -Unique $TableParams = @{ Name = "Historical Statistics - $vCenterServerName" ColumnWidths = 25, 25, 25, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vCenterHistoricalStats | Table @TableParams } #endregion vCenter Server Historical Statistics #region vCenter Server Licensing if ($UserRole.Privilege -contains 'Global.Licenses') { Section -Style Heading3 'Licensing' { $Licenses = Get-License -Licenses | Select-Object Product, @{L = 'License Key'; E = { ($_.LicenseKey) } }, Total, Used, @{L = 'Available'; E = { ($_.total) - ($_.Used) } }, Expiration -Unique if ($Healthcheck.vCenter.Licensing) { $Licenses | Where-Object { $_.Product -eq 'Product Evaluation' } | Set-Style -Style Warning $Licenses | Where-Object { $_.Expiration -eq 'Expired' } | Set-Style -Style Critical } $TableParams = @{ Name = "Licensing - $vCenterServerName" ColumnWidths = 25, 25, 12, 12, 12, 14 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $Licenses | Sort-Object 'Product', 'License Key' | Table @TableParams } } else { Write-PScriboMessage "Insufficient user privileges to report vCenter Server licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned." } #endregion vCenter Server Licensing #region vCenter Server Certificate if ($vCenter.Version -ge 6) { Section -Style Heading3 'Certificate' { $VcenterCertMgmt = [PSCustomObject]@{ 'Country' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.country' }).Value 'Email' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.email' }).Value 'Locality' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.localityName' }).Value 'State' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.state' }).Value 'Organization' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.organizationName' }).Value 'Organization Unit' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.cn.organizationalUnitName' }).Value 'Validity' = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.daysValid'}).Value / 365) years" 'Mode' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.mode' }).Value 'Soft Threshold' = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.softThreshold'}).Value) days" 'Hard Threshold' = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.hardThreshold'}).Value) days" 'Minutes Before' = ($vCenterAdvSettings | Where-Object { $_.name -eq 'vpxd.certmgmt.certs.minutesBefore' }).Value 'Poll Interval' = "$(($vCenterAdvSettings | Where-Object {$_.name -eq 'vpxd.certmgmt.certs.pollIntervalDays'}).Value) days" } $TableParams = @{ Name = "Certificate - $vCenterServerName" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VcenterCertMgmt | Table @TableParams } } #endregion vCenter Server Certificate #region vCenter Server Roles Section -Style Heading3 'Roles' { $VIRoles = Get-VIRole -Server $vCenter | Where-Object { $null -ne $_.PrivilegeList } | Sort-Object Name $VIRoleInfo = foreach ($VIRole in $VIRoles) { [PSCustomObject]@{ 'Role' = $VIRole.Name 'System Role' = Switch ($VIRole.IsSystem) { $true { 'Yes' } $false { 'No' } } 'Privilege List' = ($VIRole.PrivilegeList).Replace(".", " > ") | Select-Object -Unique } } if ($InfoLevel.vCenter -ge 4) { $VIRoleInfo | ForEach-Object { Section -Style NOTOCHeading5 -ExcludeFromTOC $($_.Role) { $TableParams = @{ Name = "Role $($_.Role) - $vCenterServerName" ColumnWidths = 35, 15, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $_ | Table @TableParams } } } else { $TableParams = @{ Name = "Roles - $vCenterServerName" Columns = 'Role', 'System Role' ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VIRoleInfo | Table @TableParams } } #endregion vCenter Server Roles #region vCenter Server Tags if ($Tags) { Section -Style Heading3 'Tags' { $TagInfo = foreach ($Tag in $Tags) { [PSCustomObject] @{ 'Tag' = $Tag.Name 'Description' = Switch ($Tag.Description) { '' { 'None' } default { $Tag.Description } } 'Category' = Switch ($Tag.Category) { '' { 'None' } default { $Tag.Category } } } } $TableParams = @{ Name = "Tags - $vCenterServerName" ColumnWidths = 30, 40, 30 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $TagInfo | Table @TableParams } } #endregion vCenter Server Tags #region vCenter Server Tag Categories if ($TagCategories) { Section -Style Heading3 'Tag Categories' { $TagCategoryInfo = foreach ($TagCategory in $TagCategories) { [PSCustomObject] @{ 'Category' = $TagCategory.Name 'Description' = Switch ($TagCategory.Description) { '' { 'None' } default { $TagCategory.Description } } 'Cardinality' = Switch ($TagCategory.Cardinality) { '' { 'None' } default { $TagCategory.Cardinality } } } } $TableParams = @{ Name = "Tag Categories - $vCenterServerName" ColumnWidths = 30, 40, 30 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $TagCategoryInfo | Table @TableParams } } #endregion vCenter Server Tag Categories #region vCenter Server Tag Assignments if ($TagAssignments) { Section -Style Heading3 'Tag Assignments' { $TagAssignmentInfo = foreach ($TagAssignment in $TagAssignments) { [PSCustomObject]@{ 'Entity' = $TagAssignment.Entity.Name 'Tag' = $TagAssignment.Tag.Name 'Category' = $TagAssignment.Tag.Category } } $TableParams = @{ Name = "Tag Assignments - $vCenterServerName" ColumnWidths = 30, 40, 30 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $TagAssignmentInfo | Sort-Object Entity | Table @TableParams } } #endregion vCenter Server Tag Assignments #region VM Storage Policies if ($UserRole.Privilege -contains 'StorageProfile.View') { $SpbmStoragePolicies = Get-SpbmStoragePolicy | Sort-Object Name if ($SpbmStoragePolicies) { Section -Style Heading3 'VM Storage Policies' { $VmStoragePolicies = foreach ($SpbmStoragePolicy in $SpbmStoragePolicies) { [PSCustomObject]@{ 'VM Storage Policy' = $SpbmStoragePolicy.Name 'Description' = $SpbmStoragePolicy.Description } } $TableParams = @{ Name = "VM Storage Policies - $vCenterServerName" ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VmStoragePolicies | Table @TableParams } } } else { Write-PScriboMessage "Insufficient user privileges to report VM storage policies. Please ensure the user account has the 'Storage Profile > View' privilege assigned." } #endregion VM Storage Policies } #endregion vCenter Server Detailed Information #region vCenter Server Advanced Detail Information if ($InfoLevel.vCenter -ge 4) { #region vCenter Alarms Section -Style Heading3 'Alarms' { $Alarms = Get-AlarmDefinition -PipelineVariable alarm | ForEach-Object -Process { Get-AlarmAction -AlarmDefinition $_ -PipelineVariable action | ForEach-Object -Process { Get-AlarmActionTrigger -AlarmAction $_ | Select-Object @{N = 'Alarm'; E = { $alarm.Name } }, @{N = 'Description'; E = { $alarm.Description } }, @{N = 'Enabled'; E = { Switch ($alarm.Enabled) { $true { 'Enabled' } $false { 'Disabled' } } } }, @{N = 'Entity'; E = { $alarm.Entity.Type } }, @{N = 'Trigger'; E = { "{0}:{1}->{2} (Repeat={3})" -f $action.ActionType, $_.StartStatus, $_.EndStatus, $_.Repeat } }, @{N = 'Trigger Info'; E = { Switch ($action.ActionType) { 'SendEmail' { "To: $($action.To -join ', ') ` Cc: $($action.Cc -join ', ') ` Subject: $($action.Subject) ` Body: $($action.Body)" } 'ExecuteScript' { "$($action.ScriptFilePath)" } default { '--' } } } } } } $Alarms = ($Alarms).Where{ $_.alarm -ne "" } | Sort-Object 'Alarm', 'Trigger' if ($Healthcheck.vCenter.Alarms) { $Alarms | Where-Object { $_.'Enabled' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Enabled' } if ($InfoLevel.vCenter -ge 5) { foreach ($Alarm in $Alarms) { Section -Style NOTOCHeading5 -ExcludeFromTOC $($Alarm.Alarm) { $TableParams = @{ Name = "$($Alarm.Alarm) - $vCenterServerName" List = $true ColumnWidths = 25, 75 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $Alarm | Table @TableParams } } } else { $TableParams = @{ Name = "Alarms - $vCenterServerName" Columns = 'Alarm', 'Description', 'Enabled', 'Entity', 'Trigger' ColumnWidths = 20, 20, 20, 20, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $Alarms | Table @TableParams } } #endregion vCenter Alarms } #endregion vCenter Server Advanced Detail Information #region vCenter Server Comprehensive Information if ($InfoLevel.vCenter -ge 5) { #region vCenter Advanced System Settings Section -Style Heading3 'Advanced System Settings' { $TableParams = @{ Name = "vCenter Advanced System Settings - $vCenterServerName" Columns = 'Name', 'Value' ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vCenterAdvSettings | Sort-Object Name | Table @TableParams } #endregion vCenter Advanced System Settings } #endregion vCenter Server Comprehensive Information } } #endregion vCenter Server Section #region Clusters Write-PScriboMessage "Cluster InfoLevel set at $($InfoLevel.Cluster)." if ($InfoLevel.Cluster -ge 1) { $Clusters = Get-Cluster -Server $vCenter | Sort-Object Name if ($Clusters) { #region Cluster Section Section -Style Heading2 'Clusters' { Paragraph "The following sections detail the configuration of vSphere HA/DRS clusters managed by vCenter Server $vCenterServerName." #region Cluster Advanced Summary if ($InfoLevel.Cluster -le 2) { BlankLine $ClusterInfo = foreach ($Cluster in $Clusters) { [PSCustomObject]@{ 'Cluster' = $Cluster.Name 'Datacenter' = $Cluster | Get-Datacenter '# of Hosts' = $Cluster.ExtensionData.Host.Count '# of VMs' = $Cluster.ExtensionData.VM.Count 'vSphere HA' = Switch ($Cluster.HAEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'vSphere DRS' = Switch ($Cluster.DrsEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Virtual SAN' = Switch ($Cluster.VsanEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'EVC Mode' = Switch ($Cluster.EVCMode) { $null { 'Disabled' } default { $EvcModeLookup."$($Cluster.EVCMode)" } } 'VM Swap File Policy' = Switch ($Cluster.VMSwapfilePolicy) { 'WithVM' { 'With VM' } 'InHostDatastore' { 'In Host Datastore' } default { $Cluster.VMSwapfilePolicy } } } } if ($Healthcheck.Cluster.HAEnabled) { $ClusterInfo | Where-Object { $_.'vSphere HA' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere HA' } if ($Healthcheck.Cluster.DrsEnabled) { $ClusterInfo | Where-Object { $_.'vSphere DRS' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere DRS' } if ($Healthcheck.Cluster.VsanEnabled) { $ClusterInfo | Where-Object { $_.'Virtual SAN' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Virtual SAN' } if ($Healthcheck.Cluster.EvcEnabled) { $ClusterInfo | Where-Object { $_.'EVC Mode' -eq 'Disabled' } | Set-Style -Style Warning -Property 'EVC Mode' } $TableParams = @{ Name = "Cluster Summary - $vCenterServerName" ColumnWidths = 15, 15, 7, 7, 11, 11, 11, 15, 8 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ClusterInfo | Table @TableParams } #endregion Cluster Advanced Summary #region Cluster Detailed Information # TODO: Test Tags if ($InfoLevel.Cluster -ge 3) { foreach ($Cluster in $Clusters) { $ClusterDasConfig = $Cluster.ExtensionData.Configuration.DasConfig $ClusterDrsConfig = $Cluster.ExtensionData.Configuration.DrsConfig $ClusterConfigEx = $Cluster.ExtensionData.ConfigurationEx #region Cluster Section Section -Style Heading3 $Cluster { Paragraph "The following table details the configuration for cluster $Cluster." BlankLine #region Cluster Configuration $ClusterDetail = [PSCustomObject]@{ 'Cluster' = $Cluster.Name 'ID' = $Cluster.Id 'Datacenter' = $Cluster | Get-Datacenter 'Number of Hosts' = $Cluster.ExtensionData.Host.Count 'Number of VMs' = ($Cluster | Get-VM).Count 'vSphere HA' = Switch ($Cluster.HAEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'vSphere DRS' = Switch ($Cluster.DrsEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Virtual SAN' = Switch ($Cluster.VsanEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'EVC Mode' = Switch ($Cluster.EVCMode) { $null { 'Disabled' } default { $EvcModeLookup."$($Cluster.EVCMode)" } } 'VM Swap File Policy' = Switch ($Cluster.VMSwapfilePolicy) { 'WithVM' { 'Virtual machine directory' } 'InHostDatastore' { 'Datastore specified by host' } default { $Cluster.VMSwapfilePolicy } } } $MemberProps = @{ 'InputObject' = $ClusterDetail 'MemberType' = 'NoteProperty' } <# if ($TagAssignments | Where-Object {$_.entity -eq $Cluster}) { Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $Cluster}).Tag -join ',') } #> if ($Healthcheck.Cluster.HAEnabled) { $ClusterDetail | Where-Object { $_.'vSphere HA' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere HA' } if ($Healthcheck.Cluster.DrsEnabled) { $ClusterDetail | Where-Object { $_.'vSphere DRS' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere DRS' } if ($Healthcheck.Cluster.VsanEnabled) { $ClusterDetail | Where-Object { $_.'Virtual SAN' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Virtual SAN' } if ($Healthcheck.Cluster.EvcEnabled) { $ClusterDetail | Where-Object { $_.'EVC Mode' -eq 'Disabled' } | Set-Style -Style Warning -Property 'EVC Mode' } #region Cluster Advanced Detailed Information if ($InfoLevel.Cluster -ge 4) { $ClusterDetail | ForEach-Object { $ClusterHosts = $Cluster | Get-VMHost | Sort-Object Name Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Hosts' -Value ($ClusterHosts.Name -join ', ') $ClusterVMs = $Cluster | Get-VM | Sort-Object Name Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Virtual Machines' -Value ($ClusterVMs.Name -join ', ') } } #endregion Cluster Advanced Detailed Information $TableParams = @{ Name = "Cluster Configuration - $Cluster" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ClusterDetail | Table @TableParams #endregion Cluster Configuration #region vSphere HA Cluster Configuration if ($Cluster.HAEnabled) { Section -Style Heading4 'vSphere HA Configuration' { Paragraph "The following section details the vSphere HA configuration for $Cluster cluster." #region vSphere HA Cluster Failures and Responses Section -Style NOTOCHeading5 -ExcludeFromTOC 'Failures and Responses' { $HAClusterResponses = [PSCustomObject]@{ 'Host Monitoring' = $TextInfo.ToTitleCase($ClusterDasConfig.HostMonitoring) } if ($ClusterDasConfig.HostMonitoring -eq 'Enabled') { $MemberProps = @{ 'InputObject' = $HAClusterResponses 'MemberType' = 'NoteProperty' } if ($ClusterDasConfig.DefaultVmSettings.RestartPriority -eq 'Disabled') { Add-Member @MemberProps -Name 'Host Failure Response' -Value 'Disabled' } else { Add-Member @MemberProps -Name 'Host Failure Response' -Value 'Restart VMs' Switch ($Cluster.HAIsolationResponse) { 'DoNothing' { Add-Member @MemberProps -Name 'Host Isolation Response' -Value 'Disabled' } 'Shutdown' { Add-Member @MemberProps -Name 'Host Isolation Response' -Value 'Shutdown and restart VMs' } 'PowerOff' { Add-Member @MemberProps -Name 'Host Isolation Response' -Value 'Power off and restart VMs' } } Add-Member @MemberProps -Name 'VM Restart Priority' -Value $Cluster.HARestartPriority Switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmStorageProtectionForPDL) { 'disabled' { Add-Member @MemberProps -Name 'Datastore with Permanent Device Loss' -Value 'Disabled' } 'warning' { Add-Member @MemberProps -Name 'Datastore with Permanent Device Loss' -Value 'Issue events' } 'restartAggressive' { Add-Member @MemberProps -Name 'Datastore with Permanent Device Loss' -Value 'Power off and restart VMs' } } Switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmStorageProtectionForAPD) { 'disabled' { Add-Member @MemberProps -Name 'Datastore with All Paths Down' -Value 'Disabled' } 'warning' { Add-Member @MemberProps -Name 'Datastore with All Paths Down' -Value 'Issue events' } 'restartConservative' { Add-Member @MemberProps -Name 'Datastore with All Paths Down' -Value 'Power off and restart VMs (conservative)' } 'restartAggressive' { Add-Member @MemberProps -Name 'Datastore with All Paths Down' -Value 'Power off and restart VMs (aggressive)' } } Switch ($ClusterDasConfig.DefaultVmSettings.VmComponentProtectionSettings.VmReactionOnAPDCleared) { 'none' { Add-Member @MemberProps -Name 'APD recovery after APD timeout' -Value 'Disabled' } 'reset' { Add-Member @MemberProps -Name 'APD recovery after APD timeout' -Value 'Reset VMs' } } } Switch ($ClusterDasConfig.VmMonitoring) { 'vmMonitoringDisabled' { Add-Member @MemberProps -Name 'VM Monitoring' -Value 'Disabled' } 'vmMonitoringOnly' { Add-Member @MemberProps -Name 'VM Monitoring' -Value 'VM monitoring only' } 'vmAndAppMonitoring' { Add-Member @MemberProps -Name 'VM Monitoring' -Value 'VM and application monitoring' } } } if ($Healthcheck.Cluster.HostFailureResponse) { $HAClusterResponses | Where-Object { $_.'Host Failure Response' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Host Failure Response' } if ($Healthcheck.Cluster.HostMonitoring) { $HAClusterResponses | Where-Object { $_.'Host Monitoring' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Host Monitoring' } if ($Healthcheck.Cluster.DatastoreOnPDL) { $HAClusterResponses | Where-Object { $_.'Datastore with Permanent Device Loss' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Datastore with Permanent Device Loss' } if ($Healthcheck.Cluster.DatastoreOnAPD) { $HAClusterResponses | Where-Object { $_.'Datastore with All Paths Down' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Datastore with All Paths Down' } if ($Healthcheck.Cluster.APDTimeout) { $HAClusterResponses | Where-Object { $_.'APD recovery after APD timeout' -eq 'Disabled' } | Set-Style -Style Warning -Property 'APD recovery after APD timeout' } if ($Healthcheck.Cluster.vmMonitoring) { $HAClusterResponses | Where-Object { $_.'VM Monitoring' -eq 'Disabled' } | Set-Style -Style Warning -Property 'VM Monitoring' } $TableParams = @{ Name = "vSphere HA Failures and Responses - $Cluster" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $HAClusterResponses | Table @TableParams } #endregion vSphere HA Cluster Failures and Responses #region vSphere HA Cluster Admission Control Section -Style NOTOCHeading5 -ExcludeFromTOC 'Admission Control' { $HAAdmissionControl = [PSCustomObject]@{ 'Admission Control' = Switch ($Cluster.HAAdmissionControlEnabled) { $true { 'Enabled' } $false { 'Disabled' } } } if ($Cluster.HAAdmissionControlEnabled) { $MemberProps = @{ 'InputObject' = $HAAdmissionControl 'MemberType' = 'NoteProperty' } Add-Member @MemberProps -Name 'Host Failures Cluster Tolerates' -Value $Cluster.HAFailoverLevel Switch ($ClusterDasConfig.AdmissionControlPolicy.GetType().Name) { 'ClusterFailoverHostAdmissionControlPolicy' { Add-Member @MemberProps -Name 'Host Failover Capacity Policy' -Value 'Dedicated failover hosts' } 'ClusterFailoverResourcesAdmissionControlPolicy' { Add-Member @MemberProps -Name 'Host Failover Capacity Policy' -Value 'Cluster resource percentage' } 'ClusterFailoverLevelAdmissionControlPolicy' { Add-Member @MemberProps -Name 'Host Failover Capacity Policy' -Value 'Slot policy' } } Switch ($ClusterDasConfig.AdmissionControlPolicy.AutoComputePercentages) { $true { Add-Member @MemberProps -Name 'Override Calculated Failover Capacity' -Value 'No' } $false { Add-Member @MemberProps -Name 'Override Calculated Failover Capacity' -Value 'Yes' Add-Member @MemberProps -Name 'CPU %' -Value $ClusterDasConfig.AdmissionControlPolicy.CpuFailoverResourcesPercent Add-Member @MemberProps -Name 'Memory %' -Value $ClusterDasConfig.AdmissionControlPolicy.MemoryFailoverResourcesPercent } } if ($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy) { Add-Member @MemberProps -Name 'Slot Policy' -Value 'Fixed slot size' Add-Member @MemberProps -Name 'CPU Slot Size' -Value "$($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy.Cpu) MHz" Add-Member @MemberProps -Name 'Memory Slot Size' -Value "$($ClusterDasConfig.AdmissionControlPolicy.SlotPolicy.Memory) MB" } else { Add-Member @MemberProps -Name 'Slot Policy' -Value 'Cover all powered-on virtual machines' } if ($ClusterDasConfig.AdmissionControlPolicy.ResourceReductionToToleratePercent) { Add-Member @MemberProps -Name 'Performance Degradation VMs Tolerate' -Value "$($ClusterDasConfig.AdmissionControlPolicy.ResourceReductionToToleratePercent)%" } } if ($Healthcheck.Cluster.HAAdmissionControl) { $HAAdmissionControl | Where-Object { $_.'Admission Control' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Admission Control' } $TableParams = @{ Name = "vSphere HA Admission Control - $Cluster" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $HAAdmissionControl | Table @TableParams } #endregion vSphere HA Cluster Admission Control #region vSphere HA Cluster Heartbeat Datastores Section -Style NOTOCHeading5 -ExcludeFromTOC 'Heartbeat Datastores' { $HeartbeatDatastores = [PSCustomObject]@{ 'Heartbeat Selection Policy' = Switch ($ClusterDasConfig.HBDatastoreCandidatePolicy) { 'allFeasibleDsWithUserPreference' { 'Use datastores from the specified list and complement automatically if needed' } 'allFeasibleDs' { 'Automatically select datastores accessible from the host' } 'userSelectedDs' { 'Use datastores only from the specified list' } default { $ClusterDasConfig.HBDatastoreCandidatePolicy } } 'Heartbeat Datastores' = try { (((Get-View -Id $ClusterDasConfig.HeartbeatDatastore -Property Name).Name | Sort-Object) -join ', ') } catch { 'None specified' } } $TableParams = @{ Name = "vSphere HA Heartbeat Datastores - $Cluster" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $HeartbeatDatastores | Table @TableParams } #endregion vSphere HA Cluster Heartbeat Datastores #region vSphere HA Cluster Advanced Options $HAAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterHA' } if ($HAAdvancedSettings) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'vSphere HA Advanced Options' { $HAAdvancedOptions = @() foreach ($HAAdvancedSetting in $HAAdvancedSettings) { $HAAdvancedOption = [PSCustomObject]@{ 'Option' = $HAAdvancedSetting.Name 'Value' = $HAAdvancedSetting.Value } $HAAdvancedOptions += $HAAdvancedOption } $TableParams = @{ Name = "vSphere HA Advanced Options - $Cluster" ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $HAAdvancedOptions | Sort-Object Option | Table @TableParams } } #endregion vSphere HA Cluster Advanced Options } } #endregion vSphere HA Cluster Configuration #region Proactive HA Configuration # TODO: Proactive HA Providers # Proactive HA is only available in vSphere 6.5 and above if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled -and $vCenter.Version -ge 6.5) { Section -Style Heading4 'Proactive HA' { Paragraph "The following section details the Proactive HA configuration for $Cluster cluster." #region Proactive HA Failures and Responses Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'Failures and Responses' { $ProactiveHa = [PSCustomObject]@{ 'Proactive HA' = Switch ($ClusterConfigEx.InfraUpdateHaConfig.Enabled) { $true { 'Enabled' } $false { 'Disabled' } } } if ($ClusterConfigEx.InfraUpdateHaConfig.Enabled) { $ProactiveHaModerateRemediation = Switch ($ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation) { 'MaintenanceMode' { 'Maintenance Mode' } 'QuarantineMode' { 'Quarantine Mode' } default { $ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation } } $ProactiveHaSevereRemediation = Switch ($ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation) { 'MaintenanceMode' { 'Maintenance Mode' } 'QuarantineMode' { 'Quarantine Mode' } default { $ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation } } $MemberProps = @{ 'InputObject' = $ProactiveHa 'MemberType' = 'NoteProperty' } Add-Member @MemberProps -Name 'Automation Level' -Value $ClusterConfigEx.InfraUpdateHaConfig.Behavior if ($ClusterConfigEx.InfraUpdateHaConfig.ModerateRemediation -eq $ClusterConfigEx.InfraUpdateHaConfig.SevereRemediation) { Add-Member @MemberProps -Name 'Remediation' -Value $ProactiveHaModerateRemediation } else { Add-Member @MemberProps -Name 'Remediation' -Value 'Mixed Mode' Add-Member @MemberProps -Name 'Moderate Remediation' -Value $ProactiveHaModerateRemediation Add-Member @MemberProps -Name 'Severe Remediation' -Value $ProactiveHaSevereRemediation } } if ($Healthcheck.Cluster.ProactiveHA) { $ProactiveHa | Where-Object { $_.'Proactive HA' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Proactive HA' } $TableParams = @{ Name = "Proactive HA - $Cluster" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ProactiveHa | Table @TableParams } #endregion Proactive HA Failures and Responses Section } } #endregion Proactive HA Configuration #region vSphere DRS Cluster Configuration if ($Cluster.DrsEnabled) { Section -Style Heading4 'vSphere DRS Configuration' { Paragraph ("The following table details the vSphere DRS configuration " + "for cluster $Cluster.") BlankLine #region vSphere DRS Cluster Specifications $DrsCluster = [PSCustomObject]@{ 'vSphere DRS' = Switch ($Cluster.DrsEnabled) { $true { 'Enabled' } $false { 'Disabled' } } } $MemberProps = @{ 'InputObject' = $DrsCluster 'MemberType' = 'NoteProperty' } Switch ($Cluster.DrsAutomationLevel) { 'Manual' { Add-Member @MemberProps -Name 'Automation Level' -Value 'Manual' } 'PartiallyAutomated' { Add-Member @MemberProps -Name 'Automation Level' -Value 'Partially Automated' } 'FullyAutomated' { Add-Member @MemberProps -Name 'Automation Level' -Value 'Fully Automated' } } Add-Member @MemberProps -Name 'Migration Threshold' -Value $ClusterDrsConfig.VmotionRate Switch ($ClusterConfigEx.ProactiveDrsConfig.Enabled) { $false { Add-Member @MemberProps -Name 'Predictive DRS' -Value 'Disabled' } $true { Add-Member @MemberProps -Name 'Predictive DRS' -Value 'Enabled' } } Switch ($ClusterDrsConfig.EnableVmBehaviorOverrides) { $true { Add-Member @MemberProps -Name 'Virtual Machine Automation' -Value 'Enabled' } $false { Add-Member @MemberProps -Name 'Virtual Machine Automation' -Value 'Disabled' } } if ($Healthcheck.Cluster.DrsEnabled) { $DrsCluster | Where-Object { $_.'vSphere DRS' -eq 'Disabled' } | Set-Style -Style Warning -Property 'vSphere DRS' } if ($Healthcheck.Cluster.DrsAutomationLevelFullyAuto) { $DrsCluster | Where-Object { $_.'Automation Level' -ne 'Fully Automated' } | Set-Style -Style Warning -Property 'Automation Level' } $TableParams = @{ Name = "vSphere DRS Configuration - $Cluster" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DrsCluster | Table @TableParams #endregion vSphere DRS Cluster Specfications #region DRS Cluster Additional Options $DrsAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterDRS' } if ($DrsAdvancedSettings) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Additional Options' { $DrsAdditionalOptions = [PSCustomObject] @{ 'VM Distribution' = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'TryBalanceVmsPerHost' }).Value) { '1' { 'Enabled' } $null { 'Disabled' } } 'Memory Metric for Load Balancing' = Switch (($DrsAdvancedSettings | Where-Object { $_.name -eq 'PercentIdleMBInMemDemand' }).Value) { '100' { 'Enabled' } $null { 'Disabled' } } 'CPU Over-Commitment' = if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerCore' }).Value) { 'Enabled' } else { 'Disabled' } } $MemberProps = @{ 'InputObject' = $DrsAdditionalOptions 'MemberType' = 'NoteProperty' } if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerCore' }).Value) { Add-Member @MemberProps -Name 'Over-Commitment Ratio' -Value "$(($DrsAdvancedSettings | Where-Object {$_.name -eq 'MaxVcpusPerCore'}).Value):1 (vCPU:pCPU)" } if (($DrsAdvancedSettings | Where-Object { $_.name -eq 'MaxVcpusPerClusterPct' }).Value) { Add-Member @MemberProps -Name 'Over-Commitment Ratio (% of cluster capacity)' -Value "$(($DrsAdvancedSettings | Where-Object {$_.name -eq 'MaxVcpusPerClusterPct'}).Value) %" } $TableParams = @{ Name = "DRS Additional Options - $Cluster" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DrsAdditionalOptions | Table @TableParams } } #endregion DRS Cluster Additional Options #region vSphere DPM Configuration if ($ClusterConfigEx.DpmConfigInfo.Enabled) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Power Management' { $DpmConfig = [PSCustomObject]@{ 'DPM' = Switch ($ClusterConfigEx.DpmConfigInfo.Enabled) { $true { 'Enabled' } $false { 'Disabled' } } } $MemberProps = @{ 'InputObject' = $DpmConfig 'MemberType' = 'NoteProperty' } Switch ($ClusterConfigEx.DpmConfigInfo.DefaultDpmBehavior) { 'manual' { Add-Member @MemberProps -Name 'Automation Level' -Value 'Manual' } 'automated' { Add-Member @MemberProps -Name 'Automation Level' -Value 'Automated' } } if ($ClusterConfigEx.DpmConfigInfo.DefaultDpmBehavior -eq 'automated') { Add-Member @MemberProps -Name 'DPM Threshold' -Value $ClusterConfigEx.DpmConfigInfo.HostPowerActionRate } $TableParams = @{ Name = "vSphere DPM - $Cluster" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DpmConfig | Table @TableParams } } #endregion vSphere DPM Configuration #region vSphere DRS Cluster Advanced Options $DrsAdvancedSettings = $Cluster | Get-AdvancedSetting | Where-Object { $_.Type -eq 'ClusterDRS' } if ($DrsAdvancedSettings) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Advanced Options' { $DrsAdvancedOptions = @() foreach ($DrsAdvancedSetting in $DrsAdvancedSettings) { $DrsAdvancedOption = [PSCustomObject]@{ 'Option' = $DrsAdvancedSetting.Name 'Value' = $DrsAdvancedSetting.Value } $DrsAdvancedOptions += $DrsAdvancedOption } $TableParams = @{ Name = "vSphere DRS Advanced Options - $Cluster" ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DrsAdvancedOptions | Sort-Object Option | Table @TableParams } } #endregion vSphere DRS Cluster Advanced Options #region vSphere DRS Cluster Group $DrsClusterGroups = $Cluster | Get-DrsClusterGroup if ($DrsClusterGroups) { #region vSphere DRS Cluster Group Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'DRS Cluster Groups' { $DrsGroups = foreach ($DrsClusterGroup in $DrsClusterGroups) { [PSCustomObject]@{ 'DRS Cluster Group' = $DrsClusterGroup.Name 'Type' = Switch ($DrsClusterGroup.GroupType) { 'VMGroup' { 'VM Group' } 'VMHostGroup' { 'Host Group' } default { $DrsClusterGroup.GroupType } } 'Members' = Switch (($DrsClusterGroup.Member).Count -gt 0) { $true { ($DrsClusterGroup.Member | Sort-Object) -join ', ' } $false { "None" } } } } $TableParams = @{ Name = "DRS Cluster Groups - $Cluster" ColumnWidths = 42, 16, 42 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DrsGroups | Sort-Object 'DRS Cluster Group', 'Type' | Table @TableParams } #endregion vSphere DRS Cluster Group Section #region vSphere DRS Cluster VM/Host Rules $DrsVMHostRules = $Cluster | Get-DrsVMHostRule if ($DrsVMHostRules) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'DRS VM/Host Rules' { $DrsVMHostRuleDetail = foreach ($DrsVMHostRule in $DrsVMHostRules) { [PSCustomObject]@{ 'DRS VM/Host Rule' = $DrsVMHostRule.Name 'Type' = Switch ($DrsVMHostRule.Type) { 'MustRunOn' { 'Must run on hosts in group' } 'ShouldRunOn' { 'Should run on hosts in group' } 'MustNotRunOn' { 'Must not run on hosts in group' } 'ShouldNotRunOn' { 'Should not run on hosts in group' } default { $DrsVMHostRule.Type } } 'Enabled' = Switch ($DrsVMHostRule.Enabled) { $true { 'Yes' } $False { 'No' } } 'VM Group' = $DrsVMHostRule.VMGroup 'Host Group' = $DrsVMHostRule.VMHostGroup } } if ($Healthcheck.Cluster.DrsVMHostRules) { $DrsVMHostRuleDetail | Where-Object { $_.Enabled -eq 'No' } | Set-Style -Style Warning -Property 'Enabled' } $TableParams = @{ Name = "DRS VM/Host Rules - $Cluster" ColumnWidths = 22, 22, 12, 22, 22 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DrsVMHostRuleDetail | Sort-Object 'DRS VM/Host Rule' | Table @TableParams } } #endregion vSphere DRS Cluster VM/Host Rules #region vSphere DRS Cluster Rules $DrsRules = $Cluster | Get-DrsRule if ($DrsRules) { #region vSphere DRS Cluster Rules Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'DRS Rules' { $DrsRuleDetail = foreach ($DrsRule in $DrsRules) { [PSCustomObject]@{ 'DRS Rule' = $DrsRule.Name 'Type' = Switch ($DrsRule.Type) { 'VMAffinity' { 'Keep Vitrual Machines Together' } 'VMAntiAffinity' { 'Separate Virtual Machines' } } 'Enabled' = Switch ($DrsRule.Enabled) { $true { 'Yes' } $False { 'No' } } 'Mandatory' = $DrsRule.Mandatory 'Virtual Machines' = ($DrsRule.VMIds | ForEach-Object { (Get-View -Id $_).name }) -join ', ' } if ($Healthcheck.Cluster.DrsRules) { $DrsRuleDetail | Where-Object { $_.Enabled -eq 'No' } | Set-Style -Style Warning -Property 'Enabled' } } $TableParams = @{ Name = "DRS Rules - $Cluster" ColumnWidths = 26, 25, 12, 12, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DrsRuleDetail | Sort-Object Type | Table @TableParams } #endregion vSphere DRS Cluster Rules Section } #endregion vSphere DRS Cluster Rules } #endregion vSphere DRS Cluster Group #region Cluster VM Overrides $DrsVmOverrides = $Cluster.ExtensionData.Configuration.DrsVmConfig $DasVmOverrides = $Cluster.ExtensionData.Configuration.DasVmConfig if ($DrsVmOverrides -or $DasVmOverrides) { #region VM Overrides Section Section -Style NOTOCHeading4 -ExcludeFromTOC 'VM Overrides' { #region vSphere DRS VM Overrides if ($DrsVmOverrides) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'vSphere DRS' { $DrsVmOverrideDetails = foreach ($DrsVmOverride in $DrsVmOverrides) { [PSCustomObject]@{ 'Virtual Machine' = $VMLookup."$($DrsVmOverride.Key.Type)-$($DrsVmOverride.Key.Value)" 'vSphere DRS Automation Level' = if ($DrsVmOverride.Enabled -eq $false) { 'Disabled' } else { Switch ($DrsVmOverride.Behavior) { 'manual' { 'Manual' } 'partiallyAutomated' { 'Partially Automated' } 'fullyAutomated' { 'Fully Automated' } default { $DrsVmOverride.Behavior } } } } } $TableParams = @{ Name = "DRS VM Overrides - $Cluster" ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DrsVmOverrideDetails | Sort-Object 'Virtual Machine' | Table @TableParams } } #endregion vSphere DRS VM Overrides #region vSphere HA VM Overrides if ($DasVmOverrides) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'vSphere HA' { $DasVmOverrideDetails = foreach ($DasVmOverride in $DasVmOverrides) { [PSCustomObject]@{ 'Virtual Machine' = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" 'VM Restart Priority' = Switch ($DasVmOverride.DasSettings.RestartPriority) { $null { '--' } 'lowest' { 'Lowest' } 'low' { 'Low' } 'medium' { 'Medium' } 'high' { 'High' } 'highest' { 'Highest' } 'disabled' { 'Disabled' } 'clusterRestartPriority' { 'Cluster default' } } 'VM Dependency Restart Condition Timeout' = Switch ($DasVmOverride.DasSettings.RestartPriorityTimeout) { $null { '--' } '-1' { 'Disabled' } default { "$($DasVmOverride.DasSettings.RestartPriorityTimeout) seconds" } } 'Host Isolation Response' = Switch ($DasVmOverride.DasSettings.IsolationResponse) { $null { '--' } 'none' { 'Disabled' } 'powerOff' { 'Power off and restart VMs' } 'shutdown' { 'Shutdown and restart VMs' } 'clusterIsolationResponse' { 'Cluster default' } } } } $TableParams = @{ Name = "HA VM Overrides - $Cluster" ColumnWidths = 25, 25, 25, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DasVmOverrideDetails | Sort-Object 'Virtual Machine' | Table @TableParams #region PDL/APD Protection Settings Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'PDL/APD Protection Settings' { $DasVmOverridePdlApd = foreach ($DasVmOverride in $DasVmOverrides) { $DasVmComponentProtection = $DasVmOverride.DasSettings.VmComponentProtectionSettings [PSCustomObject]@{ 'Virtual Machine' = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" 'PDL Failure Response' = Switch ($DasVmComponentProtection.VmStorageProtectionForPDL) { $null { '--' } 'clusterDefault' { 'Cluster default' } 'warning' { 'Issue events' } 'restartAggressive' { 'Power off and restart VMs' } 'disabled' { 'Disabled' } } 'APD Failure Response' = Switch ($DasVmComponentProtection.VmStorageProtectionForAPD) { $null { '--' } 'clusterDefault' { 'Cluster default' } 'warning' { 'Issue events' } 'restartConservative' { 'Power off and restart VMs - Conservative restart policy' } 'restartAggressive' { 'Power off and restart VMs - Aggressive restart policy' } 'disabled' { 'Disabled' } } 'VM Failover Delay' = Switch ($DasVmComponentProtection.VmTerminateDelayForAPDSec) { $null { '--' } '-1' { 'Disabled' } default { "$(($DasVmComponentProtection.VmTerminateDelayForAPDSec)/60) minutes" } } 'Response Recovery' = Switch ($DasVmComponentProtection.VmReactionOnAPDCleared) { $null { '--' } 'reset' { 'Reset VMs' } 'disabled' { 'Disabled' } 'useClusterDefault' { 'Cluster default' } } } } $TableParams = @{ Name = "HA VM Overrides PDL/APD Settings - $Cluster" ColumnWidths = 20, 20, 20, 20, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DasVmOverridePdlApd | Sort-Object 'Virtual Machine' | Table @TableParams } #endregion PDL/APD Protection Settings Section #region VM Monitoring Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'VM Monitoring' { $DasVmOverrideVmMonitoring = foreach ($DasVmOverride in $DasVmOverrides) { $DasVmMonitoring = $DasVmOverride.DasSettings.VmToolsMonitoringSettings [PSCustomObject]@{ 'Virtual Machine' = $VMLookup."$($DasVmOverride.Key.Type)-$($DasVmOverride.Key.Value)" 'VM Monitoring' = Switch ($DasVmMonitoring.VmMonitoring) { $null { '--' } 'vmMonitoringDisabled' { 'Disabled' } 'vmMonitoringOnly' { 'VM Monitoring Only' } 'vmAndAppMonitoring' { 'VM and App Monitoring' } } 'Failure Interval' = Switch ($DasVmMonitoring.FailureInterval) { $null { '--' } default { if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { '--' } else { "$($DasVmMonitoring.FailureInterval) seconds" } } } 'Minimum Uptime' = Switch ($DasVmMonitoring.MinUptime) { $null { '--' } default { if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { '--' } else { "$($DasVmMonitoring.MinUptime) seconds" } } } 'Maximum Per-VM Resets' = Switch ($DasVmMonitoring.MaxFailures) { $null { '--' } default { if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { '--' } else { $DasVmMonitoring.MaxFailures } } } 'Maximum Resets Time Window' = Switch ($DasVmMonitoring.MaxFailureWindow) { $null { '--' } '-1' { 'No window' } default { if ($DasVmMonitoring.VmMonitoring -eq 'vmMonitoringDisabled') { '--' } else { "Within $(($DasVmMonitoring.MaxFailureWindow)/3600) hrs" } } } } } $TableParams = @{ Name = "HA VM Overrides VM Monitoring - $Cluster" ColumnWidths = 40, 12, 12, 12, 12, 12 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DasVmOverrideVmMonitoring | Sort-Object 'Virtual Machine' | Table @TableParams } #endregion VM Monitoring Section } } #endregion vSphere HA VM Overrides } #endregion VM Overrides Section } #endregion Cluster VM Overrides #region Cluster VUM Baselines if ($UserRole.Privilege -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { if ($VUMConnection) { if ("Desktop" -eq $PSVersionTable.PsEdition) { $ClusterPatchBaselines = $Cluster | Get-PatchBaseline } else { Write-PScriboMessage 'Cluster VUM baseline information is not currently available with your version of PowerShell.' } if ($ClusterPatchBaselines) { Section -Style Heading4 'Update Manager Baselines' { $ClusterBaselines = foreach ($ClusterBaseline in $ClusterPatchBaselines) { [PSCustomObject]@{ 'Baseline' = $ClusterBaseline.Name 'Description' = $ClusterBaseline.Description 'Type' = $ClusterBaseline.BaselineType 'Target Type' = $ClusterBaseline.TargetType 'Last Update Time' = ($ClusterBaseline.LastUpdateTime).ToLocalTime() '# of Patches' = $ClusterBaseline.CurrentPatches.Count } } $TableParams = @{ Name = "Update Manager Baselines - $Cluster" ColumnWidths = 25, 25, 10, 10, 20, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ClusterBaselines | Sort-Object 'Baseline' | Table @TableParams } } if ($Healthcheck.Cluster.VUMCompliance) { $ClusterComplianceInfo | Where-Object { $_.Status -eq 'Unknown' } | Set-Style -Style Warning $ClusterComplianceInfo | Where-Object { $_.Status -eq 'Not Compliant' -or $_.Status -eq 'Incompatible' } | Set-Style -Style Critical } $TableParams = @{ Name = "Update Manager Compliance - $Cluster" ColumnWidths = 25, 50, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ClusterComplianceInfo | Sort-Object Name, Baseline | Table @TableParams } } else { Write-PScriboMessage "Insufficient user privileges to report Cluster baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned." } #endregion Cluster VUM Baselines #region Cluster VUM Compliance (Advanced Detail Information) if ($UserRole.Privilege -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { if ($InfoLevel.Cluster -ge 4 -and $VumServer.Name) { if ("Desktop" -eq $PSVersionTable.PsEdition) { $ClusterCompliances = $Cluster | Get-Compliance } else { Write-PScriboMessage 'Cluster VUM compliance information is not currently available with your version of PowerShell.' } if ($ClusterCompliances) { Section -Style Heading4 'Update Manager Compliance' { $ClusterComplianceInfo = foreach ($ClusterCompliance in $ClusterCompliances) { [PSCustomObject]@{ 'Entity' = $ClusterCompliance.Entity 'Baseline' = $ClusterCompliance.Baseline.Name 'Status' = Switch ($ClusterCompliance.Status) { 'NotCompliant' { 'Not Compliant' } default { $ClusterCompliance.Status } } } } if ($Healthcheck.Cluster.VUMCompliance) { $ClusterComplianceInfo | Where-Object { $_.Status -eq 'Unknown' } | Set-Style -Style Warning $ClusterComplianceInfo | Where-Object { $_.Status -eq 'Not Compliant' -or $_.Status -eq 'Incompatible' } | Set-Style -Style Critical } $TableParams = @{ Name = "Update Manager Compliance - $Cluster" ColumnWidths = 25, 50, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ClusterComplianceInfo | Sort-Object Entity, Baseline | Table @TableParams } } } } else { Write-PScriboMessage "Insufficient user privileges to report Cluster compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned." } #endregion Cluster VUM Compliance (Advanced Detail Information) #region Cluster Permissions Section -Style NOTOCHeading4 -ExcludeFromTOC 'Permissions' { Paragraph "The following table details the permissions assigned to cluster $Cluster." BlankLine $VIPermissions = $Cluster | Get-VIPermission $ClusterVIPermissions = foreach ($VIPermission in $VIPermissions) { [PSCustomObject]@{ 'User/Group' = $VIPermission.Principal 'Is Group?' = Switch ($VIPermission.IsGroup) { $true { 'Yes' } $false { 'No' } } 'Role' = $VIPermission.Role 'Defined In' = $VIPermission.Entity 'Propagate' = Switch ($VIPermission.Propagate) { $true { 'Yes' } $false { 'No' } } } } $TableParams = @{ Name = "Permissions - $Cluster" ColumnWidths = 42, 12, 20, 14, 12 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ClusterVIPermissions | Sort-Object 'User/Group' | Table @TableParams } #endregion Cluster Permissions } } #endregion vSphere DRS Cluster Configuration } #endregion Cluster Section } } #endregion Cluster Detailed Information } #endregion Cluster Section } } #endregion Clusters #region Resource Pool Section Write-PScriboMessage "ResourcePool InfoLevel set at $($InfoLevel.ResourcePool)." if ($InfoLevel.ResourcePool -ge 1) { $ResourcePools = Get-ResourcePool -Server $vCenter | Sort-Object Parent, Name if ($ResourcePools) { #region Resource Pools Section Section -Style Heading2 'Resource Pools' { Paragraph "The following sections detail the configuration of resource pools managed by vCenter Server $vCenterServerName." #region Resource Pool Advanced Summary if ($InfoLevel.ResourcePool -le 2) { BlankLine $ResourcePoolInfo = foreach ($ResourcePool in $ResourcePools) { [PSCustomObject]@{ 'Resource Pool' = $ResourcePool.Name 'Parent' = $ResourcePool.Parent 'CPU Shares Level' = $ResourcePool.CpuSharesLevel 'CPU Reservation MHz' = $ResourcePool.CpuReservationMHz 'CPU Limit MHz' = Switch ($ResourcePool.CpuLimitMHz) { '-1' { 'Unlimited' } default { $ResourcePool.CpuLimitMHz } } 'Memory Shares Level' = $ResourcePool.MemSharesLevel 'Memory Reservation' = [math]::Round($ResourcePool.MemReservationGB, 2) 'Memory Limit GB' = Switch ($ResourcePool.MemLimitGB) { '-1' { 'Unlimited' } default { [math]::Round($ResourcePool.MemLimitGB, 2) } } } } $TableParams = @{ Name = "Resource Pool Summary - $($vCenterServerName)" ColumnWidths = 20, 20, 10, 10, 10, 10, 10, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ResourcePoolInfo | Sort-Object Name | Table @TableParams } #endregion Resource Pool Advanced Summary #region Resource Pool Detailed Information # TODO: Test Tags if ($InfoLevel.ResourcePool -ge 3) { foreach ($ResourcePool in $ResourcePools) { Section -Style Heading3 $ResourcePool.Name { $ResourcePoolDetail = [PSCustomObject]@{ 'Resource Pool' = $ResourcePool.Name 'ID' = $ResourcePool.Id 'Parent' = $ResourcePool.Parent 'CPU Shares Level' = $ResourcePool.CpuSharesLevel 'Number of CPU Shares' = $ResourcePool.NumCpuShares 'CPU Reservation' = "$($ResourcePool.CpuReservationMHz) MHz" 'CPU Expandable Reservation' = Switch ($ResourcePool.CpuExpandableReservation) { $true { 'Enabled' } $false { 'Disabled' } } 'CPU Limit MHz' = Switch ($ResourcePool.CpuLimitMHz) { '-1' { 'Unlimited' } default { "$($ResourcePool.CpuLimitMHz) MHz" } } 'Memory Shares Level' = $ResourcePool.MemSharesLevel 'Number of Memory Shares' = $ResourcePool.NumMemShares 'Memory Reservation' = "$([math]::Round($ResourcePool.MemReservationGB, 2)) GB" 'Memory Expandable Reservation' = Switch ($ResourcePool.MemExpandableReservation) { $true { 'Enabled' } $false { 'Disabled' } } 'Memory Limit' = Switch ($ResourcePool.MemLimitGB) { '-1' { 'Unlimited' } default { "$([math]::Round($ResourcePool.MemLimitGB, 2)) GB" } } 'Number of VMs' = $ResourcePool.ExtensionData.VM.Count } <# $MemberProps = @{ 'InputObject' = $ResourcePoolDetail 'MemberType' = 'NoteProperty' } if ($TagAssignments | Where-Object {$_.entity -eq $ResourcePool}) { Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $ResourcePool}).Tag -join ',') } #> #region Resource Pool Advanced Detail Information if ($InfoLevel.ResourcePool -ge 4) { $ResourcePoolDetail | ForEach-Object { # Query for VMs by resource pool Id $ResourcePoolId = $_.Id $ResourcePoolVMs = $VMs | Where-Object { $_.ResourcePoolId -eq $ResourcePoolId } | Sort-Object Name Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Virtual Machines' -Value ($ResourcePoolVMs.Name -join ', ') } } #endregion Resource Pool Advanced Detail Information $TableParams = @{ Name = "Resource Pool Configuration - $($ResourcePool.Name)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ResourcePoolDetail | Table @TableParams } } } #endregion Resource Pool Detailed Information } #endregion Resource Pools Section } } #endregion Resource Pool Section #region ESXi VMHost Section Write-PScriboMessage "VMHost InfoLevel set at $($InfoLevel.VMHost)." if ($InfoLevel.VMHost -ge 1) { if ($VMHosts) { #region Hosts Section Section -Style Heading2 'Hosts' { Paragraph "The following sections detail the configuration of VMware ESXi hosts managed by vCenter Server $vCenterServerName." #region ESXi Host Advanced Summary if ($InfoLevel.VMHost -le 2) { BlankLine $VMHostInfo = foreach ($VMHost in $VMHosts) { [PSCustomObject]@{ 'Host' = $VMHost.Name 'Version' = $VMHost.Version 'Build' = $VMHost.Build 'Parent' = $VMHost.Parent 'Connection State' = Switch ($VMHost.ConnectionState) { 'NotResponding' { 'Not Responding' } default { $TextInfo.ToTitleCase($VMHost.ConnectionState) } } 'CPU Sockets' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuPackages 'CPU Cores' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuCores 'Memory GB' = [math]::Round($VMHost.MemoryTotalGB, 0) '# of VMs' = $VMHost.ExtensionData.Vm.Count } } if ($Healthcheck.VMHost.ConnectionState) { $VMHostInfo | Where-Object { $_.'Connection State' -eq 'Maintenance' } | Set-Style -Style Warning $VMHostInfo | Where-Object { $_.'Connection State' -eq 'Not Responding' } | Set-Style -Style Critical $VMHostInfo | Where-Object { $_.'Connection State' -eq 'Disconnected' } | Set-Style -Style Critical } $TableParams = @{ Name = "Host Summary - $($vCenterServerName)" ColumnWidths = 17, 9, 11, 15, 13, 9, 9, 9, 8 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostInfo | Table @TableParams } #endregion ESXi Host Advanced Summary #region ESXi Host Detailed Information if ($InfoLevel.VMHost -ge 3) { #region foreach VMHost Detailed Information loop foreach ($VMHost in ($VMHosts | Where-Object { $_.ConnectionState -eq 'Connected' -or $_.ConnectionState -eq 'Maintenance' })) { #region VMHost Section Section -Style Heading3 $VMHost { # TODO: Host Certificate, Swap File Location # TODO: Test Tags #region ESXi Host Hardware Section Section -Style Heading4 'Hardware' { Paragraph "The following section details the host hardware configuration for $VMHost." BlankLine #region ESXi Host Specifications $VMHostUptime = Get-Uptime -VMHost $VMHost $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter $ScratchLocation = Get-AdvancedSetting -Entity $VMHost | Where-Object { $_.Name -eq 'ScratchConfig.CurrentScratchLocation' } $VMHostDetail = [PSCustomObject]@{ 'Host' = $VMHost.Name 'Connection State' = Switch ($VMHost.ConnectionState) { 'NotResponding' { 'Not Responding' } default { $TextInfo.ToTitleCase($VMHost.ConnectionState) } } 'ID' = $VMHost.Id 'Parent' = $VMHost.Parent 'Manufacturer' = $VMHost.Manufacturer 'Model' = $VMHost.Model 'Serial Number' = Switch ($VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber) { $null { '--' } default { $VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber } } 'Asset Tag' = Switch ($VMHost.ExtensionData.Summary.Hardware.OtherIdentifyingInfo[0].IdentifierValue) { '' { 'Unknown' } $null { 'Unknown' } default { $VMHost.ExtensionData.Summary.Hardware.OtherIdentifyingInfo[0].IdentifierValue } } 'Processor Type' = $VMHost.Processortype 'HyperThreading' = Switch ($VMHost.HyperthreadingActive) { $true { 'Enabled' } $false { 'Disabled' } } 'Number of CPU Sockets' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuPackages 'Number of CPU Cores' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuCores 'Number of CPU Threads' = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuThreads 'CPU Total / Used' = "$([math]::Round(($VMHost.CpuTotalMhz) / 1000, 2)) GHz / $([math]::Round(($VMHost.CpuUsageMhz) / 1000, 2)) GHz" 'Memory Total / Used' = "$([math]::Round($VMHost.MemoryTotalGB, 2)) GB / $([math]::Round($VMHost.MemoryUsageGB, 2)) GB" 'NUMA Nodes' = $VMHost.ExtensionData.Hardware.NumaInfo.NumNodes 'Number of NICs' = $VMHost.ExtensionData.Summary.Hardware.NumNics 'Number of HBAs' = $VMHost.ExtensionData.Summary.Hardware.NumHBAs 'Number of Datastores' = ($VMHost.ExtensionData.Datastore).Count 'Number of VMs' = $VMHost.ExtensionData.VM.Count 'Maximum EVC Mode' = $EvcModeLookup."$($VMHost.MaxEVCMode)" 'EVC Graphics Mode' = Switch ($VMHost.ExtensionData.Summary.CurrentEVCGraphicsModeKey) { $null { 'Not applicable' } default { $VMHost.ExtensionData.Summary.CurrentEVCGraphicsModeKey } } 'Power Management Policy' = $VMHost.ExtensionData.Hardware.CpuPowerManagementInfo.CurrentPolicy 'Scratch Location' = $ScratchLocation.Value 'Bios Version' = $VMHost.ExtensionData.Hardware.BiosInfo.BiosVersion 'Bios Release Date' = $VMHost.ExtensionData.Hardware.BiosInfo.ReleaseDate 'ESXi Version' = $VMHost.Version 'ESXi Build' = $VMHost.build 'Boot Time' = ($VMHost.ExtensionData.Runtime.Boottime).ToLocalTime() 'Uptime Days' = $VMHostUptime.UptimeDays } $MemberProps = @{ 'InputObject' = $VMHostDetail 'MemberType' = 'NoteProperty' } if ($UserRole.Privilege -contains 'Global.Licenses') { $VMHostLicense = Get-License -VMHost $VMHost Add-Member @MemberProps -Name 'Product' -Value $VMHostLicense.Product Add-Member @MemberProps -Name 'License Key' -Value $VMHostLicense.LicenseKey Add-Member @MemberProps -Name 'License Expiration' -Value $VMHostLicense.Expiration } else { Write-PScriboMessage "Insufficient user privileges to report ESXi host licensing. Please ensure the user account has the 'Global > Licenses' privilege assigned." } <# if ($TagAssignments | Where-Object {$_.entity -eq $VMHost}) { Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VMHost}).Tag -join ',') } #> if ($Healthcheck.VMHost.ConnectionState) { $VMHostDetail | Where-Object { $_.'Connection State' -eq 'Maintenance' } | Set-Style -Style Warning -Property 'Connection State' } if ($Healthcheck.VMHost.HyperThreading) { $VMHostDetail | Where-Object { $_.'HyperThreading' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Disabled' } if ($Healthcheck.VMHost.Licensing) { $VMHostDetail | Where-Object { $_.'Product' -like '*Evaluation*' } | Set-Style -Style Warning -Property 'Product' $VMHostDetail | Where-Object { $_.'License Key' -like '*-00000-00000' } | Set-Style -Style Warning -Property 'License Key' $VMHostDetail | Where-Object { $_.'License Expiration' -eq 'Expired' } | Set-Style -Style Critical -Property 'License Expiration' } if ($Healthcheck.VMHost.ScratchLocation) { $VMHostDetail | Where-Object { $_.'Scratch Location' -eq '/tmp/scratch' } | Set-Style -Style Warning -Property 'Scratch Location' } if ($Healthcheck.VMHost.UpTimeDays) { $VMHostDetail | Where-Object { $_.'Uptime Days' -ge 275 -and $_.'Uptime Days' -lt 365 } | Set-Style -Style Warning -Property 'Uptime Days' $VMHostDetail | Where-Object { $_.'Uptime Days' -ge 365 } | Set-Style -Style Critical -Property 'Uptime Days' } $TableParams = @{ Name = "Hardware Configuration - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostDetail | Table @TableParams #endregion ESXi Host Specifications #region ESXi IPMI/BMC Settings Try { $VMHostIPMI = $esxcli.hardware.ipmi.bmc.get.invoke() } Catch { Write-PScriboMessage -IsWarning "Unable to collect IPMI / BMC configuration from $VMHost." } if ($VMHostIPMI) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'IPMI / BMC' { $VMHostIPMIInfo = [PSCustomObject]@{ 'Manufacturer' = $VMHostIPMI.Manufacturer 'MAC Address' = $VMHostIPMI.MacAddress 'IP Address' = $VMHostIPMI.IPv4Address 'Subnet Mask' = $VMHostIPMI.IPv4Subnet 'Gateway' = $VMHostIPMI.IPv4Gateway 'Firmware Version' = $VMHostIPMI.BMCFirmwareVersion } $TableParams = @{ Name = "IPMI / BMC - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostIPMIInfo | Table @TableParams } } #endregion ESXi IPMI/BMC Settings #region ESXi Host Boot Device Section -Style NOTOCHeading5 -ExcludeFromTOC 'Boot Device' { $ESXiBootDevice = Get-ESXiBootDevice -VMHost $VMHost $VMHostBootDevice = [PSCustomObject]@{ 'Host' = $ESXiBootDevice.Host 'Device' = $ESXiBootDevice.Device 'Boot Type' = $ESXiBootDevice.BootType 'Vendor' = $ESXiBootDevice.Vendor 'Model' = $ESXiBootDevice.Model 'Size' = Switch ($ESXiBootDevice.SizeMB) { 'N/A' { 'N/A' } default { "$([math]::Round($ESXiBootDevice.SizeMB / 1024, 2)) GB" } } 'Is SAS' = $ESXiBootDevice.IsSAS 'Is SSD' = $ESXiBootDevice.IsSSD 'Is USB' = $ESXiBootDevice.IsUSB } $TableParams = @{ Name = "Boot Device - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostBootDevice | Table @TableParams } #endregion ESXi Host Boot Devices #region ESXi Host PCI Devices Section -Style NOTOCHeading5 -ExcludeFromTOC 'PCI Devices' { <# Move away from esxcli.v2 implementation to be compatible with 8.x branch. 'Slot Description' information does not seem to be available through the API Create an array with PCI Address and VMware Devices (vmnic,vmhba,?vmgfx?) #> $PciToDeviceMapping = @{} $NetworkAdapters = Get-VMHostNetworkAdapter -VMHost $VMHost -Physical foreach ($adapter in $NetworkAdapters) { $PciToDeviceMapping[$adapter.PciId] = $adapter.DeviceName } $hbAdapters = Get-VMHostHba -VMHost $VMHost foreach ($adapter in $hbAdapters) { $PciToDeviceMapping[$adapter.Pci] = $adapter.Device } <# Data Object - HostGraphicsInfo(vim.host.GraphicsInfo) This function has been available since version 5.5, but we can't be sure if it is still valid. I don't have access to a vGPU-enabled system. #> $GpuAdapters = (Get-VMHost $VMhost | Get-View -Property Config).Config.GraphicsInfo foreach ($adapter in $GpuAdapters) { $PciToDeviceMapping[$adapter.pciId] = $adapter.deviceName } $VMHostPciDevice = @{ VMHost = $VMHost DeviceClass = @('MassStorageController', 'NetworkController', 'DisplayController', 'SerialBusController') } $PciDevices = Get-VMHostPciDevice @VMHostPciDevice # Combine PciDevices and PciToDeviceMapping $VMHostPciDevices = $PciDevices | ForEach-Object { $PciDevice = $_ $device = $PCIToDeviceMapping[$pciDevice.Id] if ($device) { [PSCustomObject]@{ 'Device' = $device 'PCI Address' = $PciDevice.Id 'Device Class' = $PciDevice.DeviceClass -replace ('Controller', "") 'Device Name' = $PciDevice.DeviceName 'Vendor Name' = $PciDevice.VendorName } } } $TableParams = @{ Name = "PCI Devices - $VMHost" ColumnWidths = 17, 18, 15, 30, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostPciDevices | Sort-Object 'Device' | Table @TableParams } #endregion ESXi Host PCI Devices #region ESXi Host PCI Devices Drivers & Firmware $VMHostPciDevicesDetails = Get-PciDeviceDetail -Server $vCenter -esxcli $esxcli | Sort-Object 'Device' if ($VMHostPciDevicesDetails) { Try { Section -Style NOTOCHeading5 -ExcludeFromTOC 'PCI Devices Drivers & Firmware' { $TableParams = @{ Name = "PCI Devices Drivers & Firmware - $VMHost" ColumnWidths = 12, 20, 11, 19, 11, 11, 16 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostPciDevicesDetails | Table @TableParams } } Catch { Write-PScriboMessage -IsWarning "Unable to collect PCI Devices Drivers & Firmware configuration from $VMhost." } } #endregion ESXi Host PCI Devices Drivers & Firmware } #endregion ESXi Host Hardware Section #region ESXi Host System Section Section -Style Heading4 'System' { Paragraph "The following section details the host system configuration for $VMHost." #region ESXi Host Profile Information if ($VMHost | Get-VMHostProfile) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Host Profile' { $VMHostProfile = $VMHost | Get-VMHostProfile | Select-Object Name, Description $TableParams = @{ Name = "Host Profile - $VMHost" ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostProfile | Sort-Object Name | Table @TableParams } } #endregion ESXi Host Profile Information #region ESXi Host Image Profile Information if ($UserRole.Privilege -contains 'Host.Config.Settings') { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Image Profile' { $installdate = Get-InstallDate $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter $ImageProfile = $esxcli.software.profile.get.Invoke() $SecurityProfile = [PSCustomObject]@{ 'Image Profile' = $ImageProfile.Name 'Vendor' = $ImageProfile.Vendor 'Installation Date' = $InstallDate.InstallDate } $TableParams = @{ Name = "Image Profile - $VMHost" #ColumnWidths = 50, 25, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $SecurityProfile | Table @TableParams } } else { Write-PScriboMessage "Insufficient user privileges to report ESXi host image profiles. Please ensure the user account has the 'Host > Configuration > Change settings' privilege assigned." } #endregion ESXi Host Image Profile Information #region ESXi Host Time Configuration Section -Style NOTOCHeading5 -ExcludeFromTOC 'Time Configuration' { $VMHostTimeSettings = [PSCustomObject]@{ 'Time Zone' = $VMHost.timezone 'NTP Service' = Switch ((Get-VMHostService -VMHost $VMHost | Where-Object { $_.key -eq 'ntpd' }).Running) { $true { 'Running' } $false { 'Stopped' } } 'NTP Server(s)' = (Get-VMHostNtpServer -VMHost $VMHost | Sort-Object) -join ', ' } if ($Healthcheck.VMHost.NTP) { $VMHostTimeSettings | Where-Object { $_.'NTP Service' -eq 'Stopped' } | Set-Style -Style Critical -Property 'NTP Service' } $TableParams = @{ Name = "Time Configuration - $VMHost" ColumnWidths = 30, 30, 40 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostTimeSettings | Table @TableParams } #endregion ESXi Host Time Configuration #region ESXi Host Syslog Configuration $SyslogConfig = $VMHost | Get-VMHostSysLogServer if ($SyslogConfig) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Syslog Configuration' { # TODO: Syslog Rotate & Size, Log Directory (Adv Settings) $SyslogConfig = $SyslogConfig | Select-Object @{L = 'SysLog Server'; E = { $_.Host } }, Port $TableParams = @{ Name = "Syslog Configuration - $VMHost" ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $SyslogConfig | Table @TableParams } } #endregion ESXi Host Syslog Configuration #region ESXi Update Manager Baseline Information if ($UserRole.Privilege -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { if ($VumServer.Name) { if ("Desktop" -eq $PSVersionTable.PsEdition) { $VMHostPatchBaselines = $VMHost | Get-PatchBaseline } else { Write-PScriboMessage 'ESXi VUM baseline information is not currently available with your version of PowerShell.' } if ($VMHostPatchBaselines) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Update Manager Baselines' { $VMHostBaselines = foreach ($VMHostBaseline in $VMHostPatchBaselines) { [PSCustomObject]@{ 'Baseline' = $VMHostBaseline.Name 'Description' = $VMHostBaseline.Description 'Type' = $VMHostBaseline.BaselineType 'Target Type' = $VMHostBaseline.TargetType 'Last Update Time' = $VMHostBaseline.LastUpdateTime '# of Patches' = $VMHostBaseline.CurrentPatches.Count } } $TableParams = @{ Name = "Update Manager Baselines - $VMHost" ColumnWidths = 25, 25, 10, 10, 20, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostBaselines | Sort-Object 'Baseline' | Table @TableParams } } } } else { Write-PScriboMessage "Insufficient user privileges to report ESXi host baselines. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned." } #endregion ESXi Update Manager Baseline Information #region ESXi Update Manager Compliance Information if ($UserRole.Privilege -contains 'VcIntegrity.Updates.com.vmware.vcIntegrity.ViewStatus') { if ($VumServer.Name) { if ("Desktop" -eq $PSVersionTable.PsEdition) { $VMHostCompliances = $VMHost | Get-Compliance } else { Write-PScriboMessage 'ESXi VUM compliance information is not currently available with your version of PowerShell.' } if ($VMHostCompliances) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Update Manager Compliance' { $VMHostComplianceInfo = foreach ($VMHostCompliance in $VMHostCompliances) { [PSCustomObject]@{ 'Baseline' = $VMHostCompliance.Baseline.Name 'Status' = Switch ($VMHostCompliance.Status) { 'NotCompliant' { 'Not Compliant' } default { $VMHostCompliance.Status } } } } if ($Healthcheck.VMHost.VUMCompliance) { $VMHostComplianceInfo | Where-Object { $_.Status -eq 'Unknown' } | Set-Style -Style Warning $VMHostComplianceInfo | Where-Object { $_.Status -eq 'Not Compliant' -or $_.Status -eq 'Incompatible' } | Set-Style -Style Critical } $TableParams = @{ Name = "Update Manager Compliance - $VMHost" ColumnWidths = 75, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostComplianceInfo | Sort-Object Baseline | Table @TableParams } } } } else { Write-PScriboMessage "Insufficient user privileges to report ESXi host compliance. Please ensure the user account has the 'VMware Update Manager / VMware vSphere Lifecycle Manager > Manage Patches and Upgrades > View Compliance Status' privilege assigned." } #endregion ESXi Update Manager Compliance Information #region ESXi Host Comprehensive Information Section if ($InfoLevel.VMHost -ge 5) { #region ESXi Host Advanced System Settings Section -Style NOTOCHeading5 -ExcludeFromTOC 'Advanced System Settings' { $AdvSettings = $VMHost | Get-AdvancedSetting | Select-Object Name, Value $TableParams = @{ Name = "Advanced System Settings - $VMHost" ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $AdvSettings | Sort-Object Name | Table @TableParams } #endregion ESXi Host Advanced System Settings #region ESXi Host Software VIBs Section -Style NOTOCHeading5 -ExcludeFromTOC 'Software VIBs' { $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter $VMHostVibs = $esxcli.software.vib.list.Invoke() $VMHostVibs = foreach ($VMHostVib in $VMHostVibs) { [PSCustomObject]@{ 'VIB' = $VMHostVib.Name 'ID' = $VMHostVib.Id 'Version' = $VMHostVib.Version 'Acceptance Level' = $VMHostVib.AcceptanceLevel 'Creation Date' = $VMHostVib.CreationDate 'Install Date' = $VMHostVib.InstallDate } } $TableParams = @{ Name = "Software VIBs - $VMHost" ColumnWidths = 15, 25, 15, 15, 15, 15 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostVibs | Sort-Object 'Install Date' -Descending | Table @TableParams } #endregion ESXi Host Software VIBs } #endregion ESXi Host Comprehensive Information Section } #endregion ESXi Host System Section #region ESXi Host Storage Section Section -Style Heading4 'Storage' { Paragraph "The following section details the host storage configuration for $VMHost." #region ESXi Host Datastore Specifications $VMHostDatastores = $VMHost | Get-Datastore | Where-Object { ($_.State -eq 'Available') -and ($_.CapacityGB -gt 0) } | Sort-Object Name if ($VMHostDatastores) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Datastores' { $VMHostDsSpecs = foreach ($VMHostDatastore in $VMHostDatastores) { [PSCustomObject]@{ 'Datastore' = $VMHostDatastore.Name 'Type' = $VMHostDatastore.Type 'Version' = Switch ($VMHostDatastore.FileSystemVersion) { $null { '--' } default { $VMHostDatastore.FileSystemVersion } } '# of VMs' = $VMHostDatastore.ExtensionData.VM.Count 'Total Capacity GB' = [math]::Round($VMHostDatastore.CapacityGB, 2) 'Used Capacity GB' = [math]::Round((($VMHostDatastore.CapacityGB) - ($VMHostDatastore.FreeSpaceGB)), 2) 'Free Space GB' = [math]::Round($VMHostDatastore.FreeSpaceGB, 2) '% Used' = [math]::Round((100 - (($VMHostDatastore.FreeSpaceGB) / ($VMHostDatastore.CapacityGB) * 100)), 2) } } if ($Healthcheck.Datastore.CapacityUtilization) { $VMHostDsSpecs | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical $VMHostDsSpecs | Where-Object { $_.'% Used' -ge 75 -and $_.'% Used' -lt 90 } | Set-Style -Style Warning } $TableParams = @{ Name = "Datastores - $VMHost" ColumnWidths = 20, 8, 9, 8, 15, 15, 15, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostDsSpecs | Sort-Object 'Datastore' | Table @TableParams } } #endregion ESXi Host Datastore Specifications #region ESXi Host Storage Adapter Information $VMHostHbas = $VMHost | Get-VMHostHba | Sort-Object Device if ($VMHostHbas) { #region ESXi Host Storage Adapters Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'Storage Adapters' { Paragraph "The following section details the storage adapter configuration for $VMHost." foreach ($VMHostHba in $VMHostHbas) { $Target = ((Get-View $VMHostHba.VMhost).Config.StorageDevice.ScsiTopology.Adapter | Where-Object { $_.Adapter -eq $VMHostHba.Key }).Target $LUNs = Get-ScsiLun -Hba $VMHostHba -LunType "disk" -ErrorAction SilentlyContinue $Paths = ($Target | ForEach-Object { $_.Lun.Count } | Measure-Object -Sum) Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostHba.Device)" { $VMHostStorageAdapter = [PSCustomObject]@{ 'Adapter' = $VMHostHba.Device 'Type' = Switch ($VMHostHba.Type) { 'FibreChannel' { 'Fibre Channel' } 'IScsi' { 'iSCSI' } 'ParallelScsi' { 'Parallel SCSI' } default { $TextInfo.ToTitleCase($VMHostHba.Type) } } 'Model' = $VMHostHba.Model 'Status' = $TextInfo.ToTitleCase($VMHostHba.Status) 'Targets' = $Target.Count 'Devices' = $LUNs.Count 'Paths' = $Paths.Sum } $MemberProps = @{ 'InputObject' = $VMHostStorageAdapter 'MemberType' = 'NoteProperty' } if ($VMHostStorageAdapter.Type -eq 'iSCSI') { $iScsiAuthenticationMethod = Switch ($VMHostHba.ExtensionData.AuthenticationProperties.ChapAuthenticationType) { 'chapProhibited' { 'None' } 'chapPreferred' { 'Use unidirectional CHAP unless prohibited by target' } 'chapDiscouraged' { 'Use unidirectional CHAP if required by target' } 'chapRequired' { Switch ($VMHostHba.ExtensionData.AuthenticationProperties.MutualChapAuthenticationType) { 'chapProhibited' { 'Use unidirectional CHAP' } 'chapRequired' { 'Use bidirectional CHAP' } } } default { $VMHostHba.ExtensionData.AuthenticationProperties.ChapAuthenticationType } } Add-Member @MemberProps -Name 'iSCSI Name' -Value $VMHostHba.IScsiName if ($VMHostHba.IScsiAlias) { Add-Member @MemberProps -Name 'iSCSI Alias' -Value $VMHostHba.IScsiAlias } else { Add-Member @MemberProps -Name 'iSCSI Alias' -Value '--' } if ($VMHostHba.CurrentSpeedMb) { Add-Member @MemberProps -Name 'Speed' -Value "$($VMHostHba.CurrentSpeedMb) Mb" } else { Add-Member @MemberProps -Name 'Speed' -Value '--' } if ($VMHostHba.ExtensionData.ConfiguredSendTarget) { Add-Member @MemberProps -Name 'Dynamic Discovery' -Value (($VMHostHba.ExtensionData.ConfiguredSendTarget | ForEach-Object { "$($_.Address)" + ":" + "$($_.Port)" }) -join [Environment]::NewLine) } else { Add-Member @MemberProps -Name 'Dynamic Discovery' -Value '--' } if ($VMHostHba.ExtensionData.ConfiguredStaticTarget) { Add-Member @MemberProps -Name 'Static Discovery' -Value (($VMHostHba.ExtensionData.ConfiguredStaticTarget | ForEach-Object { "$($_.Address)" + ":" + "$($_.Port)" + " " + "$($_.IScsiName)" }) -join [Environment]::NewLine) } else { Add-Member @MemberProps -Name 'Static Discovery' -Value '--' } if ($iScsiAuthenticationMethod -eq 'None') { Add-Member @MemberProps -Name 'Authentication Method' -Value $iScsiAuthenticationMethod } elseif ($iScsiAuthenticationMethod -eq 'Use bidirectional CHAP') { Add-Member @MemberProps -Name 'Authentication Method' -Value $iScsiAuthenticationMethod Add-Member @MemberProps -Name 'Outgoing CHAP Name' -Value $VMHostHba.ExtensionData.AuthenticationProperties.ChapName Add-Member @MemberProps -Name 'Incoming CHAP Name' -Value $VMHostHba.ExtensionData.AuthenticationProperties.MutualChapName } else { Add-Member @MemberProps -Name 'Authentication Method' -Value $iScsiAuthenticationMethod Add-Member @MemberProps -Name 'Outgoing CHAP Name' -Value $VMHostHba.ExtensionData.AuthenticationProperties.ChapName } if ($InfoLevel.VMHost -ge 4) { Add-Member @MemberProps -Name 'Advanced Options' -Value (($VMHostHba.ExtensionData.AdvancedOptions | ForEach-Object { "$($_.Key) = $($_.Value)" }) -join [Environment]::NewLine) } } if ($VMHostStorageAdapter.Type -eq 'Fibre Channel') { Add-Member @MemberProps -Name 'Node WWN' -Value (([String]::Format("{0:X}", $VMHostHba.NodeWorldWideName) -split "(\w{2})" | Where-Object { $_ -ne "" }) -join ":") Add-Member @MemberProps -Name 'Port WWN' -Value (([String]::Format("{0:X}", $VMHostHba.PortWorldWideName) -split "(\w{2})" | Where-Object { $_ -ne "" }) -join ":") Add-Member @MemberProps -Name 'Speed' -Value $VMHostHba.Speed } if ($Healthcheck.VMHost.StorageAdapter) { $VMHostStorageAdapter | Where-Object { $_.'Status' -ne 'Online' } | Set-Style -Style Warning -Property 'Status' $VMHostStorageAdapter | Where-Object { $_.'Status' -eq 'Offline' } | Set-Style -Style Critical -Property 'Status' } $TableParams = @{ Name = "Storage Adapter $($VMHostStorageAdapter.Adapter) - $VMHost" List = $true ColumnWidths = 25, 75 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostStorageAdapter | Table @TableParams } } } #endregion ESXi Host Storage Adapters Section } #endregion ESXi Host Storage Adapter Information } #endregion ESXi Host Storage Section #region ESXi Host Network Section Section -Style Heading4 'Network' { Paragraph "The following section details the host network configuration for $VMHost." BlankLine #region ESXi Host Network Configuration $VMHostNetwork = $VMHost.ExtensionData.Config.Network $VMHostVirtualSwitch = @() $VMHostVss = foreach ($vSwitch in $VMHost.ExtensionData.Config.Network.Vswitch) { $VMHostVirtualSwitch += $vSwitch.Name } $VMHostDvs = foreach ($dvSwitch in $VMHost.ExtensionData.Config.Network.ProxySwitch) { $VMHostVirtualSwitch += $dvSwitch.DvsName } $VMHostNetworkDetail = [PSCustomObject]@{ 'Host' = $VMHost.Name 'Virtual Switches' = ($VMHostVirtualSwitch | Sort-Object) -join ', ' 'VMkernel Adapters' = ($VMHostNetwork.Vnic.Device | Sort-Object) -join ', ' 'Physical Adapters' = ($VMHostNetwork.Pnic.Device | Sort-Object) -join ', ' 'VMkernel Gateway' = $VMHostNetwork.IpRouteConfig.DefaultGateway 'IPv6' = Switch ($VMHostNetwork.IPv6Enabled) { $true { 'Enabled' } $false { 'Disabled' } } 'VMkernel IPv6 Gateway' = Switch ($VMHostNetwork.IpRouteConfig.IpV6DefaultGateway) { $null { '--' } default { $VMHostNetwork.IpRouteConfig.IpV6DefaultGateway } } 'DNS Servers' = ($VMHostNetwork.DnsConfig.Address | Sort-Object) -join ', ' 'Host Name' = $VMHostNetwork.DnsConfig.HostName 'Domain Name' = $VMHostNetwork.DnsConfig.DomainName 'Search Domain' = ($VMHostNetwork.DnsConfig.SearchDomain | Sort-Object) -join ', ' } if ($Healthcheck.VMHost.IPv6) { $VMHostNetworkDetail | Where-Object { $_.'IPv6' -eq $false } | Set-Style -Style Warning -Property 'IPv6' } $TableParams = @{ Name = "Network Configuration - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostNetworkDetail | Table @TableParams #endregion ESXi Host Network Configuration #region ESXi Host Physical Adapters Section -Style NOTOCHeading5 -ExcludeFromTOC 'Physical Adapters' { Paragraph "The following section details the physical network adapter configuration for $VMHost." $PhysicalNetAdapters = $VMHost.ExtensionData.Config.Network.Pnic | Sort-Object Device $VMHostPhysicalNetAdapters = foreach ($PhysicalNetAdapter in $PhysicalNetAdapters) { [PSCustomObject]@{ 'Adapter' = $PhysicalNetAdapter.Device 'Status' = Switch ($PhysicalNetAdapter.Linkspeed) { $null { 'Disconnected' } default { 'Connected' } } 'Virtual Switch' = $( if ($VMHost.ExtensionData.Config.Network.Vswitch.Pnic -contains $PhysicalNetAdapter.Key) { ($VMHost.ExtensionData.Config.Network.Vswitch | Where-Object { $_.Pnic -eq $PhysicalNetAdapter.Key }).Name } elseif ($VMHost.ExtensionData.Config.Network.ProxySwitch.Pnic -contains $PhysicalNetAdapter.Key) { ($VMHost.ExtensionData.Config.Network.ProxySwitch | Where-Object { $_.Pnic -eq $PhysicalNetAdapter.Key }).DvsName } else { '--' } ) 'MAC Address' = $PhysicalNetAdapter.Mac 'Actual Speed, Duplex' = Switch ($PhysicalNetAdapter.LinkSpeed.SpeedMb) { $null { 'Down' } default { if ($PhysicalNetAdapter.LinkSpeed.Duplex) { "$($PhysicalNetAdapter.LinkSpeed.SpeedMb) Mbps, Full Duplex" } else { 'Auto negotiate' } } } 'Configured Speed, Duplex' = Switch ($PhysicalNetAdapter.Spec.LinkSpeed) { $null { 'Auto negotiate' } default { if ($PhysicalNetAdapter.Spec.LinkSpeed.Duplex) { "$($PhysicalNetAdapter.Spec.LinkSpeed.SpeedMb) Mbps, Full Duplex" } else { "$($PhysicalNetAdapter.Spec.LinkSpeed.SpeedMb) Mbps" } } } 'Wake on LAN' = Switch ($PhysicalNetAdapter.WakeOnLanSupported) { $true { 'Supported' } $false { 'Not Supported' } } } } if ($Healthcheck.VMHost.NetworkAdapter) { $VMHostPhysicalNetAdapters | Where-Object { $_.'Status' -ne 'Connected' } | Set-Style -Style Critical -Property 'Status' $VMHostPhysicalNetAdapters | Where-Object { $_.'Actual Speed, Duplex' -eq 'Down' } | Set-Style -Style Critical -Property 'Actual Speed, Duplex' } if ($InfoLevel.VMHost -ge 4) { foreach ($VMHostPhysicalNetAdapter in $VMHostPhysicalNetAdapters) { Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostPhysicalNetAdapter.Adapter)" { $TableParams = @{ Name = "Physical Adapter $($VMHostPhysicalNetAdapter.Adapter) - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostPhysicalNetAdapter | Table @TableParams } } } else { BlankLine $TableParams = @{ Name = "Physical Adapters - $VMHost" ColumnWidths = 11, 13, 15, 19, 14, 14, 14 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostPhysicalNetAdapters | Table @TableParams } } #endregion ESXi Host Physical Adapters #region ESXi Host Cisco Discovery Protocol $VMHostNetworkAdapterCDP = $VMHost | Get-VMHostNetworkAdapterDP | Where-Object { $_.Status -eq 'Connected' } | Sort-Object Device if ($VMHostNetworkAdapterCDP) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Cisco Discovery Protocol' { Paragraph "The following section details the CDP information for $VMHost." if ($InfoLevel.VMHost -ge 4) { foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterCDP) { Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostNetworkAdapter.Device)" { $VMHostCDP = [PSCustomObject]@{ 'Status' = $VMHostNetworkAdapter.Status 'System Name' = $VMHostNetworkAdapter.SystemName 'Hardware Platform' = $VMHostNetworkAdapter.HardwarePlatform 'Switch ID' = $VMHostNetworkAdapter.SwitchId 'Software Version' = $VMHostNetworkAdapter.SoftwareVersion 'Management Address' = $VMHostNetworkAdapter.ManagementAddress 'Address' = $VMHostNetworkAdapter.Address 'Port ID' = $VMHostNetworkAdapter.PortId 'VLAN' = $VMHostNetworkAdapter.Vlan 'MTU' = $VMHostNetworkAdapter.Mtu } $TableParams = @{ Name = "Network Adapter $($VMHostNetworkAdapter.Device) CDP Information - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostCDP | Table @TableParams } } } else { BlankLine $VMHostCDP = foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterCDP) { [PSCustomObject]@{ 'Adapter' = $VMHostNetworkAdapter.Device 'Status' = $VMHostNetworkAdapter.Status 'Hardware Platform' = $VMHostNetworkAdapter.HardwarePlatform 'Switch ID' = $VMHostNetworkAdapter.SwitchId 'Address' = $VMHostNetworkAdapter.Address 'Port ID' = $VMHostNetworkAdapter.PortId } } $TableParams = @{ Name = "Network Adapter CDP Information - $VMHost" ColumnWidths = 11, 13, 26, 22, 17, 11 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostCDP | Table @TableParams } } } #endregion ESXi Host Cisco Discovery Protocol #region ESXi Host Link Layer Discovery Protocol $VMHostNetworkAdapterLLDP = $VMHost | Get-VMHostNetworkAdapterDP | Where-Object { $null -ne $_.ChassisId } | Sort-Object Device if ($VMHostNetworkAdapterLLDP) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Link Layer Discovery Protocol' { Paragraph "The following section details the LLDP information for $VMHost." if ($InfoLevel.VMHost -ge 4) { foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterLLDP) { Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHostNetworkAdapter.Device)" { $VMHostLLDP = [PSCustomObject]@{ 'Chassis ID' = $VMHostNetworkAdapter.ChassisId 'Port ID' = $VMHostNetworkAdapter.PortId 'Time to live' = $VMHostNetworkAdapter.TimeToLive 'TimeOut' = $VMHostNetworkAdapter.TimeOut 'Samples' = $VMHostNetworkAdapter.Samples 'Management Address' = $VMHostNetworkAdapter.ManagementAddress 'Port Description' = $VMHostNetworkAdapter.PortDescription 'System Description' = $VMHostNetworkAdapter.SystemDescription 'System Name' = $VMHostNetworkAdapter.SystemName } $TableParams = @{ Name = "Network Adapter $($VMHostNetworkAdapter.Device) LLDP Information - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostLLDP | Table @TableParams } } } else { BlankLine $VMHostLLDP = foreach ($VMHostNetworkAdapter in $VMHostNetworkAdapterLLDP) { [PSCustomObject]@{ 'Adapter' = $VMHostNetworkAdapter.Device 'Chassis ID' = $VMHostNetworkAdapter.ChassisId 'Port ID' = $VMHostNetworkAdapter.PortId 'Management Address' = $VMHostNetworkAdapter.ManagementAddress 'Port Description' = $VMHostNetworkAdapter.PortDescription 'System Name' = $VMHostNetworkAdapter.SystemName } } $TableParams = @{ Name = "Network Adapter LLDP Information - $VMHost" ColumnWidths = 11, 19, 16, 19, 18, 17 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostLLDP | Table @TableParams } } } #endregion ESXi Host Link Layer Discovery Protocol #region ESXi Host VMkernel Adapaters Section -Style NOTOCHeading5 -ExcludeFromTOC 'VMkernel Adapters' { Paragraph "The following section details the VMkernel adapter configuration for $VMHost" $VMkernelAdapters = $VMHost | Get-View | ForEach-Object -Process { #$esx = $_ $netSys = Get-View -Id $_.ConfigManager.NetworkSystem $vnicMgr = Get-View -Id $_.ConfigManager.VirtualNicManager $netSys.NetworkInfo.Vnic | ForEach-Object -Process { $device = $_.Device [PSCustomObject]@{ 'Adapter' = $_.Device 'Network Label' = & { if ($_.Spec.Portgroup) { $script:pg = $_.Spec.Portgroup $script:pg } elseif ($_.Spec.DistributedVirtualPort.Portgroupkey) { $script:pg = Get-View -ViewType DistributedVirtualPortgroup -Property Name, Key -Filter @{'Key' = "$($_.Spec.DistributedVirtualPort.PortgroupKey)" } | Select-Object -ExpandProperty Name $script:pg } else { '--' } } 'Virtual Switch' = & { if ($_.Spec.Portgroup) { (Get-VirtualPortGroup -Standard -Name $script:pg -VMHost $VMHost).VirtualSwitchName } elseif ($_.Spec.DistributedVirtualPort.Portgroupkey) { (Get-VDPortgroup -Name $script:pg).VDSwitch.Name | Select-Object -Unique } else { # Haven't figured out how to gather this yet! '--' } } 'TCP/IP Stack' = Switch ($_.Spec.NetstackInstanceKey) { 'defaultTcpipStack' { 'Default' } 'vSphereProvisioning' { 'Provisioning' } 'vmotion' { 'vMotion' } 'vxlan' { 'nsx-overlay' } 'hyperbus' { 'nsx-hyperbus' } $null { 'Not Applicable' } default { $_.Spec.NetstackInstanceKey } } 'MTU' = $_.Spec.Mtu 'MAC Address' = $_.Spec.Mac 'DHCP' = Switch ($_.Spec.Ip.Dhcp) { $true { 'Enabled' } $false { 'Disabled' } } 'IP Address' = & { if ($_.Spec.IP.IPAddress) { $script:ip = $_.Spec.IP.IPAddress } else { $script:ip = '--' } $script:ip } 'Subnet Mask' = & { if ($_.Spec.IP.SubnetMask) { $script:netmask = $_.Spec.IP.SubnetMask } else { $script:netmask = '--' } $script:netmask } 'Default Gateway' = Switch ($_.Spec.IpRouteSpec.IpRouteConfig.DefaultGateway) { $null { '--' } default { $_.Spec.IpRouteSpec.IpRouteConfig.DefaultGateway } } 'vMotion' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vmotion' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } 'Provisioning' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereProvisioning' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } 'FT Logging' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'faultToleranceLogging' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } 'Management' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'management' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } 'vSphere Replication' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereReplication' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } 'vSphere Replication NFC' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereReplicationNFC' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } 'vSAN' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vsan' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } 'vSAN Witness' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vsanWitness' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } 'vSphere Backup NFC' = Switch ((($vnicMgr.Info.NetConfig | Where-Object { $_.NicType -eq 'vSphereBackupnNFC' }).SelectedVnic | ForEach-Object { $_ -match $device } ) -contains $true) { $true { 'Enabled' } $false { 'Disabled' } } } } } foreach ($VMkernelAdapter in ($VMkernelAdapters | Sort-Object 'Adapter')) { Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMkernelAdapter.Adapter)" { $TableParams = @{ Name = "VMkernel Adapter $($VMkernelAdapter.Adapter) - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMkernelAdapter | Table @TableParams } } } #endregion ESXi Host VMkernel Adapaters #region ESXi Host Standard Virtual Switches $VSSwitches = $VMHost | Get-VirtualSwitch -Standard | Sort-Object Name if ($VSSwitches) { #region Section Standard Virtual Switches Section -Style NOTOCHeading5 -ExcludeFromTOC 'Standard Virtual Switches' { Paragraph "The following section details the standard virtual switch configuration for $VMHost." BlankLine $VSSwitchNicTeaming = $VSSwitches | Get-NicTeamingPolicy #region ESXi Host Standard Virtual Switch Properties $VSSProperties = foreach ($VSSwitchNicTeam in $VSSwitchNicTeaming) { [PSCustomObject]@{ 'Virtual Switch' = $VSSwitchNicTeam.VirtualSwitch 'MTU' = $VSSwitchNicTeam.VirtualSwitch.Mtu 'Number of Ports' = $VSSwitchNicTeam.VirtualSwitch.NumPorts 'Number of Ports Available' = $VSSwitchNicTeam.VirtualSwitch.NumPortsAvailable } } $TableParams = @{ Name = "Standard Virtual Switches - $VMHost" ColumnWidths = 25, 25, 25, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VSSProperties | Table @TableParams #endregion ESXi Host Standard Virtual Switch Properties #region ESXi Host Virtual Switch Security Policy $VssSecurity = $VSSwitches | Get-SecurityPolicy if ($VssSecurity) { #region Virtual Switch Security Policy Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Security' { $VssSecurity = foreach ($VssSec in $VssSecurity) { [PSCustomObject]@{ 'Virtual Switch' = $VssSec.VirtualSwitch 'Promiscuous Mode' = Switch ($VssSec.AllowPromiscuous) { $true { 'Accept' } $false { 'Reject' } } 'MAC Address Changes' = Switch ($VssSec.MacChanges) { $true { 'Accept' } $false { 'Reject' } } 'Forged Transmits' = Switch ($VssSec.ForgedTransmits) { $true { 'Accept' } $false { 'Reject' } } } } $TableParams = @{ Name = "Virtual Switch Security Policy - $VMHost" ColumnWidths = 25, 25, 25, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VssSecurity | Sort-Object 'Virtual Switch' | Table @TableParams } #endregion Virtual Switch Security Policy } #endregion ESXi Host Virtual Switch Security Policy #region ESXi Host Virtual Switch Traffic Shaping Policy Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Traffic Shaping' { $VssTrafficShapingPolicy = foreach ($VSSwitch in $VSSwitches) { [PSCustomObject]@{ 'Virtual Switch' = $VSSwitch.Name 'Status' = Switch ($VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.Enabled) { $True { 'Enabled' } $False { 'Disabled' } } 'Average Bandwidth (kbit/s)' = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.AverageBandwidth 'Peak Bandwidth (kbit/s)' = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.PeakBandwidth 'Burst Size (KB)' = $VSSwitch.ExtensionData.Spec.Policy.ShapingPolicy.BurstSize } } $TableParams = @{ Name = "Virtual Switch Traffic Shaping Policy - $VMHost" ColumnWidths = 25, 15, 20, 20, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VssTrafficShapingPolicy | Sort-Object 'Virtual Switch' | Table @TableParams } #endregion ESXi Host Virtual Switch Traffic Shaping Policy #region ESXi Host Virtual Switch Teaming & Failover $VssNicTeamingPolicy = $VSSwitches | Get-NicTeamingPolicy if ($VssNicTeamingPolicy) { #region Virtual Switch Teaming & Failover Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Teaming & Failover' { $VssNicTeaming = foreach ($VssNicTeam in $VssNicTeamingPolicy) { [PSCustomObject]@{ 'Virtual Switch' = $VssNicTeam.VirtualSwitch 'Load Balancing' = Switch ($VssNicTeam.LoadBalancingPolicy) { 'LoadbalanceSrcId' { 'Route based on the originating port ID' } 'LoadbalanceSrcMac' { 'Route based on source MAC hash' } 'LoadbalanceIP' { 'Route based on IP hash' } 'ExplicitFailover' { 'Explicit Failover' } default { $VssNicTeam.LoadBalancingPolicy } } 'Network Failure Detection' = Switch ($VssNicTeam.NetworkFailoverDetectionPolicy) { 'LinkStatus' { 'Link status only' } 'BeaconProbing' { 'Beacon probing' } default { $VssNicTeam.NetworkFailoverDetectionPolicy } } 'Notify Switches' = Switch ($VssNicTeam.NotifySwitches) { $true { 'Yes' } $false { 'No' } } 'Failback' = Switch ($VssNicTeam.FailbackEnabled) { $true { 'Yes' } $false { 'No' } } 'Active NICs' = ($VssNicTeam.ActiveNic | Sort-Object) -join [Environment]::NewLine 'Standby NICs' = ($VssNicTeam.StandbyNic | Sort-Object) -join [Environment]::NewLine 'Unused NICs' = ($VssNicTeam.UnusedNic | Sort-Object) -join [Environment]::NewLine } } $TableParams = @{ Name = "Virtual Switch Teaming & Failover - $VMHost" ColumnWidths = 20, 17, 12, 11, 10, 10, 10, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VssNicTeaming | Sort-Object 'Virtual Switch' | Table @TableParams } #endregion Virtual Switch Teaming & Failover Section } #endregion ESXi Host Virtual Switch Teaming & Failover #region ESXi Host Virtual Switch Port Groups $VssPortgroups = $VSSwitches | Get-VirtualPortGroup -Standard if ($VssPortgroups) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Port Groups' { $VssPortgroups = foreach ($VssPortgroup in $VssPortgroups) { [PSCustomObject]@{ 'Port Group' = $VssPortgroup.Name 'VLAN ID' = $VssPortgroup.VLanId 'Virtual Switch' = $VssPortgroup.VirtualSwitchName '# of VMs' = ($VssPortgroup | Get-VM).Count } } $TableParams = @{ Name = "Virtual Switch Port Groups - $VMHost" ColumnWidths = 40, 10, 40, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VssPortgroups | Sort-Object 'Port Group', 'VLAN ID', 'Virtual Switch' | Table @TableParams } #endregion ESXi Host Virtual Switch Port Groups #region ESXi Host Virtual Switch Port Group Security Policy $VssPortgroupSecurity = $VSSwitches | Get-VirtualPortGroup | Get-SecurityPolicy if ($VssPortgroupSecurity) { #region Virtual Port Group Security Policy Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Port Group Security' { $VssPortgroupSecurity = foreach ($VssPortgroupSec in $VssPortgroupSecurity) { [PSCustomObject]@{ 'Port Group' = $VssPortgroupSec.VirtualPortGroup 'Virtual Switch' = $VssPortgroupSec.virtualportgroup.virtualswitchname 'Promiscuous Mode' = Switch ($VssPortgroupSec.AllowPromiscuous) { $true { 'Accept' } $false { 'Reject' } } 'MAC Changes' = Switch ($VssPortgroupSec.MacChanges) { $true { 'Accept' } $false { 'Reject' } } 'Forged Transmits' = Switch ($VssPortgroupSec.ForgedTransmits) { $true { 'Accept' } $false { 'Reject' } } } } $TableParams = @{ Name = "Virtual Switch Port Group Security Policy - $VMHost" ColumnWidths = 27, 25, 16, 16, 16 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VssPortgroupSecurity | Sort-Object 'Port Group', 'Virtual Switch' | Table @TableParams } #endregion Virtual Port Group Security Policy Section } #endregion ESXi Host Virtual Switch Port Group Security Policy #region ESXi Host Virtual Switch Port Group Traffic Shaping Policy Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Port Group Traffic Shaping' { $VssPortgroupTrafficShapingPolicy = foreach ($VssPortgroup in $VssPortgroups) { [PSCustomObject]@{ 'Port Group' = $VssPortgroup.Name 'Virtual Switch' = $VssPortgroup.VirtualSwitchName 'Status' = Switch ($VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.Enabled) { $True { 'Enabled' } $False { 'Disabled' } $null { 'Inherited' } } 'Average Bandwidth (kbit/s)' = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.AverageBandwidth 'Peak Bandwidth (kbit/s)' = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.PeakBandwidth 'Burst Size (KB)' = $VssPortgroup.ExtensionData.Spec.Policy.ShapingPolicy.BurstSize } } $TableParams = @{ Name = "Virtual Switch Port Group Traffic Shaping Policy - $VMHost" ColumnWidths = 19, 19, 11, 17, 17, 17 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VssPortgroupTrafficShapingPolicy | Sort-Object 'Port Group', 'Virtual Switch' | Table @TableParams } #endregion ESXi Host Virtual Switch Port Group Traffic Shaping Policy #region ESXi Host Virtual Switch Port Group Teaming & Failover $VssPortgroupNicTeaming = $VSSwitches | Get-VirtualPortGroup | Get-NicTeamingPolicy if ($VssPortgroupNicTeaming) { #region Virtual Switch Port Group Teaming & Failover Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'Virtual Switch Port Group Teaming & Failover' { $VssPortgroupNicTeaming = foreach ($VssPortgroupNicTeam in $VssPortgroupNicTeaming) { [PSCustomObject]@{ 'Port Group' = $VssPortgroupNicTeam.VirtualPortGroup 'Virtual Switch' = $VssPortgroupNicTeam.virtualportgroup.virtualswitchname 'Load Balancing' = Switch ($VssPortgroupNicTeam.LoadBalancingPolicy) { 'LoadbalanceSrcId' { 'Route based on the originating port ID' } 'LoadbalanceSrcMac' { 'Route based on source MAC hash' } 'LoadbalanceIP' { 'Route based on IP hash' } 'ExplicitFailover' { 'Explicit Failover' } default { $VssPortgroupNicTeam.LoadBalancingPolicy } } 'Network Failure Detection' = Switch ($VssPortgroupNicTeam.NetworkFailoverDetectionPolicy) { 'LinkStatus' { 'Link status only' } 'BeaconProbing' { 'Beacon probing' } default { $VssPortgroupNicTeam.NetworkFailoverDetectionPolicy } } 'Notify Switches' = Switch ($VssPortgroupNicTeam.NotifySwitches) { $true { 'Yes' } $false { 'No' } } 'Failback' = Switch ($VssPortgroupNicTeam.FailbackEnabled) { $true { 'Yes' } $false { 'No' } } 'Active NICs' = ($VssPortgroupNicTeam.ActiveNic | Sort-Object) -join [Environment]::NewLine 'Standby NICs' = ($VssPortgroupNicTeam.StandbyNic | Sort-Object) -join [Environment]::NewLine 'Unused NICs' = ($VssPortgroupNicTeam.UnusedNic | Sort-Object) -join [Environment]::NewLine } } $TableParams = @{ Name = "Virtual Switch Port Group Teaming & Failover - $VMHost" ColumnWidths = 12, 11, 11, 11, 11, 11, 11, 11, 11 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VssPortgroupNicTeaming | Sort-Object 'Port Group', 'Virtual Switch' | Table @TableParams } #endregion Virtual Switch Port Group Teaming & Failover Section } #endregion ESXi Host Virtual Switch Port Group Teaming & Failover } } #endregion Section Standard Virtual Switches } #endregion ESXi Host Standard Virtual Switches } #endregion ESXi Host Network Section #region ESXi Host Security Section Section -Style Heading4 'Security' { Paragraph "The following section details the host security configuration for $VMHost." #region ESXi Host Lockdown Mode if ($null -ne $VMHost.ExtensionData.Config.LockdownMode) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Lockdown Mode' { $LockdownMode = [PSCustomObject]@{ 'Lockdown Mode' = Switch ($VMHost.ExtensionData.Config.LockdownMode) { 'lockdownDisabled' { 'Disabled' } 'lockdownNormal' { 'Enabled (Normal)' } 'lockdownStrict' { 'Enabled (Strict)' } default { $VMHost.ExtensionData.Config.LockdownMode } } } if ($Healthcheck.VMHost.LockdownMode) { $LockdownMode | Where-Object { $_.'Lockdown Mode' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Lockdown Mode' } $TableParams = @{ Name = "Lockdown Mode - $VMHost" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $LockdownMode | Table @TableParams } } #endregion ESXi Host Lockdown Mode #region ESXi Host Services Section -Style NOTOCHeading5 -ExcludeFromTOC 'Services' { $VMHostServices = $VMHost | Get-VMHostService $Services = foreach ($VMHostService in $VMHostServices) { [PSCustomObject]@{ 'Service' = $VMHostService.Label 'Daemon' = Switch ($VMHostService.Running) { $true { 'Running' } $false { 'Stopped' } } 'Startup Policy' = Switch ($VMHostService.Policy) { 'automatic' { 'Start and stop with port usage' } 'on' { 'Start and stop with host' } 'off' { 'Start and stop manually' } default { $VMHostService.Policy } } } } if ($Healthcheck.VMHost.NTP) { $Services | Where-Object { ($_.'Service' -eq 'NTP Daemon') -and ($_.Daemon -eq 'Stopped') } | Set-Style -Style Critical -Property 'Daemon' $Services | Where-Object { ($_.'Service' -eq 'NTP Daemon') -and ($_.'Startup Policy' -ne 'Start and stop with host') } | Set-Style -Style Critical -Property 'Startup Policy' } if ($Healthcheck.VMHost.SSH) { $Services | Where-Object { ($_.'Service' -eq 'SSH') -and ($_.Daemon -eq 'Running') } | Set-Style -Style Warning -Property 'Daemon' $Services | Where-Object { ($_.'Service' -eq 'SSH') -and ($_.'Startup Policy' -ne 'Start and stop manually') } | Set-Style -Style Warning -Property 'Startup Policy' } if ($Healthcheck.VMHost.ESXiShell) { $Services | Where-Object { ($_.'Service' -eq 'ESXi Shell') -and ($_.Daemon -eq 'Running') } | Set-Style -Style Warning -Property 'Daemon' $Services | Where-Object { ($_.'Service' -eq 'ESXi Shell') -and ($_.'Startup Policy' -ne 'Start and stop manually') } | Set-Style -Style Warning -Property 'Startup Policy' } $TableParams = @{ Name = "Services - $VMHost" ColumnWidths = 40, 20, 40 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $Services | Sort-Object 'Service' | Table @TableParams } #endregion ESXi Host Services #region ESXi Host Advanced Detail Information if ($InfoLevel.VMHost -ge 4) { #region ESXi Host Firewall $VMHostFirewallExceptions = $VMHost | Get-VMHostFirewallException if ($VMHostFirewallExceptions) { #region Friewall Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'Firewall' { $VMHostFirewall = foreach ($VMHostFirewallException in $VMHostFirewallExceptions) { [PScustomObject]@{ 'Service' = $VMHostFirewallException.Name 'Status' = Switch ($VMHostFirewallException.Enabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Incoming Ports' = $VMHostFirewallException.IncomingPorts 'Outgoing Ports' = $VMHostFirewallException.OutgoingPorts 'Protocols' = $VMHostFirewallException.Protocols 'Daemon' = Switch ($VMHostFirewallException.ServiceRunning) { $true { 'Running' } $false { 'Stopped' } $null { 'N/A' } default { $VMHostFirewallException.ServiceRunning } } } } $TableParams = @{ Name = "Firewall Configuration - $VMHost" ColumnWidths = 22, 12, 21, 21, 12, 12 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostFirewall | Sort-Object 'Service' | Table @TableParams } #endregion Friewall Section } #endregion ESXi Host Firewall #region ESXi Host Authentication $AuthServices = $VMHost | Get-VMHostAuthentication if ($AuthServices.DomainMembershipStatus) { Section -Style NOTOCHeading5 -ExcludeFromTOC 'Authentication Services' { $AuthServices = $AuthServices | Select-Object Domain, @{L = 'Domain Membership'; E = { $_.DomainMembershipStatus } }, @{L = 'Trusted Domains'; E = { $_.TrustedDomains } } $TableParams = @{ Name = "Authentication Services - $VMHost" ColumnWidths = 25, 25, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $AuthServices | Table @TableParams } } #endregion ESXi Host Authentication } #endregion ESXi Host Advanced Detail Information } #endregion ESXi Host Security Section #region ESXi Host Virtual Machines Advanced Detail Information if ($InfoLevel.VMHost -ge 4) { $VMHostVMs = $VMHost | Get-VM | Sort-Object Name if ($VMHostVMs) { #region Virtual Machines Section Section -Style Heading4 'Virtual Machines' { Paragraph "The following section details the virtual machine configuration for $VMHost." BlankLine #region ESXi Host Virtual Machine Information $VMHostVMInfo = foreach ($VMHostVM in $VMHostVMs) { $VMHostVMView = $VMHostVM | Get-View [PSCustomObject]@{ 'Virtual Machine' = $VMHostVM.Name 'Power State' = Switch ($VMHostVM.PowerState) { 'PoweredOn' { 'On' } 'PoweredOff' { 'Off' } default { $VMHostVM.PowerState } } 'IP Address' = Switch ($VMHostVMView.Guest.IpAddress) { $null { '--' } default { $VMHostVMView.Guest.IpAddress } } 'CPUs' = $VMHostVM.NumCpu #'Cores per Socket' = $VMHostVM.CoresPerSocket 'Memory GB' = [math]::Round(($VMHostVM.memoryGB), 2) 'Provisioned GB' = [math]::Round(($VMHostVM.ProvisionedSpaceGB), 2) 'Used GB' = [math]::Round(($VMHostVM.UsedSpaceGB), 2) 'HW Version' = ($VMHostVM.HardwareVersion).Replace('vmx-', 'v') 'VM Tools Status' = Switch ($VMHostVM.ExtensionData.Guest.ToolsStatus) { 'toolsOld' { 'Old' } 'toolsOK' { 'OK' } 'toolsNotRunning' { 'Not Running' } 'toolsNotInstalled' { 'Not Installed' } default { $VMHostVM.ExtensionData.Guest.ToolsStatus } } } } if ($Healthcheck.VM.VMToolsStatus) { $VMHostVMInfo | Where-Object { $_.'VM Tools Status' -ne 'OK' } | Set-Style -Style Warning -Property 'VM Tools Status' } if ($Healthcheck.VM.PowerState) { $VMHostVMInfo | Where-Object { $_.'Power State' -ne 'On' } | Set-Style -Style Warning -Property 'Power State' } $TableParams = @{ Name = "Virtual Machines - $VMHost" ColumnWidths = 21, 8, 16, 9, 9, 9, 9, 9, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHostVMInfo | Table @TableParams #endregion ESXi Host Virtual Machine Information #region ESXi Host VM Startup/Shutdown Information $VMStartPolicy = $VMHost | Get-VMStartPolicy | Where-Object { $_.StartAction -ne 'None' } if ($VMStartPolicy) { #region VM Startup/Shutdown Section Section -Style NOTOCHeading5 -ExcludeFromTOC 'VM Startup/Shutdown' { $VMStartPolicies = foreach ($VMStartPol in $VMStartPolicy) { [PSCustomObject]@{ 'Start Order' = $VMStartPol.StartOrder 'VM Name' = $VMStartPol.VirtualMachineName 'Startup' = Switch ($VMStartPol.StartAction) { 'PowerOn' { 'Enabled' } 'None' { 'Disabled' } default { $VMStartPol.StartAction } } 'Startup Delay' = "$($VMStartPol.StartDelay) seconds" 'VMware Tools' = Switch ($VMStartPol.WaitForHeartbeat) { $true { 'Continue if VMware Tools is started' } $false { 'Wait for startup delay' } } 'Shutdown Behavior' = Switch ($VMStartPol.StopAction) { 'PowerOff' { 'Power Off' } 'GuestShutdown' { 'Guest Shutdown' } default { $VMStartPol.StopAction } } 'Shutdown Delay' = "$($VMStartPol.StopDelay) seconds" } } $TableParams = @{ Name = "VM Startup/Shutdown Policy - $VMHost" ColumnWidths = 11, 34, 11, 11, 11, 11, 11 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMStartPolicies | Table @TableParams } #endregion VM Startup/Shutdown Section } #endregion ESXi Host VM Startup/Shutdown Information } #endregion Virtual Machines Section } } #endregion ESXi Host Virtual Machines Advanced Detail Information } #endregion VMHost Section } #endregion foreach VMHost Detailed Information loop } #endregion ESXi Host Detailed Information } #endregion Hosts Section } } #endregion ESXi VMHost Section #region Distributed Switch Section Write-PScriboMessage "Network InfoLevel set at $($InfoLevel.Network)." if ($InfoLevel.Network -ge 1) { # Create Distributed Switch Section if they exist $VDSwitches = Get-VDSwitch -Server $vCenter | Sort-Object Name if ($VDSwitches) { Section -Style Heading2 'Distributed Switches' { Paragraph "The following sections detail the configuration of distributed switches managed by vCenter Server $vCenterServerName." #region Distributed Switch Advanced Summary if ($InfoLevel.Network -le 2) { BlankLine $VDSInfo = foreach ($VDS in $VDSwitches) { [PSCustomObject]@{ 'VDSwitch' = $VDS.Name 'Datacenter' = $VDS.Datacenter 'Manufacturer' = $VDS.Vendor 'Version' = $VDS.Version '# of Uplinks' = $VDS.NumUplinkPorts '# of Ports' = $VDS.NumPorts '# of Hosts' = $VDS.ExtensionData.Summary.HostMember.Count '# of VMs' = $VDS.ExtensionData.Summary.VM.Count } } $TableParams = @{ Name = "Distributed Switch Summary - $($vCenterServerName)" ColumnWidths = 20, 18, 18, 10, 10, 8, 8, 8 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSInfo | Table @TableParams } #endregion Distributed Switch Advanced Summary #region Distributed Switch Detailed Information if ($InfoLevel.Network -ge 3) { # TODO: LACP, NetFlow, NIOC # TODO: Test Tags foreach ($VDS in ($VDSwitches)) { #region VDS Section Section -Style Heading3 $VDS { #region Distributed Switch General Properties $VDSwitchDetail = [PSCustomObject]@{ 'Distributed Switch' = $VDS.Name 'ID' = $VDS.Id 'Datacenter' = $VDS.Datacenter 'Manufacturer' = $VDS.Vendor 'Version' = $VDS.Version 'Number of Uplinks' = $VDS.NumUplinkPorts 'Number of Ports' = $VDS.NumPorts 'Number of Port Groups' = $VDS.ExtensionData.Summary.PortGroupName.Count 'Number of Hosts' = $VDS.ExtensionData.Summary.HostMember.Count 'Number of VMs' = $VDS.ExtensionData.Summary.VM.Count 'MTU' = $VDS.Mtu 'Network I/O Control' = Switch ($VDS.ExtensionData.Config.NetworkResourceManagementEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Discovery Protocol' = $VDS.LinkDiscoveryProtocol 'Discovery Protocol Operation' = $VDS.LinkDiscoveryProtocolOperation } <# $MemberProps = @{ 'InputObject' = $VDSwitchDetail 'MemberType' = 'NoteProperty' } if ($TagAssignments | Where-Object {$_.entity -eq $VDS}) { Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VDS}).Tag -join ',') } #> #region Network Advanced Detail Information if ($InfoLevel.Network -ge 4) { $VDSwitchDetail | ForEach-Object { $VDSwitchHosts = $VDS | Get-VMHost | Sort-Object Name Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Hosts' -Value ($VDSwitchHosts.Name -join ', ') $VDSwitchVMs = $VDS | Get-VM | Sort-Object Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Virtual Machines' -Value ($VDSwitchVMs.Name -join ', ') } } #endregion Network Advanced Detail Information $TableParams = @{ Name = "Distributed Switch General Properties - $VDS" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSwitchDetail | Table @TableParams #endregion Distributed Switch General Properties #region Distributed Switch Uplink Ports $VdsUplinks = $VDS | Get-VDPortgroup | Where-Object { $_.IsUplink -eq $true } | Get-VDPort if ($VdsUplinks) { Section -Style Heading4 'Distributed Switch Uplink Ports' { $VdsUplinkDetail = foreach ($VdsUplink in $VdsUplinks) { [PSCustomObject]@{ 'Distributed Switch' = $VdsUplink.Switch 'Host' = $VdsUplink.ProxyHost 'Uplink Name' = $VdsUplink.Name 'Physical Network Adapter' = $VdsUplink.ConnectedEntity 'Uplink Port Group' = $VdsUplink.Portgroup } } $TableParams = @{ Name = "Distributed Switch Uplink Ports - $VDS" ColumnWidths = 20, 20, 20, 20, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VdsUplinkDetail | Sort-Object 'Distributed Switch', 'Host', 'Uplink Name' | Table @TableParams } } #endregion Distributed Switch Uplink Ports #region Distributed Switch Security $VDSecurityPolicy = $VDS | Get-VDSecurityPolicy if ($VDSecurityPolicy) { Section -Style Heading4 'Distributed Switch Security' { $VDSecurityPolicyDetail = [PSCustomObject]@{ 'Distributed Switch' = $VDSecurityPolicy.VDSwitch 'Allow Promiscuous' = Switch ($VDSecurityPolicy.AllowPromiscuous) { $true { 'Accept' } $false { 'Reject' } } 'Forged Transmits' = Switch ($VDSecurityPolicy.ForgedTransmits) { $true { 'Accept' } $false { 'Reject' } } 'MAC Address Changes' = Switch ($VDSecurityPolicy.MacChanges) { $true { 'Accept' } $false { 'Reject' } } } $TableParams = @{ Name = "Distributed Switch Security - $VDS" ColumnWidths = 25, 25, 25, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSecurityPolicyDetail | Table @TableParams } } #endregion Distributed Switch Security #region Distributed Switch Traffic Shaping $VDSTrafficShaping = @() $VDSTrafficShapingIn = $VDS | Get-VDTrafficShapingPolicy -Direction In $VDSTrafficShapingOut = $VDS | Get-VDTrafficShapingPolicy -Direction Out $VDSTrafficShaping += $VDSTrafficShapingIn $VDSTrafficShaping += $VDSTrafficShapingOut if ($VDSTrafficShapingIn -or $VDSTrafficShapingOut) { Section -Style Heading4 'Distributed Switch Traffic Shaping' { $VDSTrafficShapingDetail = foreach ($VDSTrafficShape in $VDSTrafficShaping) { [PSCustomObject]@{ 'Distributed Switch' = $VDSTrafficShape.VDSwitch 'Direction' = $VDSTrafficShape.Direction 'Status' = Switch ($VDSTrafficShape.Enabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Average Bandwidth (kbit/s)' = $VDSTrafficShape.AverageBandwidth 'Peak Bandwidth (kbit/s)' = $VDSTrafficShape.PeakBandwidth 'Burst Size (KB)' = $VDSTrafficShape.BurstSize } } $TableParams = @{ Name = "Distributed Switch Traffic Shaping - $VDS" ColumnWidths = 25, 13, 11, 17, 17, 17 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSTrafficShapingDetail | Sort-Object 'Direction' | Table @TableParams } } #endregion Distributed Switch Traffic Shaping #region Distributed Switch Port Groups # TODO: Test Tags $VDSPortgroups = $VDS | Get-VDPortgroup if ($VDSPortgroups) { Section -Style Heading4 'Distributed Switch Port Groups' { $VDSPortgroupDetail = foreach ($VDSPortgroup in $VDSPortgroups) { [PSCustomObject]@{ 'Port Group' = $VDSPortgroup.Name 'Distributed Switch' = $VDSPortgroup.VDSwitch 'Datacenter' = $VDSPortgroup.Datacenter 'VLAN Configuration' = Switch ($VDSPortgroup.VlanConfiguration) { $null { '--' } default { $VDSPortgroup.VlanConfiguration } } 'Port Binding' = $VDSPortgroup.PortBinding '# of Ports' = $VDSPortgroup.NumPorts <# # Tags on portgroups cause Get-TagAssignments to error 'Tags' = & { if ($TagAssignments | Where-Object {$_.entity -eq $VDSPortgroup}) { ($TagAssignments | Where-Object {$_.entity -eq $VDSPortgroup}).Tag -join ',' } else { '--' } } #> } } $TableParams = @{ Name = "Distributed Switch Port Groups - $VDS" ColumnWidths = 20, 20, 20, 15, 15, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSPortgroupDetail | Sort-Object 'Port Group' | Table @TableParams } } #endregion Distributed Switch Port Groups #region Distributed Switch Port Group Security $VDSPortgroupSecurity = $VDS | Get-VDPortgroup | Get-VDSecurityPolicy if ($VDSPortgroupSecurity) { Section -Style NOTOCHeading5 -ExcludeFromTOC "Distributed Switch Port Group Security" { $VDSSecurityPolicies = foreach ($VDSSecurityPolicy in $VDSPortgroupSecurity) { [PSCustomObject]@{ 'Port Group' = $VDSSecurityPolicy.VDPortgroup 'Distributed Switch' = $VDS.Name 'Allow Promiscuous' = Switch ($VDSSecurityPolicy.AllowPromiscuous) { $true { 'Accept' } $false { 'Reject' } } 'Forged Transmits' = Switch ($VDSSecurityPolicy.ForgedTransmits) { $true { 'Accept' } $false { 'Reject' } } 'MAC Address Changes' = Switch ($VDSSecurityPolicy.MacChanges) { $true { 'Accept' } $false { 'Reject' } } } } $TableParams = @{ Name = "Distributed Switch Port Group Security - $VDS" ColumnWidths = 20, 20, 20, 20, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSSecurityPolicies | Sort-Object 'Port Group' | Table @TableParams } } #endregion Distributed Switch Port Group Security #region Distributed Switch Port Group Traffic Shaping $VDSPortgroupTrafficShaping = @() $VDSPortgroupTrafficShapingIn = $VDS | Get-VDPortgroup | Get-VDTrafficShapingPolicy -Direction In $VDSPortgroupTrafficShapingOut = $VDS | Get-VDPortgroup | Get-VDTrafficShapingPolicy -Direction Out $VDSPortgroupTrafficShaping += $VDSPortgroupTrafficShapingIn $VDSPortgroupTrafficShaping += $VDSPortgroupTrafficShapingOut if ($VDSPortgroupTrafficShaping) { Section -Style NOTOCHeading5 -ExcludeFromTOC "Distributed Switch Port Group Traffic Shaping" { $VDSPortgroupTrafficShapingDetail = foreach ($VDSPortgroupTrafficShape in $VDSPortgroupTrafficShaping) { [PSCustomObject]@{ 'Port Group' = $VDSPortgroupTrafficShape.VDPortgroup 'Distributed Switch' = $VDS.Name 'Direction' = $VDSPortgroupTrafficShape.Direction 'Status' = Switch ($VDSPortgroupTrafficShape.Enabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Average Bandwidth (kbit/s)' = $VDSPortgroupTrafficShape.AverageBandwidth 'Peak Bandwidth (kbit/s)' = $VDSPortgroupTrafficShape.PeakBandwidth 'Burst Size (KB)' = $VDSPortgroupTrafficShape.BurstSize } } $TableParams = @{ Name = "Distributed Switch Port Group Traffic Shaping - $VDS" ColumnWidths = 16, 16, 10, 10, 16, 16, 16 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSPortgroupTrafficShapingDetail | Sort-Object 'Port Group', 'Direction', 'Port Group' | Table @TableParams } } #endregion Distributed Switch Port Group Traffic Shaping #region Distributed Switch Port Group Teaming & Failover $VDUplinkTeamingPolicy = $VDS | Get-VDPortgroup | Get-VDUplinkTeamingPolicy if ($VDUplinkTeamingPolicy) { Section -Style NOTOCHeading5 -ExcludeFromTOC "Distributed Switch Port Group Teaming & Failover" { $VDSPortgroupNICTeaming = foreach ($VDUplink in $VDUplinkTeamingPolicy) { [PSCustomObject]@{ 'Port Group' = $VDUplink.VDPortgroup 'Distributed Switch' = $VDS.Name 'Load Balancing' = Switch ($VDUplink.LoadBalancingPolicy) { 'LoadbalanceSrcId' { 'Route based on the originating port ID' } 'LoadbalanceSrcMac' { 'Route based on source MAC hash' } 'LoadbalanceIP' { 'Route based on IP hash' } 'ExplicitFailover' { 'Explicit Failover' } default { $VDUplink.LoadBalancingPolicy } } 'Network Failure Detection' = Switch ($VDUplink.FailoverDetectionPolicy) { 'LinkStatus' { 'Link status only' } 'BeaconProbing' { 'Beacon probing' } default { $VDUplink.FailoverDetectionPolicy } } 'Notify Switches' = Switch ($VDUplink.NotifySwitches) { $true { 'Yes' } $false { 'No' } } 'Failback Enabled' = Switch ($VDUplink.EnableFailback) { $true { 'Yes' } $false { 'No' } } 'Active Uplinks' = $VDUplink.ActiveUplinkPort -join [Environment]::NewLine 'Standby Uplinks' = $VDUplink.StandbyUplinkPort -join [Environment]::NewLine 'Unused Uplinks' = $VDUplink.UnusedUplinkPort -join [Environment]::NewLine } } $TableParams = @{ Name = "Distributed Switch Port Group Teaming & Failover - $VDS" ColumnWidths = 12, 12, 12, 11, 10, 10, 11, 11, 11 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSPortgroupNICTeaming | Sort-Object 'Port Group' | Table @TableParams } } #endregion Distributed Switch Port Group Teaming & Failover #region Distributed Switch Private VLANs $VDSwitchPrivateVLANs = $VDS | Get-VDSwitchPrivateVlan if ($VDSwitchPrivateVLANs) { Section -Style Heading4 'Distributed Switch Private VLANs' { $VDSPvlan = foreach ($VDSwitchPrivateVLAN in $VDSwitchPrivateVLANs) { [PSCustomObject]@{ 'Primary VLAN ID' = $VDSwitchPrivateVLAN.PrimaryVlanId 'Private VLAN Type' = $VDSwitchPrivateVLAN.PrivateVlanType 'Secondary VLAN ID' = $VDSwitchPrivateVLAN.SecondaryVlanId } } $TableParams = @{ Name = "Distributed Switch Private VLANs - $VDS" ColumnWidths = 33, 34, 33 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VDSPvlan | Sort-Object 'Primary VLAN ID', 'Secondary VLAN ID' | Table @TableParams } } #endregion Distributed Switch Private VLANs } #endregion VDS Section } } #endregion Distributed Switch Detailed Information } } } #endregion Distributed Switch Section #region vSAN Section Write-PScriboMessage "vSAN InfoLevel set at $($InfoLevel.vSAN)." if (($InfoLevel.vSAN -ge 1) -and ($vCenter.Version -gt 6)) { $VsanClusters = Get-VsanClusterConfiguration -Server $vCenter | Where-Object { $_.vsanenabled -eq $true } | Sort-Object Name if ($VsanClusters) { Section -Style Heading2 'vSAN' { BlankLine foreach ($VsanCluster in $VsanClusters) { $VsanSpaceUsage = Get-Cluster | Where-Object {$_.Name -eq $VsanCluster.Name} $VsanUsedCapacity = [math]::Round($VsanSpaceUsage.CapacityGB - $VsanSpaceUsage.FreeSpaceGB),2 $VsanSpaceInfo = [PSCustomObject]@{ 'Total Capacity' = "$([math]::Round($VsanSpaceUsage.CapacityGB,2)) GB" 'Used Capacity' = "$($VsanUsedCapacity) GB" 'Free Capacity' = "$([math]::Round($VsanSpaceUsage.FreeSpaceGB,2)) GB" } $TableParams = @{ Name = "vSAN Capacity Overview - $($vCenterServerName)" ColumnWidths = 34, 33, 33 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VsanSpaceInfo | Table @TableParams } Paragraph "The following sections detail the configuration of vSAN managed by vCenter Server $vCenterServerName." #region vSAN Cluster Advanced Summary if ($InfoLevel.vSAN -le 2) { BlankLine $VsanClusterInfo = foreach ($VsanCluster in $VsanClusters) { [PSCustomObject]@{ 'Cluster' = $VsanCluster.Name 'vSAN Enabled' = Switch ($VsanCluster.VsanEnabled) { $true { 'Yes' } $false { 'No' } } 'Stretched Cluster' = Switch ($VsanCluster.StretchedClusterEnabled) { $true { 'Yes' } $false { 'No' } } # TODO: Update for vSphere 7.0 U1 and higher - Space Efficiency: Deduplication & Compression, Compression Only, None 'Deduplication & Compression' = Switch ($VsanCluster.SpaceEfficiencyEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } 'Encryption' = Switch ($VsanCluster.EncryptionEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } 'Health Check' = Switch ($VsanCluster.HealthCheckEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } } } $TableParams = @{ Name = "vSAN Cluster Summary - $($vCenterServerName)" ColumnWidths = 25, 15, 15, 15, 15, 15 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VsanClusterInfo | Table @TableParams } #endregion vSAN Cluster Advanced Summary #region vSAN Cluster Detailed Information if ($InfoLevel.vSAN -ge 3) { foreach ($VsanCluster in $VsanClusters) { #region vSAN Cluster Section Section -Style Heading3 $VsanCluster.Name { if ($VsanCluster.VsanEsaEnabled) { $VsanStoragePoolDisk = Get-VsanStoragePoolDisk -Cluster $VsanCluster.Cluster $VsanDiskFormat = $VsanStoragePoolDisk.DiskFormatVersion | Select-Object -First 1 -Unique $VsanClusterDetail = [PSCustomObject]@{ 'Cluster' = $VsanCluster.Name 'ID' = $VsanCluster.Id 'vSAN Type' = Switch ($VsanCluster.VsanEsaEnabled) { $true { 'vSAN ESA' } $null { 'vSAN OSA' } } 'Stretched Cluster' = Switch ($VsanCluster.StretchedClusterEnabled) { $true { 'Yes' } $false { 'No' } } 'Number of Hosts' = $VsanCluster.Cluster.ExtensionData.Host.Count 'Number of Disks' = $VsanStoragePoolDisk.Count 'Disk Claim Mode' = $VsanCluster.VsanDiskClaimMode 'Disk Format Version' = $VsanDiskFormat 'Deduplication & Compression' = Switch ($VsanCluster.SpaceEfficiencyEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } 'Encryption' = Switch ($VsanCluster.EncryptionEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } 'Health Check' = Switch ($VsanCluster.HealthCheckEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } 'HCL Last Updated' = $VsanCluster.TimeOfHclUpdate } if ($InfoLevel.vSAN -ge 4) { $VsanClusterDetail | Add-Member -MemberType NoteProperty -Name 'Hosts' -Value (($VsanStoragePoolDisk.Host | Select-Object -Unique | Sort-Object Name) -join ', ') } $TableParams = @{ Name = "vSAN Configuration - $($VsanCluster.Name)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VsanClusterDetail | Table @TableParams Section -Style Heading4 'Disks' { $vDisks = foreach ($Disk in $VsanStoragePoolDisk) { [PSCustomObject]@{ 'Disk' = $Disk.Name 'Name' = $Disk.ExtensionData.DisplayName 'Drive Type' = Switch ($Disk.IsSsd) { $true { 'Flash' } $false { 'HDD' } } 'Host' = $Disk.Host.Name 'State' = Switch ($Disk.IsMounted) { $True { 'Mounted' } $False { 'Unmounted' } } 'Encrypted' = Switch ($Disk.IsEncryped) { $true { 'Yes' } $false { 'No' } } 'Capacity' = "$([math]::Round($Disk.CapacityGB, 2)) GB" 'Capacity GB' = [math]::Round($Disk.CapacityGB, 2) 'Serial Number' = $Disk.ExtensionData.SerialNumber 'Vendor' = $Disk.ExtensionData.Vendor 'Model' = $Disk.ExtensionData.Model 'Disk Type' = $Disk.DiskType 'Disk Format Version' = $Disk.DiskFormatVersion } } if ($InfoLevel.vSAN -ge 4) { $vDisks | Sort-Object Host | ForEach-Object { $vDisk = $_ Section -Style NOTOCHeading5 -ExcludeFromTOC "$($vDisk.Name) - $($vDisk.Host)" { $TableParams = @{ Name = "Disk $($vDisk.Name) - $($vDisk.Host)" List = $true Columns = 'Name', 'State', 'Drive Type', 'Encrypted', 'Capacity', 'Host', 'Serial Number', 'Vendor', 'Model', 'Disk Format Version', 'Disk Type' ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vDisk | Table @TableParams } } } else { $TableParams = @{ Name = "vSAN Disks - $($VsanCluster.Name)" Columns = 'Disk', 'Capacity GB', 'State', 'Host' ColumnWidths = 40, 15, 15, 30 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vDisks | Sort-Object Host | Table @TableParams } } } else { $VsanDiskGroup = Get-VsanDiskGroup -Cluster $VsanCluster.Cluster $NumVsanDiskGroup = $VsanDiskGroup.Count $VsanDisk = Get-VsanDisk -VsanDiskGroup $VsanDiskGroup $VsanDiskFormat = $VsanDisk.DiskFormatVersion | Select-Object -First 1 -Unique $NumVsanSsd = ($VsanDisk | Where-Object { $_.IsSsd -eq $true }).Count $NumVsanHdd = ($VsanDisk | Where-Object { $_.IsSsd -eq $false }).Count if ($NumVsanHdd -gt 0) { $VsanClusterType = "Hybrid" } else { $VsanClusterType = "All Flash" } $VsanClusterDetail = [PSCustomObject]@{ 'Cluster' = $VsanCluster.Name 'ID' = $VsanCluster.Id 'vSAN Type' = Switch ($VsanCluster.VsanEsaEnabled) { $true { 'vSAN ESA' } $null { 'vSAN OSA' } } 'Storage Type' = $VsanClusterType 'Stretched Cluster' = Switch ($VsanCluster.StretchedClusterEnabled) { $true { 'Yes' } $false { 'No' } } 'Number of Hosts' = $VsanCluster.Cluster.ExtensionData.Host.Count 'Number of Disks' = $NumVsanSsd + $NumVsanHdd 'Number of Disk Groups' = $NumVsanDiskGroup 'Disk Claim Mode' = $VsanCluster.VsanDiskClaimMode 'Disk Format Version' = $VsanDiskFormat 'Deduplication & Compression' = Switch ($VsanCluster.SpaceEfficiencyEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } 'Encryption' = Switch ($VsanCluster.EncryptionEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } 'Health Check' = Switch ($VsanCluster.HealthCheckEnabled) { $true { 'Enabled' } $false { 'Disabled' } $null { 'Disabled' } } 'HCL Last Updated' = $VsanCluster.TimeOfHclUpdate } if ($InfoLevel.vSAN -ge 4) { $VsanClusterDetail | Add-Member -MemberType NoteProperty -Name 'Hosts' -Value (($VsanDiskGroup.VMHost | Select-Object -Unique | Sort-Object Name) -join ', ') } $TableParams = @{ Name = "vSAN Configuration - $($VsanCluster.Name)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VsanClusterDetail | Table @TableParams # TODO: vSAN Services Section -Style Heading4 'Disk Groups' { $VsanDiskGroups = foreach ($DiskGroup in $VsanDiskGroup) { $Disks = $DiskGroup | Get-VsanDisk [PSCustomObject]@{ 'Disk Group' = $DiskGroup.Uuid 'Host' = $Diskgroup.VMHost '# of Disks' = $Disks.Count 'State' = Switch ($DiskGroup.IsMounted) { $true { 'Mounted' } $False { 'Unmounted' } } 'Type' = Switch ($DiskGroup.DiskGroupType) { 'AllFlash' { 'All Flash' } default { $DiskGroup.DiskGroupType } } 'Disk Format Version' = $DiskGroup.DiskFormatVersion } } $TableParams = @{ Name = "vSAN Disk Groups - $($VsanCluster.Name)" ColumnWidths = 35, 28, 7, 10, 10, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VsanDiskGroups | Sort-Object Host | Table @TableParams } Section -Style Heading4 'Disks' { $vDisks = foreach ($Disk in $VsanDisk) { [PSCustomObject]@{ 'Disk' = $Disk.Name 'Name' = $Disk.ExtensionData.DisplayName 'State' = Switch ($Disk.IsMounted) { $True { 'Mounted' } $False { 'Unmounted' } } 'Drive Type' = Switch ($Disk.IsSsd) { $true { 'Flash' } $false { 'HDD' } } 'Host' = $Disk.VsanDiskGroup.VMHost.Name 'Claimed As' = Switch ($Disk.IsCacheDisk) { $true { 'Cache' } $false { 'Capacity' } } 'Capacity' = "$([math]::Round($Disk.CapacityGB, 2)) GB" 'Capacity GB' = [math]::Round($Disk.CapacityGB, 2) 'Serial Number' = $Disk.ExtensionData.SerialNumber 'Vendor' = $Disk.ExtensionData.Vendor 'Model' = $Disk.ExtensionData.Model 'Disk Group' = $Disk.VsanDiskGroup.Uuid 'Disk Format Version' = $Disk.DiskFormatVersion } } if ($InfoLevel.vSAN -ge 4) { $vDisks | Sort-Object Host | ForEach-Object { $vDisk = $_ Section -Style NOTOCHeading5 -ExcludeFromTOC "$($vDisk.Name) - $($vDisk.Host)" { $TableParams = @{ Name = "Disk $($vDisk.Name) - $($vDisk.Host)" List = $true Columns = 'Name', 'Drive Type', 'Claimed As', 'Capacity', 'Host', 'Disk Group', 'Serial Number', 'Vendor', 'Model', 'Disk Format Version' ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vDisk | Table @TableParams } } } else { $TableParams = @{ Name = "vSAN Disks - $($VsanCluster.Name)" Columns = 'Name', 'Drive Type', 'Claimed As', 'Capacity GB', 'Host', 'Disk Group' ColumnWidths = 21, 10, 10, 10, 21, 28 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $vDisks | Sort-Object Host | Table @TableParams } } } $VsanIscsiTargets = Get-VsanIscsiTarget -Cluster $VsanCluster.Cluster -ErrorAction SilentlyContinue if ($VsanIscsiTargets) { Section -Style Heading4 'iSCSI Targets' { $VsanIscsiTargetInfo = foreach ($VsanIscsiTarget in $VsanIscsiTargets) { [PSCustomObject]@{ 'IQN' = $VsanIscsiTarget.IscsiQualifiedName 'Alias' = $VsanIscsiTarget.Name 'LUNs' = $VsanIscsiTarget.NumLuns 'Network Interface' = $VsanIscsiTarget.NetworkInterface 'I/O Owner Host' = $VsanIscsiTarget.IoOwnerVMHost 'TCP Port' = $VsanIscsiTarget.TcpPort 'Health' = $TextInfo.ToTitleCase($VsanIscsiTarget.VsanHealth) 'Storage Policy' = Switch ($VsanIscsiTarget.StoragePolicy.Name) { $null { '--' } default { $VsanIscsiTarget.StoragePolicy.Name } } 'Compliance Status' = $TextInfo.ToTitleCase($VsanIscsiTarget.SpbmComplianceStatus) 'Authentication' = $VsanIscsiTarget.AuthenticationType } } $TableParams = @{ Name = "vSAN iSCSI Targets - $($VsanCluster.Name)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VsanIscsiTargetInfo | Table @TableParams } } $VsanIscsiLuns = Get-VsanIscsiLun -Cluster $VsanCluster.Cluster -ErrorAction SilentlyContinue | Sort-Object Name, LunId if ($VsanIscsiLuns) { Section -Style Heading4 'iSCSI LUNs' { $VsanIscsiLunInfo = foreach ($VsanIscsiLun in $VsanIscsiLuns) { [PSCustomobject]@{ 'LUN' = $VsanIscsiLun.Name 'LUN ID' = $VsanIscsiLun.LunId 'Capacity GB' = [math]::Round($VsanIscsiLun.CapacityGB, 2) 'Used Capacity GB' = [math]::Round($VsanIscsiLun.UsedCapacityGB, 2) 'State' = Switch ($VsanIscsiLun.IsOnline) { $true { 'Online' } $false { 'Offline' } } 'Health' = $TextInfo.ToTitleCase($VsanIscsiLun.VsanHealth) 'Storage Policy' = Switch ($VsanIscsiLun.StoragePolicy.Name) { $null { '--' } default { $VsanIscsiLun.StoragePolicy.Name } } 'Compliance Status' = $TextInfo.ToTitleCase($VsanIscsiLun.SpbmComplianceStatus) } } if ($InfoLevel.vSAN -ge 4) { $TableParams = @{ Name = "vSAN iSCSI LUNs - $($VsanCluster.Name)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VsanIscsiLunInfo | Table @TableParams } else { $TableParams = @{ Name = "vSAN iSCSI LUNs - $($VsanCluster.Name)" ColumnWidths = 28 , 18, 18, 18, 18 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VsanIscsiLunInfo | Select-Object 'LUN', 'LUN ID', 'Capacity GB', 'Used Capacity GB', 'State' | Table @TableParams } } } } #endregion vSAN Cluster Section } } #endregion vSAN Cluster Detailed Information } } } #endregion vSAN Section #region Datastore Section Write-PScriboMessage "Datastore InfoLevel set at $($InfoLevel.Datastore)." if ($InfoLevel.Datastore -ge 1) { if ($Datastores) { Section -Style Heading2 'Datastores' { Paragraph "The following sections detail the configuration of datastores managed by vCenter Server $vCenterServerName." #region Datastore Infomative Information if ($InfoLevel.Datastore -le 2) { BlankLine $DatastoreInfo = foreach ($Datastore in $Datastores) { [PSCustomObject]@{ 'Datastore' = $Datastore.Name 'Type' = $Datastore.Type 'Version' = Switch ($Datastore.FileSystemVersion) { $null { '--' } default { $Datastore.FileSystemVersion } } '# of Hosts' = $Datastore.ExtensionData.Host.Count '# of VMs' = $Datastore.ExtensionData.VM.Count 'Total Capacity GB' = [math]::Round($Datastore.CapacityGB, 2) 'Used Capacity GB' = [math]::Round((($Datastore.CapacityGB) - ($Datastore.FreeSpaceGB)), 2) 'Free Space GB' = [math]::Round($Datastore.FreeSpaceGB, 2) '% Used' = [math]::Round((100 - (($Datastore.FreeSpaceGB) / ($Datastore.CapacityGB) * 100)), 2) } } if ($Healthcheck.Datastore.CapacityUtilization) { $DatastoreInfo | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property '% Used' $DatastoreInfo | Where-Object { $_.'% Used' -ge 75 -and $_.'% Used' -lt 90 } | Set-Style -Style Warning -Property '% Used' } $TableParams = @{ Name = "Datastore Summary - $($vCenterServerName)" ColumnWidths = 20, 9, 9, 9, 9, 11, 11, 11, 11 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DatastoreInfo | Sort-Object Datastore | Table @TableParams } #endregion Datastore Advanced Summary #region Datastore Detailed Information if ($InfoLevel.Datastore -ge 3) { foreach ($Datastore in $Datastores) { # TODO: Test Tags #region Datastore Section Section -Style Heading3 $Datastore.Name { $DatastoreDetail = [PSCustomObject]@{ 'Datastore' = $Datastore.Name 'ID' = $Datastore.Id 'Datacenter' = $Datastore.Datacenter 'Type' = $Datastore.Type 'Version' = Switch ($Datastore.FileSystemVersion) { $null { '--' } default { $Datastore.FileSystemVersion } } 'State' = $Datastore.State 'Number of Hosts' = $Datastore.ExtensionData.Host.Count 'Number of VMs' = $Datastore.ExtensionData.VM.Count 'Storage I/O Control' = Switch ($Datastore.StorageIOControlEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Congestion Threshold' = Switch ($Datastore.CongestionThresholdMillisecond) { $null { '--' } default { "$($Datastore.CongestionThresholdMillisecond) ms" } } 'Total Capacity' = "$([math]::Round($Datastore.CapacityGB, 2)) GB" 'Used Capacity' = "$([math]::Round((($Datastore.CapacityGB) - ($Datastore.FreeSpaceGB)), 2)) GB" 'Free Space' = "$([math]::Round($Datastore.FreeSpaceGB, 2)) GB" '% Used' = [math]::Round((100 - (($Datastore.FreeSpaceGB) / ($Datastore.CapacityGB) * 100)), 2) } if ($Healthcheck.Datastore.CapacityUtilization) { $DatastoreDetail | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property '% Used' $DatastoreDetail | Where-Object { $_.'% Used' -ge 75 -and $_.'% Used' -lt 90 } | Set-Style -Style Warning -Property '% Used' } $MemberProps = @{ 'InputObject' = $DatastoreDetail 'MemberType' = 'NoteProperty' } <# if ($TagAssignments | Where-Object {$_.entity -eq $Datastore}) { Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $Datastore}).Tag -join ',') } #> #region Datastore Advanced Detailed Information if ($InfoLevel.Datastore -ge 4) { $DatastoreHosts = foreach ($DatastoreHost in $Datastore.ExtensionData.Host.Key) { $VMHostLookup."$($DatastoreHost.Type)-$($DatastoreHost.Value)" } Add-Member @MemberProps -Name 'Hosts' -Value (($DatastoreHosts | Sort-Object) -join ', ') $DatastoreVMs = foreach ($DatastoreVM in $Datastore.ExtensionData.VM) { $VMLookup."$($DatastoreVM.Type)-$($DatastoreVM.Value)" } Add-Member @MemberProps -Name 'Virtual Machines' -Value (($DatastoreVMs | Sort-Object) -join ', ') } #endregion Datastore Advanced Detailed Information $TableParams = @{ Name = "Datastore Configuration - $($Datastore.Name)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DatastoreDetail | Sort-Object Datacenter, Name | Table @TableParams # Get VMFS volumes. Ignore local SCSILuns. if (($Datastore.Type -eq 'VMFS') -and ($Datastore.ExtensionData.Info.Vmfs.Local -eq $false)) { #region SCSI LUN Information Section Section -Style Heading4 'SCSI LUN Information' { $ScsiLuns = foreach ($DatastoreHost in $Datastore.ExtensionData.Host.Key) { $DiskName = $Datastore.ExtensionData.Info.Vmfs.Extent.DiskName $ScsiDeviceDetailProps = @{ 'VMHosts' = $VMHosts 'VMHostMoRef' = "$($DatastoreHost.Type)-$($DatastoreHost.Value)" 'DatastoreDiskName' = $DiskName } $ScsiDeviceDetail = Get-ScsiDeviceDetail @ScsiDeviceDetailProps [PSCustomObject]@{ 'Host' = $VMHostLookup."$($DatastoreHost.Type)-$($DatastoreHost.Value)" 'Canonical Name' = $DiskName 'Capacity GB' = $ScsiDeviceDetail.CapacityGB 'Vendor' = $ScsiDeviceDetail.Vendor 'Model' = $ScsiDeviceDetail.Model 'Is SSD' = $ScsiDeviceDetail.Ssd 'Multipath Policy' = $ScsiDeviceDetail.MultipathPolicy 'Paths' = $ScsiDeviceDetail.Paths } } $TableParams = @{ Name = "SCSI LUN Information - $($vCenterServerName)" ColumnWidths = 19, 19, 10, 10, 10, 10, 14, 8 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $ScsiLuns | Sort-Object Host | Table @TableParams } #endregion SCSI LUN Information Section } } #endregion Datastore Section } } #endregion Datastore Detailed Information } } } #endregion Datastore Section #region Datastore Clusters Write-PScriboMessage "DSCluster InfoLevel set at $($InfoLevel.DSCluster)." if ($InfoLevel.DSCluster -ge 1) { $DSClusters = Get-DatastoreCluster -Server $vCenter if ($DSClusters) { #region Datastore Clusters Section Section -Style Heading2 'Datastore Clusters' { Paragraph "The following sections detail the configuration of datastore clusters managed by vCenter Server $vCenterServerName." #region Datastore Cluster Advanced Summary if ($InfoLevel.DSCluster -le 2) { BlankLine $DSClusterInfo = foreach ($DSCluster in $DSClusters) { [PSCustomObject]@{ 'Datastore Cluster' = $DSCluster.Name 'SDRS Automation Level' = Switch ($DSCluster.SdrsAutomationLevel) { 'FullyAutomated' { 'Fully Automated' } 'Manual' { 'Manual' } default { $DSCluster.SdrsAutomationLevel } } 'Space Utilization Threshold' = "$($DSCluster.SpaceUtilizationThresholdPercent)%" 'I/O Load Balance' = Switch ($DSCluster.IOLoadBalanceEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'I/O Latency Threshold' = "$($DSCluster.IOLatencyThresholdMillisecond) ms" } } if ($Healthcheck.DSCluster.SDRSAutomationLevelFullyAuto) { $DSClusterInfo | Where-Object { $_.'SDRS Automation Level' -ne 'Fully Automated' } | Set-Style -Style Warning -Property 'SDRS Automation Level' } $TableParams = @{ Name = "Datastore Cluster Configuration - $($DSCluster.Name)" ColumnWidths = 20, 20, 20, 20, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DSClusterInfo | Sort-Object Name | Table @TableParams } #endregion Datastore Cluster Advanced Summary #region Datastore Cluster Detailed Information if ($InfoLevel.DSCluster -ge 3) { foreach ($DSCluster in $DSClusters) { # TODO: Space Load Balance Config, IO Load Balance Config, Rules # TODO: Test Tags Section -Style Heading3 $DSCluster.Name { Paragraph ("The following table details the configuration " + "for datastore cluster $DSCluster.") BlankLine $DSClusterDetail = [PSCustomObject]@{ 'Datastore Cluster' = $DSCluster.Name 'ID' = $DSCluster.Id 'SDRS Automation Level' = Switch ($DSCluster.SdrsAutomationLevel) { 'FullyAutomated' { 'Fully Automated' } 'Manual' { 'Manual' } default { $DSCluster.SdrsAutomationLevel } } 'Space Utilization Threshold' = "$($DSCluster.SpaceUtilizationThresholdPercent)%" 'I/O Load Balance' = Switch ($DSCluster.IOLoadBalanceEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'I/O Latency Threshold' = "$($DSCluster.IOLatencyThresholdMillisecond) ms" 'Capacity' = "$([math]::Round($DSCluster.CapacityGB, 2)) GB" 'Free Space' = "$([math]::Round($DSCluster.FreeSpaceGB, 2)) GB" '% Used' = Switch ($DSCluster.CapacityGB -gt 0) { $true { [math]::Round((100 - (($DSCluster.FreeSpaceGB) / ($DSCluster.CapacityGB) * 100)), 2) } $false { '0' } } } $MemberProps = @{ 'InputObject' = $DSClusterDetail 'MemberType' = 'NoteProperty' } <# if ($TagAssignments | Where-Object {$_.entity -eq $DSCluster}) { Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $DSCluster}).Tag -join ',') } #> if ($Healthcheck.DSCluster.CapacityUtilization) { $DSClusterDetail | Where-Object { $_.'% Used' -ge 90 } | Set-Style -Style Critical -Property '% Used' $DSClusterDetail | Where-Object { $_.'% Used' -ge 75 -and $_.'% Used' -lt 90 } | Set-Style -Style Critical -Property '% Used' } if ($Healthcheck.DSCluster.SDRSAutomationLevel) { $DSClusterDetail | Where-Object { $_.'SDRS Automation Level' -ne 'Fully Automated' } | Set-Style -Style Warning -Property 'SDRS Automation Level' } $TableParams = @{ Name = "Datastore Cluster Configuration - $($DSCluster.Name)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $DSClusterDetail | Table @TableParams #region SDRS VM Overrides $StoragePodProps = @{ 'ViewType' = 'StoragePod' 'Filter' = @{'Name' = $DSCluster.Name } } $StoragePod = Get-View @StoragePodProps if ($StoragePod) { $PodConfig = $StoragePod.PodStorageDrsEntry.StorageDrsConfig.PodConfig # Set default automation value variables Switch ($PodConfig.DefaultVmBehavior) { "automated" { $DefaultVmBehavior = "Default (Fully Automated)" } "manual" { $DefaultVmBehavior = "Default (No Automation (Manual Mode))" } } Switch ($PodConfig.DefaultIntraVmAffinity) { $true { $DefaultIntraVmAffinity = "Default (Yes)" } $false { $DefaultIntraVmAffinity = "Default (No)" } } $VMOverrides = $StoragePod.PodStorageDrsEntry.StorageDrsConfig.VmConfig | Where-Object { -not ( ($null -eq $_.Enabled) -and ($null -eq $_.IntraVmAffinity) ) } } if ($VMOverrides) { $VMOverrideDetails = foreach ($Override in $VMOverrides) { [PSCustomObject]@{ 'Virtual Machine' = $VMLookup."$($Override.Vm.Type)-$($Override.Vm.Value)" 'SDRS Automation Level' = Switch ($Override.Enabled) { $true { 'Fully Automated' } $false { 'Disabled' } $null { $DefaultVmBehavior } } 'Keep VMDKs Together' = Switch ($Override.IntraVmAffinity) { $true { 'Yes' } $false { 'No' } $null { $DefaultIntraVmAffinity } } } } Section -Style Heading4 'SDRS VM Overrides' { $TableParams = @{ Name = "SDRS VM Overrides - $($DSCluster.Name)" ColumnWidths = 50, 30, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMOverrideDetails | Sort-Object 'Virtual Machine' | Table @TableParams } } #endregion SDRS VM Overrides } } } #endregion Datastore Cluster Detailed Information } #endregion Datastore Clusters Section } } #endregion Datastore Clusters #region Virtual Machine Section Write-PScriboMessage "VM InfoLevel set at $($InfoLevel.VM)." if ($InfoLevel.VM -ge 1) { if ($VMs) { Section -Style Heading2 'Virtual Machines' { Paragraph "The following sections detail the configuration of virtual machines managed by vCenter Server $vCenterServerName." #region Virtual Machine Summary Information if ($InfoLevel.VM -eq 1) { BlankLine $VMSummary = [PSCustomObject]@{ 'Total VMs' = $VMs.Count 'Total vCPUs' = ($VMs | Measure-Object -Property NumCpu -Sum).Sum 'Total Memory' = "$([math]::Round(($VMs | Measure-Object -Property MemoryGB -Sum).Sum, 2)) GB" 'Total Provisioned Space' = "$([math]::Round(($VMs | Measure-Object -Property ProvisionedSpaceGB -Sum).Sum, 2)) GB" 'Total Used Space' = "$([math]::Round(($VMs | Measure-Object -Property UsedSpaceGB -Sum).Sum, 2)) GB" 'VMs Powered On' = ($VMs | Where-Object { $_.PowerState -eq 'PoweredOn' }).Count 'VMs Powered Off' = ($VMs | Where-Object { $_.PowerState -eq 'PoweredOff' }).Count 'VMs Orphaned' = ($VMs | Where-Object { $_.ExtensionData.Runtime.ConnectionState -eq 'Orphaned' }).Count 'VMs Inaccessible' = ($VMs | Where-Object { $_.ExtensionData.Runtime.ConnectionState -eq 'Inaccessible' }).Count 'VMs Suspended' = ($VMs | Where-Object { $_.PowerState -eq 'Suspended' }).Count 'VMs with Snapshots' = ($VMs | Where-Object { $_.ExtensionData.Snapshot }).Count 'Guest Operating System Types' = (($VMs | Get-View).Summary.Config.GuestFullName | Select-Object -Unique).Count 'VM Tools OK' = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsOK' }).Count 'VM Tools Old' = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsOld' }).Count 'VM Tools Not Running' = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsNotRunning' }).Count 'VM Tools Not Installed' = ($VMs | Where-Object { $_.ExtensionData.Guest.ToolsStatus -eq 'toolsNotInstalled' }).Count } $TableParams = @{ Name = "VM Summary - $($vCenterServerName)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMSummary | Table @TableParams } #endregion Virtual Machine Summary Information #region Virtual Machine Advanced Summary if ($InfoLevel.VM -eq 2) { BlankLine $VMSnapshotList = $VMs.Extensiondata.Snapshot.RootSnapshotList $VMInfo = foreach ($VM in $VMs) { $VMView = $VM | Get-View [PSCustomObject]@{ 'Virtual Machine' = $VM.Name 'Power State' = Switch ($VM.PowerState) { 'PoweredOn' { 'On' } 'PoweredOff' { 'Off' } default { $VM.PowerState } } 'IP Address' = Switch ($VMView.Guest.IpAddress) { $null { '--' } default { $VMView.Guest.IpAddress } } 'vCPUs' = $VM.NumCpu 'Memory GB' = [math]::Round(($VM.MemoryGB), 0) 'Provisioned GB' = [math]::Round(($VM.ProvisionedSpaceGB), 2) 'Used GB' = [math]::Round(($VM.UsedSpaceGB), 2) 'HW Version' = ($VM.HardwareVersion).Replace('vmx-', 'v') 'VM Tools Status' = Switch ($VMView.Guest.ToolsStatus) { 'toolsOld' { 'Old' } 'toolsOK' { 'OK' } 'toolsNotRunning' { 'Not Running' } 'toolsNotInstalled' { 'Not Installed' } default { $VMView.Guest.ToolsStatus } } } } if ($Healthcheck.VM.VMToolsStatus) { $VMInfo | Where-Object { $_.'VM Tools Status' -ne 'OK' } | Set-Style -Style Warning -Property 'VM Tools Status' } if ($Healthcheck.VM.PowerState) { $VMInfo | Where-Object { $_.'Power State' -ne 'On' } | Set-Style -Style Warning -Property 'Power State' } $TableParams = @{ Name = "VM Advanced Summary - $($vCenterServerName)" ColumnWidths = 21, 8, 16, 9, 9, 9, 9, 9, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMInfo | Table @TableParams #region VM Snapshot Information if ($VMSnapshotList -and $Options.ShowVMSnapshots) { Section -Style Heading3 'Snapshots' { $VMSnapshotInfo = foreach ($VMSnapshot in $VMSnapshotList) { [PSCustomObject]@{ 'Virtual Machine' = $VMLookup."$($VMSnapshot.VM)" 'Snapshot Name' = $VMSnapshot.Name 'Description' = $VMSnapshot.Description 'Days Old' = ((Get-Date).ToUniversalTime() - $VMSnapshot.CreateTime).Days } } if ($Healthcheck.VM.VMSnapshots) { $VMSnapshotInfo | Where-Object { $_.'Days Old' -ge 7 } | Set-Style -Style Warning $VMSnapshotInfo | Where-Object { $_.'Days Old' -ge 14 } | Set-Style -Style Critical } $TableParams = @{ Name = "VM Snapshot Summary - $($vCenterServerName)" ColumnWidths = 30, 30, 30, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMSnapshotInfo | Table @TableParams } } #endregion VM Snapshot Information } #endregion Virtual Machine Advanced Summary #region Virtual Machine Detailed Information # TODO: Test Tags if ($InfoLevel.VM -ge 3) { if ($UserRole.Privilege -contains 'StorageProfile.View') { $VMSpbmConfig = Get-SpbmEntityConfiguration -VM ($VMs) | Where-Object { $null -ne $_.StoragePolicy } } else { Write-PScriboMessage "Insufficient user privileges to report VM storage policies. Please ensure the user account has the 'Storage Profile > View' privilege assigned." } if ($InfoLevel.VM -ge 4) { $VMHardDisks = Get-HardDisk -VM ($VMs) -Server $vCenter } foreach ($VM in $VMs) { Section -Style Heading3 $VM.name { $VMUptime = Get-Uptime -VM $VM $VMSpbmPolicy = $VMSpbmConfig | Where-Object { $_.entity -eq $vm } $VMView = $VM | Get-View $VMSnapshotList = $vmview.Snapshot.RootSnapshotList $VMDetail = [PSCustomObject]@{ 'Virtual Machine' = $VM.Name 'ID' = $VM.Id 'Operating System' = $VMView.Summary.Config.GuestFullName 'Hardware Version' = ($VM.HardwareVersion).Replace('vmx-', 'v') 'Power State' = Switch ($VM.PowerState) { 'PoweredOn' { 'On' } 'PoweredOff' { 'Off' } default { $TextInfo.ToTitleCase($VM.PowerState) } } 'Connection State' = $TextInfo.ToTitleCase($VM.ExtensionData.Runtime.ConnectionState) 'VM Tools Status' = Switch ($VMView.Guest.ToolsStatus) { 'toolsOld' { 'Old' } 'toolsOK' { 'OK' } 'toolsNotRunning' { 'Not Running' } 'toolsNotInstalled' { 'Not Installed' } default { $TextInfo.ToTitleCase($VMView.Guest.ToolsStatus) } } 'Fault Tolerance State' = Switch ($VMView.Runtime.FaultToleranceState) { 'notConfigured' { 'Not Configured' } 'needsSecondary' { 'Needs Secondary' } 'running' { 'Running' } 'disabled' { 'Disabled' } 'starting' { 'Starting' } 'enabled' { 'Enabled' } default { $TextInfo.ToTitleCase($VMview.Runtime.FaultToleranceState) } } 'Host' = $VM.VMHost.Name 'Parent' = $VM.VMHost.Parent.Name 'Parent Folder' = $VM.Folder.Name 'Parent Resource Pool' = $VM.ResourcePool.Name 'vCPUs' = $VM.NumCpu 'Cores per Socket' = $VM.CoresPerSocket 'CPU Shares' = "$($VM.VMResourceConfiguration.CpuSharesLevel) / $($VM.VMResourceConfiguration.NumCpuShares)" 'CPU Reservation' = $VM.VMResourceConfiguration.CpuReservationMhz 'CPU Limit' = "$($VM.VMResourceConfiguration.CpuReservationMhz) MHz" 'CPU Hot Add' = Switch ($VMView.Config.CpuHotAddEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'CPU Hot Remove' = Switch ($VMView.Config.CpuHotRemoveEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Memory Allocation' = "$([math]::Round(($VM.memoryGB), 2)) GB" 'Memory Shares' = "$($VM.VMResourceConfiguration.MemSharesLevel) / $($VM.VMResourceConfiguration.NumMemShares)" 'Memory Hot Add' = Switch ($VMView.Config.MemoryHotAddEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'vNICs' = $VMView.Summary.Config.NumEthernetCards 'DNS Name' = if ($VMView.Guest.HostName) { $VMView.Guest.HostName } else { '--' } 'Networks' = if ($VMView.Guest.Net.Network) { (($VMView.Guest.Net | Where-Object { $null -ne $_.Network } | Select-Object Network | Sort-Object Network).Network -join ', ') } else { '--' } 'IP Address' = if ($VMView.Guest.Net.IpAddress) { (($VMView.Guest.Net | Where-Object { ($null -ne $_.Network) -and ($null -ne $_.IpAddress) } | Select-Object IpAddress | Sort-Object IpAddress).IpAddress -join ', ') } else { '--' } 'MAC Address' = if ($VMView.Guest.Net.MacAddress) { (($VMView.Guest.Net | Where-Object { $null -ne $_.Network } | Select-Object -Property MacAddress).MacAddress -join ', ') } else { '--' } 'vDisks' = $VMView.Summary.Config.NumVirtualDisks 'Provisioned Space' = "$([math]::Round(($VM.ProvisionedSpaceGB), 2)) GB" 'Used Space' = "$([math]::Round(($VM.UsedSpaceGB), 2)) GB" 'Changed Block Tracking' = Switch ($VMView.Config.ChangeTrackingEnabled) { $true { 'Enabled' } $false { 'Disabled' } } 'Storage Based Policy' = Switch ($VMSpbmPolicy.StoragePolicy.Name) { $null { '--' } default { $TextInfo.ToTitleCase($VMSpbmPolicy.StoragePolicy.Name) } } 'Storage Based Policy Compliance' = Switch ($VMSpbmPolicy.ComplianceStatus) { $null { '--' } 'compliant' { 'Compliant' } 'nonCompliant' { 'Non Compliant' } 'unknown' { 'Unknown' } default { $TextInfo.ToTitleCase($VMSpbmPolicy.ComplianceStatus) } } } $MemberProps = @{ 'InputObject' = $VMDetail 'MemberType' = 'NoteProperty' } #if ($VMView.Config.CreateDate) { # Add-Member @MemberProps -Name 'Creation Date' -Value ($VMView.Config.CreateDate).ToLocalTime() #} <# if ($TagAssignments | Where-Object {$_.entity -eq $VM}) { Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VM}).Tag -join ',') } #> if ($VM.Notes) { Add-Member @MemberProps -Name 'Notes' -Value $VM.Notes } if ($VMView.Runtime.BootTime) { Add-Member @MemberProps -Name 'Boot Time' -Value ($VMView.Runtime.BootTime).ToLocalTime() } if ($VMUptime.UptimeDays) { Add-Member @MemberProps -Name 'Uptime Days' -Value $VMUptime.UptimeDays } #region VM Health Checks if ($Healthcheck.VM.VMToolsStatus) { $VMDetail | Where-Object { $_.'VM Tools Status' -ne 'OK' } | Set-Style -Style Warning -Property 'VM Tools Status' } if ($Healthcheck.VM.PowerState) { $VMDetail | Where-Object { $_.'Power State' -ne 'On' } | Set-Style -Style Warning -Property 'Power State' } if ($Healthcheck.VM.ConnectionState) { $VMDetail | Where-Object { $_.'Connection State' -ne 'Connected' } | Set-Style -Style Critical -Property 'Connection State' } if ($Healthcheck.VM.CpuHotAdd) { $VMDetail | Where-Object { $_.'CPU Hot Add' -eq 'Enabled' } | Set-Style -Style Warning -Property 'CPU Hot Add' } if ($Healthcheck.VM.CpuHotRemove) { $VMDetail | Where-Object { $_.'CPU Hot Remove' -eq 'Enabled' } | Set-Style -Style Warning -Property 'CPU Hot Remove' } if ($Healthcheck.VM.MemoryHotAdd) { $VMDetail | Where-Object { $_.'Memory Hot Add' -eq 'Enabled' } | Set-Style -Style Warning -Property 'Memory Hot Add' } if ($Healthcheck.VM.ChangeBlockTracking) { $VMDetail | Where-Object { $_.'Changed Block Tracking' -eq 'Disabled' } | Set-Style -Style Warning -Property 'Changed Block Tracking' } if ($Healthcheck.VM.SpbmPolicyCompliance) { $VMDetail | Where-Object { $_.'Storage Based Policy Compliance' -eq 'Unknown' } | Set-Style -Style Warning -Property 'Storage Based Policy Compliance' $VMDetail | Where-Object { $_.'Storage Based Policy Compliance' -eq 'Non Compliant' } | Set-Style -Style Critical -Property 'Storage Based Policy Compliance' } #endregion VM Health Checks $TableParams = @{ Name = "VM Configuration - $($VM.Name)" List = $true ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMDetail | Table @TableParams if ($InfoLevel.VM -ge 4) { $VMnics = $VM.Guest.Nics | Where-Object { $null -ne $_.Device } | Sort-Object Device $VMHdds = $VMHardDisks | Where-Object { $_.ParentId -eq $VM.Id } | Sort-Object Name $SCSIControllers = $VMView.Config.Hardware.Device | Where-Object { $_.DeviceInfo.Label -match "SCSI Controller" } $VMGuestVols = $VM.Guest.Disks | Sort-Object Path if ($VMnics) { Section -Style Heading4 "Network Adapters" { $VMnicInfo = foreach ($VMnic in $VMnics) { [PSCustomObject]@{ 'Adapter' = $VMnic.Device 'Connected' = $VMnic.Connected 'Network Name' = Switch -wildcard ($VMnic.Device.NetworkName) { 'dvportgroup*' { $VDPortgroupLookup."$($VMnic.Device.NetworkName)" } default { $VMnic.Device.NetworkName } } 'Adapter Type' = $VMnic.Device.Type 'IP Address' = $VMnic.IpAddress -join [Environment]::NewLine 'MAC Address' = $VMnic.Device.MacAddress } } $TableParams = @{ Name = "Network Adapters - $($VM.Name)" ColumnWidths = 20, 12, 16, 12, 20, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMnicInfo | Table @TableParams } } if ($SCSIControllers) { Section -Style Heading4 "SCSI Controllers" { $VMScsiControllers = foreach ($VMSCSIController in $SCSIControllers) { [PSCustomObject]@{ 'Device' = $VMSCSIController.DeviceInfo.Label 'Controller Type' = $VMSCSIController.DeviceInfo.Summary 'Bus Sharing' = Switch ($VMSCSIController.SharedBus) { 'noSharing' { 'None' } default { $VMSCSIController.SharedBus } } } } $TableParams = @{ Name = "SCSI Controllers - $($VM.Name)" ColumnWidths = 33, 34, 33 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMScsiControllers | Sort-Object 'Device' | Table @TableParams } } if ($VMHdds) { Section -Style Heading4 "Hard Disks" { If ($InfoLevel.VM -eq 4) { $VMHardDiskInfo = foreach ($VMHdd in $VMHdds) { $SCSIDevice = $VMView.Config.Hardware.Device | Where-Object { $_.Key -eq $VMHdd.ExtensionData.Key -and $_.Backing.FileName -eq $VMHdd.FileName } $SCSIController = $SCSIControllers | Where-Object { $SCSIDevice.ControllerKey -eq $_.Key } [PSCustomObject]@{ 'Disk' = $VMHdd.Name 'Datastore' = $VMHdd.FileName.Substring($VMHdd.Filename.IndexOf("[") + 1, $VMHdd.Filename.IndexOf("]") - 1) 'Capacity' = "$([math]::Round(($VMHdd.CapacityGB), 2)) GB" 'Disk Provisioning' = Switch ($VMHdd.StorageFormat) { 'EagerZeroedThick' { 'Thick Eager Zeroed' } 'LazyZeroedThick' { 'Thick Lazy Zeroed' } $null { '--' } default { $VMHdd.StorageFormat } } 'Disk Type' = Switch ($VMHdd.DiskType) { 'RawPhysical' { 'Physical RDM' } 'RawVirtual' { "Virtual RDM" } 'Flat' { 'VMDK' } default { $VMHdd.DiskType } } 'Disk Mode' = Switch ($VMHdd.Persistence) { 'IndependentPersistent' { 'Independent - Persistent' } 'IndependentNonPersistent' { 'Independent - Nonpersistent' } 'Persistent' { 'Dependent' } default { $VMHdd.Persistence } } } } $TableParams = @{ Name = "Hard Disk Configuration - $($VM.Name)" ColumnWidths = 15, 25, 15, 15, 15, 15 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHardDiskInfo | Table @TableParams } else { foreach ($VMHdd in $VMHdds) { Section -Style NOTOCHeading5 -ExcludeFromTOC "$($VMHdd.Name)" { $SCSIDevice = $VMView.Config.Hardware.Device | Where-Object { $_.Key -eq $VMHdd.ExtensionData.Key -and $_.Backing.FileName -eq $VMHdd.FileName } $SCSIController = $SCSIControllers | Where-Object { $SCSIDevice.ControllerKey -eq $_.Key } $VMHardDiskInfo = [PSCustomObject]@{ 'Datastore' = $VMHdd.FileName.Substring($VMHdd.Filename.IndexOf("[") + 1, $VMHdd.Filename.IndexOf("]") - 1) 'Capacity' = "$([math]::Round(($VMHdd.CapacityGB), 2)) GB" 'Disk Path' = $VMHdd.Filename.Substring($VMHdd.Filename.IndexOf("]") + 2) 'Disk Shares' = "$($TextInfo.ToTitleCase($VMHdd.ExtensionData.Shares.Level)) / $($VMHdd.ExtensionData.Shares.Shares)" 'Disk Limit IOPs' = Switch ($VMHdd.ExtensionData.StorageIOAllocation.Limit) { '-1' { 'Unlimited' } default { $VMHdd.ExtensionData.StorageIOAllocation.Limit } } 'Disk Provisioning' = Switch ($VMHdd.StorageFormat) { 'EagerZeroedThick' { 'Thick Eager Zeroed' } 'LazyZeroedThick' { 'Thick Lazy Zeroed' } $null { '--' } default { $VMHdd.StorageFormat } } 'Disk Type' = Switch ($VMHdd.DiskType) { 'RawPhysical' { 'Physical RDM' } 'RawVirtual' { "Virtual RDM" } 'Flat' { 'VMDK' } default { $VMHdd.DiskType } } 'Disk Mode' = Switch ($VMHdd.Persistence) { 'IndependentPersistent' { 'Independent - Persistent' } 'IndependentNonPersistent' { 'Independent - Nonpersistent' } 'Persistent' { 'Dependent' } default { $VMHdd.Persistence } } 'SCSI Controller' = $SCSIController.DeviceInfo.Label 'SCSI Address' = "$($SCSIController.BusNumber):$($VMHdd.ExtensionData.UnitNumber)" } $TableParams = @{ Name = "Hard Disk $($VMHdd.Name) - $($VM.Name)" List = $true ColumnWidths = 25, 75 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMHardDiskInfo | Table @TableParams } } } } } if ($VMGuestVols) { Section -Style Heading4 "Guest Volumes" { $VMGuestDiskInfo = foreach ($VMGuestVol in $VMGuestVols) { [PSCustomObject]@{ 'Path' = $VMGuestVol.Path 'Capacity' = "$([math]::Round(($VMGuestVol.CapacityGB), 2)) GB" 'Used Space' = "$([math]::Round((($VMGuestVol.CapacityGB) - ($VMGuestVol.FreeSpaceGB)), 2)) GB" 'Free Space' = "$([math]::Round($VMGuestVol.FreeSpaceGB, 2)) GB" } } $TableParams = @{ Name = "Guest Volumes - $($VM.Name)" ColumnWidths = 25, 25, 25, 25 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMGuestDiskInfo | Table @TableParams } } } if ($VMSnapshotList -and $Options.ShowVMSnapshots) { Section -Style Heading4 "Snapshots" { $VMSnapshots = foreach ($VMSnapshot in $VMSnapshotList) { [PSCustomObject]@{ 'Snapshot Name' = $VMSnapshot.Name 'Description' = $VMSnapshot.Description 'Days Old' = ((Get-Date).ToUniversalTime() - $VMSnapshot.CreateTime).Days } } if ($Healthcheck.VM.VMSnapshots) { $VMSnapshots | Where-Object { $_.'Days Old' -ge 7 } | Set-Style -Style Warning $VMSnapshots | Where-Object { $_.'Days Old' -ge 14 } | Set-Style -Style Critical } $TableParams = @{ Name = "VM Snapshots - $($VM.Name)" ColumnWidths = 45, 45, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VMSnapshots | Table @TableParams } } } } } #endregion Virtual Machine Detailed Information } } } #endregion Virtual Machine Section #region VMware Update Manager Section Write-PScriboMessage "VUM InfoLevel set at $($InfoLevel.VUM)." if (($InfoLevel.VUM -ge 1) -and ($VumServer.Name)) { if ("Desktop" -eq $PSVersionTable.PsEdition) { $VUMBaselines = Get-PatchBaseline -Server $vCenter } else { Write-PScriboMessage 'VUM patch baseline information is not currently available with your version of PowerShell.' } if ($VUMBaselines) { Section -Style Heading2 'VMware Update Manager' { Paragraph "The following sections detail the configuration of VMware Update Manager managed by vCenter Server $vCenterServerName." #region VUM Baseline Detailed Information Section -Style Heading3 'Baselines' { $VUMBaselineInfo = foreach ($VUMBaseline in $VUMBaselines) { [PSCustomObject]@{ 'Baseline' = $VUMBaseline.Name 'Description' = $VUMBaseline.Description 'Type' = $VUMBaseline.BaselineType 'Target Type' = $VUMBaseline.TargetType 'Last Update Time' = ($VUMBaseline.LastUpdateTime).ToLocalTime() '# of Patches' = $VUMBaseline.CurrentPatches.Count } } $TableParams = @{ Name = "VMware Update Manager Baseline Summary - $($vCenterServerName)" ColumnWidths = 25, 25, 10, 10, 20, 10 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VUMBaselineInfo | Sort-Object Baseline | Table @TableParams } #endregion VUM Baseline Detailed Information #region VUM Comprehensive Information if ("Desktop" -eq $PSVersionTable.PsEdition) { $VUMPatches = Get-Patch -Server $vCenter | Sort-Object -Descending ReleaseDate } else { Write-PScriboMessage 'VUM patch information is not currently available with your version of PowerShell.' } if ($VUMPatches -and $InfoLevel.VUM -ge 5) { BlankLine Section -Style Heading3 'Patches' { $VUMPatchInfo = foreach ($VUMPatch in $VUMPatches) { [PSCustomObject]@{ 'Patch' = $VUMPatch.Name 'Product' = ($VUMPatch.Product).Name 'Description' = $VUMPatch.Description 'Release Date' = $VUMPatch.ReleaseDate 'Vendor ID' = $VUMPatch.IdByVendor } } $TableParams = @{ Name = "VMware Update Manager Patch Information - $($vCenterServerName)" ColumnWidths = 20, 20, 20, 20, 20 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $VUMPatchInfo | Table @TableParams } } #endregion VUM Comprehensive Information } } } #endregion VMware Update Manager Section } #endregion vCenter Server Heading1 Section # Disconnect vCenter Server $Null = Disconnect-VIServer -Server $VIServer -Confirm:$false -ErrorAction SilentlyContinue } # End of If $vCenter #endregion Generate vSphere report #region Variable cleanup Clear-Variable -Name vCenter #endregion Variable cleanup } # End of Foreach $VIServer #endregion Script Body } # End Invoke-AsBuiltReport.VMware.vSphere function |