Public/Get-SPPatchReport.ps1
function Get-SPPatchReport { <# .SYNOPSIS This function is used to check if a given SQL server is patched to the latest applicable patch in a given patch file directory. .DESCRIPTION The function accepts a server name and the patch directory which stores the SQL Server patches, and then checks to see if a newer applicable patch is available in the patch file directory. .EXAMPLE Get-SPPatchReport -TargetServer "Server1", "Server2", "Server3" -SoftwareRootDirectory "C:\SqlPatches\" This will check the current version of SQL installed on Server1 on an automatically retrieved instance name, compare it with the latest applicable patch availble within C:\SqlPatches\ and it's subfolders, and then apply it if it's not already patched to that level. .EXAMPLE Get-Content serverlist.txt | Get-SPPatchReport -PatchFileObject $PatchFileList This does the same as the first example, but instead of scanning a directory for patches, it uses the $PatchFileObject to get the latest applicable patch from. The $PatchFileObject object can be created with the Get-SPPatchFileInfo function. .NOTES Author : Patrick Cull Date : 2019-09-01 #> [Cmdletbinding(DefaultParameterSetName = 'PatchDirectory')] param( #The servers to check [Parameter(Mandatory, ValueFromPipeline)] [string[]] $TargetServer, #Directory that contains the patch files. This directory will be scanned recursively for SQL Server patch files. Mutually exclusive of the $PatchFileObject parameter. [Parameter(ParameterSetName = 'PatchDirectory', Mandatory)] [string]$PatchFileDirectory, #Patch object that is returned by the function Get-SPPatchFileInfo. Mutually exclusive of the $PatchFileDirectory parameter. [Parameter(ParameterSetName = 'PatchObject', Mandatory)] [object[]]$PatchFileObject ) begin { #If a patchfile object is passed, we need to ensure it's in the correct format. if($PatchFileObject) { $requiredProperties = @("SqlVersion","PatchFileVersion","PatchType","ServicePack","PatchFileDirectory","PatchFileName","PatchFileSizeMB") $PatchObjectMembers = Get-Member -InputObject $PatchFileObject[0] -MemberType NoteProperty if(!$PatchObjectMembers){ Write-SPUpdate "Could not get object properties. Ensure a Get-SPPatchFileInfo object was passed." -UpdateType Error -Logfile $LogFile break 0 } $missingProperties = Compare-Object -ReferenceObject $requiredProperties -DifferenceObject $PatchObjectMembers.Name -PassThru -ErrorAction SilentlyContinue if ($missingProperties){ Write-SPUpdate "-PatchFileObject not in the correct format." -UpdateType Error -Logfile $LogFile Write-SPUpdate "Expected object properties:" -UpdateType Error -Logfile $LogFile $requiredProperties | Out-String | Write-SPUpdate -Logfile $LogFile -NoTimeStamp Write-SPUpdate "Given object properties:" -UpdateType Error -Logfile $LogFile $PatchObjectMembers.Name | Out-String | Write-SPUpdate -Logfile $LogFile -NoTimeStamp Write-SPUpdate "You need to pass the requried output of Get-SPPatchFileInfo as the -PatchFileObject parameter" -UpdateType Error -Logfile $LogFile break 0 } } #If a PatchFileObject has not been passed, create one by passing the PatchFileDirectory to the Get-SPPatchFileInfo function. if(!$PatchFileObject) { $PatchFileInfo = Get-SPPatchFileInfo -Path $PatchFileDirectory } else { $PatchFileInfo = $PatchFileObject } } process { foreach($server in $TargetServer) { #Get an instance to check the patchlevel. $InstanceList = Get-SPSqlInstanceList $server -InstanceNamesOnly -RunningOnly if(!$InstanceList) { Write-Warning "Could not get SQL instance list on $server. There are no running instances or server is inaccessible." } else { #If there are more than 1 instances, we use the first one. Otherwise the single instance is used. if($InstanceList.Count -gt 1) { $InstanceName = $InstanceList[0] } else { $InstanceName = $InstanceList } Write-Verbose "Using instance $InstanceName to check the patch number of $server" #Set target instance - we don't include the MSSQLServer part if it's a default if ($InstanceName -eq "MSSQLSERVER") { $TargetInstance = $server } else { $TargetInstance = "$server\$InstanceName" } $InstancePatchDetails = Get-SPInstancePatchDetails -SqlInstance $TargetInstance $SqlVersion = $InstancePatchDetails.SqlVersion $ApplicablePathes = $PatchFileInfo | Where-Object SqlVersion -eq $SqlVersion $HighestPatchAvailable = ($ApplicablePathes.PatchFileVersion | Measure-Object -Maximum).Maximum $InstancePatchVersion = $InstancePatchDetails.PatchVersion if($HighestPatchAvailable -gt $InstancePatchVersion) { $PatchRequired = $true } else { $PatchRequired = $false } [PSCustomObject][Ordered] @{ ServerName = $server InstanceChecked = $InstanceName InstancePatchVersion = $InstancePatchVersion LatestPatchVersion = $HighestPatchAvailable PatchRequired = $PatchRequired } } }#end foreach server }#end process }#end Get-SPPatchReport |