Public/Lenovo.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    Lenovo driver management functions
#>


function Initialize-LenovoModule {
    <#
    .SYNOPSIS
        Ensures LSUClient module is available
    #>

    [CmdletBinding()]
    param()
    
    if (-not (Get-Module -ListAvailable -Name LSUClient)) {
        Write-DriverLog -Message "Installing LSUClient module" -Severity Info
        
        # Try to install from PSGallery
        try {
            Install-Module -Name LSUClient -Force -Scope AllUsers -ErrorAction Stop
        }
        catch {
            Write-DriverLog -Message "Failed to install LSUClient: $($_.Exception.Message)" -Severity Warning
            throw
        }
    }
    
    Import-Module LSUClient -Force -ErrorAction Stop
}

function Get-LenovoDriverUpdates {
    <#
    .SYNOPSIS
        Scans for available Lenovo driver updates
    .DESCRIPTION
        Uses LSUClient module to scan for applicable updates
    .PARAMETER UpdateTypes
        Types of updates to scan for
    .PARAMETER UnattendedOnly
        Only return updates that support unattended installation
    .EXAMPLE
        Get-LenovoDriverUpdates -UpdateTypes Driver
    .OUTPUTS
        Array of available update objects
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('Driver', 'BIOS', 'Firmware', 'All')]
        [string[]]$UpdateTypes = @('Driver'),
        
        [Parameter()]
        [switch]$UnattendedOnly = $true
    )
    
    try {
        Initialize-LenovoModule
    }
    catch {
        Write-DriverLog -Message "LSUClient not available" -Severity Warning
        return @()
    }
    
    $oemInfo = Get-OEMInfo
    Write-DriverLog -Message "Scanning for Lenovo updates - MTM: $($oemInfo.MTM)" -Severity Info
    
    # Get all updates
    $allUpdates = Get-LSUpdate
    
    # Filter for unattended
    if ($UnattendedOnly) {
        $allUpdates = $allUpdates | Where-Object { $_.Installer.Unattended }
    }
    
    # Filter by type
    $typeMap = @{
        'Driver' = 'Driver'
        'BIOS' = 'BIOS'
        'Firmware' = 'Firmware'
    }
    
    $filteredUpdates = $allUpdates | Where-Object {
        if ('All' -in $UpdateTypes) { return $true }
        $_.Type -in ($UpdateTypes | ForEach-Object { $typeMap[$_] })
    }
    
    $updates = $filteredUpdates | ForEach-Object {
        [PSCustomObject]@{
            ID = $_.ID
            Title = $_.Title
            Version = $_.Version
            Type = $_.Type
            Severity = $_.Severity
            RebootType = $_.Reboot.Type
            Size = $_.Size
            Unattended = $_.Installer.Unattended
        }
    }
    
    Write-DriverLog -Message "Found $($updates.Count) Lenovo updates" -Severity Info `
        -Context @{ Updates = ($updates | Select-Object Title, Version, Type) }
    
    return $updates
}

function Install-LenovoDriverUpdates {
    <#
    .SYNOPSIS
        Installs Lenovo driver updates
    .DESCRIPTION
        Uses LSUClient to install applicable updates
    .PARAMETER UpdateTypes
        Types of updates to install
    .PARAMETER Severity
        Severity levels to include
    .PARAMETER NoReboot
        Suppress automatic reboot
    .EXAMPLE
        Install-LenovoDriverUpdates -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 Lenovo drivers"
    
    $result = [DriverUpdateResult]::new()
    $result.CorrelationId = $script:CorrelationId
    
    try {
        Initialize-LenovoModule
    }
    catch {
        # Fallback to Thin Installer
        Write-DriverLog -Message "LSUClient unavailable, using Thin Installer fallback" -Severity Warning
        return Install-LenovoThinInstallerFallback -UpdateTypes $UpdateTypes -NoReboot:$NoReboot
    }
    
    # Get applicable updates
    $updates = Get-LSUpdate | Where-Object { 
        $_.Installer.Unattended -and
        (('All' -in $UpdateTypes) -or ($_.Type -in $UpdateTypes)) -and
        ($_.Severity -in $Severity)
    }
    
    if (-not $updates) {
        $result.Success = $true
        $result.Message = "No applicable updates"
        $result.ExitCode = 0
        Write-DriverLog -Message $result.Message -Severity Info
        return $result
    }
    
    Write-DriverLog -Message "Installing $($updates.Count) Lenovo updates" -Severity Info
    
    $rebootRequired = $false
    $successCount = 0
    $failCount = 0
    $details = @()
    
    if ($PSCmdlet.ShouldProcess("$($updates.Count) Lenovo updates", "Install")) {
        foreach ($update in $updates) {
            Write-DriverLog -Message "Installing: $($update.Title) v$($update.Version)" -Severity Info
            
            try {
                $installResult = Invoke-WithRetry -ScriptBlock {
                    Install-LSUpdate -Package $update -Verbose
                } -MaxAttempts 3 -ExponentialBackoff
                
                $successCount++
                $details += @{
                    Title = $update.Title
                    Version = $update.Version
                    Success = $true
                }
                
                if ($update.Reboot.Required -or $installResult.ExitCode -in @(1, 3, 3010)) {
                    $rebootRequired = $true
                }
            }
            catch {
                $failCount++
                $details += @{
                    Title = $update.Title
                    Version = $update.Version
                    Success = $false
                    Error = $_.Exception.Message
                }
                Write-DriverLog -Message "Failed to install $($update.Title): $($_.Exception.Message)" -Severity Warning
            }
        }
    }
    
    $result.Success = $successCount -gt 0
    $result.Message = "Installed $successCount of $($updates.Count) updates"
    $result.UpdatesApplied = $successCount
    $result.UpdatesFailed = $failCount
    $result.RebootRequired = $rebootRequired
    $result.ExitCode = if ($rebootRequired) { 3010 } elseif ($result.Success) { 0 } else { 1 }
    $result.Details = @{ Updates = $details }
    
    Write-DriverLog -Message $result.Message -Severity Info -Context $result.ToHashtable()
    
    return $result
}

function Install-LenovoThinInstallerFallback {
    <#
    .SYNOPSIS
        Fallback to Thin Installer when LSUClient unavailable
    #>

    [CmdletBinding()]
    param(
        [string[]]$UpdateTypes,
        [switch]$NoReboot
    )
    
    $result = [DriverUpdateResult]::new()
    $result.CorrelationId = $script:CorrelationId
    
    $thinInstaller = "${env:ProgramFiles(x86)}\Lenovo\ThinInstaller\ThinInstaller.exe"
    if (-not (Test-Path $thinInstaller)) {
        $result.Success = $false
        $result.Message = "No Lenovo tools available"
        $result.ExitCode = 1
        return $result
    }
    
    # Map update types (1=App, 2=Driver, 3=BIOS, 4=Firmware)
    $packageTypes = @()
    if ('Driver' -in $UpdateTypes -or 'All' -in $UpdateTypes) { $packageTypes += '2' }
    if ('BIOS' -in $UpdateTypes -or 'All' -in $UpdateTypes) { $packageTypes += '3' }
    if ('Firmware' -in $UpdateTypes -or 'All' -in $UpdateTypes) { $packageTypes += '4' }
    
    $args = @(
        '/CM'
        '-search', 'R'
        '-action', 'INSTALL'
        '-packagetypes', ($packageTypes -join ',')
        '-includerebootpackages', '0,3'
        '-noicon'
        '-nolicense'
        '-exporttowmi'
    )
    
    if ($NoReboot) {
        $args += '-noreboot'
    }
    
    Write-DriverLog -Message "Running Thin Installer fallback" -Severity Info
    
    $process = Start-Process -FilePath $thinInstaller -ArgumentList $args -Wait -PassThru -NoNewWindow
    
    $result.Success = $process.ExitCode -in @(0, 1, 3)
    $result.Message = "Thin Installer exit code: $($process.ExitCode)"
    $result.RebootRequired = $process.ExitCode -in @(1, 3)
    $result.ExitCode = if ($process.ExitCode -in @(1, 3)) { 3010 } elseif ($process.ExitCode -eq 0) { 0 } else { 1 }
    
    Write-DriverLog -Message $result.Message -Severity Info -Context $result.ToHashtable()
    
    return $result
}

function Install-LenovoFullDriverPack {
    <#
    .SYNOPSIS
        Installs the complete Lenovo driver pack
    .DESCRIPTION
        Downloads and installs all applicable drivers
    .PARAMETER NoReboot
        Suppress automatic reboot
    .EXAMPLE
        Install-LenovoFullDriverPack -NoReboot
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType('DriverUpdateResult')]
    param(
        [Parameter()]
        [switch]$NoReboot
    )
    
    Assert-Elevation -Operation "Installing Lenovo full driver pack"
    
    $result = [DriverUpdateResult]::new()
    $result.CorrelationId = $script:CorrelationId
    
    try {
        Initialize-LenovoModule
        
        $oemInfo = Get-OEMInfo
        Write-DriverLog -Message "Installing Lenovo full driver pack - MTM: $($oemInfo.MTM)" -Severity Info
        
        # Get ALL updates
        $allUpdates = Get-LSUpdate -All | Where-Object { $_.Installer.Unattended }
        
        if ($PSCmdlet.ShouldProcess("$($allUpdates.Count) Lenovo packages", "Install full driver pack")) {
            Write-DriverLog -Message "Installing $($allUpdates.Count) packages" -Severity Info
            
            # Download all
            $downloadPath = "$env:TEMP\LenovoDriverPack"
            $allUpdates | Save-LSUpdate -Path $downloadPath -ShowProgress
            
            # Install all
            $installResults = $allUpdates | Install-LSUpdate -Verbose
            
            $result.Success = $true
            $result.Message = "Full pack install complete"
            $result.UpdatesApplied = $allUpdates.Count
            $result.RebootRequired = $true  # Always recommend reboot for full pack
            $result.ExitCode = 3010
        }
    }
    catch {
        Write-DriverLog -Message "Full pack install failed: $($_.Exception.Message)" -Severity Error
        return Install-LenovoThinInstallerFallback -UpdateTypes @('All') -NoReboot:$NoReboot
    }
    
    Write-DriverLog -Message $result.Message -Severity Info -Context $result.ToHashtable()
    
    return $result
}