Public/Dell.Client.Update/Private/Resolve-DellModelComponent.ps1

function ConvertTo-DellIdentityVersion {
    param([string]$Version)

    if ([string]::IsNullOrWhiteSpace($Version)) { return $null }
    $dellVersionMatch = [regex]::Match($Version, '^A(?<number>\d+)[A-Z]?$')
    if ($dellVersionMatch.Success) {
        return ('0001.{0}.0000' -f $dellVersionMatch.Groups['number'].Value.PadLeft(4, '0'))
    }
    $parts = @($Version -split '\.')
    if ($parts.Count -lt 2 -or $parts.Count -gt 4 -or @($parts | Where-Object { $_ -notmatch '^\d+$' }).Count) { return $null }
    return (($parts | ForEach-Object { $_.PadLeft(4, '0') }) -join '.')
}

function Get-DellModelInventory {
    $inventory = [System.Collections.Generic.List[object]]::new()
    foreach ($driver in (Get-CimInstance -ClassName Win32_PnPSignedDriver -ErrorAction Stop)) {
        $version = $null
        if (-not [version]::TryParse(([string]$driver.DriverVersion).Trim(), [ref]$version)) { continue }

        $hardwareIds = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
        if ($driver.DeviceID) { $null = $hardwareIds.Add([string]$driver.DeviceID) }
        foreach ($hardwareId in @($driver.HardWareID)) {
            if ($hardwareId) { $null = $hardwareIds.Add([string]$hardwareId) }
        }

        $inventory.Add([pscustomobject]@{
            DeviceName = [string]$driver.DeviceName
            DeviceId = [string]$driver.DeviceID
            HardwareIds = @($hardwareIds)
            DriverVersion = $version
            IdentityType = 'PnP'
            ExtensionId = ''
        })
    }

    foreach ($infFile in (Get-ChildItem -LiteralPath (Join-Path $env:SystemRoot 'INF') -Filter 'oem*.inf' -File -ErrorAction Stop)) {
        try {
            $content = [IO.File]::ReadAllText($infFile.FullName)
        }
        catch {
            Write-Warning "Could not read installed driver extension INF '$($infFile.FullName)': $($_.Exception.Message)"
            continue
        }
        $extensionMatch = [regex]::Match($content, '(?im)^\s*ExtensionId\s*=\s*(?<id>\{[0-9A-F-]+\})\s*$')
        $versionMatch = [regex]::Match($content, '(?im)^\s*DriverVer\s*=\s*[^,]+,(?<version>[0-9.]+)\s*$')
        if (-not $extensionMatch.Success -or -not $versionMatch.Success) { continue }
        $version = $null
        if (-not [version]::TryParse($versionMatch.Groups['version'].Value, [ref]$version)) { continue }
        $hardwareIds = @([regex]::Matches($content, '(?im)^\s*[^;=\r\n]+\s*=\s*[^,\r\n]+,\s*(?<id>[A-Za-z0-9_*{}&\\.\-]+)\s*$') | ForEach-Object { $_.Groups['id'].Value } | Sort-Object -Unique)
        $inventory.Add([pscustomobject]@{
            DeviceName = "Driver extension $($extensionMatch.Groups['id'].Value)"
            DeviceId = $infFile.Name
            HardwareIds = $hardwareIds
            DriverVersion = $version
            IdentityType = 'Extension'
            ExtensionId = $extensionMatch.Groups['id'].Value
        })
    }
    $hardwareIndex = @{}
    $hardwareIdIndex = @{}
    foreach ($item in $inventory) {
        foreach ($hardwareId in $item.HardwareIds) {
            $normalizedHardwareId = $hardwareId.ToUpperInvariant()
            if (-not $hardwareIdIndex.ContainsKey($normalizedHardwareId)) {
                $hardwareIdIndex[$normalizedHardwareId] = [System.Collections.Generic.List[object]]::new()
            }
            if (-not $hardwareIdIndex[$normalizedHardwareId].Contains($item)) { $hardwareIdIndex[$normalizedHardwareId].Add($item) }
            $hardwareMatch = [regex]::Match($hardwareId, '(?i)(?:VEN_|VID_)(?<vendor>[0-9A-F]{4}).*(?:DEV_|PID_)(?<device>[0-9A-F]{4})')
            if (-not $hardwareMatch.Success) { continue }
            $key = "$($hardwareMatch.Groups['vendor'].Value):$($hardwareMatch.Groups['device'].Value)".ToUpperInvariant()
            if (-not $hardwareIndex.ContainsKey($key)) {
                $hardwareIndex[$key] = [System.Collections.Generic.List[object]]::new()
            }
            if (-not $hardwareIndex[$key].Contains($item)) { $hardwareIndex[$key].Add($item) }
        }
    }
    return [pscustomobject]@{
        Items = @($inventory)
        HardwareIndex = $hardwareIndex
        HardwareIdIndex = $hardwareIdIndex
        SelectorCache = @{}
    }
}

function Test-DellModelOperatingSystem {
    param(
        [Parameter(Mandatory)][System.Xml.XmlElement]$Component,
        [Parameter(Mandatory)][object]$OperatingSystem
    )

    $supportedSystems = @($Component.SelectNodes("./*[local-name()='SupportedOperatingSystems']/*[local-name()='OperatingSystem']"))
    if (-not $supportedSystems.Count) { return $true }

    $currentFamily = if ([int]$OperatingSystem.BuildNumber -ge 22000) { 'Windows 11' } else { 'Windows 10' }
    $currentArchitecture = if ([Environment]::Is64BitOperatingSystem) { 'x64' } else { 'x86' }
    foreach ($supportedSystem in $supportedSystems) {
        $display = $supportedSystem.SelectSingleNode("./*[local-name()='Display']")
        if ($display -and $supportedSystem.GetAttribute('osArch') -ieq $currentArchitecture -and $display.InnerText -like "$currentFamily*") {
            return $true
        }
    }
    return $false
}

function Test-DellModelHardwareSelector {
    param(
        [Parameter(Mandatory)][System.Xml.XmlElement]$Selector,
        [Parameter(Mandatory)][string[]]$HardwareIds
    )

    if ($Selector.LocalName -eq 'PCIInfo') {
        $vendorId = [regex]::Escape($Selector.GetAttribute('vendorID'))
        $deviceId = [regex]::Escape($Selector.GetAttribute('deviceID'))
        $subVendorId = $Selector.GetAttribute('subVendorID')
        $subDeviceId = $Selector.GetAttribute('subDeviceID')
        foreach ($hardwareId in $HardwareIds) {
            if ($hardwareId -notmatch "(?i)(?:VEN_|VID_)$vendorId.*(?:DEV_|PID_)$deviceId") { continue }
            if (-not $subVendorId -or -not $subDeviceId) { return $true }
            if ($hardwareId -match "(?i)SUBSYS_(?:$([regex]::Escape($subDeviceId))$([regex]::Escape($subVendorId))|$([regex]::Escape($subVendorId))$([regex]::Escape($subDeviceId)))") {
                return $true
            }
        }
        return $false
    }

    if ($Selector.LocalName -eq 'PnPInfo') {
        $vendorNode = $Selector.SelectSingleNode("./*[local-name()='PNPID']")
        $productNode = $Selector.SelectSingleNode("./*[local-name()='PnPProductID']")
        if (-not $vendorNode -or -not $productNode) { return $false }
        $vendorId = [regex]::Escape($vendorNode.InnerText)
        $productId = [regex]::Escape($productNode.InnerText)
        return [bool]($HardwareIds -match "(?i)(?:VEN_|VID_)$vendorId.*(?:DEV_|PID_)$productId")
    }

    if ($Selector.LocalName -eq 'Generic') {
        $expected = $Selector.InnerText.Trim()
        return [bool]($HardwareIds | Where-Object { $_.StartsWith($expected, [StringComparison]::OrdinalIgnoreCase) })
    }

    return $false
}

function Get-DellModelSelectorMatches {
    param(
        [Parameter(Mandatory)][System.Xml.XmlElement]$Selector,
        [Parameter(Mandatory)][object]$Inventory
    )

    $cacheKey = $Selector.OuterXml
    if ($Inventory.SelectorCache.ContainsKey($cacheKey)) { return @($Inventory.SelectorCache[$cacheKey]) }

    $candidates = @()
    if ($Selector.LocalName -eq 'PCIInfo') {
        $key = "$($Selector.GetAttribute('vendorID')):$($Selector.GetAttribute('deviceID'))".ToUpperInvariant()
        if ($Inventory.HardwareIndex.ContainsKey($key)) { $candidates = @($Inventory.HardwareIndex[$key]) }
    }
    elseif ($Selector.LocalName -eq 'PnPInfo') {
        $vendorNode = $Selector.SelectSingleNode("./*[local-name()='PNPID']")
        $productNode = $Selector.SelectSingleNode("./*[local-name()='PnPProductID']")
        if ($vendorNode -and $productNode) {
            $key = "$($vendorNode.InnerText):$($productNode.InnerText)".ToUpperInvariant()
            if ($Inventory.HardwareIndex.ContainsKey($key)) { $candidates = @($Inventory.HardwareIndex[$key]) }
        }
    }
    elseif ($Selector.LocalName -eq 'Generic') {
        $expected = $Selector.InnerText.Trim().ToUpperInvariant()
        if ($Inventory.HardwareIdIndex.ContainsKey($expected)) {
            $candidates = @($Inventory.HardwareIdIndex[$expected])
        }
        else {
            $candidates = @($Inventory.Items | Where-Object { $_.HardwareIds | Where-Object { $_.StartsWith($expected, [StringComparison]::OrdinalIgnoreCase) } | Select-Object -First 1 })
        }
    }

    $results = @($candidates | Where-Object { Test-DellModelHardwareSelector -Selector $Selector -HardwareIds $_.HardwareIds })
    $Inventory.SelectorCache[$cacheKey] = $results
    return $results
}

function Get-DellModelComponentMatches {
    param(
        [Parameter(Mandatory)][System.Xml.XmlElement]$Component,
        [Parameter(Mandatory)][object]$Inventory
    )

    $componentResults = [System.Collections.Generic.List[object]]::new()
    $devices = @($Component.SelectNodes("./*[local-name()='SupportedDCHDevices' or local-name()='SupportedDevices']/*[local-name()='Device']"))
    foreach ($device in $devices) {
        $infType = $device.GetAttribute('infType')
        $expectedVersionText = $device.GetAttribute('version')
        if (-not $expectedVersionText) { $expectedVersionText = $Component.GetAttribute('vendorVersion') }
        $expectedVersion = $null
        if (-not [version]::TryParse($expectedVersionText, [ref]$expectedVersion)) { continue }

        $selectors = @($device.ChildNodes | Where-Object { $_.NodeType -eq [System.Xml.XmlNodeType]::Element -and $_.LocalName -in @('PCIInfo', 'PnPInfo', 'Generic') })
        $matchingInventory = [System.Collections.Generic.List[object]]::new()
        foreach ($selector in $selectors) {
            foreach ($item in (Get-DellModelSelectorMatches -Selector $selector -Inventory $Inventory)) {
                if (-not $matchingInventory.Contains($item)) { $matchingInventory.Add($item) }
            }
        }

        foreach ($installedDevice in $matchingInventory) {
            if ($infType -eq 'extension' -and $installedDevice.IdentityType -ne 'Extension') { continue }
            if ($infType -ne 'extension' -and $installedDevice.IdentityType -eq 'Extension') { continue }
            $componentResults.Add([pscustomobject]@{
                ComponentId = $device.GetAttribute('componentID')
                DeviceName = $installedDevice.DeviceName
                DeviceId = $installedDevice.DeviceId
                InstalledVersion = $installedDevice.DriverVersion
                ExpectedVersion = $expectedVersion
                IdentityType = $installedDevice.IdentityType
                InfType = $infType
            })
        }
    }

    return @($componentResults | Sort-Object DeviceId, ExpectedVersion, InfType -Unique)
}

function Get-DellModelComponentState {
    param(
        [Parameter(Mandatory)][System.Xml.XmlElement]$Component,
        [Parameter(Mandatory)][object]$Inventory
    )

    $componentTypeNode = $Component.SelectSingleNode("./*[local-name()='ComponentType']")
    if (-not $componentTypeNode) { return $null }
    $componentType = $componentTypeNode.GetAttribute('value')
    if ($componentType -eq 'BIOS') {
        $installedVersionText = [string](Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SMBIOSBIOSVersion
        $expectedVersionText = $Component.GetAttribute('dellVersion')
        $installedVersion = ConvertTo-DellIdentityVersion -Version $installedVersionText
        $expectedVersion = ConvertTo-DellIdentityVersion -Version $Component.GetAttribute('dellVersion')
        if (-not $installedVersion -or -not $expectedVersion) { return $null }
        $isInstalled = [string]::Compare($installedVersion, $expectedVersion, [StringComparison]::OrdinalIgnoreCase) -ge 0
        return [pscustomobject]@{
            IsApplicable = $true
            IsInstalled = $isInstalled
            Matches = @()
            ApplicabilityReason = if ($isInstalled) {
                "Installed BIOS $installedVersionText meets or exceeds catalog version $expectedVersionText."
            }
            else {
                "Installed BIOS $installedVersionText is older than catalog version $expectedVersionText."
            }
        }
    }

    $componentResults = @(Get-DellModelComponentMatches -Component $Component -Inventory $Inventory)
    if (-not $componentResults.Count) { return $null }
    # Current base and extension markers identify an installed bundle even
    # when superseded INFs remain in the Windows driver store.
    $hasCurrentExtensionMarker = [bool]($componentResults | Where-Object { $_.IdentityType -eq 'Extension' -and $_.InfType -eq 'extension' -and $_.InstalledVersion -ge $_.ExpectedVersion } | Select-Object -First 1)
    $hasCurrentBaseMarker = [bool]($componentResults | Where-Object { $_.IdentityType -eq 'PnP' -and $_.InfType -eq 'base' -and $_.InstalledVersion -ge $_.ExpectedVersion } | Select-Object -First 1)
    $outdatedResults = @($componentResults | Where-Object { $_.InstalledVersion -lt $_.ExpectedVersion })
    $isInstalled = $hasCurrentExtensionMarker -or $hasCurrentBaseMarker -or $outdatedResults.Count -eq 0
    $applicabilityReason = if ($isInstalled) {
        'Installed component markers meet or exceed the Dell catalog versions.'
    }
    else {
        @($outdatedResults | ForEach-Object {
            "$($_.DeviceName): installed $($_.InstalledVersion), catalog $($_.ExpectedVersion)"
        } | Sort-Object -Unique) -join '; '
    }
    return [pscustomobject]@{
        IsApplicable = $true
        IsInstalled = $isInstalled
        Matches = $componentResults
        ApplicabilityReason = $applicabilityReason
    }
}