public/Save-LSUpdate.ps1
function Save-LSUpdate { <# .SYNOPSIS Downloads Lenovo update packages to disk .DESCRIPTION Downloads Lenovo update packages to disk .PARAMETER Package The Lenovo package or packages to download .PARAMETER Proxy Specifies the URL of a proxy server to use for the connection to the update repository. .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 the files already exist in the target path. .PARAMETER Path The target directory to download the packages to. In this directory, a subfolder will be created for each downloaded package containing its files. #> [CmdletBinding()] Param ( [Parameter( Position = 0, ValueFromPipeline = $true, Mandatory = $true )] [pscustomobject]$Package, [Uri]$Proxy = $script:LSUClientConfiguration.Proxy, [pscredential]$ProxyCredential = $script:LSUClientConfiguration.ProxyCredential, [switch]$ProxyUseDefaultCredentials = $script:LSUClientConfiguration.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) { Write-Verbose "Processing package '$($PackageToGet.ID) - $($PackageToGet.Title)'" $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 } # Ensure all the packages' files are present locally, download if not foreach ($File in $PackageToGet.Files) { $DownloadSrc = $File.AbsoluteLocation $DownloadDest = Join-Path -Path $DownloadDirectory -ChildPath $File.Name Write-Debug "Testing whether PkgFile ${DownloadDest} is already cached, downloading if not" if ($Force -or -not (Test-Path -Path $DownloadDest -PathType Leaf) -or ( (Get-FileHash -Path $DownloadDest -Algorithm SHA256).Hash -ne $File.Checksum)) { # 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 { $DownloadTimer = [System.Diagnostics.Stopwatch]::StartNew() [TimeSpan]$LastPrinted = [TimeSpan]::FromMinutes(9) while ($transfers.IsCompleted -contains $false) { # Print message once every minute after an initial 10 minutes of silence if ($DownloadTimer.Elapsed - $LastPrinted -ge [TimeSpan]::FromMinutes(1)) { [array]$PendingDownloads = @($transfers | Where-Object IsCompleted -eq $false) Write-Warning "Downloads have been running for $($DownloadTimer.Elapsed) - $($PendingDownloads.Count) remaining:" $PendingDownloads.AsyncState | ForEach-Object -MemberName ToString | ForEach-Object { Write-Warning "- $_" } $LastPrinted = $DownloadTimer.Elapsed } Start-Sleep -Milliseconds 200 } $DownloadTimer.Stop() } 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() } } } |