Public/Invoke-DriverManagement.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    Main driver management orchestration function
#>


function Invoke-DriverManagement {
    <#
    .SYNOPSIS
        Orchestrates driver and Windows update management
    .DESCRIPTION
        Main entry point for driver management. Detects OEM, applies appropriate
        driver updates, and optionally includes Windows Updates.
    .PARAMETER Mode
        Update mode: Individual (surgical updates) or FullPack (complete reinstall)
    .PARAMETER UpdateTypes
        Types of updates: Driver, BIOS, Firmware, All
    .PARAMETER Severity
        Severity levels: Critical, Recommended, Optional
    .PARAMETER IncludeWindowsUpdates
        Also install Windows cumulative updates
    .PARAMETER IncludeIntel
        Include Intel driver updates (default: auto-detect Intel devices)
    .PARAMETER IntelOnly
        Only install Intel driver updates (skip OEM and Windows Updates)
    .PARAMETER NoReboot
        Suppress automatic reboot
    .PARAMETER Force
        Skip prerequisite checks
    .EXAMPLE
        Invoke-DriverManagement
        # Individual driver updates for detected OEM
    .EXAMPLE
        Invoke-DriverManagement -Mode FullPack -NoReboot
        # Full driver pack reinstall without reboot
    .EXAMPLE
        Invoke-DriverManagement -IncludeWindowsUpdates -UpdateTypes Driver, BIOS
        # Update drivers and BIOS plus Windows Updates
    .OUTPUTS
        DriverUpdateResult object with success status and details
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Update')]
    [OutputType('DriverUpdateResult')]
    param(
        [Parameter(ParameterSetName = 'Update')]
        [ValidateSet('Individual', 'FullPack')]
        [string]$Mode = 'Individual',
        
        [Parameter(ParameterSetName = 'Update')]
        [ValidateSet('Driver', 'BIOS', 'Firmware', 'All')]
        [string[]]$UpdateTypes = @('Driver'),
        
        [Parameter(ParameterSetName = 'Update')]
        [ValidateSet('Critical', 'Recommended', 'Optional')]
        [string[]]$Severity = @('Critical', 'Recommended'),
        
        [Parameter(ParameterSetName = 'Update')]
        [switch]$IncludeWindowsUpdates,
        
        [Parameter(ParameterSetName = 'Update')]
        [switch]$IncludeIntel = $true,
        
        [Parameter(ParameterSetName = 'Update')]
        [switch]$IntelOnly,
        
        [Parameter(ParameterSetName = 'Update')]
        [switch]$NoReboot,
        
        [Parameter(ParameterSetName = 'Update')]
        [switch]$Force
        ,

        #region Status Mode (Read-only Driver Health)

        [Parameter(Mandatory, ParameterSetName = 'Status')]
        [switch]$Status,

        [Parameter(ParameterSetName = 'Status')]
        [ValidateRange(1, 1680)]
        [int]$LookbackHours = 24,

        [Parameter(ParameterSetName = 'Status')]
        [ValidateRange(50, 5000)]
        [int]$MaxEventsPerChannel = 500,

        [Parameter(ParameterSetName = 'Status')]
        [switch]$IncludeDriverInventory,

        [Parameter(ParameterSetName = 'Status')]
        [string]$OutputPath

        #endregion
    )
    
    # Status mode (read-only telemetry + logging). Must not run update flows.
    if ($PSCmdlet.ParameterSetName -eq 'Status') {
        Initialize-DriverManagementLogging

        $report = New-DriverStatusReportInternal -LookbackHours $LookbackHours -MaxEventsPerChannel $MaxEventsPerChannel -IncludeDriverInventory:$IncludeDriverInventory
        $logging = Write-DriverStatusLogsInternal -Report $report -OutputPath $OutputPath

        # Attach logging info (stable schema field)
        try { $report | Add-Member -NotePropertyName 'Logging' -NotePropertyValue $logging -Force } catch { }

        # Write enriched compliance.json from status mode (machine-readable for Intune/FleetDM)
        try {
            $pendingItems = @()
            $hwIssues = @()
            $scanErrors = @()

            try { $pendingItems = @($report.Updates.PendingUpdates) } catch { $pendingItems = @() }
            try { $hwIssues = @($report.Devices.NonOkDevices) } catch { $hwIssues = @() }
            try { $scanErrors = @($report.Updates.Errors) } catch { $scanErrors = @() }

            $cs = [ComplianceStatus]::Unknown
            try { $cs = [ComplianceStatus]$report.Compliance.Status } catch { $cs = [ComplianceStatus]::Unknown }

            $summary = @{
                UpdatesApplied = 0
                UpdatesPending = @($pendingItems).Count
                PendingUpdatesCount = @($pendingItems).Count
                HardwareIssuesCount = @($hwIssues).Count
                ErrorsCount = @($scanErrors).Count
            }

            Update-DriverComplianceStatus -Status $cs `
                -UpdatesApplied 0 `
                -UpdatesPending (@($pendingItems).Count) `
                -Message $report.Compliance.Message `
                -UpToDate $report.Compliance.UpToDate `
                -ScanIncomplete $report.Compliance.ScanIncomplete `
                -RebootPending $report.Compliance.RebootPending `
                -PendingUpdates $pendingItems `
                -HardwareIssues $hwIssues `
                -Errors $scanErrors `
                -Summary $summary | Out-Null
        }
        catch {
            Write-DriverLog -Message "Status mode: failed to write compliance.json: $($_.Exception.Message)" -Severity Warning
        }

        return $report
    }

    # Initialize
    $result = [DriverUpdateResult]::new()
    $result.CorrelationId = $script:CorrelationId
    
    Write-DriverLog -Message "=== Driver Management Session Started ===" -Severity Info `
        -Context @{ Mode = $Mode; UpdateTypes = $UpdateTypes; Severity = $Severity }
    
    try {
        # Check prerequisites unless forced
        if (-not $Force) {
            if (-not (Test-IsElevated)) {
                throw "Elevation required. Run as Administrator or use -Force to skip checks."
            }
            
            # Only warn about pending reboot, don't block - allow Intel/Windows Updates to proceed
            if (Test-PendingReboot) {
                Write-DriverLog -Message "Pending reboot detected - proceeding with updates anyway" -Severity Warning
            }
        }
        
        # Intel-only mode: skip OEM and Windows Updates
        if ($IntelOnly) {
            Write-DriverLog -Message "Intel-only mode: skipping OEM and Windows Updates" -Severity Info
            
            # Detect Intel devices
            $intelDevices = Get-IntelDevices
            if ($intelDevices.Count -eq 0) {
                $result.Success = $true
                $result.Message = "No Intel devices detected"
                $result.ExitCode = 0
                Update-DriverComplianceStatus -Status Compliant -Message $result.Message
                return $result
            }
            
            # Install Intel updates
            $intelResult = Install-IntelDriverUpdates -NoReboot:$NoReboot
            
            $result.Success = $intelResult.Success
            $result.Message = $intelResult.Message
            $result.UpdatesApplied = $intelResult.UpdatesApplied
            $result.RebootRequired = $intelResult.RebootRequired
            $result.Details['IntelResult'] = $intelResult.ToHashtable()
        }
        else {
            # Normal mode: OEM + Intel + Windows Updates
            
            # Detect OEM
            $oemInfo = Get-OEMInfo
            Write-DriverLog -Message "Detected: $($oemInfo.OEM) $($oemInfo.Model)" -Severity Info `
                -Context @{ OEM = $oemInfo.OEM.ToString(); Model = $oemInfo.Model; Supported = $oemInfo.IsSupported }
            
            $oemUpdatesApplied = $false
            
            # Execute OEM-specific updates (if supported)
            if ($oemInfo.IsSupported) {
                $oemResult = switch ($oemInfo.OEM) {
                    'Dell' {
                        if ($Mode -eq 'Individual') {
                            Install-DellDriverUpdates -UpdateTypes $UpdateTypes -Severity $Severity -NoReboot:$NoReboot
                        }
                        else {
                            Install-DellFullDriverPack -NoReboot:$NoReboot
                        }
                    }
                    'Lenovo' {
                        if ($Mode -eq 'Individual') {
                            Install-LenovoDriverUpdates -UpdateTypes $UpdateTypes -Severity $Severity -NoReboot:$NoReboot
                        }
                        else {
                            Install-LenovoFullDriverPack -NoReboot:$NoReboot
                        }
                    }
                    default {
                        $r = [DriverUpdateResult]::new()
                        $r.Success = $false
                        $r.Message = "Unknown OEM: $($oemInfo.OEM)"
                        $r.ExitCode = 1
                        $r
                    }
                }
                
                # Merge OEM result
                $result.Success = $oemResult.Success
                $result.Message = $oemResult.Message
                $result.UpdatesApplied = $oemResult.UpdatesApplied
                $result.RebootRequired = $oemResult.RebootRequired
                $result.Details['OEMResult'] = $oemResult.ToHashtable()
                $oemUpdatesApplied = $true
            }
            else {
                Write-DriverLog -Message "Unsupported OEM hardware - skipping OEM driver updates" -Severity Warning
                # Don't set result.Success = false here - allow Intel/Windows Updates to proceed
                $result.Success = $true
                $result.Message = "Skipped OEM updates - unsupported model: $($oemInfo.Model)"
            }
            
            # Intel Updates if requested (proceed even if OEM failed or was skipped)
            $intelDevices = @()
            if ($IncludeIntel) {
                Write-DriverLog -Message "Processing Intel driver updates" -Severity Info
                
                $intelDevices = Get-IntelDevices
                if ($intelDevices.Count -gt 0) {
                    $intelResult = Install-IntelDriverUpdates -NoReboot:$NoReboot
                    
                    $result.UpdatesApplied += $intelResult.UpdatesApplied
                    $result.RebootRequired = $result.RebootRequired -or $intelResult.RebootRequired
                    $result.Details['IntelResult'] = $intelResult.ToHashtable()
                    
                    if (-not $intelResult.Success) {
                        if ($result.Message) {
                            $result.Message += "; Intel driver update issues"
                        }
                        else {
                            $result.Message = $intelResult.Message
                        }
                        # Only fail if OEM also failed and Intel failed
                        if (-not $oemUpdatesApplied -or -not $result.Success) {
                            $result.Success = $false
                        }
                    }
                    elseif ($intelResult.UpdatesApplied -gt 0) {
                        if ($result.Message -and $result.Message -notmatch "Intel") {
                            $result.Message += "; Intel updates applied"
                        }
                        elseif (-not $result.Message) {
                            $result.Message = $intelResult.Message
                        }
                        $result.Success = $true  # At least Intel updates succeeded
                    }
                }
                else {
                    Write-DriverLog -Message "No Intel devices detected" -Severity Info
                }
            }
            
            # Windows Updates if requested (proceed even if OEM/Intel failed)
            if ($IncludeWindowsUpdates) {
                Write-DriverLog -Message "Processing Windows Updates" -Severity Info
                
                $wuResult = Install-WindowsUpdates -NoReboot:$NoReboot
                
                $result.UpdatesApplied += $wuResult.UpdatesApplied
                $result.RebootRequired = $result.RebootRequired -or $wuResult.RebootRequired
                $result.Details['WindowsUpdateResult'] = $wuResult.ToHashtable()
                
                if (-not $wuResult.Success) {
                    if ($result.Message) {
                        $result.Message += "; Windows Update issues"
                    }
                    else {
                        $result.Message = $wuResult.Message
                    }
                    # Only fail if nothing else succeeded
                    if ($result.UpdatesApplied -eq 0) {
                        $result.Success = $false
                    }
                }
                elseif ($wuResult.UpdatesApplied -gt 0) {
                    if ($result.Message -and $result.Message -notmatch "Windows") {
                        $result.Message += "; Windows Updates applied"
                    }
                    elseif (-not $result.Message) {
                        $result.Message = $wuResult.Message
                    }
                    $result.Success = $true  # At least Windows Updates succeeded
                }
            }
            
            # If no updates were applied at all, set appropriate message
            # But only set Success = $true if there wasn't already a failure
            if ($result.UpdatesApplied -eq 0) {
                # If any provider explicitly failed, do not mark the overall run as success.
                # (Previously we treated "0 updates applied" as success even when OEM/Intel/WU failed.)
                $anyProviderFailed = $false
                foreach ($providerKey in @('OEMResult', 'IntelResult', 'WindowsUpdateResult')) {
                    if (-not $result.Details.ContainsKey($providerKey)) { continue }
                    $provider = $result.Details[$providerKey]
                    if ($provider -is [hashtable] -and $provider.ContainsKey('Success') -and $provider.Success -eq $false) {
                        $anyProviderFailed = $true
                        break
                    }
                }

                if (-not $oemInfo.IsSupported -and $intelDevices.Count -eq 0 -and -not $IncludeWindowsUpdates) {
                    if (-not $result.Message) {
                        $result.Message = "No OEM support, no Intel devices, and Windows Updates not requested"
                    }
                }
                elseif (-not $oemInfo.IsSupported -and $intelDevices.Count -eq 0) {
                    if (-not $result.Message) {
                        $result.Message = "No OEM support and no Intel devices detected"
                    }
                }
                # Handle success/failure based on provider results
                if ($anyProviderFailed) {
                    # Keep the provider's message (if already set), but ensure overall status reflects failure.
                    if (-not $result.Message) {
                        $result.Message = "No updates applied - one or more update providers failed"
                    }
                    $result.Success = $false
                }
                elseif ($result.Success -eq $null -or ($result.Success -eq $true -and -not $result.Message)) {
                    $result.Success = $true  # Not an error, just nothing to update
                }
            }
        }
        
        # Set final exit code
        $result.ExitCode = if ($result.RebootRequired) { 3010 } 
                          elseif ($result.Success) { 0 } 
                          else { 1 }
        
        # Update compliance status (SchemaVersion 2.0: machine-readable details)
        try {
            $hwIssues = @()
            try { $hwIssues = @((Get-PnpNonOkDevicesInternal).NonOkDevices) } catch { $hwIssues = @() }

            $rebootPending = $false
            try { $rebootPending = (Test-PendingReboot -or [bool]$result.RebootRequired) } catch { $rebootPending = [bool]$result.RebootRequired }

            $pendingUpdates = @()
            $errors = @()

            # Provider-level failures
            foreach ($providerKey in @('OEMResult', 'IntelResult', 'WindowsUpdateResult')) {
                if (-not $result.Details.ContainsKey($providerKey)) { continue }
                $provider = $result.Details[$providerKey]
                if ($provider -is [hashtable] -and $provider.ContainsKey('Success') -and $provider.Success -eq $false) {
                    $errors += [pscustomobject]@{
                        Provider = $providerKey
                        Stage = 'Provider'
                        Message = (if ($provider.ContainsKey('Message')) { $provider.Message } else { 'Provider failure' })
                        Details = $provider
                    }
                }
            }

            # Per-update failures/pending where details are available
            try {
                if ($result.Details.ContainsKey('OEMResult')) {
                    $o = $result.Details['OEMResult']
                    if ($o -is [hashtable] -and $o.ContainsKey('Details') -and $o.Details -is [hashtable] -and $o.Details.ContainsKey('Updates')) {
                        foreach ($u in @($o.Details.Updates)) {
                            if ($u -is [hashtable] -and $u.ContainsKey('Success') -and $u.Success -eq $false) {
                                $pendingUpdates += [pscustomobject]@{
                                    Provider = 'OEM'
                                    UpdateType = $null
                                    Title = $u.Title
                                    Identifier = $null
                                    InstalledVersion = $null
                                    AvailableVersion = $u.Version
                                    Severity = $null
                                    RebootRequired = $null
                                    Source = 'ProviderDetails'
                                }
                                $errors += [pscustomobject]@{
                                    Provider = 'OEM'
                                    Stage = 'Install'
                                    Message = (if ($u.ContainsKey('Error')) { $u.Error } else { 'Update install failed' })
                                    Details = $u
                                }
                            }
                        }
                    }
                }
            }
            catch { }

            try {
                if ($result.Details.ContainsKey('WindowsUpdateResult')) {
                    $wu = $result.Details['WindowsUpdateResult']
                    if ($wu -is [hashtable] -and $wu.ContainsKey('Details') -and $wu.Details -is [hashtable] -and $wu.Details.ContainsKey('Updates')) {
                        foreach ($u in @($wu.Details.Updates)) {
                            # PSWindowsUpdate returns objects; best-effort map to Title/KB/Result
                            $title = $null
                            $kb = $null
                            $res = $null
                            try { $title = $u.Title } catch { }
                            try { $kb = $u.KB } catch { }
                            try { $res = $u.Result } catch { }

                            if ($res -and ($res -notmatch 'Installed|Succeeded')) {
                                $pendingUpdates += [pscustomobject]@{
                                    Provider = 'WindowsUpdate'
                                    UpdateType = 'Software'
                                    Title = $title
                                    Identifier = $kb
                                    InstalledVersion = $null
                                    AvailableVersion = $null
                                    Severity = $null
                                    RebootRequired = $null
                                    Source = 'PSWindowsUpdate'
                                }
                                $errors += [pscustomobject]@{
                                    Provider = 'WindowsUpdate'
                                    Stage = 'Install'
                                    Message = "Windows Update install result: $res"
                                    Details = @{ KB = $kb; Title = $title; Result = $res }
                                }
                            }
                        }
                    }
                }
            }
            catch { }

            $scanIncomplete = $false
            $upToDate = (($errors.Count -eq 0) -and ($pendingUpdates.Count -eq 0) -and ($hwIssues.Count -eq 0) -and (-not $rebootPending))

            $complianceStatus =
                if ($errors.Count -gt 0) { [ComplianceStatus]::Error }
                elseif ($rebootPending) { [ComplianceStatus]::Pending }
                elseif (($pendingUpdates.Count -gt 0) -or ($hwIssues.Count -gt 0)) { [ComplianceStatus]::NonCompliant }
                else { [ComplianceStatus]::Compliant }

            $message = $result.Message
            if (-not $message) {
                if ($upToDate) { $message = 'UpToDate' }
                else { $message = "UpdatesPending=$($pendingUpdates.Count); HardwareIssues=$($hwIssues.Count); RebootPending=$rebootPending; Errors=$($errors.Count)" }
            }

            $summary = @{
                UpdatesApplied = $result.UpdatesApplied
                UpdatesPending = $pendingUpdates.Count
                PendingUpdatesCount = $pendingUpdates.Count
                HardwareIssuesCount = $hwIssues.Count
                ErrorsCount = $errors.Count
            }

            Update-DriverComplianceStatus -Status $complianceStatus `
                -UpdatesApplied $result.UpdatesApplied `
                -UpdatesPending $pendingUpdates.Count `
                -Message $message `
                -UpToDate $upToDate `
                -ScanIncomplete $scanIncomplete `
                -RebootPending $rebootPending `
                -PendingUpdates $pendingUpdates `
                -HardwareIssues $hwIssues `
                -Errors $errors `
                -Summary $summary | Out-Null
        }
        catch {
            # Last-resort fallback to legacy behavior
            $complianceStatus = if ($result.Success) { [ComplianceStatus]::Compliant } else { [ComplianceStatus]::Error }
            Update-DriverComplianceStatus -Status $complianceStatus `
                -UpdatesApplied $result.UpdatesApplied `
                -Message $result.Message | Out-Null
        }
        
        Write-DriverLog -Message "=== Driver Management Session Complete ===" -Severity Info `
            -Context $result.ToHashtable()
    }
    catch {
        Write-DriverLog -Message "Fatal error: $($_.Exception.Message)" -Severity Error `
            -Context @{ 
                ExceptionType = $_.Exception.GetType().FullName
                StackTrace = $_.ScriptStackTrace 
            }
        
        $result.Success = $false
        $result.Message = $_.Exception.Message
        $result.ExitCode = 1
        
        Update-DriverComplianceStatus -Status Error -Message $_.Exception.Message
    }
    
    return $result
}

# Export for direct script invocation
if ($MyInvocation.InvocationName -ne '.') {
    # Script was invoked directly, not dot-sourced
    # This allows: powershell -File Invoke-DriverManagement.ps1 -Mode Individual
}