Public/Dell.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    Dell driver management functions
.DESCRIPTION
    Comprehensive Dell driver and update management using Dell Command Update.
    Includes catalog-based version detection, offline catalog support, and
    comprehensive exit code handling inspired by Gary Blok's Dell-EMPS.ps1.
.NOTES
    Reference: https://github.com/gwblok/garytown/blob/master/hardware/Dell/CommandUpdate/EMPS/Dell-EMPS.ps1
    DCU Reference Guide: https://dl.dell.com/content/manual13608255-dell-command-update-version-5-x-reference-guide.pdf
#>


#region DCU Exit Codes

# Comprehensive DCU exit codes per Dell documentation
$script:DCUExitCodes = @{
    0   = @{ Description = "Command execution successful"; Resolution = "None required" }
    1   = @{ Description = "Reboot required"; Resolution = "Reboot the system to complete updates" }
    2   = @{ Description = "Unknown application error"; Resolution = "Check DCU logs for details" }
    3   = @{ Description = "Incomplete command line"; Resolution = "Verify command syntax" }
    4   = @{ Description = "Invalid command line option"; Resolution = "Check available options" }
    5   = @{ Description = "Unable to get admin privilege"; Resolution = "Run as administrator" }
    6   = @{ Description = "No update filters found"; Resolution = "Check update type/severity filters" }
    7   = @{ Description = "Duplicate command line option"; Resolution = "Remove duplicate options" }
    8   = @{ Description = "Cannot create the scheduled task"; Resolution = "Check Task Scheduler permissions" }
    9   = @{ Description = "Cannot remove the scheduled task"; Resolution = "Check Task Scheduler permissions" }
    10  = @{ Description = "Download failed, no update(s) to apply"; Resolution = "Check network connectivity" }
    11  = @{ Description = "Suspend Bitlocker failed"; Resolution = "Manually suspend BitLocker" }
    12  = @{ Description = "Another instance of DCU running"; Resolution = "Wait for other instance to complete" }
    13  = @{ Description = "Invalid catalog file"; Resolution = "Re-download or regenerate catalog" }
    14  = @{ Description = "Unable to schedule updates"; Resolution = "Check scheduled task configuration" }
    15  = @{ Description = "Invalid export file format"; Resolution = "Check export file path/format" }
    16  = @{ Description = "Invalid password"; Resolution = "Verify BIOS password" }
    17  = @{ Description = "System is not supported"; Resolution = "Verify Dell system compatibility" }
    18  = @{ Description = "No updates available"; Resolution = "System is up to date" }
    19  = @{ Description = "Network error"; Resolution = "Check network connectivity to Dell servers" }
    20  = @{ Description = "Catalog sync failed"; Resolution = "Check internet connectivity" }
    21  = @{ Description = "Running in OS pre-boot"; Resolution = "Run after Windows boot completes" }
    500 = @{ Description = "No updates available"; Resolution = "System is up to date" }
    501 = @{ Description = "Soft dependency error"; Resolution = "Check for prerequisite updates" }
    502 = @{ Description = "Hard dependency error"; Resolution = "Install prerequisite updates first" }
    503 = @{ Description = "Already running"; Resolution = "Wait for other DCU instance" }
    504 = @{ Description = "System reboot pending"; Resolution = "Reboot system first" }
    505 = @{ Description = "Rollback"; Resolution = "Update failed and was rolled back" }
    506 = @{ Description = "Update failed"; Resolution = "Check DCU logs for failure details" }
    507 = @{ Description = "Download progress"; Resolution = "Update is still downloading" }
    508 = @{ Description = "Install progress"; Resolution = "Update is still installing" }
}

function Get-DCUExitInfo {
    <#
    .SYNOPSIS
        Gets information about Dell Command Update exit codes
    .DESCRIPTION
        Returns description and resolution for DCU exit codes.
        Can return all exit codes or a specific one.
    .PARAMETER ExitCode
        Specific exit code to get information for
    .EXAMPLE
        Get-DCUExitInfo -ExitCode 1
    .EXAMPLE
        Get-DCUExitInfo # Returns all exit codes
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [int]$ExitCode
    )
    
    if ($PSBoundParameters.ContainsKey('ExitCode')) {
        if ($script:DCUExitCodes.ContainsKey($ExitCode)) {
            $info = $script:DCUExitCodes[$ExitCode]
            return [PSCustomObject]@{
                ExitCode    = $ExitCode
                Description = $info.Description
                Resolution  = $info.Resolution
            }
        }
        else {
            return [PSCustomObject]@{
                ExitCode    = $ExitCode
                Description = "Unknown exit code"
                Resolution  = "Check Dell Command Update logs"
            }
        }
    }
    
    # Return all exit codes
    $script:DCUExitCodes.GetEnumerator() | ForEach-Object {
        [PSCustomObject]@{
            ExitCode    = $_.Key
            Description = $_.Value.Description
            Resolution  = $_.Value.Resolution
        }
    } | Sort-Object ExitCode
}

#endregion

#region DCU Installation and Version Detection

function Get-DellCommandUpdatePath {
    <#
    .SYNOPSIS
        Finds the Dell Command Update CLI executable
    .OUTPUTS
        Path to dcu-cli.exe or $null if not found
    #>

    [CmdletBinding()]
    param()
    
    $paths = @(
        "${env:ProgramFiles}\Dell\CommandUpdate\dcu-cli.exe",
        "${env:ProgramFiles(x86)}\Dell\CommandUpdate\dcu-cli.exe"
    )
    return $paths | Where-Object { Test-Path $_ } | Select-Object -First 1
}

function Get-DCUInstallDetails {
    <#
    .SYNOPSIS
        Gets Dell Command Update installation details from registry
    .DESCRIPTION
        Queries the registry to determine installed DCU version and type
        (Universal Windows Platform vs Classic).
    .EXAMPLE
        Get-DCUInstallDetails
    .OUTPUTS
        PSCustomObject with Version, AppType, Path properties
    #>

    [CmdletBinding()]
    param()
    
    $result = [PSCustomObject]@{
        IsInstalled = $false
        Version     = $null
        AppType     = $null  # 'Universal' or 'Classic'
        Path        = $null
        InstallDate = $null
    }
    
    # Check Universal Windows Platform (UWP) version
    $uwpReg = "HKLM:\SOFTWARE\Dell\UpdateService\Clients\CommandUpdate"
    if (Test-Path $uwpReg) {
        try {
            $regData = Get-ItemProperty -Path $uwpReg -ErrorAction Stop
            $result.IsInstalled = $true
            $result.Version = $regData.Version ?? $regData.ProductVersion
            $result.AppType = 'Universal'
        }
        catch { }
    }
    
    # Check Classic version
    $classicReg = "HKLM:\SOFTWARE\DELL\CommandUpdate"
    if (-not $result.IsInstalled -and (Test-Path $classicReg)) {
        try {
            $regData = Get-ItemProperty -Path $classicReg -ErrorAction Stop
            $result.IsInstalled = $true
            $result.Version = $regData.Version ?? $regData.ProductVersion
            $result.AppType = 'Classic'
        }
        catch { }
    }
    
    # Also check Programs and Features via Uninstall registry
    $uninstallPaths = @(
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
        "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
    )
    
    foreach ($path in $uninstallPaths) {
        $dcu = Get-ItemProperty $path -ErrorAction SilentlyContinue | 
            Where-Object { $_.DisplayName -like "*Dell Command*Update*" } |
            Select-Object -First 1
        
        if ($dcu) {
            $result.IsInstalled = $true
            $result.Version = $result.Version ?? $dcu.DisplayVersion
            $result.InstallDate = $dcu.InstallDate
            break
        }
    }
    
    # Get executable path
    $result.Path = Get-DellCommandUpdatePath
    
    return $result
}

function Get-DellCatalog {
    <#
    .SYNOPSIS
        Downloads and parses the Dell CatalogIndexPC.cab
    .DESCRIPTION
        Retrieves the Dell update catalog which contains information about
        supported models, available updates, and download URLs.
        Based on Gary Blok's Get-DellSupportedModels function.
    .PARAMETER Force
        Force re-download even if cached
    .EXAMPLE
        Get-DellCatalog
    .OUTPUTS
        Array of supported Dell models with SystemID, Model, URL, Date
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]$Force
    )
    
    $cabPath = "$env:ProgramData\PSDriverManagement\DellCatalog\CatalogIndexPC.cab"
    $extractPath = "$env:ProgramData\PSDriverManagement\DellCatalog\Extract"
    $xmlPath = "$extractPath\CatalogIndexPC.xml"
    
    # Check cache (valid for 24 hours)
    $cacheValid = $false
    if (-not $Force -and (Test-Path $xmlPath)) {
        $cacheAge = (Get-Date) - (Get-Item $xmlPath).LastWriteTime
        if ($cacheAge.TotalHours -lt 24) {
            $cacheValid = $true
            Write-DriverLog -Message "Using cached Dell catalog (age: $([math]::Round($cacheAge.TotalHours, 1)) hours)" -Severity Info
        }
    }
    
    if (-not $cacheValid) {
        # Ensure directories exist
        $cabDir = Split-Path $cabPath -Parent
        if (-not (Test-Path $cabDir)) {
            New-Item -Path $cabDir -ItemType Directory -Force | Out-Null
        }
        if (-not (Test-Path $extractPath)) {
            New-Item -Path $extractPath -ItemType Directory -Force | Out-Null
        }
        
        Write-DriverLog -Message "Downloading Dell catalog from downloads.dell.com" -Severity Info
        
        try {
            Invoke-WebRequest -Uri "https://downloads.dell.com/catalog/CatalogIndexPC.cab" -OutFile $cabPath -UseBasicParsing -ErrorAction Stop
            
            # Extract CAB
            if (Test-Path "$extractPath\CatalogIndexPC.xml") {
                Remove-Item "$extractPath\CatalogIndexPC.xml" -Force
            }
            
            $expandResult = & expand.exe $cabPath -F:CatalogIndexPC.xml $extractPath 2>&1
            
            if (-not (Test-Path $xmlPath)) {
                throw "Failed to extract catalog XML"
            }
            
            Write-DriverLog -Message "Dell catalog downloaded and extracted" -Severity Info
        }
        catch {
            Write-DriverLog -Message "Failed to download Dell catalog: $($_.Exception.Message)" -Severity Error
            throw
        }
    }
    
    # Parse XML
    Write-DriverLog -Message "Parsing Dell catalog XML" -Severity Info
    [xml]$catalogXml = Get-Content $xmlPath
    
    $models = $catalogXml.ManifestIndex.GroupManifest | ForEach-Object {
        [PSCustomObject]@{
            SystemID = $_.SupportedSystems.Brand.Model.systemID
            Model    = $_.SupportedSystems.Brand.Model.Display.'#cdata-section'
            URL      = $_.ManifestInformation.path
            Date     = $_.ManifestInformation.version
        }
    }
    
    return $models
}

function Get-LatestDCUVersion {
    <#
    .SYNOPSIS
        Gets the latest available Dell Command Update version
    .DESCRIPTION
        Queries Dell's catalog to find the latest DCU version available
        for download. Optionally checks against the currently installed version.
    .PARAMETER CheckUpdate
        Compare with installed version and return if update is available
    .EXAMPLE
        Get-LatestDCUVersion
    .EXAMPLE
        Get-LatestDCUVersion -CheckUpdate
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]$CheckUpdate
    )
    
    # Dell Command Update package info URL
    $dcuInfoUrl = "https://downloads.dell.com/catalog/CatalogIndexPC.cab"
    
    # Try to get version from Dell's driver catalog
    try {
        $cabPath = "$env:ProgramData\PSDriverManagement\DellCatalog\DCUVersion.cab"
        $extractPath = "$env:ProgramData\PSDriverManagement\DellCatalog\DCUExtract"
        
        $cabDir = Split-Path $cabPath -Parent
        if (-not (Test-Path $cabDir)) {
            New-Item -Path $cabDir -ItemType Directory -Force | Out-Null
        }
        
        # Download CatalogPC.cab which contains DCU info
        $catalogUrl = "https://downloads.dell.com/catalog/CatalogPC.cab"
        Invoke-WebRequest -Uri $catalogUrl -OutFile $cabPath -UseBasicParsing -ErrorAction Stop
        
        if (-not (Test-Path $extractPath)) {
            New-Item -Path $extractPath -ItemType Directory -Force | Out-Null
        }
        
        # Extract
        & expand.exe $cabPath -F:* $extractPath 2>&1 | Out-Null
        
        # Find DCU in catalog
        $catalogXmlPath = Get-ChildItem $extractPath -Filter "*.xml" | Select-Object -First 1
        if ($catalogXmlPath) {
            [xml]$catalog = Get-Content $catalogXmlPath.FullName
            
            $dcuPackage = $catalog.Manifest.SoftwareComponent | 
                Where-Object { $_.Name.Display.'#cdata-section' -like "*Dell Command*Update*" } |
                Sort-Object { [version]$_.vendorVersion } -Descending |
                Select-Object -First 1
            
            if ($dcuPackage) {
                $latestInfo = [PSCustomObject]@{
                    Version     = $dcuPackage.vendorVersion
                    ReleaseDate = $dcuPackage.releaseDate
                    DownloadUrl = "https://downloads.dell.com/$($dcuPackage.path)"
                    FileName    = Split-Path $dcuPackage.path -Leaf
                    Size        = $dcuPackage.size
                }
                
                if ($CheckUpdate) {
                    $installed = Get-DCUInstallDetails
                    
                    $latestInfo | Add-Member -NotePropertyName 'InstalledVersion' -NotePropertyValue $installed.Version
                    $latestInfo | Add-Member -NotePropertyName 'UpdateAvailable' -NotePropertyValue $false
                    
                    if ($installed.IsInstalled -and $installed.Version) {
                        try {
                            $latestInfo.UpdateAvailable = ([version]$latestInfo.Version) -gt ([version]$installed.Version)
                        }
                        catch {
                            # Version comparison failed
                            $latestInfo.UpdateAvailable = $latestInfo.Version -ne $installed.Version
                        }
                    }
                    else {
                        $latestInfo.UpdateAvailable = $true  # Not installed
                    }
                }
                
                return $latestInfo
            }
        }
    }
    catch {
        Write-DriverLog -Message "Failed to get latest DCU version from catalog: $($_.Exception.Message)" -Severity Warning
    }
    
    # Fallback: Return known latest version
    return [PSCustomObject]@{
        Version     = "5.4.0"
        ReleaseDate = "2024-01-01"
        DownloadUrl = "https://dl.dell.com/FOLDER11914155M/1/Dell-Command-Update-Windows-Universal-Application_601KT_WIN_5.4.0_A00.EXE"
        FileName    = "Dell-Command-Update-Windows-Universal-Application_601KT_WIN_5.4.0_A00.EXE"
        Size        = $null
    }
}

function Install-DellCommandUpdate {
    <#
    .SYNOPSIS
        Downloads and installs Dell Command Update
    .DESCRIPTION
        Automatically downloads the latest Dell Command Update from Dell's website
        and performs a silent installation. Checks if update is needed first.
    .PARAMETER Force
        Install even if current version is up to date
    .PARAMETER Version
        Specific version to install (default: latest)
    .EXAMPLE
        Install-DellCommandUpdate
    .EXAMPLE
        Install-DellCommandUpdate -Force
    .NOTES
        Dell Command Update Universal Windows Platform application
        https://www.dell.com/support/kbdoc/en-us/000177325/dell-command-update
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]$Force
    )
    
    Assert-Elevation -Operation "Installing Dell Command Update"
    
    $config = $script:ModuleConfig
    
    # Check if update is needed
    if (-not $Force) {
        $installed = Get-DCUInstallDetails
        
        if ($installed.IsInstalled) {
            $latestInfo = Get-LatestDCUVersion -CheckUpdate
            
            if (-not $latestInfo.UpdateAvailable) {
                Write-DriverLog -Message "Dell Command Update is already up to date (v$($installed.Version))" -Severity Info
                return
            }
            
            Write-DriverLog -Message "Update available: $($installed.Version) -> $($latestInfo.Version)" -Severity Info
        }
    }
    
    # Determine download URL
    # Priority: 1) Environment variable, 2) Module config, 3) Catalog lookup, 4) Default
    $dcuUrl = if ($env:PSDM_DCU_URL) {
        Write-DriverLog -Message "Using DCU URL from environment variable" -Severity Info
        $env:PSDM_DCU_URL
    } elseif ($config.DellCommandUpdateUrl) {
        $config.DellCommandUpdateUrl
    } else {
        # Try to get from catalog
        $latestInfo = Get-LatestDCUVersion
        if ($latestInfo.DownloadUrl) {
            $latestInfo.DownloadUrl
        } else {
            "https://dl.dell.com/FOLDER11914155M/1/Dell-Command-Update-Windows-Universal-Application_601KT_WIN_5.4.0_A00.EXE"
        }
    }
    
    $installerPath = Join-Path $env:TEMP "DellCommandUpdate_$(Get-Date -Format 'yyyyMMddHHmmss').exe"
    
    Write-DriverLog -Message "Downloading Dell Command Update from $dcuUrl" -Severity Info
    
    try {
        # Download with retry logic
        Invoke-WithRetry -ScriptBlock {
            # Use BITS for reliable download, fallback to WebRequest
            try {
                Start-BitsTransfer -Source $dcuUrl -Destination $installerPath -ErrorAction Stop
            }
            catch {
                Invoke-WebRequest -Uri $dcuUrl -OutFile $installerPath -UseBasicParsing -ErrorAction Stop
            }
        } -MaxAttempts 3 -ExponentialBackoff
        
        if (-not (Test-Path $installerPath)) {
            throw "Download failed - installer not found"
        }
        
        $fileSize = (Get-Item $installerPath).Length / 1MB
        Write-DriverLog -Message "Downloaded DCU installer ($([math]::Round($fileSize, 1)) MB)" -Severity Info
        
        # Silent install
        Write-DriverLog -Message "Installing Dell Command Update silently..." -Severity Info
        
        $installProcess = Start-Process -FilePath $installerPath -ArgumentList "/s" -Wait -PassThru -NoNewWindow
        $exitCode = $installProcess.ExitCode
        
        # Interpret exit code
        $exitInfo = Get-DCUExitInfo -ExitCode $exitCode
        
        if ($exitCode -eq 0) {
            Write-DriverLog -Message "Dell Command Update installed successfully" -Severity Info
        }
        elseif ($exitCode -eq 1) {
            Write-DriverLog -Message "Dell Command Update installed - reboot required" -Severity Warning
        }
        else {
            throw "Installation failed: $($exitInfo.Description) (Exit: $exitCode)"
        }
        
        return [PSCustomObject]@{
            Success = $exitCode -in @(0, 1)
            ExitCode = $exitCode
            Message = $exitInfo.Description
            RebootRequired = ($exitCode -eq 1)
        }
    }
    catch {
        Write-DriverLog -Message "Failed to install Dell Command Update: $($_.Exception.Message)" -Severity Error
        throw
    }
    finally {
        # Cleanup installer
        if (Test-Path $installerPath) {
            Remove-Item $installerPath -Force -ErrorAction SilentlyContinue
        }
    }
}

#endregion

#region DCU Settings

function Get-DCUSettings {
    <#
    .SYNOPSIS
        Gets current Dell Command Update settings
    .DESCRIPTION
        Reads DCU configuration from registry.
    .EXAMPLE
        Get-DCUSettings
    #>

    [CmdletBinding()]
    param()
    
    $settings = [PSCustomObject]@{
        UserConsent = $null
        AutoSuspendBitLocker = $null
        ScheduleAction = $null
        ScheduleAuto = $null
        InstallationDeferral = $null
        SystemRestartDeferral = $null
        AdvancedDriverRestore = $null
        LockSettings = $null
        CatalogLocation = $null
    }
    
    $regPath = "HKLM:\SOFTWARE\Dell\UpdateService\Clients\CommandUpdate\Preferences\Settings"
    
    if (Test-Path $regPath) {
        try {
            $regData = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
            
            $settings.UserConsent = $regData.UserConsent
            $settings.AutoSuspendBitLocker = $regData.AutoSuspendBitLocker
            $settings.ScheduleAction = $regData.ScheduleAction
            $settings.ScheduleAuto = $regData.ScheduleAuto
            $settings.InstallationDeferral = $regData.InstallationDeferral
            $settings.SystemRestartDeferral = $regData.SystemRestartDeferral
            $settings.AdvancedDriverRestore = $regData.AdvancedDriverRestore
            $settings.LockSettings = $regData.LockSettings
            $settings.CatalogLocation = $regData.CatalogLocation
        }
        catch {
            Write-DriverLog -Message "Failed to read DCU settings: $($_.Exception.Message)" -Severity Warning
        }
    }
    
    return $settings
}

function Set-DCUSettings {
    <#
    .SYNOPSIS
        Configures Dell Command Update settings
    .DESCRIPTION
        Uses dcu-cli.exe to configure DCU settings.
    .PARAMETER UserConsent
        Enable or disable user consent prompts
    .PARAMETER AutoSuspendBitLocker
        Enable automatic BitLocker suspension for BIOS updates
    .PARAMETER AdvancedDriverRestore
        Enable driver restore points
    .PARAMETER ScheduleAction
        Schedule action: DownloadOnly, DownloadAndNotify, DownloadInstallAndNotify
    .PARAMETER InstallationDeferral
        Number of days to defer installation
    .PARAMETER SystemRestartDeferral
        Number of days to defer restart
    .EXAMPLE
        Set-DCUSettings -AutoSuspendBitLocker enable -AdvancedDriverRestore enable
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter()]
        [ValidateSet('enable', 'disable')]
        [string]$UserConsent,
        
        [Parameter()]
        [ValidateSet('enable', 'disable')]
        [string]$AutoSuspendBitLocker,
        
        [Parameter()]
        [ValidateSet('enable', 'disable')]
        [string]$AdvancedDriverRestore,
        
        [Parameter()]
        [ValidateSet('DownloadOnly', 'DownloadAndNotify', 'DownloadInstallAndNotify')]
        [string]$ScheduleAction,
        
        [Parameter()]
        [ValidateRange(0, 365)]
        [int]$InstallationDeferral,
        
        [Parameter()]
        [ValidateRange(0, 365)]
        [int]$SystemRestartDeferral
    )
    
    $dcuPath = Get-DellCommandUpdatePath
    if (-not $dcuPath) {
        throw "Dell Command Update is not installed"
    }
    
    $configArgs = @('/configure', '-silent')
    $changes = @()
    
    if ($UserConsent) {
        $configArgs += "-userConsent=$UserConsent"
        $changes += "UserConsent=$UserConsent"
    }
    
    if ($AutoSuspendBitLocker) {
        $configArgs += "-autoSuspendBitLocker=$AutoSuspendBitLocker"
        $changes += "AutoSuspendBitLocker=$AutoSuspendBitLocker"
    }
    
    if ($AdvancedDriverRestore) {
        $configArgs += "-advancedDriverRestore=$AdvancedDriverRestore"
        $changes += "AdvancedDriverRestore=$AdvancedDriverRestore"
    }
    
    if ($ScheduleAction) {
        $configArgs += "-scheduleAction=$ScheduleAction"
        $changes += "ScheduleAction=$ScheduleAction"
    }
    
    if ($PSBoundParameters.ContainsKey('InstallationDeferral')) {
        if ($ScheduleAction -eq 'DownloadInstallAndNotify' -or -not $ScheduleAction) {
            $configArgs += "-installationDeferral=$InstallationDeferral"
            $changes += "InstallationDeferral=$InstallationDeferral"
        }
        else {
            Write-DriverLog -Message "InstallationDeferral only applies to DownloadInstallAndNotify schedule action" -Severity Warning
        }
    }
    
    if ($PSBoundParameters.ContainsKey('SystemRestartDeferral')) {
        if ($ScheduleAction -eq 'DownloadInstallAndNotify' -or -not $ScheduleAction) {
            $configArgs += "-systemRestartDeferral=$SystemRestartDeferral"
            $changes += "SystemRestartDeferral=$SystemRestartDeferral"
        }
        else {
            Write-DriverLog -Message "SystemRestartDeferral only applies to DownloadInstallAndNotify schedule action" -Severity Warning
        }
    }
    
    if ($changes.Count -eq 0) {
        Write-DriverLog -Message "No settings specified to change" -Severity Warning
        return
    }
    
    if ($PSCmdlet.ShouldProcess("DCU Settings: $($changes -join ', ')", "Configure")) {
        Write-DriverLog -Message "Configuring DCU: $($changes -join ', ')" -Severity Info
        
        & $dcuPath @configArgs 2>&1 | Out-Null
        $exitCode = $LASTEXITCODE
        
        $exitInfo = Get-DCUExitInfo -ExitCode $exitCode
        
        if ($exitCode -eq 0) {
            Write-DriverLog -Message "DCU settings configured successfully" -Severity Info
        }
        else {
            Write-DriverLog -Message "DCU configuration returned: $($exitInfo.Description)" -Severity Warning
        }
        
        return Get-DCUSettings
    }
}

#endregion

#region Offline Catalog Support

function Get-DCUCatalogPath {
    <#
    .SYNOPSIS
        Gets the configured DCU catalog path
    .DESCRIPTION
        Returns the custom catalog path if configured, or the default Dell catalog location.
    .EXAMPLE
        Get-DCUCatalogPath
    #>

    [CmdletBinding()]
    param()
    
    # Check environment variable first
    if ($env:PSDM_DCU_CATALOG) {
        return $env:PSDM_DCU_CATALOG
    }
    
    # Check DCU settings
    $settings = Get-DCUSettings
    if ($settings.CatalogLocation) {
        return $settings.CatalogLocation
    }
    
    # Return default
    return $null  # DCU uses Dell's online catalog by default
}

function Set-DCUCatalogPath {
    <#
    .SYNOPSIS
        Sets a custom catalog path for Dell Command Update
    .DESCRIPTION
        Configures DCU to use a local or network catalog file instead of Dell's online catalog.
        Useful for offline environments or controlled update deployments.
    .PARAMETER CatalogPath
        Path to the local catalog XML file or network share
    .PARAMETER Reset
        Reset to use Dell's online catalog
    .EXAMPLE
        Set-DCUCatalogPath -CatalogPath 'C:\DCUCatalog\Catalog.xml'
    .EXAMPLE
        Set-DCUCatalogPath -CatalogPath '\\server\share\DCUCatalog\Catalog.xml'
    .EXAMPLE
        Set-DCUCatalogPath -Reset
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, ParameterSetName = 'SetPath')]
        [string]$CatalogPath,
        
        [Parameter(Mandatory, ParameterSetName = 'Reset')]
        [switch]$Reset
    )
    
    $dcuPath = Get-DellCommandUpdatePath
    if (-not $dcuPath) {
        throw "Dell Command Update is not installed"
    }
    
    if ($Reset) {
        if ($PSCmdlet.ShouldProcess("DCU catalog", "Reset to online")) {
            Write-DriverLog -Message "Resetting DCU to use online catalog" -Severity Info
            
            & $dcuPath /configure -catalogLocation= -silent 2>&1 | Out-Null
            
            Write-DriverLog -Message "DCU catalog reset to online" -Severity Info
        }
    }
    else {
        if (-not (Test-Path $CatalogPath)) {
            throw "Catalog path not found: $CatalogPath"
        }
        
        if ($PSCmdlet.ShouldProcess($CatalogPath, "Set as DCU catalog")) {
            Write-DriverLog -Message "Setting DCU catalog path: $CatalogPath" -Severity Info
            
            & $dcuPath /configure "-catalogLocation=$CatalogPath" -silent 2>&1 | Out-Null
            $exitCode = $LASTEXITCODE
            
            if ($exitCode -eq 0) {
                Write-DriverLog -Message "DCU catalog path configured" -Severity Info
            }
            else {
                $exitInfo = Get-DCUExitInfo -ExitCode $exitCode
                Write-DriverLog -Message "DCU catalog configuration: $($exitInfo.Description)" -Severity Warning
            }
        }
    }
}

function New-DCUOfflineCatalog {
    <#
    .SYNOPSIS
        Creates an offline Dell Command Update catalog
    .DESCRIPTION
        Downloads the Dell catalog and optionally driver packages for offline use.
        Rewrites the catalog base location for local paths.
    .PARAMETER OutputPath
        Directory to store the offline catalog and drivers
    .PARAMETER SystemID
        Specific system ID to create catalog for (default: current system)
    .PARAMETER IncludeDrivers
        Download driver packages along with catalog
    .PARAMETER DriverTypes
        Types of drivers to include: Driver, BIOS, Firmware, Application
    .EXAMPLE
        New-DCUOfflineCatalog -OutputPath 'C:\DCUOffline' -IncludeDrivers
    .EXAMPLE
        New-DCUOfflineCatalog -OutputPath '\\server\share\DCU' -SystemID '0A5C'
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$OutputPath,
        
        [Parameter()]
        [string]$SystemID,
        
        [Parameter()]
        [switch]$IncludeDrivers,
        
        [Parameter()]
        [ValidateSet('Driver', 'BIOS', 'Firmware', 'Application', 'All')]
        [string[]]$DriverTypes = @('Driver', 'BIOS', 'Firmware')
    )
    
    # Get system ID if not provided
    if (-not $SystemID) {
        $systemInfo = Get-CimInstance -ClassName Win32_ComputerSystem
        $SystemID = $systemInfo.SystemSKUNumber
        
        if (-not $SystemID) {
            throw "Could not determine system ID. Please provide -SystemID parameter."
        }
    }
    
    Write-DriverLog -Message "Creating offline catalog for SystemID: $SystemID" -Severity Info
    
    # Create output directory
    if (-not (Test-Path $OutputPath)) {
        New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
    }
    
    $catalogDir = Join-Path $OutputPath "Catalog"
    $driversDir = Join-Path $OutputPath "Drivers"
    
    if (-not (Test-Path $catalogDir)) {
        New-Item -Path $catalogDir -ItemType Directory -Force | Out-Null
    }
    
    # Download main catalog
    $cabPath = Join-Path $catalogDir "CatalogPC.cab"
    $extractPath = Join-Path $catalogDir "Extract"
    
    Write-DriverLog -Message "Downloading Dell catalog" -Severity Info
    
    Invoke-WebRequest -Uri "https://downloads.dell.com/catalog/CatalogPC.cab" -OutFile $cabPath -UseBasicParsing
    
    if (-not (Test-Path $extractPath)) {
        New-Item -Path $extractPath -ItemType Directory -Force | Out-Null
    }
    
    & expand.exe $cabPath -F:* $extractPath 2>&1 | Out-Null
    
    # Parse catalog
    $catalogXmlPath = Get-ChildItem $extractPath -Filter "*.xml" | Select-Object -First 1
    [xml]$catalog = Get-Content $catalogXmlPath.FullName
    
    # Filter for system
    $systemComponents = $catalog.Manifest.SoftwareComponent | Where-Object {
        $_.SupportedSystems.Brand.Model.systemID -eq $SystemID -or
        $_.SupportedSystems.Brand.Model.systemID -contains $SystemID
    }
    
    Write-DriverLog -Message "Found $($systemComponents.Count) components for system" -Severity Info
    
    # Download drivers if requested
    $downloadedFiles = @()
    
    if ($IncludeDrivers -and $systemComponents) {
        if (-not (Test-Path $driversDir)) {
            New-Item -Path $driversDir -ItemType Directory -Force | Out-Null
        }
        
        $filteredComponents = $systemComponents | Where-Object {
            $type = $_.ComponentType.Display.'#cdata-section'
            'All' -in $DriverTypes -or 
            ($type -like '*Driver*' -and 'Driver' -in $DriverTypes) -or
            ($type -like '*BIOS*' -and 'BIOS' -in $DriverTypes) -or
            ($type -like '*Firmware*' -and 'Firmware' -in $DriverTypes) -or
            ($type -like '*Application*' -and 'Application' -in $DriverTypes)
        }
        
        Write-DriverLog -Message "Downloading $($filteredComponents.Count) driver packages" -Severity Info
        
        $count = 0
        foreach ($component in $filteredComponents) {
            $count++
            $downloadUrl = "https://downloads.dell.com/$($component.path)"
            $fileName = Split-Path $component.path -Leaf
            $localPath = Join-Path $driversDir $fileName
            
            Write-Progress -Activity "Downloading drivers" -Status "$count of $($filteredComponents.Count): $fileName" -PercentComplete (($count / $filteredComponents.Count) * 100)
            
            try {
                if (-not (Test-Path $localPath)) {
                    Invoke-WebRequest -Uri $downloadUrl -OutFile $localPath -UseBasicParsing -ErrorAction Stop
                }
                $downloadedFiles += $fileName
            }
            catch {
                Write-DriverLog -Message "Failed to download $fileName`: $($_.Exception.Message)" -Severity Warning
            }
        }
        
        Write-Progress -Activity "Downloading drivers" -Completed
    }
    
    # Create modified catalog with local paths
    $offlineCatalogPath = Join-Path $catalogDir "OfflineCatalog_$SystemID.xml"
    
    # Modify base location in catalog
    $catalogContent = Get-Content $catalogXmlPath.FullName -Raw
    $catalogContent = $catalogContent -replace 'downloads\.dell\.com', $OutputPath.Replace('\', '/')
    $catalogContent | Set-Content -Path $offlineCatalogPath -Encoding UTF8
    
    $result = [PSCustomObject]@{
        SystemID = $SystemID
        CatalogPath = $offlineCatalogPath
        DriversPath = $driversDir
        ComponentCount = $systemComponents.Count
        DownloadedFiles = $downloadedFiles.Count
        OutputPath = $OutputPath
    }
    
    Write-DriverLog -Message "Offline catalog created: $offlineCatalogPath" -Severity Info `
        -Context @{ SystemID = $SystemID; Components = $systemComponents.Count }
    
    return $result
}

#endregion

#region Core Functions

function Initialize-DellModule {
    <#
    .SYNOPSIS
        Ensures Dell Command Update is available
    .DESCRIPTION
        Checks if Dell Command Update is installed. If not, automatically
        downloads and installs it from Dell's website.
    .OUTPUTS
        Path to dcu-cli.exe
    .EXAMPLE
        $dcuPath = Initialize-DellModule
    #>

    [CmdletBinding()]
    param()
    
    $dcuPath = Get-DellCommandUpdatePath
    
    if (-not $dcuPath) {
        Write-DriverLog -Message "Dell Command Update not found, installing..." -Severity Info
        
        try {
            Install-DellCommandUpdate
            
            # Wait a moment for installation to complete
            Start-Sleep -Seconds 2
            
            # Re-check for DCU
            $dcuPath = Get-DellCommandUpdatePath
            
            if (-not $dcuPath) {
                throw "Dell Command Update installation completed but dcu-cli.exe not found"
            }
            
            Write-DriverLog -Message "Dell Command Update ready at: $dcuPath" -Severity Info
        }
        catch {
            Write-DriverLog -Message "Failed to initialize Dell Command Update: $($_.Exception.Message)" -Severity Error
            throw "Dell Command Update could not be installed: $($_.Exception.Message)"
        }
    }
    
    return $dcuPath
}

function Get-DellDriverUpdates {
    <#
    .SYNOPSIS
        Scans for available Dell driver updates
    .DESCRIPTION
        Uses Dell Command Update to scan for applicable updates
    .PARAMETER UpdateTypes
        Types of updates to scan for: Driver, BIOS, Firmware, All
    .PARAMETER Severity
        Severity levels: Critical, Recommended, Optional
    .EXAMPLE
        Get-DellDriverUpdates -UpdateTypes Driver -Severity Critical, Recommended
    .OUTPUTS
        Array of available update objects
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('Driver', 'BIOS', 'Firmware', 'All')]
        [string[]]$UpdateTypes = @('Driver'),
        
        [Parameter()]
        [ValidateSet('Critical', 'Recommended', 'Optional')]
        [string[]]$Severity = @('Critical', 'Recommended')
    )
    
    try {
        $dcuCli = Initialize-DellModule
    }
    catch {
        Write-DriverLog -Message "Dell Command Update not available: $($_.Exception.Message)" -Severity Warning
        return @()
    }
    
    $reportPath = "$env:ProgramData\Dell\UpdateScan"
    if (-not (Test-Path $reportPath)) {
        New-Item -Path $reportPath -ItemType Directory -Force | Out-Null
    }
    
    Write-DriverLog -Message "Scanning for Dell updates" -Severity Info
    
    # Run scan
    $scanArgs = @('/scan', '-silent', "-report=$reportPath")
    $scanResult = & $dcuCli @scanArgs 2>&1
    $scanExitCode = $LASTEXITCODE
    
    # Log exit code info
    $exitInfo = Get-DCUExitInfo -ExitCode $scanExitCode
    Write-DriverLog -Message "DCU scan completed: $($exitInfo.Description)" -Severity Info
    
    # Parse results
    $xmlPath = Join-Path $reportPath "DCUApplicableUpdates.xml"
    if (-not (Test-Path $xmlPath)) {
        Write-DriverLog -Message "No updates report generated" -Severity Info
        return @()
    }
    
    [xml]$updatesXml = Get-Content $xmlPath
    
    $updates = $updatesXml.updates.update | Where-Object {
        $typeMatch = switch ($_.type) {
            'Driver' { 'Driver' -in $UpdateTypes -or 'All' -in $UpdateTypes }
            'BIOS' { 'BIOS' -in $UpdateTypes -or 'All' -in $UpdateTypes }
            'Firmware' { 'Firmware' -in $UpdateTypes -or 'All' -in $UpdateTypes }
            default { 'All' -in $UpdateTypes }
        }
        $typeMatch
    } | ForEach-Object {
        [PSCustomObject]@{
            Name = $_.name
            Version = $_.version
            Type = $_.type
            Category = $_.category
            Urgency = $_.urgency
            ReleaseDate = $_.date
            Size = $_.size
            Description = $_.description
        }
    }
    
    Write-DriverLog -Message "Found $($updates.Count) Dell updates" -Severity Info `
        -Context @{ Updates = ($updates | Select-Object Name, Version, Type) }
    
    return $updates
}

function Install-DellDriverUpdates {
    <#
    .SYNOPSIS
        Installs Dell driver updates
    .DESCRIPTION
        Uses Dell Command Update to install applicable updates
    .PARAMETER UpdateTypes
        Types of updates to install
    .PARAMETER Severity
        Severity levels to include
    .PARAMETER NoReboot
        Suppress automatic reboot
    .EXAMPLE
        Install-DellDriverUpdates -UpdateTypes Driver -NoReboot
    .OUTPUTS
        DriverUpdateResult object
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType('DriverUpdateResult')]
    param(
        [Parameter()]
        [ValidateSet('Driver', 'BIOS', 'Firmware', 'All')]
        [string[]]$UpdateTypes = @('Driver'),
        
        [Parameter()]
        [ValidateSet('Critical', 'Recommended', 'Optional')]
        [string[]]$Severity = @('Critical', 'Recommended'),
        
        [Parameter()]
        [switch]$NoReboot
    )
    
    Assert-Elevation -Operation "Installing Dell drivers"
    
    $result = [DriverUpdateResult]::new()
    $result.CorrelationId = $script:CorrelationId
    
    try {
        $dcuCli = Initialize-DellModule
    }
    catch {
        $result.Success = $false
        $result.Message = "Dell Command Update not available: $($_.Exception.Message)"
        $result.ExitCode = 1
        Write-DriverLog -Message $result.Message -Severity Error
        return $result
    }
    
    # Configure DCU for silent operation
    $configArgs = @('/configure', '-userConsent=disable', '-autoSuspendBitLocker=enable', '-silent')
    & $dcuCli @configArgs 2>&1 | Out-Null
    
    # Map update types
    $typeParam = ($UpdateTypes | ForEach-Object { $_.ToLower() }) -join ','
    if ('All' -in $UpdateTypes) { $typeParam = 'driver,bios,firmware,application' }
    
    # Build apply command
    $applyArgs = @(
        '/applyUpdates'
        "-updateType=$typeParam"
        '-updateSeverity=security,critical,recommended'
        '-autoSuspendBitLocker=enable'
        '-silent'
        "-outputLog=$env:ProgramData\Dell\Logs\DCU_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
    )
    
    if ($NoReboot) {
        $applyArgs += '-reboot=disable'
    }
    
    if ($PSCmdlet.ShouldProcess("Dell drivers", "Install updates")) {
        Write-DriverLog -Message "Installing Dell updates: $typeParam" -Severity Info
        
        $applyResult = & $dcuCli @applyArgs 2>&1
        $exitCode = $LASTEXITCODE
        
        # Get detailed exit info
        $exitInfo = Get-DCUExitInfo -ExitCode $exitCode
        
        # Interpret exit codes
        switch ($exitCode) {
            0 {
                $result.Success = $true
                $result.Message = "Updates applied successfully"
                $result.RebootRequired = $false
            }
            1 {
                $result.Success = $true
                $result.Message = "Updates applied - reboot required"
                $result.RebootRequired = $true
            }
            { $_ -in @(500, 18) } {
                $result.Success = $true
                $result.Message = "No applicable updates"
                $result.RebootRequired = $false
            }
            default {
                $result.Success = $false
                $result.Message = "$($exitInfo.Description) - $($exitInfo.Resolution)"
                $result.RebootRequired = $false
            }
        }
        
        $result.ExitCode = if ($result.RebootRequired) { 3010 } elseif ($result.Success) { 0 } else { 1 }
        $result.Details = @{ DCUExitCode = $exitCode; DCUExitInfo = $exitInfo }
        
        Write-DriverLog -Message "Dell update complete: $($result.Message)" -Severity Info `
            -Context $result.ToHashtable()
    }
    
    return $result
}

function Install-DellFullDriverPack {
    <#
    .SYNOPSIS
        Installs the complete Dell driver pack
    .DESCRIPTION
        Performs a full driver reinstallation using Dell Command Update
    .PARAMETER NoReboot
        Suppress automatic reboot
    .EXAMPLE
        Install-DellFullDriverPack -NoReboot
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType('DriverUpdateResult')]
    param(
        [Parameter()]
        [switch]$NoReboot
    )
    
    Assert-Elevation -Operation "Installing Dell full driver pack"
    
    $result = [DriverUpdateResult]::new()
    $result.CorrelationId = $script:CorrelationId
    
    try {
        $dcuCli = Initialize-DellModule
    }
    catch {
        $result.Success = $false
        $result.Message = "Dell Command Update not available: $($_.Exception.Message)"
        $result.ExitCode = 1
        Write-DriverLog -Message $result.Message -Severity Error
        return $result
    }
    
    if ($PSCmdlet.ShouldProcess("Dell full driver pack", "Install")) {
        Write-DriverLog -Message "Starting Dell full driver pack install" -Severity Info
        
        $installArgs = @(
            '/driverInstall'
            '-autoSuspendBitLocker=enable'
            '-silent'
        )
        
        if ($NoReboot) {
            $installArgs += '-reboot=disable'
        }
        
        & $dcuCli @installArgs 2>&1 | Out-Null
        $exitCode = $LASTEXITCODE
        
        $exitInfo = Get-DCUExitInfo -ExitCode $exitCode
        
        $result.Success = $exitCode -in @(0, 1, 500, 18)
        $result.Message = "$($exitInfo.Description)"
        $result.RebootRequired = $exitCode -eq 1
        $result.ExitCode = if ($exitCode -eq 1) { 3010 } elseif ($exitCode -in @(0, 500, 18)) { 0 } else { 1 }
        $result.Details = @{ DCUExitCode = $exitCode; DCUExitInfo = $exitInfo }
        
        Write-DriverLog -Message $result.Message -Severity Info -Context $result.ToHashtable()
    }
    
    return $result
}

#endregion