public/Install-LSUpdate.ps1

function Install-LSUpdate {
    <#
        .SYNOPSIS
        Installs a Lenovo update package. Downloads it if not previously downloaded.
 
        .PARAMETER Package
        The Lenovo package object to install
 
        .PARAMETER Path
        If you previously downloaded the Lenovo package to a custom directory, specify its path here so that the package can be found
 
        .PARAMETER SaveBIOSUpdateInfoToRegistry
        If a BIOS update is successfully installed, write information about it to 'HKLM\Software\LSUClient\BIOSUpdate'.
        This is useful in automated deployment scenarios, especially the 'ActionNeeded' key which will tell you whether a shutdown or reboot is required to apply the BIOS update.
        The created registry values will not be deleted by this module, only overwritten on the next installed BIOS Update.
    #>


    [CmdletBinding()]
    [OutputType('PackageInstallResult')]
    Param (
        [Parameter( Position = 0, ValueFromPipeline = $true, Mandatory = $true )]
        [pscustomobject]$Package,
        [ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
        [System.IO.DirectoryInfo]$Path = "$env:TEMP\LSUPackages",
        [switch]$SaveBIOSUpdateInfoToRegistry,
        [Uri]$Proxy,
        [pscredential]$ProxyCredential,
        [switch]$ProxyUseDefaultCredentials
    )

    begin {
        if ($PSBoundParameters['Debug'] -and $DebugPreference -eq 'Inquire') {
            Write-Verbose "Adjusting the DebugPreference to 'Continue'."
            $DebugPreference = 'Continue'
        }
    }

    process {
        foreach ($PackageToProcess in $Package) {
            $Extracter = $PackageToProcess.Files | Where-Object { $_.Kind -eq 'Installer' }
            $PackageDirectory = Join-Path -Path $Path -ChildPath $PackageToProcess.ID
            if (-not (Test-Path -LiteralPath $PackageDirectory -PathType Container)) {
                $null = New-Item -Path $PackageDirectory -Force -ItemType Directory
            }

            $SpfParams = @{
                'SourceFile' = $Extracter
                'Directory' = $PackageDirectory
                'Proxy' = $Proxy
                'ProxyCredential' = $ProxyCredential
                'ProxyUseDefaultCredentials' = $ProxyUseDefaultCredentials
            }
            $FullPath = Save-PackageFile @SpfParams
            if (-not $FullPath) {
                Write-Error "The installer of package '$($PackageToProcess.ID)' could not be accessed or found and will be skipped"
                continue
            }

            Expand-LSUpdate -Package $PackageToProcess -WorkingDirectory $PackageDirectory

            Write-Verbose "Installing package $($PackageToProcess.ID) ..."

            switch ($PackageToProcess.Installer.InstallType) {
                'CMD' {
                    # Special-case ThinkPad and ThinkCentre (winuptp.exe and Flash.cmd/wflash2.exe)
                    # BIOS updates because we can install them silently and unattended with custom arguments
                    # Other BIOS updates are not classified as unattended and will be treated like any other package.
                    if ($PackageToProcess.Installer.Command -match 'winuptp\.exe|Flash\.cmd') {
                        # We are dealing with a known kind of BIOS Update
                        $installProcess = Install-BiosUpdate -PackageDirectory $PackageDirectory
                    } else {
                        # Correct typo from Lenovo ... yes really...
                        $InstallCMD = $PackageToProcess.Installer.Command -replace '-overwirte', '-overwrite'
                        $installProcess = Invoke-PackageCommand -Path $PackageDirectory -Command $InstallCMD
                    }

                    $Success = $installProcess.Err -eq [ExternalProcessError]::NONE -and $(
                        if ($installProcess.Info -is [BiosUpdateInfo] -and $null -ne $installProcess.Info.SuccessOverrideValue) {
                            $installProcess.Info.SuccessOverrideValue
                        } else {
                            $installProcess.Info.ExitCode -in $PackageToProcess.Installer.SuccessCodes
                        }
                    )

                    $PendingAction = if (-not $Success) {
                        'NONE'
                    } elseif ($installProcess.Info -is [BiosUpdateInfo]) {
                        if ($installProcess.Info.ActionNeeded -eq 'SHUTDOWN') {
                            'SHUTDOWN'
                        } elseif ($installProcess.Info.ActionNeeded -eq 'REBOOT') {
                            'REBOOT_MANDATORY'
                        }
                    } elseif ($PackageToProcess.RebootType -eq 0) {
                        'NONE'
                    } elseif ($PackageToProcess.RebootType -eq 3) {
                        'REBOOT_SUGGESTED'
                    } elseif ($PackageToProcess.RebootType -eq 5) {
                        'REBOOT_MANDATORY'
                    }

                    [PackageInstallResult]@{
                        ID             = $PackageToProcess.ID
                        Title          = $PackageToProcess.Title
                        Type           = $PackageToProcess.Type
                        Success        = $Success
                        FailureReason  = if ($installProcess.Err) { "$($installProcess.Err)" } elseif (-not $Success) { 'INSTALLER_EXITCODE' } else { '' }
                        PendingAction  = $PendingAction
                        ExitCode       = $installProcess.Info.ExitCode
                        StandardOutput = $installProcess.Info.StandardOutput
                        StandardError  = $installProcess.Info.StandardError
                        LogOutput      = if ($installProcess.Info -is [BiosUpdateInfo]) { $installProcess.Info.LogMessage } else { '' }
                        Runtime        = if ($installProcess.Err) { [TimeSpan]::Zero } else { $installProcess.Info.Runtime }
                    }

                    # Extra handling for BIOS updates
                    if ($installProcess.Info -is [BiosUpdateInfo]) {
                        if ($Success) {
                            # BIOS Update successful
                            Write-Information -MessageData "BIOS UPDATE SUCCESS: An immediate full $($installProcess.Info.ActionNeeded) is strongly recommended to allow the BIOS update to complete!" -InformationAction Continue
                            if ($SaveBIOSUpdateInfoToRegistry) {
                                Set-BIOSUpdateRegistryFlag -Timestamp $installProcess.Info.Timestamp -ActionNeeded $installProcess.Info.ActionNeeded -PackageHash $Extracter.Checksum
                            }
                        }
                    }
                }
                'INF' {
                    $InfSuccessCodes = @(0, 3010) + $PackageToProcess.Installer.SuccessCodes
                    $InstallCMD = "${env:SystemRoot}\system32\pnputil.exe /add-driver $($PackageToProcess.Installer.InfFile) /install"
                    $installProcess = Invoke-PackageCommand -Path $PackageDirectory -Command $InstallCMD

                    $Success = $installProcess.Err -eq [ExternalProcessError]::NONE -and $installProcess.Info.ExitCode -in $InfSuccessCodes

                    [PackageInstallResult]@{
                        ID             = $PackageToProcess.ID
                        Title          = $PackageToProcess.Title
                        Type           = $PackageToProcess.Type
                        Success        = $Success
                        FailureReason  = if ($installProcess.Err) { "$($installProcess.Err)" } elseif (-not $Success) { 'INSTALLER_EXITCODE' } else { '' }
                        PendingAction  = if ($Success -and $installProcess.Info.ExitCode -eq 3010) { 'REBOOT_SUGGESTED' } else { 'NONE' }
                        ExitCode       = $installProcess.Info.ExitCode
                        StandardOutput = $installProcess.Info.StandardOutput
                        StandardError  = $installProcess.Info.StandardError
                        LogOutput      = ''
                        Runtime        = if ($installProcess.Err) { [TimeSpan]::Zero } else { $installProcess.Info.Runtime }
                    }
                }
                default {
                    Write-Warning "Unsupported package installtype '$_', skipping installation!"
                }
            }
        }
    }
}