Src/Private/Get-AbrVSphereVMHostHardware.ps1

function Get-AbrVSphereVMHostHardware {
    <#
    .SYNOPSIS
        Used by As Built Report to retrieve VMware vSphere ESXi VMHost Hardware information.
    .NOTES
        Version: 2.0.0
        Author: Tim Carman
        Twitter: @tpcarman
        Github: tpcarman
    #>

    [CmdletBinding()]
    param ()

    begin {
        $LocalizedData = $reportTranslate.GetAbrVSphereVMHostHardware
    }

    process {
        try {
            Write-PScriboMessage -Message $LocalizedData.Collecting
            #region ESXi Host Hardware Section
            Section -Style Heading4 $LocalizedData.SectionHeading {
                Paragraph ($LocalizedData.ParagraphSummary -f $VMHost)
                BlankLine

                #region ESXi Host Specifications
                $VMHostUptime = Get-Uptime -VMHost $VMHost
                $esxcli = Get-EsxCli -VMHost $VMHost -V2 -Server $vCenter
                $ScratchLocation = Get-AdvancedSetting -Entity $VMHost | Where-Object { $_.Name -eq 'ScratchConfig.CurrentScratchLocation' }
                $VMHostCpuTotal = [math]::Round(($VMHost.CpuTotalMhz) / 1000, 2)
                $VMHostCpuUsed = [math]::Round(($VMHost.CpuUsageMhz) / 1000, 2)
                $VMHostCpuFree = $VMHostCpuTotal - $VMHostCpuUsed
                $VMHostDetail = [PSCustomObject]@{
                    $LocalizedData.Host = $VMHost.Name
                    $LocalizedData.ConnectionState = switch ($VMHost.ConnectionState) {
                        'NotResponding' { $LocalizedData.NotResponding }
                        default { $TextInfo.ToTitleCase($VMHost.ConnectionState) }
                    }
                    $LocalizedData.ID = $VMHost.Id
                    $LocalizedData.Parent = $VMHost.Parent.Name
                    $LocalizedData.Manufacturer = $VMHost.Manufacturer
                    $LocalizedData.Model = $VMHost.Model
                    $LocalizedData.SerialNumber = if ($VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber) {
                        $VMHost.ExtensionData.Hardware.SystemInfo.SerialNumber
                    } else {
                        '--'
                    }
                    $LocalizedData.AssetTag = if ($VMHost.ExtensionData.Summary.Hardware.OtherIdentifyingInfo[0].IdentifierValue) {
                        $VMHost.ExtensionData.Summary.Hardware.OtherIdentifyingInfo[0].IdentifierValue
                    } else {
                        $LocalizedData.Unknown
                    }
                    $LocalizedData.ProcessorType = $VMHost.Processortype
                    $LocalizedData.HyperThreading = if ($VMHost.HyperthreadingActive) {
                        $LocalizedData.Enabled
                    } else {
                        $LocalizedData.Disabled
                    }
                    $LocalizedData.NumberOfCPUSockets = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuPackages
                    $LocalizedData.NumberOfCPUCores = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuCores
                    $LocalizedData.NumberOfCPUThreads = $VMHost.ExtensionData.Hardware.CpuInfo.NumCpuThreads
                    $LocalizedData.CPUTotalUsedFree = "{0:N2} GHz / {1:N2} GHz / {2:N2} GHz" -f $VMHostCpuTotal, $VMHostCpuUsed, $VMHostCpuFree
                    $LocalizedData.MemoryTotalUsedFree = "{0} / {1} / {2}" -f (Convert-DataSize $VMHost.MemoryTotalGB), (Convert-DataSize $VMHost.MemoryUsageGB), (Convert-DataSize ($VMHost.MemoryTotalGB - $VMHost.MemoryUsageGB))
                    $LocalizedData.NUMANodes = $VMHost.ExtensionData.Hardware.NumaInfo.NumNodes
                    $LocalizedData.NumberOfNICs = $VMHost.ExtensionData.Summary.Hardware.NumNics
                    $LocalizedData.NumberOfHBAs = $VMHost.ExtensionData.Summary.Hardware.NumHBAs
                    $LocalizedData.NumberOfDatastores = ($VMHost.ExtensionData.Datastore).Count
                    $LocalizedData.NumberOfVMs = $VMHost.ExtensionData.VM.Count
                    $LocalizedData.MaximumEVCMode = $EvcModeLookup."$($VMHost.MaxEVCMode)"
                    $LocalizedData.EVCGraphicsMode = if ($VMHost.ExtensionData.Summary.CurrentEVCGraphicsModeKey) {
                        $VMHost.ExtensionData.Summary.CurrentEVCGraphicsModeKey
                    } else {
                        $LocalizedData.NotApplicable
                    }
                    $LocalizedData.PowerManagementPolicy = $VMHost.ExtensionData.Hardware.CpuPowerManagementInfo.CurrentPolicy
                    $LocalizedData.ScratchLocation = $ScratchLocation.Value
                    $LocalizedData.BiosVersion = $VMHost.ExtensionData.Hardware.BiosInfo.BiosVersion
                    $LocalizedData.BiosReleaseDate = ($VMHost.ExtensionData.Hardware.BiosInfo.ReleaseDate).ToString()
                    $LocalizedData.Version = $VMHost.Version
                    $LocalizedData.Build = $VMHost.build
                    $LocalizedData.BootTime = ($VMHost.ExtensionData.Runtime.Boottime).ToLocalTime().ToString()
                    $LocalizedData.UptimeDays = $VMHostUptime.UptimeDays
                }
                $MemberProps = @{
                    'InputObject' = $VMHostDetail
                    'MemberType' = 'NoteProperty'
                }
                if ($UserPrivileges -contains 'Global.Licenses') {
                    try {
                        $VMHostLicense = Get-License -VMHost $VMHost
                        Add-Member @MemberProps -Name $LocalizedData.Product           -Value $VMHostLicense.Product
                        Add-Member @MemberProps -Name $LocalizedData.LicenseKey        -Value $VMHostLicense.LicenseKey
                        Add-Member @MemberProps -Name $LocalizedData.LicenseExpiration -Value $VMHostLicense.Expiration
                    } catch {
                        Write-PScriboMessage -IsWarning ($LocalizedData.LicenseError -f $VMHost.Name, $_.Exception.Message)
                    }
                } else {
                    Write-PScriboMessage -Message $LocalizedData.InsufficientPrivLicense
                }
                <#
                if ($TagAssignments | Where-Object {$_.entity -eq $VMHost}) {
                    Add-Member @MemberProps -Name 'Tags' -Value $(($TagAssignments | Where-Object {$_.entity -eq $VMHost}).Tag -join ',')
                }
                #>

                if ($Healthcheck.VMHost.ConnectionState) {
                    $VMHostDetail | Where-Object { $_.$($LocalizedData.ConnectionState) -eq $LocalizedData.Maintenance } | Set-Style -Style Warning -Property $LocalizedData.ConnectionState
                }
                if ($Healthcheck.VMHost.HyperThreading) {
                    $VMHostDetail | Where-Object { $_.$($LocalizedData.HyperThreading) -eq $LocalizedData.Disabled } | Set-Style -Style Warning -Property $LocalizedData.HyperThreading
                }
                if ($Healthcheck.VMHost.Licensing) {
                    $VMHostDetail | Where-Object { $_.$($LocalizedData.Product) -like '*Evaluation*' } | Set-Style -Style Warning -Property $LocalizedData.Product
                    $VMHostDetail | Where-Object { $_.$($LocalizedData.LicenseKey) -like '*-00000-00000' } | Set-Style -Style Warning -Property $LocalizedData.LicenseKey
                    $VMHostDetail | Where-Object { $_.$($LocalizedData.LicenseExpiration) -eq 'Expired' } | Set-Style -Style Critical -Property $LocalizedData.LicenseExpiration
                }
                if ($Healthcheck.VMHost.ScratchLocation) {
                    $VMHostDetail | Where-Object { $_.$($LocalizedData.ScratchLocation) -eq '/tmp/scratch' } | Set-Style -Style Warning -Property $LocalizedData.ScratchLocation
                }
                if ($Healthcheck.VMHost.UpTimeDays) {
                    $VMHostDetail | Where-Object { $_.$($LocalizedData.UptimeDays) -ge 275 -and $_.$($LocalizedData.UptimeDays) -lt 365 } | Set-Style -Style Warning -Property $LocalizedData.UptimeDays
                    $VMHostDetail | Where-Object { $_.$($LocalizedData.UptimeDays) -ge 365 } | Set-Style -Style Critical -Property $LocalizedData.UptimeDays
                }
                $TableParams = @{
                    Name = ($LocalizedData.TableHardwareConfig -f $VMHost)
                    List = $true
                    ColumnWidths = 40, 60
                }
                if ($Report.ShowTableCaptions) {
                    $TableParams['Caption'] = "- $($TableParams.Name)"
                }
                $VMHostDetail | Table @TableParams
                #endregion ESXi Host Specifications

                #region ESXi IPMI/BMC Settings
                try {
                    $VMHostIPMI = $esxcli.hardware.ipmi.bmc.get.invoke()
                } catch {
                    Write-PScriboMessage -Message ($LocalizedData.IPMIBMCError -f $VMHost.Name, $_.Exception.Message)
                }
                if ($VMHostIPMI) {
                    try {
                        Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.IPMIBMC {
                            $VMHostIPMIInfo = [PSCustomObject]@{
                                $LocalizedData.Manufacturer = $VMHostIPMI.Manufacturer
                                $LocalizedData.MACAddress = $VMHostIPMI.MacAddress
                                $LocalizedData.IPMIBMCIPAddress = $VMHostIPMI.IPv4Address
                                $LocalizedData.SubnetMask = $VMHostIPMI.IPv4Subnet
                                $LocalizedData.Gateway = $VMHostIPMI.IPv4Gateway
                                $LocalizedData.FirmwareVersion = $VMHostIPMI.BMCFirmwareVersion
                            }

                            $TableParams = @{
                                Name = ($LocalizedData.TableIPMIBMC -f $VMHost)
                                List = $true
                                ColumnWidths = 40, 60
                            }
                            if ($Report.ShowTableCaptions) {
                                $TableParams['Caption'] = "- $($TableParams.Name)"
                            }
                            $VMHostIPMIInfo | Table @TableParams
                        }
                    } catch {
                        Write-PScriboMessage -Message ($LocalizedData.IPMIBMCError -f $VMHost.Name, $_.Exception.Message)
                    }
                }
                #endregion ESXi IPMI/BMC Settings

                #region ESXi Host Boot Device
                try {
                    Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.BootDevice {
                        $ESXiBootDevice = Get-ESXiBootDevice -VMHost $VMHost
                        $VMHostBootDevice = [PSCustomObject]@{
                            $LocalizedData.Host = $ESXiBootDevice.Host
                            $LocalizedData.Device = $ESXiBootDevice.Device
                            $LocalizedData.BootType = $ESXiBootDevice.BootType
                            $LocalizedData.Vendor = $ESXiBootDevice.Vendor
                            $LocalizedData.Model = $ESXiBootDevice.Model
                            $LocalizedData.Size = switch ($ESXiBootDevice.SizeMB) {
                                'N/A' { 'N/A' }
                                default { Convert-DataSize $ESXiBootDevice.SizeMB -InputUnit MB -RoundUnits 0 }
                            }
                            $LocalizedData.BootDeviceIsSAS = $ESXiBootDevice.IsSAS
                            $LocalizedData.BootDeviceIsSSD = $ESXiBootDevice.IsSSD
                            $LocalizedData.BootDeviceIsUSB = $ESXiBootDevice.IsUSB
                        }
                        $TableParams = @{
                            Name = ($LocalizedData.TableBootDevice -f $VMHost)
                            List = $true
                            ColumnWidths = 40, 60
                        }
                        if ($Report.ShowTableCaptions) {
                            $TableParams['Caption'] = "- $($TableParams.Name)"
                        }
                        $VMHostBootDevice | Table @TableParams
                    }
                } catch {
                    Write-PScriboMessage -Message ($LocalizedData.BootDeviceError -f $VMHost.Name, $_.Exception.Message)
                }
                #endregion ESXi Host Boot Devices

                #region ESXi Host PCI Devices
                try {
                    Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.PCIDevices {
                        <# Move away from esxcli.v2 implementation to be compatible with 8.x branch.
                        'Slot Description' information does not seem to be available through the API
                        Create an array with PCI Address and VMware Devices (vmnic,vmhba,?vmgfx?)
                        #>

                        $PciToDeviceMapping = @{}
                        $NetworkAdapters = Get-VMHostNetworkAdapter -VMHost $VMHost -Physical
                        foreach ($adapter in $NetworkAdapters) {
                            $PciToDeviceMapping[$adapter.PciId] = $adapter.DeviceName
                        }
                        $hbAdapters = Get-VMHostHba -VMHost $VMHost
                        foreach ($adapter in $hbAdapters) {
                            $PciToDeviceMapping[$adapter.Pci] = $adapter.Device
                        }
                        <# Data Object - HostGraphicsInfo(vim.host.GraphicsInfo)
                        This function has been available since version 5.5, but we can't be sure if it is still valid.
                        I don't have access to a vGPU-enabled system.
                        #>

                        $GpuAdapters = (Get-VMHost $VMhost | Get-View -Property Config).Config.GraphicsInfo
                        foreach ($adapter in $GpuAdapters) {
                            $PciToDeviceMapping[$adapter.pciId] = $adapter.deviceName
                        }

                        $VMHostPciDevice = @{
                            VMHost = $VMHost
                            DeviceClass = @('MassStorageController', 'NetworkController', 'DisplayController', 'SerialBusController')
                        }
                        $PciDevices = Get-VMHostPciDevice @VMHostPciDevice

                        # Combine PciDevices and PciToDeviceMapping

                        $VMHostPciDevices = $PciDevices | ForEach-Object {
                            $PciDevice = $_
                            $device = $PCIToDeviceMapping[$pciDevice.Id]

                            if ($device) {
                                [PSCustomObject]@{
                                    $LocalizedData.Device = $device
                                    $LocalizedData.PCIAddress = $PciDevice.Id
                                    $LocalizedData.DeviceClass = $PciDevice.DeviceClass -replace ('Controller', "")
                                    $LocalizedData.PCIDeviceName = $PciDevice.DeviceName
                                    $LocalizedData.PCIVendorName = $PciDevice.VendorName
                                }
                            }
                        }

                        $TableParams = @{
                            Name = ($LocalizedData.TablePCIDevices -f $VMHost)
                            ColumnWidths = 17, 18, 15, 30, 20
                        }
                        if ($Report.ShowTableCaptions) {
                            $TableParams['Caption'] = "- $($TableParams.Name)"
                        }
                        $VMHostPciDevices | Sort-Object $LocalizedData.Device | Table @TableParams
                    }
                } catch {
                    Write-PScriboMessage -Message ($LocalizedData.PCIDeviceError -f $VMHost.Name, $_.Exception.Message)
                }
                #endregion ESXi Host PCI Devices

                #region ESXi Host PCI Devices Drivers & Firmware
                $VMHostPciDevicesDetails = Get-PciDeviceDetail -Server $vCenter -esxcli $esxcli -VMHost $VMHost | Sort-Object 'Device'
                if ($VMHostPciDevicesDetails) {
                    try {
                        Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.PCIDriversFirmware {
                            $TableParams = @{
                                Name = ($LocalizedData.TablePCIDriversFirmware -f $VMHost)
                                ColumnWidths = 12, 20, 11, 19, 11, 11, 16
                            }
                            if ($Report.ShowTableCaptions) {
                                $TableParams['Caption'] = "- $($TableParams.Name)"
                            }
                            $VMHostPciDevicesDetails | Table @TableParams
                        }
                    } catch {
                        Write-PScriboMessage -Message ($LocalizedData.PCIDriversFirmwareError -f $VMHost.Name, $_.Exception.Message)
                    }
                }
                #endregion ESXi Host PCI Devices Drivers & Firmware

                #region ESXi Host I/O Device Identifiers
                try {
                    # Build PCI address -> VMkernel name mapping (VMkernelName is unpopulated on ESXi 8)
                    # Guard against null keys — software HBAs (iSCSI, software NVMe) have no PCI address
                    $IOPciToDevice = @{}
                    Get-VMHostNetworkAdapter -VMHost $VMHost -Physical | ForEach-Object {
                        if ($_.PciId) { $IOPciToDevice[$_.PciId] = $_.DeviceName }
                    }
                    Get-VMHostHba -VMHost $VMHost | ForEach-Object {
                        if ($_.Pci) { $IOPciToDevice[$_.Pci] = $_.Device }
                    }
                    (Get-VMHost $VMHost | Get-View -Property Config).Config.GraphicsInfo | ForEach-Object {
                        if ($_.pciId) { $IOPciToDevice[$_.pciId] = $_.deviceName }
                    }
                    $VMHostIODevices = $esxcli.hardware.pci.list.Invoke() |
                        Where-Object { $IOPciToDevice.ContainsKey($_.Address) } |
                        Sort-Object -Property Address
                    if ($VMHostIODevices) {
                        Section -Style NOTOCHeading5 -ExcludeFromTOC $LocalizedData.IODeviceIdentifiers {
                            $VMHostIODeviceInfo = $VMHostIODevices | ForEach-Object {
                                [PSCustomObject]@{
                                    $LocalizedData.Device      = $IOPciToDevice[$_.Address]
                                    $LocalizedData.Model       = $_.DeviceName
                                    $LocalizedData.VendorID    = [String]::Format("{0:x4}", $_.VendorID)
                                    $LocalizedData.DeviceID    = [String]::Format("{0:x4}", $_.DeviceID)
                                    $LocalizedData.SubVendorID = [String]::Format("{0:x4}", $_.SubVendorID)
                                    $LocalizedData.SubDeviceID = [String]::Format("{0:x4}", $_.SubDeviceID)
                                }
                            }
                            $TableParams = @{
                                Name         = ($LocalizedData.TableIODeviceIdentifiers -f $VMHost)
                                ColumnWidths = 13, 31, 14, 14, 14, 14
                            }
                            if ($Report.ShowTableCaptions) {
                                $TableParams['Caption'] = "- $($TableParams.Name)"
                            }
                            $VMHostIODeviceInfo | Sort-Object $LocalizedData.Device | Table @TableParams
                        }
                    }
                } catch {
                    Write-PScriboMessage -Message ($LocalizedData.IODeviceIdentifiersError -f $VMHost.Name, $_.Exception.Message)
                }
                #endregion ESXi Host I/O Device Identifiers
            }
        } catch {
            Write-PScriboMessage -Message ($LocalizedData.HardwareError -f $VMHost.Name, $_.Exception.Message)
        }
        #endregion ESXi Host Hardware Section
    }

    end {}
}