public/Save-LSUpdate.ps1

function Save-LSUpdate {
    <#
        .SYNOPSIS
        Downloads a Lenovo update package to disk
 
        .PARAMETER Package
        The Lenovo package or packages to download
 
        .PARAMETER Proxy
        Specifies a proxy server for the connection to Lenovo. Enter the URI of a network proxy server.
 
        .PARAMETER ProxyCredential
        Specifies a user account that has permission to use the proxy server that is specified by the -Proxy
        parameter.
 
        .PARAMETER ProxyUseDefaultCredentials
        Indicates that the cmdlet uses the credentials of the current user to access the proxy server that is
        specified by the -Proxy parameter.
 
        .PARAMETER ShowProgress
        Shows a progress animation during the downloading process, recommended for interactive use
        as downloads can be quite large and without any progress output the script may appear stuck
 
        .PARAMETER Force
        Redownload and overwrite packages even if they have already been downloaded previously
 
        .PARAMETER Path
        The target directory to which to download the packages to. In this directory,
        a subfolder will be created for each downloaded package.
    #>


    [CmdletBinding()]
    Param (
        [Parameter( Position = 0, ValueFromPipeline = $true, Mandatory = $true )]
        [pscustomobject]$Package,
        [Uri]$Proxy,
        [pscredential]$ProxyCredential,
        [switch]$ProxyUseDefaultCredentials,
        [switch]$ShowProgress,
        [switch]$Force,
        [System.IO.DirectoryInfo]$Path = "$env:TEMP\LSUPackages"
    )

    begin {
        if ($PSBoundParameters['Debug'] -and $DebugPreference -eq 'Inquire') {
            Write-Verbose "Adjusting the DebugPreference to 'Continue'."
            $DebugPreference = 'Continue'
        }
        $transfers = [System.Collections.Generic.List[System.Threading.Tasks.Task]]::new()
    }

    process {
        foreach ($PackageToGet in $Package) {
            $DownloadDirectory = Join-Path -Path $Path -ChildPath $PackageToGet.id

            if (-not (Test-Path -Path $DownloadDirectory -PathType Container)) {
                Write-Verbose "Destination directory did not exist, created it: '$DownloadDirectory'"
                $null = New-Item -Path $DownloadDirectory -Force -ItemType Directory
            }

            $PackageUrlRoot = $PackageToGet.URL -replace "[^/]*$"

            # The packages XML file
            $DownloadSrc  = $PackageToGet.URL.AbsoluteUri
            $DownloadDest = Join-Path -Path $DownloadDirectory -ChildPath ($DownloadSrc -replace "^.*/")
            $webClient = New-WebClient -Proxy $Proxy -ProxyCredential $ProxyCredential -ProxyUseDefaultCredentials $ProxyUseDefaultCredentials
            $transfers.Add( $webClient.DownloadFileTaskAsync($DownloadSrc, $DownloadDest) )

            # Installer and other files
            foreach ($file in $PackageToGet.Files) {
                $DownloadSrc  = [String]::Concat($PackageUrlRoot, $file.Name)
                $DownloadDest = Join-Path -Path $DownloadDirectory -ChildPath $file.Name

                if ($Force -or -not (Test-Path -Path $DownloadDest -PathType Leaf) -or (
                   (Get-FileHash -Path $DownloadDest -Algorithm SHA256).Hash -ne $file.CRC)) {
                    # Checking if this package was already downloaded, if yes skipping redownload
                    $webClient = New-WebClient -Proxy $Proxy -ProxyCredential $ProxyCredential -ProxyUseDefaultCredentials $ProxyUseDefaultCredentials
                    Write-Verbose "Starting download of '$DownloadSrc'"
                    $transfers.Add( $webClient.DownloadFileTaskAsync($DownloadSrc, $DownloadDest) )
                }
            }
        }
    }

    end {
        if ($ShowProgress -and $transfers) {
            Show-DownloadProgress -Transfers $transfers
        } else {
            while ($transfers.IsCompleted -contains $false) {
                Start-Sleep -Milliseconds 500
            }
        }

        if ($transfers.Status -contains "Faulted" -or $transfers.Status -contains "Canceled") {
            $errorString = "Not all packages could be downloaded, the following errors were encountered:"
            foreach ($transfer in $transfers.Where{ $_.Status -in "Faulted", "Canceled"}) {
                $errorString += "`r`n$($transfer.AsyncState.AbsoluteUri) : $($transfer.Exception.InnerExceptions.Message)"
            }
            Write-Error $errorString
        }

        foreach ($webClient in $transfers) {
            $webClient.Dispose()
        }
    }
}