Public/Save-SPSqlPatch.ps1
function Save-SPSqlPatch { <# .SYNOPSIS Function to download the latest available Service Packs and Cumulative Updates for SQL Server. .DESCRIPTION This function uses the Get-SPSqlPatch function from this module to get all available patches for SQL Server. It then gets the KB number from the resultset, and uses the Save-KBFile function to download the latest Service Pack and/or Cumulative Update, into a special folder structure within $DownloadDirectory. The folder structure is; - For versions that don't have Service Packs (2017 and newer) - $DownloadDirectory\SQL $SqlVersion\$CUNumber - For versions that have Service Packs (2016 and older) - $DownloadDirectory\SQL $SqlVersion\$SPNumber\$SPandCUName .PARAMETER SqlVersion Version of SQL to check and download patches for. .PARAMETER DownloadDirectory Where to download the files and create the special folder structure. .PARAMETER PatchAge Minimum age of the patch in days. Defaults to 28. .PARAMETER ServicePack The Service Pack number to download. Format is SPx - e.g. SP4 .PARAMETER CumulativeUpdate The Cumulative Update to download. Format is CUx - e.g CU16, CU4 .PARAMETER DoNotCreateFolderStructure By default the function will create a folder structure within the DownloadDirectory, if this switch is passed, the file(s) are downloaded directly into the DownloadDirectory instead. .EXAMPLE PS C:\> Save-SPSqlPatch -DownloadDirectory "C:\SqlPatches\" This will download all of the latest Cumulative Updates and Service Packs for every version into "C:\SqlPatches\" and create structured folders within. .EXAMPLE PS C:\> Save-SPSqlPatch -SqlVersion "2017", "2016" -DownloadDirectory "C:\SqlPatches\" -PatchAge 28 -DoNotCreateFolderStructure This will download all of the latest SQL Server 2016 and 2017, but only downloads the patch if it's older than 28 days. It will not create subfolders and the files will be saved directly into "C:\SqlPatches". .EXAMPLE PS C:\> Save-SPSqlPatch -SqlVersion "2016" -ServicePack SP2 -CumulativeUpdate CU2 Downloads Cumulative Update 2 for Service Pack 2 for SQL Server 2016. It will also download Service Pack 2 if it hasn't already been downloaded. .NOTES Author: Patrick Cull This function uses the brilliant Save-KBFile to download the patches, which was taken from here https://gist.github.com/potatoqualitee/b5ed9d584c79f4b662ec38bd63e70a2d I've slightly modified the Save-KBFile so it retries downloads if they fail, and made it only checks for english patchfiles so only one is returned. #> [Cmdletbinding()] param( [Parameter(Mandatory)] [string] $DownloadDirectory, [string[]] $SqlVersion = @("2008", "2008 R2", "2012", "2014", "2016", "2017", "2019"), [string] $ServicePack, [string] $CumulativeUpdate, [int]$PatchAge = 0, [switch] $DoNotCreateFolderStructure ) if(!(Test-Path $DownloadDirectory)) { Throw "$DownloadDirectory not acccessible" } if(($SqlVersion.Count -gt 1) -and ($ServicePack -or $CumulativeUpdate)) { Throw "If more than one SQL Version is passed, you cannot specify ServicePack and CumulativeUpdate." } if($SqlVersion -gt 2016 -and $ServicePack) { Throw "There are no service packs for SQL Server 2017 and up." } #Setup proxy credentials in case they're needed. $browser = New-Object System.Net.WebClient $browser.Proxy.Credentials =[System.Net.CredentialCache]::DefaultNetworkCredentials #remove trailing slash if it exists. if($DownloadDirectory[-1] -eq '\') { $DownloadDirectory = $DownloadDirectory -replace "$" } $PatchInfo = Get-SPSqlPatch -SqlVersion $SqlVersion foreach($Version in $SqlVersion) { Write-Verbose "`r`n`r`n################################################`r`nSQL Server $Version`r`n################################################" $VersionPatchInfo = $PatchInfo | Where-Object SqlVersion -like "*$Version" #If not specifed, we get the latest available. if(!$ServicePack -and !$CumulativeUpdate) { Write-Verbose "No Service Pack or Cumulative Update passed, defaulting to latest patch available." $RequestedPatch = $VersionPatchInfo | Sort-Object ReleaseDate | Select-Object -Last 1 } else { #If Cumulative update is not set, we only download the SP specified. if(!$CumulativeUpdate) { Write-Verbose "Service Pack $ServicePack specified." $RequestedPatch = $VersionPatchInfo | Where-Object {$_.PatchType -eq "Service Pack" -and $_.ServicePack -eq $ServicePack} | Sort-Object ReleaseDate | Select-Object -Last 1 } else { if($SqlVersion -gt 2016) { Write-Verbose "Cumulative update $CumulativeUpdate specifed." $RequestedPatch = $VersionPatchInfo | Where-Object {$_.PatchType -eq "Cumulative Update" -and $_.CumulativeUpdate -eq $CumulativeUpdate} } else { Write-Verbose "Service Pack $ServicePack Cumulative update $CumulativeUpdate specifed." $RequestedPatch = $VersionPatchInfo | Where-Object {$_.PatchType -eq "Cumulative Update" -and $_.ServicePack -eq $ServicePack -and $_.CumulativeUpdate -eq $CumulativeUpdate} } } } $RequestedPatch | Format-list | Out-String | Write-Verbose #Check the patch is old enough before downloading. $MinimumPatchAge = (Get-Date).AddDays(-$PatchAge) if($RequestedPatch.ReleaseDate -lt $MinimumPatchAge) { $PatchesToDownload = @() $CUNumber = $RequestedPatch.CumulativeUpdate $SPNumber = $RequestedPatch.ServicePack #We do this for folder naming. if($CUNumber.Length -eq 3) { $CUNumber = $CUNumber -replace "CU", "CU0" } #If there's a Service Pack to related to the requested patch, add it to download list (if the user has not specifically requested a Service Pack on it's own) if($SPNumber -and $RequestedPatch.PatchType -ne 'Service Pack') { $SPPatch = $VersionPatchInfo | Where-Object {$_.PatchType -eq "Service Pack" -and $_.ServicePack -eq $RequestedPatch.ServicePack} | Sort-Object ReleaseDate | Select-Object -Last 1 $PatchesToDownload += $SPPatch } $PatchesToDownload += $RequestedPatch foreach($patch in $PatchesToDownload) { $PatchKBNumber = $patch.KBNumber $PatchType = $patch.PatchType if($DoNotCreateFolderStructure) { $PatchDownloadDirectory = "$DownloadDirectory" Write-Verbose "Downloading patch for SQL $Version KB $PatchKBNumber" } elseif($PatchType -eq 'Service Pack') { $PatchDownloadDirectory = "$DownloadDirectory\SQL $Version\$SPNumber" Write-Verbose "Downloading SP $SPNumber for SQL $Version KB $PatchKBNumber" } elseif($PatchType -ne 'Service Pack' -and $patch.SqlVersion -lt "SQL Server 2017") { if($PatchType -eq "Cumulative Update") { $PatchDownloadDirectory = "$DownloadDirectory\SQL $Version\$SPNumber\${SPNumber}${CUNumber}" Write-Verbose "Downloading $CUNumber for SP $SPNumber SQL $Version KB $PatchKBNumber" } else { $PatchDownloadDirectory = "$DownloadDirectory\SQL $Version\$SPNumber\$PatchType" Write-Verbose "Downloading $PatchType for SP $SPNumber SQL $Version KB $PatchKBNumber" } } else { $PatchDownloadDirectory = "$DownloadDirectory\SQL $Version\$CUNumber" Write-Verbose "Downloading $CUNumber for SQL $Version KB $PatchKBNumber" } mkdir $PatchDownloadDirectory -Force | Out-Null $DownloadOutput = @() $DownloadOutput = Save-KBFile -Name $PatchKBNumber -Path $PatchDownloadDirectory -Architecture x64 $DownloadResult = $DownloadOutput[0] $DownloadFile = $DownloadOutput[1] if($DownloadResult -eq "AlreadyDownloaded") { Write-Verbose "$DownloadFile file already exists in $PatchDownloadDirectory, skipping download." $DownloadStatus = "AlreadyDownloaded" } elseif($DownloadResult -eq "CantQueryWebsite") { Write-Verbose "Error querying the microsoft website for the KBFile." $DownloadStatus = "Error" } elseif($DownloadResult -eq "CantConnectToDownloadWebsite") { Write-Error "Unable to connect to the download website http://download.windowsupdate.com - the file will have to be downloaded manually" } elseif($DownloadResult -eq "DownloadedSucessfully"){ Write-Verbose "$DownloadFile for SQL $Version successfully downloaded to $PatchDownloadDirectory" $DownloadStatus = "Success" } else { Write-Warning "Issue downloading $SPNumber for SQL $Version. Try downloading manually. " $DownloadStatus = "Error" } [PSCustomObject][Ordered] @{ SqlVersion = $patch.SqlVersion PatchType = $patch.PatchType ServicePack = $patch.ServicePack CumulativeUpdate = $patch.CumulativeUpdate ReleaseDate = $patch.ReleaseDate DownloadStatus = $DownloadStatus FilePath = "$PatchDownloadDirectory\$DownloadFile" Description = $patch.Description Link = $patch.Link } }#end foreach patch } else { Write-Warning "Patch has not been out for over $PatchAge days yet, skipping download. Change the -PatchAge parameter if needed." } }#end foreach version. } |