Private/IoDeviceCompatibility.ps1

# Copyright (c) 2026 Broadcom. All Rights Reserved.
# Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
# and/or its subsidiaries.
#
# =============================================================================
#
# SOFTWARE LICENSE AGREEMENT
#
# Copyright (c) CA, Inc. All rights reserved.
#
# You are hereby granted a non-exclusive, worldwide, royalty-free license
# under CA, Inc.'s copyrights to use, copy, modify, and distribute this
# software in source code or binary form for use in connection with CA, Inc.
# products.
#
# This copyright notice shall be included in all copies or substantial
# portions of the software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
# =============================================================================
#region IoDeviceCompatibility

function Get-VcfCheckVMHostCpuInfo {

    <#
        .SYNOPSIS
        Returns CPU series information and specifications for an ESX host.

        .OUTPUTS
        [PSCustomObject] with CpuSeries, TotalCores, TotalThreads, Sockets, MhzPerCpu.
    #>


    [CmdletBinding()]
    [OutputType([PSObject])]
    Param (
        [Parameter(Mandatory = $true)] [PSObject]$VMHost
    )

    $cpuInfo = $VMHost.ExtensionData.Summary.Hardware.CpuModel
    $numCpuCores = $VMHost.ExtensionData.Summary.Hardware.NumCpuCores
    $numCpuThreads = $VMHost.ExtensionData.Summary.Hardware.NumCpuThreads
    $numCpuPkgs = $VMHost.ExtensionData.Summary.Hardware.NumCpuPkgs
    $cpuHz = $VMHost.ExtensionData.Summary.Hardware.CpuMhz

    return [PSCustomObject]@{
        CpuSeries    = $cpuInfo
        TotalCores   = $numCpuCores
        TotalThreads = $numCpuThreads
        Sockets      = $numCpuPkgs
        MhzPerCpu    = $cpuHz
    }
}
function Get-VcfCheckVMHostNetworkDevices {

    <#
        .SYNOPSIS
        Returns physical network adapters (vmnics) for an ESX host, including driver and firmware details.

        .OUTPUTS
        [PSCustomObject[]] with Name, Vendor, Model, Driver, DriverVersion, FirmwareVersion, DeviceType.
    #>


    [CmdletBinding()]
    [OutputType([PSObject[]])]
    Param (
        [Parameter(Mandatory = $true)] [PSObject]$VMHost
    )

    $devices = @()
    $nics = @($VMHost | Get-VMHostNetworkAdapter -Physical -ErrorAction SilentlyContinue)
    $pciDevices = @($VMHost | Get-VMHostPciDevice -ErrorAction SilentlyContinue)
    $esxcli = $null

    try {
        $esxcli = Get-EsxCli -VMHost $VMHost -V2 -ErrorAction SilentlyContinue
    } catch {
        $esxcli = $null
    }

    foreach ($nic in $nics) {
        if ($nic) {
            $vendor = ''
            $model = ''
            $driver = ''
            $driverVersion = ''
            $firmwareVersion = ''

            if ($nic.ExtensionData.Pci -and $pciDevices) {
                $pciId = $nic.ExtensionData.Pci
                $pciDevice = $pciDevices | Where-Object { $_.Id -eq $pciId } | Select-Object -First 1
                if ($pciDevice) {
                    $vendor = $pciDevice.VendorName
                    $model = $pciDevice.DeviceName
                }
            }

            if ($esxcli) {
                try {
                    $nicDetails = Invoke-VcfCheckWithTimeout -TimeoutSeconds 30 -ArgumentList $esxcli, $nic.Name -ScriptBlock {
                        param($EsxCli, $NicName)
                        $EsxCli.network.nic.get.Invoke(@{nicname = $NicName })
                    }
                    if ($nicDetails) {
                        $driver = if ($nicDetails.Driver) { $nicDetails.Driver } else { '' }
                        $driverVersion = if ($nicDetails.DriverVersion) { $nicDetails.DriverVersion } else { '' }
                        $firmwareVersion = if ($nicDetails.FirmwareVersion) { $nicDetails.FirmwareVersion } else { '' }
                    }
                } catch {
                    $driver = ''
                    $driverVersion = ''
                    $firmwareVersion = ''
                }
            }

            $devices += [PSCustomObject]@{
                Name             = $nic.Name
                Vendor           = $vendor
                Model            = $model
                Driver           = $driver
                DriverVersion    = $driverVersion
                FirmwareVersion  = $firmwareVersion
                DeviceType       = 'Network'
            }
        }
    }

    return $devices
}
function Get-VcfCheckVMHostStorageAdapters {

    <#
        .SYNOPSIS
        Returns storage Host Bus Adapters (SAS, Fibre Channel, SATA, NVMe) for an ESX host with human-readable type descriptions.

        .OUTPUTS
        [PSCustomObject[]] with Name, Vendor, Model, Type, Driver, Status, DeviceType.
    #>


    [CmdletBinding()]
    [OutputType([PSObject[]])]
    Param (
        [Parameter(Mandatory = $true)] [PSObject]$VMHost
    )

    $devices = @()
    $hbas = @($VMHost | Get-VMHostHba -ErrorAction SilentlyContinue)
    $pciDevices = @($VMHost | Get-VMHostPciDevice -ErrorAction SilentlyContinue)

    foreach ($hba in $hbas) {
        if ($hba) {
            $vendor = ''
            $model = ''
            $pciDevice = $null

            if ($hba.ExtensionData.Pci -and $pciDevices) {
                $pciId = $hba.ExtensionData.Pci
                $pciDevice = $pciDevices | Where-Object { $_.Id -eq $pciId } | Select-Object -First 1
                if ($pciDevice) {
                    $vendor = $pciDevice.VendorName
                    $model = $pciDevice.DeviceName
                }
            }

            if (-not $vendor) {
                $vendor = $hba.Vendor
            }
            if (-not $model) {
                $model = if ($hba.Model -and $hba.Model -ne 'N/A') { $hba.Model } else { $hba.Name }
            }

            $rawType = switch ($hba.Type) {
                0 { 'Block' }
                1 { 'FibreChannel' }
                2 { 'iSCSI' }
                3 { 'ParallelScsi' }
                'FibreChannel' { 'FibreChannel' }
                'iSCSI' { 'iSCSI' }
                'SAS' { 'SAS' }
                'SATA' { 'SATA' }
                'ParallelScsi' { 'ParallelScsi' }
                'SoftwareNVMe' { 'SoftwareNVMe' }
                default { $hba.Type }
            }

            $humanType = switch -Wildcard ($rawType) {
                'FibreChannel' { 'Fibre Channel Host Bus Adapter (HBA)' }
                'iSCSI' {
                    if ($hba.IsSoftware) { 'Software iSCSI Initiator' } else { 'Hardware iSCSI Adapter' }
                }
                'Block' {
                    if ($model -like '*NVMe*') {
                        'NVMe Controller'
                    } elseif ($model -like '*RAID*' -or $model -like '*PERC*' -or $model -like '*Smart Array*') {
                        'Hardware RAID Controller'
                    } else {
                        'SAS / SATA Host Bus Adapter'
                    }
                }
                'SAS' { 'SAS Host Bus Adapter' }
                'SATA' { 'SATA Controller' }
                'ParallelScsi' { 'Parallel SCSI Controller' }
                'SoftwareNVMe' { 'Software NVMe over Fabrics (NVMe-oF)' }
                default {
                    if ($model -like '*NVMe*') { 'NVMe Controller' } else { $rawType }
                }
            }

            $devices += [PSCustomObject]@{
                Name       = $hba.Name
                Vendor     = $vendor
                Model      = $model
                Type       = $humanType
                Driver     = $hba.Driver
                Status     = $hba.Status
                DeviceType = $humanType
            }
        }
    }

    return $devices
}
function Get-VcfCheckVMHostScsiDevices {

    <#
        .SYNOPSIS
        Returns SCSI logical units (storage devices) for an ESX host with protocol and media type information.

        .OUTPUTS
        [PSCustomObject[]] with Name, Vendor, Model, Type, Capacity, Status, DeviceType.
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    Param (
        [Parameter(Mandatory = $true)] 
        [PSObject]$VMHost
    )

    $luns = Get-ScsiLun -VMHost $VMHost -LunType disk -ErrorAction SilentlyContinue

    # Assign directly to array to avoid expensive array rebuilding (+=$)
    $devices = foreach ($lun in $luns) {
        if (-not $lun) { continue }

        $media = if ($lun.IsSsd) { 'SSD' } else { 'HDD' }

        # Determine protocol using switch statement
        $protocol = switch ($lun) {
            { $_.CanonicalName -like 'nvme.*' -or $_.CanonicalName -like 'eui.*' -or $_.Model -like '*NVMe*' } { 'NVMe'; break }
            { $_.CanonicalName -like 't10.ATA*' -or $_.Model -like '*SATA*' } { 'SATA'; break }
            { $_.CanonicalName -like 'naa.5*' } { 'SAS'; break }
            default { 'SCSI' }
        }

        # Trim vendor to handle fixed-width trailing spaces typical in vSphere SCSI inquiry data
        $trimmedVendor = "$($lun.Vendor)".Trim()

        # Check Vendor and override generic bus protocol names with 'Unknown'
        $vendor = switch -Exact ($trimmedVendor) {
            'ATA'     { 'Unknown' }
            'NVMe'    { 'Unknown' }
            'SAS'     { 'Unknown' }
            'SATA'    { 'Unknown' }
            'SCSI'    { 'Unknown' }
            default   { $trimmedVendor }
        }

        [PSCustomObject]@{
            Name       = $lun.CanonicalName
            Vendor     = $vendor
            Model      = $lun.Model
            Type       = "$protocol $media"
            Capacity   = [Math]::Round($lun.CapacityGB, 2)
            Status     = $lun.RuntimeStatus
            DeviceType = 'SCSI'
        }
    }

    return $devices
}

#endregion