- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
Patches (service packs and cumulative updates) are periodically released from Microsoft. Judging by the importance of the data contained in databases, it's critical that these servers are patched ASAP. How do you install service packs and cumulative updates on SQL servers today? Are you still going to download.microsoft.com, downloading the bits, extracting them, transferring the bits to each SQL server, opening up remote desktop and executing the update manually? If so, there's a better way:
Update-SqlServer –ComputerName 'SERVER1','SERVER2','SERVER3' –ServicePack Latest –CumulativeUpdate Latest
This requires a bit of planning and work. Lucky for you, I've already done that! To get started, we'll need to create more than just a single Update-SqlServer function. Since we'll be installing service packs and cumulative updates, we'll need at least three different functions. In fact, we'll end up with a lot of various functions. Remember that it's always better to build smaller functions so they can be used as building blocks to create the tool.
The SQL Server update tool (Update-SqlServer) we'll be creating will consist of a total of seven custom functions along with some helper functions. We're going big time here!
- Install-SqlServerCumulativeUpdate
- Get-SQLServerVersion
- Find-SqlServerCumulativeUpdateInstaller
- Get-LatestSqlServerCumulativeUpdateVersion
- Install-SqlServerServicePack
- Get-SQLServerVersion
- Find-SqlServerServicePackInstaller
- Get-LatestSqlServerServicePackVersion
Since there are so many functions, I'm going to emphasize the flow of this tool rather than diving into the code. If you want to grab the code for this tool, feel free to download it from my Github repository.
For this tool to work, you must prepare the service packs and cumulative updates ahead of time and ensure they all follow a standard naming convention. Here's how mine are arranged and the schema the tool expects:
You can see that I have folders structured like this: Microsoft/SQL/<2012/2014/2016>/Updates. I also have service pack 1 for download as well as CU5 for SP1 and CU12 for RTM. As long as you follow the same naming convention, this tool will allow you to select whatever service pack and cumulative update you'd like to install.
Once all of the installers have been downloaded, I then use a CSV file called sqlserverversions.csv that exists in the same folder as my SqlUpdater.ps1 script. This is where I keep track of all of the available service packs and cumulative updates for each version. I use this in the tool as a way to define the "latest" SPs and CUs for every version.
This CSV will need to be updated periodically when new SPs and CUs are released for the latest SQL Server version.
That's it for the ancillary files needed. Let's get into some code!
The primary function is Update-SqlServer. This function is simply a wrapper function for both Install-SqlServerServicePack and Install-SqlServerCumulativeUpdate. As you can see below, Update-SqlServer has a few parameters; ComputerName which indicates the server to install the update on, the service pack and cumulative update defaulting to the latest (remember that CSV file?) and an optional Credential if you need to use alternate credentials.
function Update-SqlServer { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateNotNull0rEmpty()] [string]$ComputerName, [Parameter()] [ValidateNotNull0rEmpty()] [ValidateSet(1, 2, 3, 4, 5, ‘Latest’)] [string]$ServicePack = ‘Latest’, [Parameter()] [ValidateNotNull0rEmpty()] [ValidateSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, ‘Latest’)] [string]$CumulativeUpdate = ‘Latest’, [Parameter()] [ValidateNotNull0rEmpty()] [pscredential]$Credential ) … }
As you can see above, Update-SqlServer has a few parameters: ComputerName, which indicates the server on which to install the update; the service pack and cumulative update, which defaults to the latest (remember that CSV file?); and an optional Credential if you need to use alternate credentials.
Let's say that you need to install the latest SP and CU on a SQL Server. If so, you'd run Update-SqlServer like this:
Update-SqlServer –ComputerName 'SERVER1' –ServicePack Latest –CumulativeUpdate Latest
This will kick off the following flow:
Subscribe to 4sysops newsletter!
- Find the version of SQL Server that's installed on SERVER1.
- Find the latest SP for that version in the CSV file.
- Find the appropriate installer in the shared UNC path.
- Install the service pack.
- (Repeat for the CU)
The only thing that must be done for this to work seamlessly is to ensure that the standard naming convention is followed for the installers and that each version's CSV exists. Otherwise, it should work great! However, no code is perfect, so feel free to try it out and let me know how it works.
This looks great, just what I was looking for!
I need step by step process to follow for bulk update
Seems that there are a couple of things required in the script to make it work:
1. Get-Callerpreference is found here: https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d
2. Write-Log is found here: https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
3. -Source is not a valid Write-Log parameter, so I couldn’t go any further, I tried deleting all occurrences of it to continue.
4. Computer name “…does not match the “^([\w-]+\.){2}\w+$” pattern”, I commented ValidatePattern out, as the name was valid, to continue.
5. ConvertTo-VersionObject I can’t find anywhere, Microsoft has documentation for: ConvertTo-Csv, ConvertTo-Html, ConvertTo-Json and ConvertTo-Xml, no idea what to do from here.
Apologies. There were still references to private functions in my internal modules. I’ve removed or replaced all of them. Take a look again.
This error still happened for me:
4. Computer name “…does not match the “^([\w-]+\.){2}\w+$” pattern”, I commented ValidatePattern out, as the name was valid, to continue.
Thanks! Fixed.
Adam,
Just a few notes on what I have found so far:
The Validate set is only allowing a few version:
ie. ValidateSet(‘2008R2’, ‘2012’, ‘2014’)
Can this handle older versions?
$verKey only accounts for sql 2012 and 2014.
Caption doesn’t account for newer versions:
Caption = {
switch -regex ($version)
{
“^11” { “SQL Server 2012”; break }
“^10\.5” { “SQL Server 2008 R2”; break }
“^10” { “SQL Server 2008”; break }
“^9” { “SQL Server 2005”; break }
“^8” { “SQL Server 2000”; break }
default { “Unknown”; }
}
Will this work in version < 2008r2?
I can’t seem to get a valid file returned for this:
$servicePacks = Get-ChildItem -Path $SqlServerInstallerBasePath | Where-Object { $_.Name -match ‘^\d{4}’ } | Get-ChildItem -Filter ‘Updates’ | Get-ChildItem -Filter ‘SQLServer*-SP?-*.exe’
it feels like this isn’t quite right: { $_.Name -match ‘^\d{4}’ } | Get-ChildItem -Filter ‘Updates’ | Get-ChildItem -Filter ‘SQLServer*-SP?-*.exe’
Is there any way to make the sql version code (ie, sql 2016, sql 2017) more dynamic so the file doesn’t have to be updated with each new major release? I’m guessing not.
Thanks,
Jeff
nevermind the “$servicePacks = Get-ChildItem”. I got past that part,
I cannot get past this
Cannot process argument transformation on parameter ‘Version’. Cannot convert the “System.Object[]” value of type “System.Object[]” to type “System.Version”
Its because it is trying to pass multiple rows… I changed it to the below and I get further. I don’t think this is the most suitable solution.
$currentVersion = ConvertTo-VersionObject -Version $sqlInstance.Version[0].ToString()
You shouldn’t need to make a change for
$currentVersion = ConvertTo-VersionObject -Version $sqlInstance.Version
Check and make sure your csv has the correct versions.
If this is for 2016 you will need to look thru the script (look for 2012 or 2014) and add similar entries for 2016. Here are a few tweaks needed:
elseif ($version.Split(‘.’)[0] -eq ’13’)
{
$verKey = $reg.OpenSubKey(‘SOFTWARE\\Microsoft\\Microsoft SQL Server\\130\\SQLServer2016\\CurrentVersion’)
$version = $verKey.GetValue(‘Version’)
}
Caption = {
switch -regex ($version)
{
“^13” { “SQL Server 2016”; break }
“^12” { “SQL Server 2014”; break }
“^11” { “SQL Server 2012”; break }
“^10\.5” { “SQL Server 2008 R2”; break }
“^10” { “SQL Server 2008”; break }
“^9” { “SQL Server 2005”; break }
“^8” { “SQL Server 2000”; break }
default { “Unknown”; }
}
[Parameter()]
[ValidateNotNullOrEmpty()]
[ValidateSet(‘2008R2’, ‘2012’, ‘2014’, ‘2016’)]
[string]$SqlServerVersion,
I’m going to rework it to be able to add newer versions more easily.
Thanks Jeff, I will give that a try. But to clarify I tested this against a standard 2012 and 2014. Just trying to get the basics working before I add the extra version info.
Hi
I cannot figure out what is going on…. if I don’t explicitly return 1 record it fails
$currentVersion = ConvertTo-VersionObject -Version $sqlInstance.Version[0]
I tried this script on 3 different editions and 2 different versions and I can confirm I am getting multiple records on the first 2…. below is what it returns (the first 2 attempts return 2 records… the last just the one record… which will then work. They all have similar components installed as well.
@{ComputerName=xxxxxx; InstanceType=Database Engine; InstanceName=MSSQLSERVER; InstanceID=MSSQL11.MSSQLSERVER; InstanceDir=E:\MSSQL; Edition=Enterprise Edition; Version=11.2.5058.0; Caption=SQL S
erver 2012; IsCluster=False; IsClusterNode=False; ClusterName=; ClusterNodes=System.Object[]; FullName=xxxxxx}
@{ComputerName=xxxxxx; InstanceType=Analysis Services; InstanceName=MSSQLSERVER; InstanceID=MSAS11.MSSQLSERVER; InstanceDir=E:\MSSQL; Edition=Enterprise Edition; Version=11.2.5058.0; Caption=SQL
Server 2012; IsCluster=False; IsClusterNode=False; ClusterName=; ClusterNodes=System.Object[]; FullName=xxxxx}
@{ComputerName=yyyyyy; InstanceType=Database Engine; InstanceName=yinstance; InstanceID=MSSQL12.yinstance; InstanceDir=d:\Program Files\Microsoft SQL Server; Edition=Developer Edition; Ve
rsion=12.2.5522.0; Caption=Unknown; IsCluster=False; IsClusterNode=False; ClusterName=; ClusterNodes=System.Object[]; FullName=yyyyyy\yinstance}
@{ComputerName=yyyyyy; InstanceType=Reporting Services; InstanceName=yinstance; InstanceID=MSRS12.yinstance; InstanceDir=D:\Program Files\Microsoft SQL Server; Edition=Developer Edition;
Version=12.2.5522.0; Caption=Unknown; IsCluster=False; IsClusterNode=False; ClusterName=; ClusterNodes=System.Object[]; FullName=yyyyyy\yinstance}
@{ComputerName=zzzzz; InstanceType=Database Engine; InstanceName=zinstance; InstanceID=MSSQL12.zinstance; InstanceDir=d:\Program Files\Microsoft SQL Server; Edition=Standard Edition; Ver
sion=12.2.5522.0; Caption=Unknown; IsCluster=False; IsClusterNode=False; ClusterName=; ClusterNodes=System.Object[]; FullName=zzzzz\zinstance}
Thanks
Kez
Can you file an issue on the GitHub repository?
Hi Adam,
Thanks for wonderful post. We would want to have more control on working with patched and update the SQL with desired patch. We are saving it in one location and then executing the same from remote machine but this is giving error somehow. Error thrown while executing an exe file for sQL server patch on remote computer using invoke-command. The command runs without any error but do not start the patching process.
$Servicepack = “Location of service pack”
$PatchCMD = “$Servicepack /action=patch /InstanceName=$Instance /IAcceptSQLServerLicenseTerms”
$Server = $GetPatchFile.servernames
invoke-command -ComputerName $Server scriptblock{$PatchCMD}
Please help here.
Thanks,
Neeraj
Can you file an issue on the Github repository? I’m trying to bring together all bug reports so they can be fixed?
https://github.com/adbertram/PSSqlUpdater
Hi, great script. My first problem is to download all SP’s and CU’s and rename them correctly… Do you have any script for that ?
/Staffan
Unfortunately, no. I looked for a good way to automate that but I chose not to because I would have had to figure out all of the URLs and they could potentially change. There’s no real one place where all of them are stored. I had to download them manually at first.
– Adam Bertram
Adam, the Automator Blog
As soon as I download and open the file in PowerShell Editior, I can see error in the below line
function Install-SqlServerCumulativeUpdate
{
[CmdletBinding(SupportsShouldProcess)]
param
The “=” operator is missing after a named argument.
At C:\PSSqlUpdater-master\PSSqlUpdater-master\SqlUpdater.ps1:5 char:39
+ [CmdletBinding(SupportsShouldProcess) <<<< ]
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : MissingEqualsInNamedArgument
I am just able to hit the exe with this code but not able to do an unattended installation. Can you please help me on that.
How to get over this error:
Install-SqlServerCumulativeUpdate : Could not find installer for cumulative update [1]
I have re-checked the path and naming conventions at least 10 times.
If use UpdateSource like below:
.\setup.exe /Q /ACTION=Upgrade /IACCEPTSQLSERVERLICENSETERMS /INSTANCENAME=SQL2014 /UpdateSource=”C:\Temp\SQLUpdates”
SQL Server will figure out latest SP and CU and install them, if you want to update to specific version, just copy that SP or(and) CU to that folder, you don’t need to update their name too.
C:\Temp\SQLUpdates>dir
Volume in drive C has no label.
Volume Serial Number is 40D5-5975
Directory of C:\Temp\SQLUpdates
11/07/2018 02:12 PM <DIR> .
11/07/2018 02:12 PM <DIR> ..
29/06/2018 04:31 PM 575,305,688 SQLServer2016-KB4135048-x64.exe
29/06/2018 04:35 PM 811,699,376 SQLServer2016SP2-KB4052908-x64-ENU.exe
2 File(s) 1,387,005,064 bytes
2 Dir(s) 111,825,092,608 bytes free
C:\Temp\SQLUpdates>
what about when several instances on computer , the script doesn’t work:
Get-SQLServerVersion : Cannot process argument transformation on parameter ‘Version’. Cannot convert the
“System.Object[]” value of type “System.Object[]” to type “System.Version”.
At line:1 char:2
+ Get-SQLServerVersion FC1
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-SQLServerVersion
Is there an option or opportunity to run this on multiple server/instances vs stand-alone calls to individual servers?
I have made the modifications as Jeff (above) suggested to add SQL 2016. Attempted to execute and receive an error:
Cannot find the type for custom attribute ‘ValidateNotNull0rEmpty’. Make sure that the assembly that contains this type is loaded.
At D:\SQL Server Updater\PSSqlUpdater-master\PSSqlUpdater-master\function Update-SqlServer.psm1:7 char:9
+ [ValidateNotNull0rEmpty()]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: ([ValidateNotNull0rEmpty()]:AttributeAst) [], RuntimeException
+ FullyQualifiedErrorId : CustomAttributeTypeNotFound
Any suggestions on where to look for this to make the required change?
Thank you!
Hello,
Has anyone been able to get this to work? If so, can you please post your working code? Many hours on this repeating exactly the instructions and no joy.
Thank you in advance.
Bob
Hi – has any one an updated version supporting SQL server 2016+ ?
step by step process to follow fo rBulk updating with CU on SQL 2016
Hi All, did you guys have been able to fix that script so it can run for multiple servers as well as updating with CU on SQL 2016, 2019?
Can you please share fixed script?
Thx, Mike