Private/Save-KBFile.ps1
#################################################################################### # Save-KBFIle function below taken from the internet and modified by me, Patrick Cull, to work better with Sql KBs #################################################################################### function Save-KBFile { <# .SYNOPSIS Downloads patches from Microsoft .DESCRIPTION Downloads patches from Microsoft .PARAMETER Name The KB name or number. For example, KB4057119 or 4057119. .PARAMETER Path The directory to save the file. .PARAMETER FilePath The exact file name to save to, otherwise, it uses the name given by the webserver .PARAMETER Architecture Defaults to x64. Can be x64, x86 or "All" .NOTES Props to https://keithga.wordpress.com/2017/05/21/new-tool-get-the-latest-windows-10-cumulative-updates/ Adapted for dbatools by Chrissy LeMaire (@cl) Then adapted again for general use without dbatools See https://github.com/sqlcollaborative/dbatools/pull/5863 for screenshots Patrick Cull - Changes I've made; - I've added a retry loop to this function as I found it failed intermittently. - Changed some of the download code so it works a bit better with SQL Server exe files, because that's all this module uses it for. .EXAMPLE PS C:\> Save-KBFile -Name KB4057119 Downloads KB4057119 to the current directory. This works for SQL Server or any other KB. .EXAMPLE PS C:\> Save-KBFile -Name KB4057119, 4057114 -Path C:\temp Downloads KB4057119 and the x64 version of KB4057114 to C:\temp. This works for SQL Server or any other KB. .EXAMPLE PS C:\> Save-KBFile -Name KB4057114 -Architecture All -Path C:\temp Downloads the x64 version of KB4057114 and the x86 version of KB4057114 to C:\temp. This works for SQL Server or any other KB. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string[]]$Name, [string]$Path = ".", [string]$FilePath, [ValidateSet("x64", "x86", "All")] [string]$Architecture = "x64" ) begin { function Get-KBLink { param( [Parameter(Mandatory)] [string]$Name ) $kb = $Name.Replace("KB", "") $results = Invoke-WebRequest -Uri "http://www.catalog.update.microsoft.com/Search.aspx?q=KB$kb" $kbids = $results.InputFields | Where-Object { $_.type -eq 'Button' -and $_.Value -eq 'Download' } | Select-Object -ExpandProperty ID Write-Verbose -Message "$kbids" if (-not $kbids) { Write-Warning -Message "No results found for $Name" return } $guids = $results.Links | Where-Object ID -match '_link' | Where-Object { $_.OuterHTML -match ( "(?=.*" + ( $Filter -join ")(?=.*" ) + ")" ) } | ForEach-Object { $_.id.replace('_link', '') } | Where-Object { $_ -in $kbids } if (-not $guids) { Write-Warning -Message "No file found for $Name id" return } foreach ($guid in $guids) { Write-Verbose -Message "Downloading information for $guid" $post = @{ size = 0; updateID = $guid; uidInfo = $guid } | ConvertTo-Json -Compress $body = @{ updateIDs = "[$post]" } # Added loop, as sometimes the DownloadDialog below doesn't work on the Microsoft website itself, so we retry up to 10 times. $RetryCount = 0 while(!$links -and $RetryCount -ne 10) { $links = Invoke-WebRequest -Uri 'https://www.catalog.update.microsoft.com/DownloadDialog.aspx' -Method Post -Body $body | Select-Object -ExpandProperty Content | Select-String -AllMatches -Pattern "(http[s]?\://download\.windowsupdate\.com\/[^\'\""]*)" | Select-Object -Unique Write-Verbose "DownloadDialog did not return the download file, retrying." $RetryCount ++ Start-Sleep 1 } if (-not $links) { Write-Warning -Message "No file found for $Name id" return } else { Write-Verbose "Success. Download link returned, proceeding with the download." } foreach ($link in $links) { $link.matches.value | Where-Object {$_ -like '*x64*exe'} } } } } process { if ($Name.Count -gt 0 -and $PSBoundParameters.FilePath) { throw "You can only specify one KB when using FilePath" } foreach ($kb in $Name) { $links = Get-KBLink -Name $kb if ($links.Count -gt 1 -and $Architecture -ne "All") { $templinks = $links | Where-Object { $PSItem -like "*$Architecture*" } if ($templinks) { $links = $templinks } else { Write-Warning -Message "Could not find architecture match, downloading all" } } foreach ($link in $links) { if (-not $PSBoundParameters.FilePath) { $FilePath = Split-Path -Path $link -Leaf } else { $Path = Split-Path -Path $FilePath } #Tidy up the filename $FilePath = ($FilePath -split "_")[0] + ".exe" $FilePath = (($FilePath -replace "sqlserver", "SQLServer") -replace "kb", "KB") -replace "sp", "SP" $file = "$Path$([IO.Path]::DirectorySeparatorChar)$FilePath" #Make sure the download is english version. (Only SP's have langauge versions) if($filePath -like '*-enu*' -or $filepath -notlike '*SP*') { if(Test-Path $file) { return "AlreadyDownloaded", $FilePath } else { #Test connection to website. try { Invoke-WebRequest "http://download.windowsupdate.com" -UseBasicParsing -ErrorAction Stop | Out-Null } catch { return "CantConnectToDownloadWebsite", $FilePath } #try to download using BitsTransfer first try { Start-BitsTransfer -Source $link -Destination $file -ErrorAction Stop -Asynchronous | Out-Null $BitsJob = Get-BitsTransfer -Name "BITS Transfer" if($BitsJob.JobState -eq "Error" -or !$BitsJob) { Throw "Error with BitsTransfer" } while ($BitsJob.JobState -eq "Transferring" -or $BitsJob.JobState -eq "Connecting" -and $BitsPctComplete -ne 100){ $BitsPctComplete = ($BitsJob.BytesTransferred / $BitsJob.BytesTotal)*100 $PctCompleteForDisplay = [math]::Round($BitsPctComplete) Write-Progress -PercentComplete $BitsPctComplete -Id 1 -Activity "Downloading $FilePath to $path" -Status "${PctCompleteForDisplay}% complete" if($BitsJob.JobState -eq "Error") { Throw "Error with BitsTransfer" } Start-Sleep 1 } } #If BitsTransfer fails, try DownloadFile instead. catch { Get-BitsTransfer -Name "BITS Transfer" | Complete-BitsTransfer Write-Verbose "Start-BitsTransfer failed trying DownloadFile method instead." Write-Progress -Activity "Downloading $FilePath to $path" -Id 1 (New-Object Net.WebClient).DownloadFile($link, $file) Write-Progress -Activity "Downloading $FilePath" -Id 1 -Completed } #cleanup in case BitsTransfer stopped mid download. finally { Get-BitsTransfer -Name "BITS Transfer" | Complete-BitsTransfer } if (Test-Path -Path $file) { return "DownloadedSucessfully", $FilePath } else { return "Error", $FilePath } } } } } } } |