Public/Intel.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    Intel driver management functions
.DESCRIPTION
    Provides detection, installation, and management of Intel drivers.
    Uses catalog-based approach since Intel DSA has no CLI/API support.
.NOTES
    Intel vendor ID: VEN_8086
    Device IDs follow pattern: PCI\VEN_8086&DEV_XXXX
#>


#region Intel Device Detection

function Get-IntelDevices {
    <#
    .SYNOPSIS
        Detects Intel devices on the system
    .DESCRIPTION
        Queries Win32_PnPSignedDriver for devices manufactured by Intel.
        Groups devices by device class (Display, Network, Audio, etc.)
    .PARAMETER DeviceClass
        Filter by device class (e.g., 'Display', 'Net', 'Media')
    .EXAMPLE
        Get-IntelDevices
    .EXAMPLE
        Get-IntelDevices -DeviceClass 'Display'
    .OUTPUTS
        Array of Intel device objects with DeviceID, DeviceName, DeviceClass, DriverVersion
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$DeviceClass
    )
    
    try {
        # Query PnP signed drivers for Intel devices
        # Intel vendor ID is 8086, or Manufacturer contains "Intel"
        $devices = Get-CimInstance -ClassName Win32_PnPSignedDriver -ErrorAction Stop | Where-Object {
            if (-not $_.DeviceID) { return $false }
            
            # Check for Intel vendor ID (VEN_8086) or Manufacturer contains Intel
            $isIntel = ($_.DeviceID -match 'VEN_8086') -or 
                      ($_.Manufacturer -like '*Intel*') -or
                      ($_.DriverProviderName -like '*Intel*')
            
            # Filter by device class if specified
            if ($DeviceClass -and $isIntel) {
                return $_.DeviceClass -like "*$DeviceClass*"
            }
            
            return $isIntel
        }
        
        $intelDevices = $devices | ForEach-Object {
            [PSCustomObject]@{
                DeviceID       = $_.DeviceID
                DeviceName     = $_.DeviceName
                DeviceClass    = $_.DeviceClass
                Manufacturer   = $_.Manufacturer
                DriverVersion  = $_.DriverVersion
                DriverDate     = $_.DriverDate
                InfName        = $_.InfName
                DriverProviderName = $_.DriverProviderName
                IsSigned       = $_.IsSigned
                HardwareID     = $_.HardwareID
            }
        }
        
        Write-DriverLog -Message "Detected $($intelDevices.Count) Intel devices" -Severity Info `
            -Context @{ DeviceCount = $intelDevices.Count; DeviceClasses = ($intelDevices.DeviceClass | Sort-Object -Unique) }
        
        return $intelDevices
    }
    catch {
        Write-DriverLog -Message "Failed to detect Intel devices: $($_.Exception.Message)" -Severity Error
        return @()
    }
}

#endregion

#region Intel Download Helpers (internal)

function Get-IntelDownloadFileName {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Url,
        
        [Parameter()]
        [string]$DefaultBaseName = 'intel_driver'
    )
    
    $leaf = $null
    try {
        $u = [uri]$Url
        $leaf = [System.IO.Path]::GetFileName($u.AbsolutePath)
    }
    catch {
        $leaf = Split-Path $Url -Leaf
    }
    
    if ([string]::IsNullOrWhiteSpace($leaf)) {
        $leaf = "$DefaultBaseName.bin"
    }
    
    # Sanitize invalid filename characters (handles URLs with query strings, etc.)
    $leaf = $leaf -replace '[<>:"/\\|?*]', '_'
    return $leaf
}

#endregion

#region Intel Driver Catalog

function Get-IntelDriverCatalog {
    <#
    .SYNOPSIS
        Loads the Intel driver catalog from JSON
    .DESCRIPTION
        Loads the catalog file from Config/intel_drivers.json
    .OUTPUTS
        Hashtable with drivers array
    #>

    [CmdletBinding()]
    param()
    
    $catalogPath = Join-Path $script:ModuleRoot "Config\intel_drivers.json"
    
    if (-not (Test-Path $catalogPath)) {
        Write-DriverLog -Message "Intel driver catalog not found: $catalogPath" -Severity Warning
        return @{ drivers = @() }
    }
    
    try {
        $catalog = Get-Content -Path $catalogPath -Raw | ConvertFrom-Json
        return @{
            drivers = $catalog.drivers
            lastUpdated = if ($catalog.lastUpdated) { $catalog.lastUpdated } else { $null }
        }
    }
    catch {
        Write-DriverLog -Message "Failed to load Intel driver catalog: $($_.Exception.Message)" -Severity Error
        return @{ drivers = @() }
    }
}

function Match-IntelDeviceToCatalog {
    <#
    .SYNOPSIS
        Matches an Intel device to catalog entries
    .DESCRIPTION
        Matches device by DeviceID, HardwareID, or device class
    .PARAMETER Device
        Intel device object from Get-IntelDevices
    .PARAMETER Catalog
        Catalog object from Get-IntelDriverCatalog
    .OUTPUTS
        Matching catalog entries
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Device,
        
        [Parameter(Mandatory)]
        [hashtable]$Catalog
    )
    
    $catalogMatches = @()
    
    foreach ($driver in $Catalog.drivers) {
        $isMatch = $false
        
        # Match by device IDs
        if ($driver.deviceIds) {
            foreach ($deviceId in $driver.deviceIds) {
                # Support wildcard matching (e.g., PCI\VEN_8086&DEV_*)
                # Escape special regex characters first, then replace wildcards
                $escaped = [regex]::Escape($deviceId)
                $pattern = $escaped -replace '\\\*', '.*' -replace '\\\?', '.'
                try {
                    if ($Device.DeviceID -match $pattern -or $Device.HardwareID -match $pattern) {
                        $isMatch = $true
                        break
                    }
                }
                catch {
                    # Invalid regex pattern - skip this device ID
                    Write-DriverLog -Message "Invalid device ID pattern: $deviceId - $($_.Exception.Message)" -Severity Warning
                }
            }
        }
        
        # Match by device class
        if (-not $isMatch -and $driver.deviceClass -and $Device.DeviceClass) {
            if ($Device.DeviceClass -like "*$($driver.deviceClass)*") {
                $isMatch = $true
            }
        }
        
        if ($isMatch) {
            $catalogMatches += $driver
        }
    }
    
    return $catalogMatches
}

#endregion

#region Intel Driver Update Detection

function Get-IntelDriverUpdates {
    <#
    .SYNOPSIS
        Scans for available Intel driver updates
    .DESCRIPTION
        Compares installed Intel drivers with catalog entries to find updates.
        Uses catalog-based approach since Intel DSA has no CLI support.
    .PARAMETER DeviceClass
        Filter by device class
    .EXAMPLE
        Get-IntelDriverUpdates
    .EXAMPLE
        Get-IntelDriverUpdates -DeviceClass 'Display'
    .OUTPUTS
        Array of available update objects
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$DeviceClass
    )
    
    Write-DriverLog -Message "Scanning for Intel driver updates" -Severity Info
    
    # Get installed Intel devices
    $devices = Get-IntelDevices -DeviceClass $DeviceClass
    
    if ($devices.Count -eq 0) {
        Write-DriverLog -Message "No Intel devices detected" -Severity Info
        return @()
    }
    
    # Load catalog
    $catalog = Get-IntelDriverCatalog
    
    if ($catalog.drivers.Count -eq 0) {
        Write-DriverLog -Message "Intel driver catalog is empty" -Severity Warning
        return @()
    }
    
    $updates = @()
    
    foreach ($device in $devices) {
        # Find matching catalog entries
        $catalogEntries = Match-IntelDeviceToCatalog -Device $device -Catalog $catalog
        
        foreach ($entry in $catalogEntries) {
            # Compare versions
            $installedVersion = $device.DriverVersion
            $availableVersion = $entry.driverVersion
            
            if (-not $installedVersion -or -not $availableVersion) {
                continue
            }
            
            # Try version comparison
            $needsUpdate = $false
            try {
                $installed = [version]$installedVersion
                $available = [version]$availableVersion
                $needsUpdate = $available -gt $installed
            }
            catch {
                # Fallback to string comparison
                $needsUpdate = $availableVersion -ne $installedVersion
            }
            
            if ($needsUpdate) {
                $updates += [PSCustomObject]@{
                    DeviceID         = $device.DeviceID
                    DeviceName       = $device.DeviceName
                    DeviceClass      = $device.DeviceClass
                    InstalledVersion = $installedVersion
                    AvailableVersion = $availableVersion
                    DownloadUrl      = $entry.downloadUrl
                    ReleaseDate      = $entry.releaseDate
                    Severity         = if ($entry.severity) { $entry.severity } else { 'Recommended' }
                    Description      = if ($entry.description) { $entry.description } else { "Intel $($device.DeviceClass) Driver Update" }
                    CatalogEntry     = $entry
                }
            }
        }
    }
    
    Write-DriverLog -Message "Found $($updates.Count) Intel driver updates" -Severity Info `
        -Context @{ Updates = ($updates | Select-Object DeviceName, InstalledVersion, AvailableVersion) }
    
    return $updates
}

#endregion

#region Intel Driver Installation

function Install-IntelDriverUpdates {
    <#
    .SYNOPSIS
        Installs Intel driver updates
    .DESCRIPTION
        Downloads and installs Intel driver updates from catalog URLs.
        Creates driver snapshot before installation for rollback capability.
    .PARAMETER DeviceClass
        Filter by device class (only install updates for specific class)
    .PARAMETER NoReboot
        Suppress automatic reboot
    .PARAMETER Force
        Force installation even if version check fails
    .EXAMPLE
        Install-IntelDriverUpdates
    .EXAMPLE
        Install-IntelDriverUpdates -DeviceClass 'Display' -NoReboot
    .OUTPUTS
        DriverUpdateResult object
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType('DriverUpdateResult')]
    param(
        [Parameter()]
        [string]$DeviceClass,
        
        [Parameter()]
        [switch]$NoReboot,
        
        [Parameter()]
        [switch]$Force
    )
    
    Assert-Elevation -Operation "Installing Intel drivers"
    
    $result = [DriverUpdateResult]::new()
    $result.CorrelationId = $script:CorrelationId
    
    # Get available updates
    $updates = Get-IntelDriverUpdates -DeviceClass $DeviceClass
    
    if ($updates.Count -eq 0) {
        $result.Success = $true
        $result.Message = "No Intel driver updates available"
        $result.ExitCode = 0
        Write-DriverLog -Message $result.Message -Severity Info
        return $result
    }
    
    # Create driver snapshot before installation
    try {
        $snapshotName = "Pre-Intel-Update-$(Get-Date -Format 'yyyyMMdd_HHmmss')"
        Write-DriverLog -Message "Creating driver snapshot: $snapshotName" -Severity Info
        $snapshot = New-DriverSnapshot -Name $snapshotName -IncludeInfFiles
        $result.Details['PreUpdateSnapshot'] = $snapshot.ID
    }
    catch {
        Write-DriverLog -Message "Failed to create snapshot: $($_.Exception.Message)" -Severity Warning
        if (-not $Force) {
            $result.Success = $false
            $result.Message = "Failed to create driver snapshot - aborting (use -Force to override)"
            $result.ExitCode = 1
            return $result
        }
    }
    
    if ($PSCmdlet.ShouldProcess("$($updates.Count) Intel driver updates", "Install")) {
        $installed = 0
        $failed = 0
        $rebootRequired = $false
        
        foreach ($update in $updates) {
            Write-DriverLog -Message "Processing: $($update.DeviceName) ($($update.InstalledVersion) -> $($update.AvailableVersion))" -Severity Info
            
            # Check if the download URL is a placeholder (Intel homepage, not a real driver)
            if (-not (Test-IntelDownloadUrl -Url $update.DownloadUrl)) {
                Write-DriverLog -Message "Skipping $($update.DeviceName) - catalog URL is placeholder (use Install-IntelDSA for Intel's official tool)" -Severity Warning
                $result.Details["Skipped_$($update.DeviceName)"] = "Placeholder URL - install Intel DSA for real updates"
                continue
            }
            
            try {
                # Download driver
                $tempPath = Join-Path $env:TEMP "IntelDriver_$(Get-Random)"
                New-Item -Path $tempPath -ItemType Directory -Force | Out-Null
                
                $fileName = Get-IntelDownloadFileName -Url $update.DownloadUrl -DefaultBaseName ($update.DeviceName -replace '\s+', '_')
                $driverFile = Join-Path $tempPath $fileName
                
                Write-DriverLog -Message "Downloading from: $($update.DownloadUrl)" -Severity Info
                
                # Use the module's resilient downloader (BITS + retry + web fallback) and ensure TLS 1.2
                Invoke-WithRetry -ScriptBlock {
                    Start-DownloadWithVerification -SourceUrl $update.DownloadUrl -DestinationPath $driverFile | Out-Null
                    if (-not (Test-Path $driverFile)) {
                        throw "Download failed - file not found after download"
                    }
                } -MaxAttempts 3 -ExponentialBackoff
                
                # Extract if it's a ZIP
                if ($driverFile -match '\.zip$') {
                    $extractPath = Join-Path $tempPath "extracted"
                    Expand-Archive -Path $driverFile -DestinationPath $extractPath -Force
                    
                    # Find INF file or installer
                    $infFiles = Get-ChildItem -Path $extractPath -Filter "*.inf" -Recurse -ErrorAction SilentlyContinue
                    $installerExe = Get-ChildItem -Path $extractPath -Filter "*.exe" -Recurse -ErrorAction SilentlyContinue | 
                        Where-Object { $_.Name -match 'setup|install|driver' } | Select-Object -First 1
                    
                    if ($infFiles) {
                        # Install via pnputil
                        foreach ($infFile in $infFiles) {
                            $pnputilResult = & pnputil /add-driver "$($infFile.FullName)" /install 2>&1
                            if ($LASTEXITCODE -eq 0) {
                                $installed++
                                Write-DriverLog -Message "Installed via pnputil: $($infFile.Name)" -Severity Info
                            }
                            else {
                                $failed++
                                Write-DriverLog -Message "pnputil failed for $($infFile.Name): exit $LASTEXITCODE" -Severity Warning
                            }
                        }
                    }
                    elseif ($installerExe) {
                        # Run installer silently
                        $installArgs = @('/S', '/SILENT', '/VERYSILENT', '/quiet', '/qn')
                        $installResult = Start-Process -FilePath $installerExe.FullName -ArgumentList $installArgs -Wait -PassThru -NoNewWindow
                        
                        if ($installResult.ExitCode -eq 0 -or $installResult.ExitCode -eq 3010) {
                            $installed++
                            if ($installResult.ExitCode -eq 3010) {
                                $rebootRequired = $true
                            }
                            Write-DriverLog -Message "Installer completed: exit $($installResult.ExitCode)" -Severity Info
                        }
                        else {
                            $failed++
                            Write-DriverLog -Message "Installer failed: exit $($installResult.ExitCode)" -Severity Warning
                        }
                    }
                    else {
                        Write-DriverLog -Message "No INF or installer found in driver package" -Severity Warning
                        $failed++
                    }
                }
                elseif ($driverFile -match '\.exe$') {
                    # Direct installer
                    $installArgs = @('/S', '/SILENT', '/VERYSILENT', '/quiet', '/qn')
                    $installResult = Start-Process -FilePath $driverFile -ArgumentList $installArgs -Wait -PassThru -NoNewWindow
                    
                    if ($installResult.ExitCode -eq 0 -or $installResult.ExitCode -eq 3010) {
                        $installed++
                        if ($installResult.ExitCode -eq 3010) {
                            $rebootRequired = $true
                        }
                    }
                    else {
                        $failed++
                    }
                }
                else {
                    Write-DriverLog -Message "Unsupported driver file format: $driverFile" -Severity Warning
                    $failed++
                }
                
                # Cleanup
                Remove-Item -Path $tempPath -Recurse -Force -ErrorAction SilentlyContinue
            }
            catch {
                $failed++
                Write-DriverLog -Message "Failed to install $($update.DeviceName): $($_.Exception.Message)" -Severity Error `
                    -Context @{ DeviceName = $update.DeviceName; DownloadUrl = $update.DownloadUrl; InstalledVersion = $update.InstalledVersion; AvailableVersion = $update.AvailableVersion }
            }
        }
        
        # Count skipped updates (placeholder URLs)
        $skipped = ($result.Details.Keys | Where-Object { $_ -like 'Skipped_*' }).Count
        
        $result.Success = ($failed -eq 0)
        if ($installed -eq 0 -and $skipped -gt 0 -and $failed -eq 0) {
            $result.Message = "No Intel drivers installed - $skipped skipped (catalog has placeholder URLs, install Intel DSA for real updates)"
            $result.Success = $true  # Not a failure, just no actionable updates
        }
        elseif ($installed -gt 0) {
            $result.Message = "Installed $installed Intel driver updates"
            if ($failed -gt 0) {
                $result.Message += ", $failed failed"
            }
            if ($skipped -gt 0) {
                $result.Message += ", $skipped skipped"
            }
        }
        else {
            $result.Message = "Intel driver installation: $installed installed, $failed failed"
        }
        $result.UpdatesApplied = $installed
        $result.UpdatesFailed = $failed
        $result.RebootRequired = $rebootRequired -and (-not $NoReboot)
        $result.ExitCode = if ($result.RebootRequired) { 3010 } elseif ($result.Success) { 0 } else { 1 }
        
        Write-DriverLog -Message $result.Message -Severity Info -Context $result.ToHashtable()
    }
    
    return $result
}

#endregion

#region Intel DSA (Driver & Support Assistant) Integration

function Test-IntelDSAInstalled {
    <#
    .SYNOPSIS
        Checks if Intel Driver & Support Assistant is installed
    .OUTPUTS
        PSCustomObject with installation status and path
    #>

    [CmdletBinding()]
    param()
    
    $dsaPaths = @(
        "$env:ProgramFiles\Intel\Driver and Support Assistant\DSATray.exe",
        "${env:ProgramFiles(x86)}\Intel\Driver and Support Assistant\DSATray.exe",
        "$env:LocalAppData\Programs\Intel\Driver and Support Assistant\DSATray.exe"
    )
    
    foreach ($path in $dsaPaths) {
        if (Test-Path $path) {
            $version = (Get-Item $path).VersionInfo.ProductVersion
            return [PSCustomObject]@{
                Installed = $true
                Path = $path
                Version = $version
            }
        }
    }
    
    return [PSCustomObject]@{
        Installed = $false
        Path = $null
        Version = $null
    }
}

function Install-IntelDSA {
    <#
    .SYNOPSIS
        Downloads and installs Intel Driver & Support Assistant
    .DESCRIPTION
        Intel DSA is Intel's official tool for driver updates. This function
        downloads and installs it silently.
    .PARAMETER Force
        Force reinstall even if already installed
    .EXAMPLE
        Install-IntelDSA
    .OUTPUTS
        PSCustomObject with installation result
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter()]
        [switch]$Force
    )
    
    $result = [PSCustomObject]@{
        Success = $false
        Message = ""
        DSAPath = $null
    }
    
    # Check if already installed
    $existing = Test-IntelDSAInstalled
    if ($existing.Installed -and -not $Force) {
        $result.Success = $true
        $result.Message = "Intel DSA already installed (version $($existing.Version))"
        $result.DSAPath = $existing.Path
        Write-DriverLog -Message $result.Message -Severity Info
        return $result
    }
    
    Assert-Elevation -Operation "Installing Intel DSA"
    
    # Intel DSA download URL (this is the stable download location)
    $dsaUrl = "https://dsadata.intel.com/installer"
    $installerPath = Join-Path $env:TEMP "Intel-Driver-and-Support-Assistant-Installer.exe"
    
    try {
        Write-DriverLog -Message "Downloading Intel Driver & Support Assistant" -Severity Info
        
        if ($PSCmdlet.ShouldProcess("Intel DSA", "Download and Install")) {
            # Force TLS 1.2
            [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
            
            # Download installer
            $downloadResult = Start-DownloadWithVerification -SourceUrl $dsaUrl -DestinationPath $installerPath
            
            if (-not (Test-Path $installerPath)) {
                throw "Download failed - installer not found"
            }
            
            Write-DriverLog -Message "Installing Intel DSA silently" -Severity Info
            
            # Silent install
            $installProcess = Start-Process -FilePath $installerPath -ArgumentList "/silent" -Wait -PassThru -NoNewWindow
            
            if ($installProcess.ExitCode -eq 0) {
                $result.Success = $true
                $result.Message = "Intel DSA installed successfully"
                $newInstall = Test-IntelDSAInstalled
                $result.DSAPath = $newInstall.Path
            }
            else {
                $result.Message = "Intel DSA installation failed with exit code $($installProcess.ExitCode)"
            }
        }
    }
    catch {
        $result.Message = "Failed to install Intel DSA: $($_.Exception.Message)"
        Write-DriverLog -Message $result.Message -Severity Error
    }
    finally {
        # Cleanup
        Remove-Item -Path $installerPath -Force -ErrorAction SilentlyContinue
    }
    
    Write-DriverLog -Message $result.Message -Severity $(if ($result.Success) { 'Info' } else { 'Error' })
    return $result
}

function Test-IntelDownloadUrl {
    <#
    .SYNOPSIS
        Tests if an Intel download URL is a real driver download vs placeholder
    .PARAMETER Url
        The URL to test
    .OUTPUTS
        Boolean - true if URL appears to be a real driver download
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Url
    )
    
    # Placeholder URLs that don't lead to actual downloads
    $placeholderPatterns = @(
        'intel.com/content/www/.*/download-center',
        'intel.com/content/www/.*/download-center/home.html',
        'downloadcenter\.intel\.com/?$'
    )
    
    foreach ($pattern in $placeholderPatterns) {
        if ($Url -match $pattern) {
            return $false
        }
    }
    
    # Valid download URLs typically end in .exe, .zip, or have downloadmirror in the path
    if ($Url -match '\.exe$' -or $Url -match '\.zip$' -or $Url -match 'downloadmirror\.intel\.com') {
        return $true
    }
    
    # If it doesn't match known patterns, assume it might work
    return $true
}

#endregion

#region Intel Module Initialization

function Initialize-IntelModule {
    <#
    .SYNOPSIS
        Initializes Intel driver management
    .DESCRIPTION
        Checks for Intel devices, loads catalog, and validates configuration.
    .OUTPUTS
        PSCustomObject with initialization status
    #>

    [CmdletBinding()]
    param()
    
    $status = [PSCustomObject]@{
        Initialized     = $false
        DevicesDetected = 0
        CatalogLoaded   = $false
        CatalogPath     = $null
        Message         = ""
    }
    
    # Detect Intel devices
    $devices = Get-IntelDevices
    $status.DevicesDetected = $devices.Count
    
    # Load catalog
    $catalogPath = Join-Path $script:ModuleRoot "Config\intel_drivers.json"
    $status.CatalogPath = $catalogPath
    
    if (Test-Path $catalogPath) {
        $catalog = Get-IntelDriverCatalog
        $status.CatalogLoaded = ($catalog.drivers.Count -gt 0)
        
        if ($status.CatalogLoaded) {
            $status.Message = "Intel module initialized: $($status.DevicesDetected) devices, $($catalog.drivers.Count) catalog entries"
        }
        else {
            $status.Message = "Intel module initialized: $($status.DevicesDetected) devices, but catalog is empty"
        }
    }
    else {
        $status.Message = "Intel module initialized: $($status.DevicesDetected) devices, but catalog not found at $catalogPath"
    }
    
    $status.Initialized = $true
    
    # Write-DriverLog requires a hashtable for -Context
    $logContext = @{
        Initialized     = $status.Initialized
        DevicesDetected = $status.DevicesDetected
        CatalogLoaded   = $status.CatalogLoaded
        CatalogPath     = $status.CatalogPath
    }
    Write-DriverLog -Message $status.Message -Severity Info -Context $logContext
    
    return $status
}

#endregion