Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Automated Patch Reference Guide

Automated Patch Reference Guide

Overview

The installation and configuration of SharePoint 2013 Cumulative Updates, Service Packs and Security Updates are a time intensive and tedious process. In an effort to reduce the installation time, a series of PowerShell scripts was developed to reduce the effort required in patch installation and PSCONFIG execution. Each script described in this guide is independent from one another, allowing a SharePoint administrator to execute any number of scripts to aid with patch installation.

PowerShell Remoting Configuration

The majority of the scripts referenced in this document require PowerShell Remoting to be configured on each of the SharePoint servers in the farm. PowerShell Remoting allows a PowerShell script to execute commands on each of the SharePoint servers, without an administrator having to logon to the SharePoint server and manually execute a script. The following section outlines the steps necessary to configure PowerShell Remoting on the servers in the SharePoint farm.

Enable PowerShell Remoting

On Windows Server® 2012, Windows PowerShell remoting is enabled by default. If the SharePoint servers are not running Windows 2012 or higher, or your environment has a non-default configuration, an administrator will need to enable PowerShell Remoting. To enable PowerShell remoting, execute the following cmdlet from an elevated PowerShell console window:

Enable-PSRemoting

Image of Enable-PSRemoting Command

Enable PowerShell WSManCredSSP

The Credential Security Support Provider (CredSSP) enables PowerShell to use a client-side Security Support Provider (SSP) to delegate user credentials from the client computer (a SharePoint server) to the target server (the other SharePoint servers in the farm). Credential delegation allows a PowerShell script block to execute on remote SharePoint servers as a specific domain user, like the farm administrator. Credential delegation is a requirement for several of the scripts.

  1. On each server in the SharePoint farm, open a PowerShell console window and execute the following command. Choose Yes to enable the CredSSP authentication provider.
Enable-WSManCredSSP -Role Server

Image of Enable-WSManCredSSP Command

This step must be performed on the SharePoint server acting as the client machine. Best practice dictates that all SharePoint servers in the farm are configured identically so it is recommended that the following step be done on all servers in the farm.

  1. Open the Local Group Policy Editor (gpedit.msc). Navigate to Computer Configuration > Administrative Templates > System > Credentials Delegation and enable the Allow Delegating Fresh Credentials setting.

PowerShell Remoting and CredSSP Validation

To validate that PowerShell Remoting and CredSSP are configured correctly on all servers in the farm, administrators can execute the Test-PowerShellRemoting.ps1 script. This script will execute a PowerShell Remoting call to each of the target SharePoint servers in the farm and validate the response. The script will output the results for each server in the farm. A response of True indicates the connection and response from the remote server was successful.

Image of Test-PowerShellRemoting Result

It is recommended to execute this script after configuring PowerShell Remoting and CredSSP, as well as prior to any patch installations. Periodic verification of the configuration will minimize any negative impact if configuration changes have caused PowerShell Remoting or CredSSP to not work correctly.

Copying SharePoint Updates to Servers

Some administrators prefer to install SharePoint updates from a common UNC share that is accessible from all SharePoint servers. However, many administrators prefer to copy the updates to a local drive on each SharePoint server and install from the local source. To aid in copying the updates to all servers in the farm, administrators can utilize the Copy-SharePointUpdateToServer.ps1 script.

To configure the script, you will need to update the source and destination directory paths. The first value (yellow) is the UNC path to the source files that need to be copied to the farm servers. The second value (green) is the destination directory path on the SharePoint server. The destination drive letter (C:, E:, etc.) must exist on all servers. The script will create the specified folder structure automatically, so it is not required to pre-create the specified folder structure.

Script Parameters:

$foldersToCopy = [ordered]@{
   # July 2014 CU
   "\\server\_binaries\SharePointUpdates\2014_07_CU\" = "C:\_SharePointUpdates\2014_07_CU\";
}

The script will report progress as it starts and completes the copy to each server in the farm.

Image of Copy-SharePointUpdateToServer.ps1 Result

SharePoint Patch Installation

Installing the SharePoint update packages is one of the more tedious and time consuming steps in the SharePoint patching process. To automate this process, administrators can use the Install-SharePointUpdate.ps1 script. The script has numerous configuration options, but at its core, the script allows administrators to install SharePoint updates on all servers in the farm in parallel, without having to physically login to each server and manually start the package installation.

When the script starts, an installation job will be created for each server in the farm and the job will be added to the installation queue. By default, the script will execute the installations simultaneously on all servers in the farm, but can be adjusted with $numberOfConcurrentInstallations variable. Setting the $stopServicesDuringPatching variable to true will instruct the script to stop and disable the IISADMIN, OWSTIMER and Search Services on the SharePoint servers prior to patch installation. The script will enable and restart the respective services after the patch installation completes. Testing by the Microsoft Premier Field Engineer (PFE) Russ Maxwell has shown significant time reductions when the services are stopped on the server during patch installations.

Image of Progress for Install-SharePointUpdate.ps1

As each of the patch installation jobs complete, the console will be updated. As you can see in the screenshot below, 19 of 20 servers in the farm have completed the installation process.

Image of Progress for Install-SharePointUpdate.ps1

After the installation processes have completed, a table is displayed with the installation results, as well as a summary of the results written to a .csv file in the local execution directory.

Image of Progress for Install-SharePointUpdate.ps1

Administrators can use the data in the .csv file to report installation statistics for future reference. Here’s an example of the summary data in table format.

Computer Name Patch Name Start Time End Time Total Installation Time Result Message
SP01 ubersrv.exe 14:45:21 15:07:10 00:21:49 Success: Reboot Required
SP02 ubersrv.exe 14:45:50 15:07:33 00:21:43 Success: Reboot Required
SP03 ubersrv.exe 14:45:52 15:07:14 00:21:22 Success: Reboot Required
SP04 ubersrv.exe 14:45:55 15:06:56 00:21:01 Success: Reboot Required
SP05 ubersrv.exe 14:45:57 15:06:29 00:20:32 Success: Reboot Required
SP06 ubersrv.exe 14:45:59 15:08:34 00:22:35 Success: Reboot Required
SP07 ubersrv.exe 14:46:01 15:08:26 00:22:25 Success: Reboot Required
SP08 ubersrv.exe 14:46:03 15:11:00 00:24:57 Success: Reboot Required
SP09 ubersrv.exe 14:46:06 15:09:18 00:23:12 Success: Reboot Required
SP10 ubersrv.exe 14:46:08 15:10:14 00:24:06 Success: Reboot Required
SP11 ubersrv.exe 14:46:10 15:12:01 00:25:51 Success: Reboot Required
SP12 ubersrv.exe 14:46:13 15:16:27 00:30:14 Success: Reboot Required
SP13 ubersrv.exe 14:46:15 15:08:51 00:22:36 Success: Reboot Required
SP14 ubersrv.exe 14:46:17 15:09:24 00:23:07 Success: Reboot Required
SP15 ubersrv.exe 14:46:19 15:11:36 00:25:17 Success: Reboot Required
SP16 ubersrv.exe 14:46:22 15:10:19 00:23:57 Success: Reboot Required
SP17 ubersrv.exe 14:46:24 15:11:06 00:24:42 Success: Reboot Required
SP18 ubersrv.exe 14:46:26 15:11:00 00:24:34 Success: Reboot Required
SP19 ubersrv.exe 14:46:28 15:08:37 00:22:09 Success: Reboot Required
SP20 ubersrv.exe 14:46:30 15:12:12 00:25:42 Success: Reboot Required

Patch Installation and PSCONFIG Execution Monitoring

A drawback of these automation scripts is the progress bars and dialog windows administrators rely on to determine if a process is still executing or has finished are no longer present. To mitigate this deficiency, administrators can leverage the Watch-SharePointProcess.ps1 script. This script will query all SharePoint servers in the farm, searching for the specified process. The script will refresh the table every 10 seconds (configurable) and will exit automatically after three refresh cycles where no matching processes were found running on the servers.

In the case of SharePoint patches, administrators will query for “ubersrv” during patch installation. As seen in the screenshot below, all 20 servers in this farm are being patched actively as they all have an instance of ubersrv.exe running.

Image of Progress for Watch-SharePointProcess.ps1

Much later in the installation process, the screenshot below shows server SP12 is the only remaining server with an instance of ubersvr.exe running.

Image of Progress for Watch-SharePointProcess.ps1

Administrators also can use the Watch-SharePointProcess.ps1 script to monitor the servers in the farm while PSCONFIG is upgrading a server. In the screenshot below, server SP05 is the only server with PSCONIFG running.

Image of Progress for Watch-SharePointProcess.ps1

Administrators can snap the two console windows side by side to see a near real-time status of the patch installation process. Updates to the progress list for Install-SharePointUpdate.ps1 will lag behind by several seconds if you have chosen to stop and start the Windows services as part of the patch installation script.

Image of Progress for Watch-SharePointProcess.ps1

Restart Servers in the Farm

Many of the Sharepoint updates require a restart after installation, which can be determined from the patch installation summary .csv file. To automate restarting the servers in the farm, administrators can use the Restart-SharePointServers.ps1 script. This script will restart all servers in the farm, except for the server executing the script. The local machine will need to be restarted manually. After issuing the restart command to all remote servers, the script will continuously query the WinRM service, refreshing the results table every 10 seconds. The script will exit after all servers have responded successfully to the WinRM query.

Image of Restart-SharePointServers.ps1

Content Database Upgrades

On SharePoint farms with thousands of site collections and multiple terabytes of data, execution of PSCONFIG is often the longest part of the patch process. When the PSCONFIG wizard executes, step 9 of the wizard executes an upgrade content database command against each of the content databases in the farm. The reason this step of PSCONFIG takes a long time is it executes the content database upgrades sequentially. For example, if you have 400 content databases, PSCONFIG will upgrade all 400 content databases, one after another. To significantly reduce the time spent in step 9 of the PSCONFIG wizard, administrators can use the Upgrade-ContentDatabaseB2B.ps1 script.
The Upgrade-ContentDatabaseB2B.ps1 script is designed to run after the updates have been applied to all servers in the farm and before PSCONFIG is executed. This script will execute content database upgrades like PSCONFIG does, but it will execute the upgrades in parallel. The parallel nature of the script allows the total content database upgrade time to be much shorter then executing the upgrade sequentially. In large SharePoint farms, this script can be executed simultaneously from multiple servers, allowing administrators to substantially scale out content database upgrades.

When the Upgrade-ContentDatabaseB2B.ps1 script executes, the user will see an upgrade job created for each content database specified in the configuration. As upgrade jobs complete the databases will be listed in the status table. After all of the content database upgrade jobs have finished, the results will be written to the screen as well as a summary .csv file. In the event of a failure, the script can be run repeatedly without modification. The script will not execute the upgrade command against any content database with the NeedsUpgrade property equal to false.

Image of Upgrade-ContentDatabaseB2B.ps1

Before the Upgrade-ContentDatabaseB2B.ps1 script is executed, the upgrade status page in Central Admin will show the databases in compatibility mode. After the script executes, all of the content databases should show No action required. Executing PSCONFIG will upgrade the configuration database, central admin content database and service application databases.

Image of Manage Database Upgrade Status

The data from the Upgrade-ContentDatabaseB2B.ps1 script output file can be displayed in Excel and used to report content database upgrade statistics. Below is an example of the data being reported in an Excel chart, which shows the individual database upgrade execution times and cumulative execution time.

Image of Manage Database Upgrade Status

Automated PSCONFIG Execution

Logging onto each server in the farm, executing PSCONFIG and watching it run is a monotonous and time consuming process. To reduce the administrative overhead, administrators can leverage the Invoke-PSCONFIGWizard.ps1 script. This script will execute PSCONFIG on every server in the farm that has an installation status of “Upgrade Required” or “Upgrade Available.”

It is advisable, but not required, to execute PSCONFIG on one server in the farm successfully before running the Invoke-PSCONFIGWizard.ps1 script. Any errors that occur during the first PSCONFIG execution should be remediated prior to running the Invoke-PSCONFIGWizard.ps1 script.

NOTE: Launch a new SharePoint management shell window to execute this script. A new console window will prevent any issues with cached or stale data.

Image of Invoke-PSConfigWizard.ps1

As a reminder, administrators can leverage the Watch-SharePointProcess.ps1 script by snapping the two console windows side by side to see a near real-time status of the PSCONFIG execution across the server farm.

Image of Invoke-PSConfigWizard.ps1

Delete SharePoint Updates from Servers

If an administrator copied the updates to each SharePoint server in the farm, it is good practice to remove those files from the server after patching. To automate the removal process, administrators can leverage the Remove-SharePointUpdateFromServer.ps1 script. The script will delete the specified directories from all the servers in the farm.

Image of Remove-SharePointUpdateFromServer.ps1

Collect Upgrade Logs

After patching a SharePoint farm or upgrading a content database it is recommended that an administrator review the respective upgrade log(s) for errors and warnings. To aid in the collection of upgrade logs, an administrator can leverage the Copy-SharePointServerUpgradeLogs.ps1 script. This script will copy all logs files that match the upgrade.log name convention to the specified directory. The script will create a sub-folder under the target directory for each server the farm.

Verify Patch Installation

After patching a SharePoint farm, an administrator should confirm all the servers have been patched. To the aid the verification, administrators can use the Get-PatchReferenceCount.ps1 script. The script will query the registry on each server and total the number of references to the specified KB number(s). The screenshot below shows that each server in the farm has 36 reference to KB 2883068. If the reference counts are not identical on all servers, further investigation is required by an administrator.

Image of Get-PatchReferenceCount.ps1

<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
09-10-2015 - Created
SUMMARY:
This script will copy all server, database and site upgrade logs from all servers in the farm to a central location.
==============================================================#>
# destination for the upgrade logs. Subfolders will be created for each server
$logCollectionBasePath= "C:\LogFiles\PostUpgradeLogCollection"
# how many days from the past should the script pull logs?
$maxAgeOfLogs = 5
# Do multi-threaded copies with n threads (default 8). n must be at least 1 and not greater than 128.
$copyThreads = 8
<############ YOU SHOULD NOT HAVE TO MODIFY ANYTHING BELOW THIS POINT ############>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
# get the ULS log path configured for the farm
$di = New-Object System.IO.DirectoryInfo((Get-SPDiagnosticConfig).LogLocation)
# emumerate all the servers in the farm
Get-SPServer | ? { $_.Role -ne "Invalid" } | % {
$server = $_
# build a UNC Path to the logs directory for the target server
$targetDirectory = $di.FullName.Replace(":", "$")
$uncPath = "\\$($server.Name)\$targetDirectory"
# using this method so we don't create empty destination folders for servers that don't have logs that meet the criteria
Get-ChildItem -Path $uncPath -Filter "*upgrade*.log" | ? { $_.CreationTime -gt (Get-Date).AddDays(-$maxAgeOfLogs) } | % {
# create a subfolder for the target server
$targetPath = Join-Path $logCollectionBasePath $server.Name
$targetFolder = New-Item -Path $targetPath -ItemType "Directory" -ErrorAction SilentlyContinue
# copy using robocopy
Invoke-Expression "$env:windir\system32\Robocopy.exe '$uncPath' '$targetPath' '*upgrade*.log' /MT:$copyThreads /MAXAGE:$maxAgeOfLogs"
}
}
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
08-19-2015 - Created
02-29-2016 - Updated to remove dependancy on PowerShell v3.
SUMMARY:
This script will copy sharepoint updates from a central location to a local path on each server in the farm.
==============================================================#>
$foldersToCopy = @{
# July 2014 CU
"\\dc01\_binaries\Software\Microsoft\SharePoint\2013\SharePointUpdates\2014_08_CU\" = "C:\_SharePointUpdates\2014_08_CU\";
}
$roboCopyThreads = 50
<############ YOU SHOULD NOT HAVE TO MODIFY ANYTHING BELOW THIS POINT ############>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
$averageCopyTime = 0
$serverCount = 0
Write-Host "`n"
Get-SPServer | ? { $_.Role -ne "Invalid" } | Sort $_.Name | % {
$serverCount++
$serverName = $_.Name
$foldersToCopy.GetEnumerator() | % {
$sourceFilePath = $_.Name
$targetDirectory = $_.Value
# turn the local path into a UNC path
$targetDirectory = $targetDirectory.Replace(":", "$")
$targetDirectory = "\\$serverName\$targetDirectory"
# create the folder structure
New-Item -Path $targetDirectory -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
# start the copy
Write-Host "$serverCount`t$(Get-Date) - $($serverName): Starting RoboCopy. " -NoNewline
$copyTime = Measure-Command { Invoke-Expression "$env:windir\system32\Robocopy.exe '$sourceFilePath' '$targetDirectory' /MT:$roboCopyThreads" }
Write-Host "Completed in $($copyTime.TotalSeconds.ToString('0.00')) seconds"
$averageCopyTime += $copyTime.TotalSeconds
}
}
Write-Host "`nAverage File Copy Time: $($($averageCopyTime/$serverCount).ToString('0.00')) seconds`n`n"
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
09-30-2019 - Created
SUMMARY:
This script will continuously query all of the servers in the farm, looking for a process with
the name specified and display the results in table format. The script will stop when the specified
process is not running on any of the servers in the farm.
==============================================================#>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
if( (Get-SPFarm).BuildVersion.Major -ge 16 )
{
$webApplications = Get-SPWebApplication
foreach( $webApplication in $webApplications )
{
Write-Host "Disabling Side by Side upgrade on $($webApplication.Url)"
$webApplication.WebService.EnableSideBySide = $false
$webApplication.WebService.update()
}
}
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
08-06-2015 - Created
08-22-2017 - Added "Start Time" to the display output
SUMMARY:
This script will continuously query all of the servers in the farm, looking for a process with
the name specified and display the results in table format. The script will stop when the specified
process is not running on any of the servers in the farm.
==============================================================#>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
if( (Get-SPFarm).BuildVersion.Major -ge 16 )
{
$webApplications = Get-SPWebApplication
foreach( $webApplication in $webApplications )
{
Write-Host "Enabling Side by Side upgrade on $($webApplication.Url)"
$webApplication.WebService.EnableSideBySide = $true
$webApplication.WebService.update()
}
}
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
08-18-2015 - Created
09-10-2015 - Simplification
10-29-2015 - Added logging to show if a server is skipped because of it's installation status
SUMMARY:
This script will execute PSCONFIG on all servers in the farm that have a status of "Needs Upgrade"
REQUIREMENTS:
You MUST enable CredSSP authentication on all SharePoint servers in the farm before executing this script.
To enable CredSSP authentication, execute the following:
Enable-WSManCredSSP -Role Server
To disable CredSSP authentication, execute the following:
Disable-WSManCredSSP -Role Server
See the following for more information:
https://technet.microsoft.com/en-us/library/hh849872.aspx
==============================================================#>
Add-PSSnapin Microsoft.SharePoint.PowerShell
# This example will query all the servers in the farm for any Application event log entires made by the patch installation scripts
$eventLogQueryFilter = @{
Logname = "Application"
ProviderName = "SharePoint Patch Installation Script";
StartTime = $(Get-Date).AddMinutes(-180);
EndTime = $(Get-Date)
}
$scriptEventLogEntries = @()
Get-SPServer | ? { $_.Role -ne "Invalid" } | % {
Get-WinEvent -ComputerName $_.Name -FilterHashTable $eventLogQueryFilter -ErrorAction SilentlyContinue | % {
$scriptEventLogEntries += New-Object PSObject -Property @{
Server = $_.MachineName;
TimeStamp = $_.TimeCreated;
Message = $_.Message;
Id = $_.Id
}
}
}
$scriptEventLogEntries | Sort Server | FT Server, TimeStamp, Message -AutoSize
# This example will query the specified services on all the farm servers and report the service status
$serviceNames = @("W3SVC", "SPTimerV4", "OSearch15", "SPSearchHostController")
$servicesStatus = @()
Get-SPServer | ? { $_.Role -ne "Invalid" } | % {
$ComputerName = $_.Name
$serviceNames | % {
$serviceName = $_
Get-Service -Name $serviceName -ComputerName $ComputerName | % {
$svc = Get-WmiObject -Class Win32_Service -Property StartMode -Filter "Name=`"$ServiceName`"" -ComputerName $ComputerName
$servicesStatus += New-Object PSObject -Property @{
Server = $ComputerName
Service = $serviceName
Status = $_.Status
StartupType = $svc.StartMode
}
}
}
}
$servicesStatus | Sort Server, Service | FT Server, Service, Status, StartupType -AutoSize
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
08-03-2015 - Created
11-01-2017 - Updated to be .NET Framework 2.0 compliant
==============================================================#>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
function Get-PatchReferenceCount
{
[cmdletbinding()]
param
(
[parameter(Mandatory=$true)][string]$ComputerName,
[parameter(Mandatory=$true)][string[]]$KnowlegeBaseId
)
begin
{
$patchCounts = @{}
$patchCounts.Add($ComputerName, @{})
}
process
{
$hklm = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $ComputerName)
$uninstallKey = $hklm.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", $false)
# enumerate each uninstall key
foreach( $keyName in $uninstallKey.GetSubKeyNames() )
{
# open the key
$key = $uninstallKey.OpenSubKey($keyName, $false)
# get the display name value
$displayNameValue = $key.GetValue("DisplayName")
if($key)
{
$key.Close()
if( $server.Farm.BuildVersion.Major -gt 14 )
{
$key.Dispose()
}
}
# enumerate each of KBs we are searching for
foreach( $kb in $KnowlegeBaseId )
{
if( $displayNameValue -and $displayNameValue -match $kb )
{
if(-not $patchCounts[$ComputerName].ContainsKey($displayNameValue))
{
# first time we matched, start the hit count at 1
$patchCounts[$computerName].Add($displayNameValue, 1)
}
else
{
# increment the hit count
$patchCounts[$computerName][$displayNameValue] = $patchCounts[$computerName][$displayNameValue] + 1
}
}
}
}
}
end
{
if($uninstallKey)
{
$uninstallKey.Close()
if( $server.Farm.BuildVersion.Major -gt 14 )
{
$uninstallKey.Dispose() # framework 2.x doesn't contain a .Dispose() method
}
}
if($hklm)
{
$hklm.Close()
if( $server.Farm.BuildVersion.Major -gt 14 )
{
$hklm.Dispose() # framework 2.x doesn't contain a .Dispose() method
}
}
$patchCounts.GetEnumerator() | % {
New-Object PSObject -Property @{
"ComputerName" = $_.Name;
"PatchName" = $_.Value.GetEnumerator() | % { $_.Name }
"ReferenceCount" = $_.Value.GetEnumerator() | % { $_.Value }
}
}
}
}
$kbs = @("4011599")
# query all the SharePoint servers in the farm
foreach( $ervers in Get-SPServer | ? { $_.Role -ne "Invalid" } )
{
Get-PatchReferenceCount -ComputerName $server.Name -KnowlegeBaseId $kbs | SELECT ComputerName, PatchName, ReferenceCount
}
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
06-05-2015 - Created
Purpose: This script will attempt to extract the following information from content
databases upgrade logs and create a consolidated csv that can be used for
reporting purposes:
- Start Time
- End Time
- Elapsed Time
- Success or Error Summary
- Content Database Name
- Upgrade Log Name
- Server
- User
Note: This script will only work with SharePoint 2013, the upgrade log file format
is different in SharePoint 2010.
==============================================================#>
$upgradeLogDirectory = "C:\LogFiles\PostUpgradeLogCollection"
$outputFile = "UpgadeLogInfoSummary_{0}.csv" -f [DateTime]::Now.ToString("yyyy-MM-dd_hh-mm-ss")
<############ YOU SHOULD NOT HAVE TO MODIFY ANYTHING BELOW THIS POINT ############>
$results = @()
$upgradeLogs = Get-ChildItem $upgradeLogDirectory -Filter "Upgrade-????????-??????-???.log" -Recurse | % { $_.FullName }
Write-Host "Discovered $($upgradeLogs.Count) Upgrade Logs"
$upgradeLogs | % {
$logfilePath = $_
Write-Host "`tProcessing: $logfilePath"
# suck in the log file
$fileContent = Get-Content $logfilePath | ConvertFrom-Csv -Delimiter "`t" -Header "Timestamp","Process","TID","Area","Category","EventID","Level","Message","Correlation"
$farmRecord = $fileContent | ? { $_.EventID -match 'ajxlv' } | SELECT -First 1
if($farmRecord)
{
# we are only looking for content database upgrade logs
return
}
# query the contents by EventId
$userRecord = $fileContent | ? { $_.EventID -match 'ajxlp' } | SELECT -First 1
$machineRecord = $fileContent | ? { $_.EventID -match 'ajxlq' } | SELECT -First 1
$databaseNameRecord = $fileContent | ? { $_.EventID -match 'ajxlw' } | SELECT -First 1
$successRecord = $fileContent | ? { $_.EventID -match 'ajxl2' } | SELECT -First 1
$errorRecord = $fileContent | ? { $_.EventID -match 'ajxl5' } | SELECT -First 1
# upgrade start time
$startTime = [System.DateTime]::Parse($userRecord.Timestamp)
# User: CONTOSO\Administrator
$userName = $userRecord.Message.Replace("User: ", "")
# Current Machine: SP01
$machineName = $machineRecord.Message.Replace("Current Machine: ", "")
# Upgrade Content Database [SPContentDatabase Name=SP2013_CONTENT_UPGRADE_020] from Build [15.0.4569.1000]
$databaseName = $databaseNameRecord.Message.Replace("Upgrade Content Database [SPContentDatabase Name=", "")
$databaseName = $databaseName.Substring(0, $databaseName.IndexOf("]"))
if($successRecord)
{
# result
$result = $successRecord.Message
# upgrade stop time
$endTime = [System.DateTime]::Parse($successRecord.Timestamp)
}
else
{
# result
$result = $errorRecord.Message
# upgrade stop time
$endTime = [System.DateTime]::Parse($errorRecord.Timestamp)
}
# calculate elapsed upgrade time
$elapsedUpgradeTime = New-TimeSpan -Start $startTime -End $endTime
# log it
$results += New-Object PSObject -Property @{
LogFile = $logfilePath;
StartTime = $startTime;
EndTime = $endTime;
User = $userName;
Server = $machineName;
Database = $databaseName;
Results = $result;
ElapsedTime = $elapsedUpgradeTime;
}
}
$results | Export-Csv -Path $outputFile -NoTypeInformation
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
07-22-2015 - Created
07-23-2015 - Fixed a bug that prevented the services from restarting
07-23-2015 - Update to force a local product refresh after patch installation
07-23-2015 - Update to write server & patch info to the screen as installation jobs complete.
07-24-2015 - Update to allow the user to specify the number of concurrent servers
07-24-2015 - Update to allow the user to specify a group of servers to patch
07-28-2015 - Update to disable the service before stopping it, to prevent it from restarting before being disabled.
07-29-2015 - Update to stop/start search services only on search component hosts machines
08-15-2015 - Update to logging, reg updates and fixed a bug with session disposal
10-29-2015 - Fixed a bug causing search services to not stop when specified
10-29-2015 - Added event log logging to track starting/stopping of windows services
02-19-2016 - Added the capability to specify a PS configuration, which is usefull when running on 2010 servers that need to start PS v2
02-19-2016 - Updated to prevent the failure of search services stopping if running on SharePoint 2010
03-17-2016 - Added ablity to add multiple arguments to installer
11-28-2016 - Updated code to make PowerShell Remoting use the server's FQDN instead of netbios name
08-22-2018 - Added retry logic to New-PSSession calls to improve quality of script
10-01-2019 - Added some additional support for stoping/starting search services on 2013/2016/2019 farms
10-01-2019 - Added some additional support multiple SSA servers
NOTE: You MUST enable CredSSP authentication on all SharePoint servers in the farm before executing
this script.
To enable CredSSP authentication, execute the following:
Enable-WSManCredSSP -Role Server
To disable CredSSP authentication, execute the following:
Disable-WSManCredSSP -Role Server
See the following for more information:
https://technet.microsoft.com/en-us/library/hh849872.aspx
SharePoint 2010 Note: To add a PS v2.0 configuration, run the following on all SharePoint servers
Register-PSSessionConfiguration -Name "PowerShellv2Config" -PowerShellVersion 2.0 -Confirm:$false
==============================================================#>
# an ordered list of patch path and patch arguments
$patchesToInstall = New-Object System.Collections.Specialized.OrderedDictionary
<#
Examples on how to add patches to the queue:
# May 2014 CU UNC
$patchesToInstall.Add("\\dc01\_binaries\Software\Microsoft\SharePoint\2013\SharePointUpdates\2014_05_CU\ubersrv.exe", "/log:$env:TEMP\Logfilename.txt /quiet")
# June 2014 CU UNC
$patchesToInstall.Add("\\dc01\_binaries\Software\Microsoft\SharePoint\2013\SharePointUpdates\2014_06_CU\ubersrv.exe", "/quiet")
# June 2014 CU Local
$patchesToInstall.Add("C:\_SharePointUpdates\2014_08_CU\ubersrv.exe", "/quiet")
#>
# March 2016 CU from a UNC
$patchesToInstall.Add( "\\dc01\_binaries\Software\Microsoft\SharePoint\2013\SharePointUpdates\2017_08_CU\ubersrv2013-kb4011076-fullfile-x64-glb.exe", "/log:E:\LogFiles\ULS\ubersrv2013-kb4011076-fullfile-x64-glb.log /quiet")
# leave this empty or $null to not specify a PSSession configuation option
$ConfigurationName = $null # "PowerShellv2Config"
# log file name
$outputFile = "Install-SharePointUpdate.Results.{0}.csv" -f [DateTime]::Now.ToString("yyyy-MM-dd_hh-mm-ss")
# stop IIS, Timer Service and search services on servers prior to patch install and restart afterwards
$stopServicesDuringPatching = $true
# If you do not want to install updates on ALL of the servers in parallel, set this variable to the
# the number of parallel installations you desire. Setting the value to zero (0) will
# remove the limit and install on all the servers in the farm in parallel
$numberOfConcurrentInstallations = 0
# if you only want to patch a specific group of servers, list names below.
# leave this array empty to patch all of the servers in the farm.
# Example:
# $specificServersToPatch = @( "SP01", "SP02" )
$specificServersToPatch = @()
<############ YOU SHOULD NOT HAVE TO MODIFY ANYTHING BELOW THIS POINT ############>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
$patchInstallationJobs = $results = @()
filter Write-ColorCodedTable {
param
(
[HashTable]$ColorMappings
)
$ForegroundColor = "White" # default color for table headers
if( $Host.UI.RawUI.ForegroundColor -eq -1 )
{
$ForegroundColor = $Host.UI.RawUI.ForegroundColor
}
$lines = $_ -split '\r\n'
foreach( $line in $lines )
{
$ColorMappings.GetEnumerator() | % {
if($line -match $_.Key)
{
$ForegroundColor = $_.Value
}
}
Write-host $line -ForegroundColor $ForegroundColor
}
}
$returnCodeInfo = @{
0 = "Installed Successfully";
# Windows Return Codes
# http://support.microsoft.com/kb/229683
1603 = "Fatal error during installation";
1618 = "Another installation is already in progress. Complete that installation before proceeding with this install.";
1622 = "Error opening installation log file. Verify that the specified log file location exists and is writable"
1623 = "This language of this installation package is not supported by your system"
1625 = "This installation is forbidden by system policy. Contact your system administrator."
1632 = "The temp folder is either full or inaccessible. Verify that the temp folder exists and that you can write to it";
1633 = "This installation package is not supported on this platform";
1635 = "This patch package could not be opened. Verify that the patch package exists and that you can access it";
1636 = "This patch package could not be opened.";
1638 = "Another version of this product is already installed.";
1639 = "Invalid command line argument";
3010 = "A restart is required to complete the install.";
# Office Return Codes
# http://technet.microsoft.com/en-us/library/cc179058(v=office.14).aspx
17301 = "Error: General Detection error";
17302 = "Error: Applying patch";
17303 = "Error: Extracting file";
17022 = "Success: Reboot Required";
17023 = "Error: User cancelled installation";
17024 = "Error: Creating folder failed";
17025 = "Patch already installed";
17026 = "Patch already installed to admin installation";
17027 = "Installation source requires full file update";
17028 = "No product installed for contained patch";
17029 = "Patch failed to install";
17030 = "Detection: Invalid CIF format";
17031 = "Detection: Invalid baseline";
17034 = "Error: Required patch does not apply to the machine";
17038 = "You do not have sufficient privileges to complete this installation for all users of the machine. Log on as administrator and then retry this installation.";
17044 = "Installer was unable to run detection for this package.";
17048 = "This installation requires Windows Installer 3.1 or greater.";
}
$InstallSharePointUpdateScriptBlock = {
param(
[Parameter(Mandatory=$true)][string]$ComputerName,
[Parameter(Mandatory=$true)][string]$FilePath,
[Parameter(Mandatory=$true)][string[]]$ArgumentList,
[Parameter(Mandatory=$false)][string]$Configuration,
[Parameter(Mandatory=$false)][switch]$StopServices,
[Parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential)
begin
{
$serverFQDN = [System.Net.Dns]::GetHostByName($ComputerName).HostName
function Write-EventLogEntry
{
[cmdletbinding()]
param(
[string]$ComputerName = $env:COMPUTERNAME,
[string]$Message = "",
[System.Diagnostics.EventLogEntryType]$EntryType = "Information"
)
if(-not [System.Diagnostics.EventLog]::SourceExists("SharePoint Patch Installation Script", $ComputerName))
{
# write a start entry in the application event log
New-EventLog -LogName Application -Source "SharePoint Patch Installation Script" -ComputerName $ComputerName
}
Write-EventLog -ComputerName $ComputerName -LogName Application -Source "SharePoint Patch Installation Script" -EntryType $EntryType -EventID 1001 -Message $Message
}
function Stop-WindowsService()
{
[cmdletbinding()]
param(
[string]$ServiceName,
[string]$ComputerName,
[switch]$Disable
)
Get-Service -Name $ServiceName -ComputerName $ComputerName -ErrorAction SilentlyContinue | % {
if($Disable.IsPresent)
{
Write-EventLogEntry -ComputerName $ComputerName -EntryType Information -Message "$Servicename - Disabling Service"
$_ | Set-Service -StartupType Disabled
$stopWatch = [Diagnostics.Stopwatch]::StartNew()
do
{
Start-Sleep -Seconds 1
$svc = Get-WmiObject -Class Win32_Service -Property StartMode -Filter "Name=`"$ServiceName`"" -ComputerName $ComputerName
}
while( $svc.StartMode -ne "Disabled" -and $stopWatch.Elapsed.Seconds -le 10 )
$svc = Get-WmiObject -Class Win32_Service -Property StartMode -Filter "Name=`"$ServiceName`"" -ComputerName $ComputerName
Write-EventLogEntry -ComputerName $ComputerName -EntryType Information -Message "$Servicename - Startup Type $($svc.StartMode)"
}
if($_.Status -ne "Stopped")
{
Write-EventLogEntry -ComputerName $ComputerName -EntryType Information -Message "$ServiceName - Attempting to Stop Service"
$_.Stop()
$_.WaitForStatus("Stopped", (New-TimeSpan -Seconds 180))
}
Write-EventLogEntry -ComputerName $ComputerName -EntryType Information -Message "$Servicename - Service Status is $($_.Status)"
}
}
function Start-WindowsService()
{
[cmdletbinding()]
param(
[string]$ServiceName,
[string]$ComputerName,
[string]$StartupType
)
Get-Service -Name $ServiceName -ComputerName $ComputerName | % {
$_ | Set-Service -StartupType $StartupType
$stopWatch = [Diagnostics.Stopwatch]::StartNew()
if($StartupType -eq "Automatic"){ $StartupType = "Auto" }
do
{
Start-Sleep -Seconds 1
$svc = Get-WmiObject -Class Win32_Service -Property StartMode -Filter "Name=`"$ServiceName`"" -ComputerName $ComputerName
}
while( $svc.StartMode -ne $StartupType -and $stopWatch.Elapsed.Seconds -le 10 )
$svc = Get-WmiObject -Class Win32_Service -Property StartMode -Filter "Name=`"$ServiceName`"" -ComputerName $ComputerName
Write-EventLogEntry -ComputerName $ComputerName -EntryType Information -Message "$Servicename - Startup Type is $($svc.StartMode)"
if($_.Status -ne "Running")
{
Write-EventLogEntry -ComputerName $ComputerName -EntryType Information -Message "$ServiceName - Starting Service"
$_.Start()
$_.WaitForStatus("Running", (New-TimeSpan -Seconds 180))
}
Write-EventLogEntry -ComputerName $ComputerName -EntryType Information -Message "$ServiceName - Service Status is $($_.Status)"
}
}
function New-CredSSPPSSessionWithRetry
{
[cmdletbinding()]
param(
[Parameter(Mandatory=$true)][string]$ComputerName,
[Parameter(Mandatory=$false)][string]$Configuration,
[Parameter(Mandatory=$true)][System.Management.Automation.PSCredential]$Credential
)
begin
{
$session = $null
$sessionFailure = $null
}
process
{
for($retry = 0; $retry -le 5; $retry++)
{
try
{
if( $Configuration )
{
$session = New-PSSession -ComputerName $ComputerName -Authentication CredSSP -Credential $Credential -ErrorAction SilentlyContinue -ErrorVariable sessionFailure -ConfigurationName $Configuration
}
else
{
$session = New-PSSession -ComputerName $ComputerName -Authentication CredSSP -Credential $Credential -ErrorAction SilentlyContinue -ErrorVariable sessionFailure
}
if ($session)
{
break
}
Write-Warning -Message "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. Retrying in 30 seconds."
}
catch
{
Write-Warning -Message "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. Retrying in 30 seconds."
}
Start-Sleep -Seconds 30
}
if( -not $session )
{
Write-Error "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. $($_.Exception)"
throw $sessionFailure
}
}
end
{
$session
}
}
$hostsSearchComponent = $false
# this script block is used to execute the installation on the target machine
$scriptBlock = {
param(
[Parameter(Mandatory=$true)][string]$FilePath,
[Parameter(Mandatory=$true)][string[]]$ArgumentList
)
function Write-EventLogEntry
{
param(
[string]$Message,
[System.Diagnostics.EventLogEntryType]$EntryType = "Information"
)
$eventCategory = "SharePoint Patch Installation Script"
$eventID = 1001
if(-not [System.Diagnostics.EventLog]::SourceExists($eventCategory))
{
# write a start entry in the application event log
New-EventLog -LogName Application -Source $eventCategory
}
Write-EventLog -LogName Application -Source $eventCategory -EntryType $EntryType -EventID $eventID -Message $Message
}
$exitCode = -1
try
{
# Increase the max memory size for the session to 1GB
Set-Item -Path WSMan:\localhost\Shell\MaxMemoryPerShellMB -Value 2048
# Add the Microsoft.SharePoint.PowerShell snap-in into the current remote session
Add-PsSnapin Microsoft.SharePoint.PowerShell | Out-Null
# this will prevent the O/S from prompting the user to run .exe files from a UNC path
New-Item -Path "HKCU:\Software\Microsoft\Windows\Currentversion\Policies\Associations" -ErrorAction SilentlyContinue | Out-null
$lowRiskFileTypes = $(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\Associations" -Name LowRiskFileTypes -ErrorAction SilentlyContinue).LowRiskFileTypes
if($lowRiskFileTypes -notmatch ".exe"){
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies\Associations" -Name LowRiskFileTypes -Value ".exe;$lowRiskFileTypes" -ErrorAction SilentlyContinue -Force | Out-Null
}
Write-EventLogEntry -Message "SharePoint Patch Installation Starting.`nCurrent User:$env:USERDOMAIN\$env:USERNAME`nInstallation syntax: $FilePath $ArgumentList"
# you can use this for debugging/testing without having to install a patch
if($false) # $true == debug mode
{
# this is used for testing/debugging to vary the thread execution time
Start-Sleep -Seconds $(Get-Random -Minimum 5 -Maximum 15)
$exitCode = "DEBUG MODE"
}
else
{
$process = Start-Process -FilePath $FilePath -ArgumentList $ArgumentList -Wait -PassThru
$exitCode = $process.ExitCode
# force the SharePoint server to refresh the list of installed products, this will prevent
# PSConfig from incorrectly thinking a patch is missing
$refreshed = Get-SPProduct -local
}
}
catch
{
Write-EventLogEntry -Message "SharePoint Patch Installation Exception.`n`n$($_.Exception.ToString())" -EntryType Error
$exitCode = "`nFailed to install patch on $ComputerName.`n$($_.Exception.ToString())"
}
finally
{
Write-EventLogEntry -Message "SharePoint patch installation script exiting. Installation return code: $($exitCode)"
}
return $exitCode
}
}
process
{
$startTime = Get-Date
if($StopServices.IsPresent)
{
# 10/01/2019 - updated to support multiple SSAs
$hostsSearchComponent = @(Get-SPEnterpriseSearchServiceApplication | SELECT -ExpandProperty ActiveTopology | SELECT @{ Name="Components"; E={$_.GetComponents()}} | SELECT -ExpandProperty Components | SELECT -Unique -ExpandProperty ServerName) -contains $ComputerName
Stop-WindowsService -ServiceName "SPTimerV4" -ComputerName $serverFQDN -Disable
# 10/01/2019 - updated to support versions '13/'16/'19
if($hostsSearchComponent -and $(Get-SPFarm).BuildVersion.Major -eq 15 )
{
Stop-WindowsService -ServiceName "OSearch15" -ComputerName $serverFQDN -Disable
Stop-WindowsService -ServiceName "SPSearchHostController" -ComputerName $serverFQDN -Disable
}
elseif($hostsSearchComponent -and $(Get-SPFarm).BuildVersion.Major -ge 16 )
{
Stop-WindowsService -ServiceName "OSearch16" -ComputerName $serverFQDN -Disable
Stop-WindowsService -ServiceName "SPSearchHostController" -ComputerName $serverFQDN -Disable
}
}
if($ComputerName -ne $env:COMPUTERNAME)
{
# remote installation
try
{
$credSSPSettings = Get-WSManCredSSP
if( $credSSPSettings -and ( $credSSPSettings[0] -eq "The machine is not configured to allow delegating fresh credentials." -or $credSSPSettings[0] -notmatch $serverFQDN ))
{
Enable-WSManCredSSP -Role Client -DelegateComputer $serverFQDN -Force | Out-Null
Start-Sleep -Seconds 5 # avoid an update conflict with the registry
}
elseif ($VerbosePreference -eq "Continue")
{
Write-Verbose "$(Get-Date): $($MyInvocation.MyCommand.Name) - Existing CredSSP Delegations: $($credSSPSettings[0])"
}
$session = New-CredSSPPSSessionWithRetry -ComputerName $serverFQDN -Configuration $Configuration -Credential $Credential
if( -not $session ) { throw }
try
{
$patchReturnCode = Invoke-Command -Session $session -ScriptBlock $scriptBlock -ArgumentList $FilePath,$ArgumentList
}
catch
{
$patchReturnCode = $_.Exception.ToString()
}
finally
{
if($session -and $session.State -eq "Opened" -or $session.Availability -eq "Available")
{
Remove-PSSession -Session $session
}
}
}
catch
{
$patchReturnCode = "Could not establish a remote session to $serverFQDN.`n$($_.Exception.ToString())"
}
}
else
{
try
{
# local installation
$patchReturnCode = $scriptBlock.Invoke($FilePath,$ArgumentList)
}
catch
{
$patchReturnCode = "Failed to install patch on $serverFQDN.`n$($_.Exception.ToString())"
}
}
$endTime = Get-Date
if($StopServices.IsPresent)
{
# 10/01/2019 - updated to support versions '13/'16/'19
if($hostsSearchComponent -and $(Get-SPFarm).BuildVersion.Major -eq 15 )
{
Start-WindowsService -ServiceName "SPSearchHostController" -ComputerName $serverFQDN -StartupType Automatic
Start-WindowsService -ServiceName "OSearch15" -ComputerName $serverFQDN -StartupType Manual
}
elseif($hostsSearchComponent -and $(Get-SPFarm).BuildVersion.Major -ge 16 )
{
Start-WindowsService -ServiceName "SPSearchHostController" -ComputerName $serverFQDN -StartupType Automatic
Start-WindowsService -ServiceName "OSearch16" -ComputerName $serverFQDN -StartupType Manual
}
Start-WindowsService -ServiceName "SPTimerV4" -ComputerName $serverFQDN -StartupType Automatic
}
}
end
{
$result = New-Object PSObject -Property @{
StartTime = $startTime;
EndTime = $endTime;
ReturnCode = $patchReturnCode;
}
return $result
}
}
function New-SharePointUpdateInstallationJob()
{
param(
[Parameter(Mandatory=$true)][string]$ComputerName,
[Parameter(Mandatory=$true)][string]$FilePath,
[Parameter(Mandatory=$true)][string[]]$ArgumentList,
[Parameter(Mandatory=$false)][string]$Configuration,
[Parameter(Mandatory=$true)][bool]$StopServices,
[Parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
[Parameter(Mandatory=$true)][System.Management.Automation.Runspaces.RunspacePool]$RunspacePool
)
$ErrorActionPreference = "Stop"
try
{
# create an patch job
$powershell = [System.Management.Automation.PowerShell]::Create()
# set the runspace pool
$powershell.RunspacePool = $RunspacePool
# add the script block and the parameters to execute
$shell = $powershell.AddScript( $InstallSharePointUpdateScriptBlock )
$shell = $powershell.AddParameter( "ComputerName", $ComputerName)
$shell = $powershell.AddParameter( "FilePath", $FilePath )
$shell = $powershell.AddParameter( "ArgumentList", $ArgumentList )
$shell = $powershell.AddParameter( "StopServices", $StopServices )
$shell = $powershell.AddParameter( "Credential", $Credential )
$shell = $powershell.AddParameter( "Configuration",$Configuration )
return $powershell
}
catch
{
throw $_.Exception
}
finally
{
$ErrorActionPreference = "Continue"
}
}
function New-RunspacePool()
{
param([Parameter(Mandatory=$true)][int]$MaxRunspaces)
$ErrorActionPreference = "Stop"
try
{
$warning = ""
# create a shared session state that imports the SharePoint Snap-In
$defaultSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$snapInInfo = $defaultSessionState.ImportPSSnapIn( "Microsoft.SharePoint.PowerShell", [ref]$warning )
$defaultSessionState.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::UseNewThread
# create the runspace pool that will be unique for all of the upgrade jobs
$runspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool($defaultSessionState)
$added = $runspacePool.SetMinRunspaces(1)
$added = $runspacePool.SetMaxRunspaces($MaxRunspaces)
$runspacePool.Open()
return $runspacePool
}
catch
{
throw $_.Exception
}
finally
{
$ErrorActionPreference = "Continue"
}
}
$resultColorHash = @{
"Successfully" = "Green"
"Reboot" = "Yellow"
"Restart" = "Yellow"
"Error" = "Red"
"Failed" = "Red"
"Invalid" = "Red"
}
if( (-not $numberOfConcurrentInstallations) -or $numberOfConcurrentInstallations -lt 1)
{
# make the number of concurrent threads equal to the number of SharePoint servers in the farm
$numberOfConcurrentInstallations = $(Get-SPServer | ? { $_.Role -ne "Invalid" }).Count
}
# credential used for remote connections
$credential = $null
try
{
# create the runspace pool
$runspacePool = New-RunspacePool -MaxRunspaces $numberOfConcurrentInstallations
Write-Host "`nPatch Installation Job Queue" -ForegroundColor Green
# enumerate all the SharePoint Servers in the farm
Get-SPServer | ? { $_.Role -ne "Invalid" } | SORT Name | % {
# See if we need a credential for a remote connection
if($credential -eq $null -and $_.Address -ne $env:COMPUTERNAME)
{
# get a credential for remote installations
$credential = Get-Credential -Credential "$env:USERDOMAIN\$env:USERNAME"
}
# processsing server
$serverName = $_.Address
# restrict patching to the list of servers, if specified
if($specificServersToPatch.Count -eq 0 -or $specificServersToPatch -contains $serverName)
{
# enumerate all the patches and create an installation job for each patch on each server.
$patchesToInstall.GetEnumerator() | % {
# get the patch and patch args
$filePath = $_.Key
$arguments = $_.Value
# don't crush the machine with starting jobs.
Start-Sleep -Seconds 3
Write-Host "`t$(Get-Date) - $($serverName): Created job for $($filePath.Split('\')[-1])"
# create a new patch installation job
$sharePointUpdateInstallationJob = New-SharePointUpdateInstallationJob `
-ComputerName $serverName `
-FilePath $filePath `
-ArgumentList $arguments `
-StopServices:$stopServicesDuringPatching `
-Credential $credential `
-RunspacePool $runspacePool `
-Configuration $ConfigurationName
# add the job to our job list and start the job
$patchInstallationJobs += New-Object PSObject -Property @{
PowerShell = $sharePointUpdateInstallationJob
Runspace = $sharePointUpdateInstallationJob.BeginInvoke()
ComputerName = $serverName;
PatchName = $filePath.Split('\')[-1]
Result = $null;
}
}
}
}
Write-Host "`nPatch Installation Progress" -ForegroundColor Green
$lastCheckedCompletedInstallationJobs = @()
# keep looping until all the installation jobs are complete
do
{
# pause for a few seconds between checks for completed jobs
Start-Sleep -Seconds 5
# get a list of all the completed computers
$completedInstallationJobs = @($patchInstallationJobs | ? { $_.Runspace.IsCompleted })
# check if any more jobs completed since last check
if($lastCheckedCompletedInstallationJobs.Count -ne $completedInstallationJobs.Count)
{
# print out the computer names for each job that completed since the last check
$completedInstallationJobs | ? { $lastCheckedCompletedInstallationJobs -notcontains $_ } | % {
Write-Host "`t$(Get-Date) - $($_.ComputerName): Installation of '$($_.PatchName)' has completed."
}
# update the list of completed jobs
$lastCheckedCompletedInstallationJobs = $completedInstallationJobs
# update the progress bar
Write-Progress `
-Activity "Patch Installation Progress" `
-Status "Progress: $($completedInstallationJobs.Count) of $($patchInstallationJobs.Count) Installations Completed" `
-PercentComplete $(($($completedInstallationJobs.Count)/$($patchInstallationJobs.Count))*100)
}
}
while( $patchInstallationJobs | ? { !$_.Runspace.IsCompleted } )
}
catch
{
Write-Host "`t`tError: Error installating SharePoint Update" -ForegroundColor Red
Write-Host "`t`tDetails: $($_.Exception.ToString())" -ForegroundColor Red
return
}
finally
{
if( $runspacePool -and $runspacePool.RunspacePoolStateInfo -and $runspacePool.RunspacePoolStateInfo.State -eq [System.Management.Automation.Runspaces.RunspaceState]::Opened)
{
$runspacePool.Dispose()
}
}
# collect the job results
$patchInstallationJobs | % {
$_.Result = $_.PowerShell.EndInvoke($_.Runspace)
}
# build the results summary log
$patchInstallationJobs | % {
# default return message
$resultMessage = "Unknown Result Code Returned"
$returnCode = $_.Result | % { $_.ReturnCode }
# check for the message that maps to this code
if($returnCodeInfo.ContainsKey($returnCode))
{
# found it, update the return message
$resultMessage = $returnCodeInfo[$returnCode]
}
# save the to data to our object arrray
$results += New-Object PSObject -Property @{
"Computer Name" = $_.ComputerName;
"Patch Name" = $_.PatchName;
"Result Message" = $resultMessage;
"Return Code" = $_.Result | % { $_.ReturnCode }; # hack for PowerShell 2.0
"Start Time" = $_.Result | % { $_.StartTime }; # hack for PowerShell 2.0
"End Time" = $_.Result | % { $_.EndTime }; # hack for PowerShell 2.0
}
}
Write-Host "`nInstallation Summary" -ForegroundColor Green
# write out the results to the screne and .csv file
$results | Sort "Computer Name" | FT "Computer Name", "Result Message", "Start Time", "End Time" -AutoSize | Out-String | Write-ColorCodedTable -ColorMappings $resultColorHash
$results | Export-Csv -Path $outputFile -NoTypeInformation -Force
if( $outputFile.IndexOfAny("\") -gt 0 )
{
# path contains full path
Write-Host "`nUpgrade results written to $outputFile`n"
}
else
{
# include full path to file
Write-Host "`nUpgrade results written to $((Get-Item -Path ".\" -Verbose).FullName)\$outputFile`n"
}
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
08-18-2015 - Created
09-10-2015 - Simplification
10-29-2015 - Added logging to show if a server is skipped because of it's installation status
01-19-2016 - Updated PSConfig execution arguments to include -cmd applicationcontent -install -cmd installfeatures -cmd secureresources
10-01-2016 - Added a summary table of results after execution finishes and also writes the results to a log file in the ULS logs
directory for reference
11-28-2016 - Updated code to make PowerShell Remoting use the server's FQDN instead of netbios name
08-22-2017 - Added retry logic to New-PSSession calls to improve quality of script
07-30-2018 - Closed pssession
SUMMARY:
This script will execute PSCONFIG on all servers in the farm that have a status of "Needs Upgrade"
REQUIREMENTS:
You MUST enable CredSSP authentication on all SharePoint servers in the farm before executing this script.
To enable CredSSP authentication, execute the following:
Enable-WSManCredSSP -Role Server
To disable CredSSP authentication, execute the following:
Disable-WSManCredSSP -Role Server
See the following for more information:
https://technet.microsoft.com/en-us/library/hh849872.aspx
SharePoint 2010 Note: To add a PS v2.0 configuration, run the following on all SharePoint servers
Register-PSSessionConfiguration -Name "PowerShellv2Config" -PowerShellVersion 2.0 -Confirm:$false
==============================================================#>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
# leave this empty or $null to not specify a PSSession configuation option
$ConfigurationName = "" # "PowerShellv2Config"
# build the version specific path to psconfig.exe
$configWizardPath = "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\$($(Get-SPFarm).BuildVersion.Major)\BIN\PSCONFIG.EXE"
# upgrade command arguments
$configWizardArgs = "-cmd upgrade -inplace b2b -wait -cmd applicationcontent -install -cmd installfeatures -cmd secureresources"
# credential used for remote connections
$credential = $null
$serverCount = 0
$results = @()
function New-CredSSPPSSessionWithRetry
{
[cmdletbinding()]
param(
[Parameter(Mandatory=$true)][string]$ComputerName,
[Parameter(Mandatory=$false)][string]$Configuration,
[Parameter(Mandatory=$true)][System.Management.Automation.PSCredential]$Credential
)
begin
{
$session = $null
$sessionFailure = $null
}
process
{
for($retry = 0; $retry -le 5; $retry++)
{
try
{
if( $Configuration )
{
$session = New-PSSession -ComputerName $ComputerName -Authentication CredSSP -Credential $Credential -ErrorAction SilentlyContinue -ErrorVariable sessionFailure -ConfigurationName $Configuration
}
else
{
$session = New-PSSession -ComputerName $ComputerName -Authentication CredSSP -Credential $Credential -ErrorAction SilentlyContinue -ErrorVariable sessionFailure
}
if ($session)
{
break
}
Write-Warning -Message "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. Retrying in 30 seconds."
}
catch
{
Write-Warning -Message "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. Retrying in 30 seconds."
}
Start-Sleep -Seconds 30
}
if( -not $session )
{
Write-Error "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. $($_.Exception)"
throw $sessionFailure
}
}
end
{
$session
}
}
Write-Host "`n"
# enumerate all the SharePoint Servers in the farm
Get-SPServer | ? { $_.Role -ne "Invalid" } | Sort $_.Name | % {
$serverCount++
$serverName = $_.Name
$productVersions = [Microsoft.SharePoint.Administration.SPProductVersions]::GetProductVersions()
$installationStatus = $productVersions.GetServerProductInfo($_.Id).InstallStatus
$invokeResult = "Completed"
$invokeError = $null
$serverNameFQDN = [System.Net.Dns]::GetHostByName($serverName).HostName
# $installationStatus = "UpgradeRequired" # FORCE PSCONFIG TO RUN
# if the server has a status of "Upgrade Required" or "Upgrade Available" run PSConfig on it
if( @("UpgradeRequired", "UpgradeAvailable") -contains $installationStatus )
{
Write-Host "$serverCount`t$(Get-Date) - $($serverNameFQDN): Executing PSCONFIG." -NoNewline
# build the invoke-command parameter set
$params = @{
ArgumentList = $configWizardPath,$configWizardArgs;
ScriptBlock = {
param( [string]$FilePath, [string]$ArgumentList )
Start-Process -FilePath $FilePath -ArgumentList $ArgumentList -Wait
}
}
if($serverName -ne $env:COMPUTERNAME)
{
$credSSPSettings = Get-WSManCredSSP
if( $credSSPSettings -and ( $credSSPSettings[0] -eq "The machine is not configured to allow delegating fresh credentials." -or $credSSPSettings[0] -notmatch $serverNameFQDN ))
{
Enable-WSManCredSSP -Role Client -DelegateComputer $serverNameFQDN -Force | Out-Null
Start-Sleep -Seconds 5 # avoid an update conflict with the registry
}
elseif ($VerbosePreference -eq "Continue")
{
Write-Verbose "$(Get-Date): $($MyInvocation.MyCommand.Name) - Existing CredSSP Delegations: $($credSSPSettings[0])"
}
# make sure we credssp delegation is enabled to the remote server
if(-not $credential)
{
$farmAccountDomainUserName = (Get-SPFarm).TimerService.ProcessIdentity.Username
# get a windows credential for remote executions
$credential = Get-Credential -Credential $farmAccountDomainUserName
}
$session = New-CredSSPPSSessionWithRetry -ComputerName $serverNameFQDN -Configuration $ConfigurationName -Credential $credential
$params["Session"] = $session
}
$executionTime = Measure-Command { $result = Invoke-Command @params -ErrorVariable "invokeError" -ErrorAction SilentlyContinue }
if($invokeError.Count -ne 0)
{
$invokeResult = $invokeError[0]
Write-Host " Error: $invokeResult"
}
else
{
Write-Host " Completed in $($executionTime.TotalMinutes.ToString('0.00')) minutes"
}
$results += New-Object PSObject -Property @{
ComputerName = $serverNameFQDN
TotalMinutes = $executionTime.TotalMinutes.ToString("0.00")
Result = $invokeResult
}
}
else
{
$results += New-Object PSObject -Property @{
ComputerName = $serverNameFQDN
TotalMinutes = "0.00"
Result = "SKIPPED - Patch Status: $installationStatus"
}
Write-Host "$serverCount`t$(Get-Date) - $($serverNameFQDN): SKIPPED - Patch Status: $installationStatus" -ForegroundColor Yellow
}
if($session -and $session.State -eq "Opened" -or $session.Availability -eq "Available")
{
Remove-PSSession -Session $session
}
}
Write-Host ""
$results | SORT ComputerName | FT ComputerName, TotalMinutes, Result -AutoSize
$results | Export-Csv -Path "$([System.Environment]::ExpandEnvironmentVariables($(Get-SPDiagnosticConfig).LogLocation))\PSConfigExecutionSummary_$($(Get-Date).ToString('yyyyMMdd')).csv" -NoTypeInformation
filter Write-ColorCodedTable {
$defaultForegroundColor = "White" # default color for table headers
$ColorMappings = [ordered]@{
"Upgrading" = "Cyan" # show databases being upgraded in cyan
"False" = "Green" # show upgraded databases in green
"True" = "Yellow" # show non-upgraded databases in yellow
}
if( $Host.UI.RawUI.ForegroundColor -eq -1 )
{
$defaultForegroundColor = $Host.UI.RawUI.ForegroundColor
}
$lines = $_ -split '\r\n'
foreach( $line in $lines )
{
$ForegroundColor = $defaultForegroundColor
foreach( $mapping in $ColorMappings.GetEnumerator() )
{
if($line -match $mapping.Key)
{
$ForegroundColor = $mapping.Value
break;
}
}
Write-Host $line -ForegroundColor $ForegroundColor
}
}
function Poll-ContentDatabaseUpgrade
{
do
{
$x = 0
Clear-Host
Write-Host "`n`n`n`n`n`n" # bump the table down below the progress bar
$results = Get-SPDatabase | ? { $_.Type -eq "Content Database" } | Sort NeedsUpgrade, Name | % { $x++; $_| SELECT @{ N='#'; E={$x}}, Name, Status, @{ n="SiteCount"; e={$_.Sites.Count}}, NeedsUpgrade}
$completed = (($results | ? { -not $_.NeedsUpgrade} ) | Measure-Object).Count
$total = $results.Count
$percentComplete = [Math]::Round( ($completed / $total) * 100, 2)
Write-Progress -PercentComplete $percentComplete -Activity "Content Database $percentComplete% Upgraded" -Status "$completed of $total content databases upgraded"
$results | FT * -AutoSize | Out-String | Write-ColorCodedTable
Start-Sleep -Seconds 10
}
while( $results | ? {$_.NeedsUpgrade} )
}
Poll-ContentDatabaseUpgrade
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
08-19-2015 - Created
SUMMARY:
This script will delete the specified directories from the SharePoint servers
==============================================================#>
$foldersToDelete = @(
# June 2014 CU
"C:\_SharePointUpdates\2014_06_CU\",
# July 2014 CU
"C:\_SharePointUpdates\2014_07_CU\"
)
$confirmDeletion = $true
<############ YOU SHOULD NOT HAVE TO MODIFY ANYTHING BELOW THIS POINT ############>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
Write-Host "`n"
$serverCount = 0
Get-SPServer | ? { $_.Role -ne "Invalid" } | Sort $_.Name | % {
$serverName = $_.Name
$foldersToDelete | % {
$targetDirectory = $_
# turn the local path into a UNC path
$targetDirectory = $targetDirectory.Replace(":", "$")
$targetDirectory = "\\$serverName\$targetDirectory"
Write-Host "$(Get-Date) - $($serverName): Deleting $_"
Remove-Item -Path $targetDirectory -Force -Confirm:$(-not $confirmDeletion) -Recurse -ErrorAction SilentlyContinue
}
}
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
08-18-2015 - Created
SUMMARY:
This script will restart all the servers in a farm, except the localhost. The script will poll
the WinRM service and report it's status in table format. The script will exit when the WinRM
service responds on all servers.
==============================================================#>
# refresh interval in seconds
$interval = 10
<############ YOU SHOULD NOT HAVE TO MODIFY ANYTHING BELOW THIS POINT ############>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
Write-Host "`n"
$format = "{0,-20}{1,-15}"
$origForegroundColor = $Host.UI.RawUI.ForegroundColor
$message = 'Restart Confirmation'
$question = "Please confirm you want to restart all the servers in the farm."
$choices = New-Object Collections.ObjectModel.Collection[Management.Automation.Host.ChoiceDescription]
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes'))
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&No'))
# since this is a very impactful script, confirm you want to exeucte it.
if ($Host.UI.PromptForChoice($message, $question, $choices, 1) -ne 0)
{
return
}
# reboot all the servers in the farm except the localhost
Get-SPServer | ? { $_.Role -ne "Invalid" -and $_.Name -ne $env:COMPUTERNAME } | Sort $_.Name | % {
Write-Host "$(Get-Date) - $($_.Name): Restarting"
Restart-Computer -ComputerName $_.Name -Force
Start-Sleep -Seconds 3
}
do
{
$continue = $false
# print the status info and custom table header
Write-Host "`n`nStatus Check: $((Get-Date).ToString("MM/dd/yyyy hh:mm:ss"))`n"
Write-Host $($format -f 'Computer Name', 'WinRM Status')
Write-Host $($format -f '-------------', '------------')
Get-SPServer | ? { $_.Role -ne "Invalid" } | Sort $_.Name | % {
# query the WinRM service on the machine
$winRMServiceRunning = Test-WSMan -ComputerName $_.Name -ErrorAction SilentlyContinue
# build the result object to print to the screen
$result = New-Object PSObject -Property @{ "Computer Name" = $_.Name }
if($winRMServiceRunning)
{
$result | Add-Member -MemberType NoteProperty -Name "PowerShell Status" -Value "Responding"
$Host.UI.RawUI.ForegroundColor = "Green" # print the successful results in green
}
else
{
$result | Add-Member -MemberType NoteProperty -Name "PowerShell Status" -Value "Not Responding"
$Host.UI.RawUI.ForegroundColor = "Red" # print the unsuccessful results in red
$continue = $true
}
# print the table row
Write-Host $($format -f $result.'Computer Name', $result.'PowerShell Status')
# reset the font color
$Host.UI.RawUI.ForegroundColor = $origForegroundColor
}
if($continue)
{
Start-Sleep -Seconds $interval
Clear-Host
}
}
while( $continue )
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
09-30-2019 - Created
SUMMARY:
This script will continuously query all of the servers in the farm, looking for a process with
the name specified and display the results in table format. The script will stop when the specified
process is not running on any of the servers in the farm.
==============================================================#>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
if( (Get-SPFarm).BuildVersion.Major -ge 16 )
{
$webApplications = Get-SPWebApplication
$buildVersion = (Get-SPFarm).BuildVersion.ToString()
foreach( $webApplication in $webApplications )
{
Write-Host "Updating Side by Side token to $buildVersion on $($webApplication.Url)"
$webApplication.WebService.SideBySideToken = $buildVersion
$webApplication.WebService.update()
}
}
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
08-18-2015 - Created
02-19-2016 - Added the capability to specify a PS configuration, which is usefull when running on 2010 servers that need to start PS v2
08-22-2018 - Added retry logic to New-PSSession calls to improve quality of script
SUMMARY:
This script will execute a remote call to each Sharepoint servers in the farm, except the localhost, and
return the farm build version. If the remote call succeeds, the connection will return true, otherwise false.
REQUIREMENTS:
You MUST enable CredSSP authentication on all SharePoint servers in the farm before executing this script.
To enable CredSSP authentication, execute the following:
Enable-WSManCredSSP -Role Server
To disable CredSSP authentication, execute the following:
Disable-WSManCredSSP -Role Server
See the following for more information:
https://technet.microsoft.com/en-us/library/hh849872.aspx
TIP:
On target servers, you can register a new PowerShell configuration like this:
Register-PSSessionConfiguration -Name "PowerShellv2Config" -PowerShellVersion 2.0 -Confirm:$false
==============================================================#>
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue | Out-Null
function New-CredSSPPSSessionWithRetry
{
[cmdletbinding()]
param(
[Parameter(Mandatory=$true)][string]$ComputerName,
[Parameter(Mandatory=$false)][string]$Configuration,
[Parameter(Mandatory=$true)][System.Management.Automation.PSCredential]$Credential
)
begin
{
$session = $null
$sessionFailure = $null
}
process
{
for($retry = 0; $retry -le 5; $retry++)
{
try
{
if( $Configuration )
{
$session = New-PSSession -ComputerName $ComputerName -Authentication CredSSP -Credential $Credential -ErrorAction SilentlyContinue -ErrorVariable sessionFailure -ConfigurationName $Configuration
}
else
{
$session = New-PSSession -ComputerName $ComputerName -Authentication CredSSP -Credential $Credential -ErrorAction SilentlyContinue -ErrorVariable sessionFailure
}
if ($session)
{
break
}
Write-Warning -Message "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. Retrying in 30 seconds."
}
catch
{
Write-Warning -Message "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. Retrying in 30 seconds."
}
Start-Sleep -Seconds 30
}
if( -not $session )
{
Write-Error "$(Get-Date): $($MyInvocation.MyCommand.Name) - Failure creating a session to $ComputerName. $($_.Exception)"
throw $sessionFailure
}
}
end
{
$session
}
}
function Test-SPPowerShellRemotingCredSSP
{
[cmdletbinding()]
param(
[Parameter(Mandatory=$true)][string]$ComputerName,
[Parameter(Mandatory=$false)][string]$Configuration,
[Parameter(Mandatory=$true)][System.Management.Automation.PSCredential]$Credential
)
begin
{
}
process
{
Write-Verbose "$(Get-Date): $($MyInvocation.MyCommand.Name) - Testing a remote connection to $ComputerName"
try
{
$credSSPSettings = Get-WSManCredSSP
if( $credSSPSettings -and ( $credSSPSettings[0] -eq "The machine is not configured to allow delegating fresh credentials." -or $credSSPSettings[0] -notmatch $ComputerName ))
{
$output = Enable-WSManCredSSP -Role Client -DelegateComputer $ComputerName -Force
Start-Sleep -Seconds 5 # avoid an update conflict with the registry
}
else
{
Write-Verbose "$(Get-Date): $($MyInvocation.MyCommand.Name) - Existing CredSSP Delegations: $($credSSPSettings[0])"
}
$session = New-CredSSPPSSessionWithRetry -ComputerName $ComputerName -Configuration $Configuration -Credential $Credential
Write-Verbose "$(Get-Date): $($MyInvocation.MyCommand.Name) - Executing remote session command on $ComputerName"
$buildVersion = Invoke-Command -Session $session -ScriptBlock {
Add-PsSnapin Microsoft.SharePoint.PowerShell
try
{
if(-not [System.Diagnostics.EventLog]::SourceExists("PowerShell Script"))
{
# write a test entry in the application event log
New-EventLog -LogName Application -Source "PowerShell Script" -ErrorAction SilentlyContinue
}
Write-EventLog -LogName Application -Source "PowerShell Script" `
-EntryType Information `
-EventID 1001 `
-Message "PowerShell remoting test"
}
catch
{
# not much we can do here...
}
return (Get-SPFarm).BuildVersion
}
# return true if the remote call returned a build version object that is 14+
if($buildVersion.GetType().Name -eq "Version" -and $buildVersion.Major -ge 14)
{
return $true
}
return $false
}
catch
{
Write-Error "Error establishing session to $ComputerName.`nDetails: $($_.Exception.ToString())"
return $false
}
finally
{
if($session)
{
Remove-PSSession $session
}
}
}
end
{
}
}
# leave this empty or $null to not specify a PSSession configuation option
$ConfigurationName = "" # "PowerShellv2Config"
$credential = Get-Credential -Credential "$env:USERDOMAIN\$env:USERNAME"
$results = @()
$serverCount = 1;
# get all the servers in the farm, except the local machine (you can't remote to the localhost)
foreach( $server in Get-SPServer | ? { $_.Role -ne "Invalid" -and $_.Name -ne $env:COMPUTERNAME } )
{
# issue a remote command
$connected = Test-SPPowerShellRemotingCredSSP -ComputerName $server.Name -Credential $credential -Configuration $ConfigurationName # -Verbose
# save the results
$results += New-Object PSObject -Property @{
"#" = $serverCount;
"Server Name" = $server.Name;
"Connected" = $connected;
}
$serverCount++
}
$results | FT "#", "Server Name", "Connected" -AutoSize
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
06-05-2015 - Created
Purpose: This script will attempt to extract the following information from content
databases upgrade logs and create a consolidated csv that can be used for
reporting purposes:
==============================================================#>
$sharePointServiceToWindowsServiceMappings = @{
"Search Host Controller Service" = "SPSearchHostController"
"SharePoint Server Search" = "OSearch15"
"User Profile Synchronization Service" = "FIMSynchronizationService"
"Claims to Windows Token Service" = "c2wts"
"Microsoft SharePoint Foundation Tracing" = "SPTraceV4"
"Microsoft SharePoint Foundation Timer" = "SPTimerV4"
"Microsoft SharePoint Foundation Administration" = "SPAdminV4"
"Microsoft SharePoint Foundation Sandboxed Code Service" = "SPUserCodeV4"
"Document Conversions Launcher Service" = "DCLauncher15"
"Document Conversions Load Balancer Service" = "DCLoadBalancer15"
"Distributed Cache" = "AppFabricCachingService"
"Microsoft SharePoint Foundation Web Application" = "W3SVC"
"Central Administration" = "W3SVC"
}
$serviceStatus = @()
$resultColorHash = @{
"Running" = "Green"
"Stopped" = "Red"
"Stopping" = "Yellow"
"Starting" = "Yellow"
}
filter Write-ColorCodedTable {
param
(
[HashTable]$ColorMappings
)
$ForegroundColor = "White" # default color for table headers
if( $Host.UI.RawUI.ForegroundColor -eq -1 )
{
$ForegroundColor = $Host.UI.RawUI.ForegroundColor
}
$lines = $_ -split '\r\n'
foreach( $line in $lines )
{
$ColorMappings.GetEnumerator() | % {
if($line -match $_.Key)
{
$ForegroundColor = $_.Value
}
}
Write-host $line -ForegroundColor $ForegroundColor
}
}
Get-SPServer | ? { $_.Role -ne "Invalid" } | % {
$server = $_
foreach( $svc in $_.ServiceInstances | ? { $_.Status -eq "Online" -and -not $_.Hidden } )
{
if($sharePointServiceToWindowsServiceMappings.ContainsKey($svc.TypeName))
{
$windowsService = Get-Service -ComputerName $server.Name -Name $sharePointServiceToWindowsServiceMappings[$svc.TypeName]
$serviceStatus += New-Object PSObject -Property @{
Server = $server.Name
"SharePoint Service" = $svc.TypeName
"Windows Service" = $windowsService.Name
Status = $windowsService.Status
}
}
}
}
$serviceStatus | SORT Server, "Windows Service" | FT Server, "SharePoint Service", "Windows Service", Status -AutoSize | Out-String | Write-ColorCodedTable -ColorMappings $resultColorHash
<#
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
We grant you a nonexclusive, royalty-free right to use and modify the sample code and to reproduce and distribute the object
code form of the Sample Code, provided that you agree:
(i) to not use our name, logo, or trademarks to market your software product in which the sample code is embedded;
(ii) to include a valid copyright notice on your software product in which the sample code is embedded; and
(iii) to indemnify, hold harmless, and defend us and our suppliers from and against any claims or lawsuits, including
attorneys' fees, that arise or result from the use or distribution of the sample code.
Please note: None of the conditions outlined in the disclaimer above will supercede the terms and conditions contained within
the Premier Customer Services Description.
----------------------------------------------------------
History
----------------------------------------------------------
06-05-2015 - Created
08-27-2015 - Updated error handling and added more progress reporting
09-18-2016 - Fixed a very minor write-host bug (thanks to Spence Harbar)
07-09-2018 - fixed bug which occurs when a content database by the same name exists on multiple SQL
instances and administrator does not upgrade databases by SQL instance
(i.e. db name is added to $contentDatabaseNamesToUpgrade more than once)
Purpose: This script will attempt to upgrade, in parallel, the specified content databases. The number
of parallel threads is limited to the number of logical processsors on the machine.
You can scale out this task by executing copies of this script from multiple SharePoint servers,
however each server should contain a distinct list of content databases. Ideally each SharePoint
server would also target content databases that are hosted on distinct SQL servers to
prevent a overloading a SQL server.
Note: If an upgrade session fails, you can attempt to re-run the script as is after it completes,
the script will not attempt to upgrade content databases that have NeedsUpgrade == False.
Note: Script has been successfully testing with SharePoint 2010 and SharePoint 2013.
==============================================================#>
if ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544") -ne $true)
{
Write-Host "Current PowerShell instance is not running elevated!" -ForegroundColor Yellow
Write-Host "Launching elevated PowerShell windows for script" -ForegroundColor Yellow
$currentScript = $MyInvocation.MyCommand.Definition
Start-Process "powershell.exe" -NoNewWindow -ArgumentList "Start-Process powershell.exe -Verb runAs -ArgumentList $currentScript"
Write-Host "Elevated console has been launched." -ForegroundColor Yellow
Write-Host "Exiting." -ForegroundColor Yellow
Exit
}
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
# Number of current threads to upgrade content databases in parallel.
# This is limited to a max of logical processors on the box, but the really stress will be on the SQL server
$maxUpgradeThreads = 4
# Example: upgrade specific databases
#$contentDatabaseNamesToUpgrade = @("SP2010_CONTENT_001", "SP2010_CONTENT_002", "SP2010_CONTENT_003" )
# Example: upgrade all the content databases hosted on SQL01
#$contentDatabaseNamesToUpgrade = Get-SPContentDatabase | ? { $_.Server -eq "SQL01" } | % { $_.Name }
# Example: upgrade all the content databases on the finance web application
#$contentDatabaseNamesToUpgrade = Get-SPContentDatabase | ? { $_.WebApplication.Url -match "finance.contoso.com" } | % { $_.Name }
# Example: upgrade all the content databases attached to the SharePoint farm
$contentDatabaseNamesToUpgrade = Get-SPContentDatabase | % { $_.Name }
# log file to write the results to
$outputFile = "Upgrade-ContentDatabaseB2B.results.{0}.csv" -f [DateTime]::Now.ToString("yyyy-MM-dd_hh-mm-ss")
<############ YOU SHOULD NOT HAVE TO MODIFY ANYTHING BELOW THIS POINT ############>
$startTime = Get-Date
# hack for powershell 2.0, so we always have an array of database names
if($contentDatabaseNamesToUpgrade.GetType().Name -eq "String")
{
$contentDatabaseNamesToUpgrade = @($contentDatabaseNamesToUpgrade)
}
# filter out any duplicate content database names and sort them alphabeticaly
$contentDatabaseNamesToUpgrade = $contentDatabaseNamesToUpgrade | SELECT -Unique | SORT
function Get-ProcessorCount()
{
return [int](@(Get-WmiObject -Class Win32_Processor) | Measure-Object -Property NumberOfLogicalProcessors -Sum).Sum
}
function New-ContentDatabaseUpgradeJob()
{
param(
[Parameter(Mandatory=$true)][string]$ContentDatabaseName,
[Parameter(Mandatory=$true)][string]$ContentDatabaseId,
[Parameter(Mandatory=$true)][System.Management.Automation.Runspaces.RunspacePool]$RunspacePool
)
$ErrorActionPreference = "Stop"
try
{
# create an upgrade job
$upgradeJob = [System.Management.Automation.PowerShell]::Create()
# set the runspace pool
$upgradeJob.RunspacePool = $RunspacePool
# add the script block and the parameters to execute
$powershell = $upgradeJob.AddScript( $UpgradeContentDatabaseScriptBlock )
$powershell = $upgradeJob.AddParameter( "ContentDatabaseName", $ContentDatabaseName )
$powershell = $upgradeJob.AddParameter( "ContentDatabaseId", $ContentDatabaseId )
return $upgradeJob
}
catch
{
throw $_.Exception
}
finally
{
$ErrorActionPreference = "Continue"
}
}
function New-RunspacePool()
{
param([Parameter(Mandatory=$true)][int]$MaxRunspaces)
$ErrorActionPreference = "Stop"
try
{
$warning = ""
# create a shared session state that imports the SharePoint Snap-In
$defaultSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$snapInInfo = $defaultSessionState.ImportPSSnapIn( "Microsoft.SharePoint.PowerShell", [ref]$warning )
$defaultSessionState.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::UseNewThread
# create the runspace pool that will be unique for all of the upgrade jobs
$runspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool($defaultSessionState)
$added = $runspacePool.SetMinRunspaces(1)
$added = $runspacePool.SetMaxRunspaces($MaxRunspaces)
$runspacePool.Open()
return $runspacePool
}
catch
{
throw $_.Exception
}
finally
{
$ErrorActionPreference = "Continue"
}
}
filter Write-ColorCodedTable {
param
(
[HashTable]$ColorMappings
)
$ForegroundColor = "White" # default color for table headers
if( $Host.UI.RawUI.ForegroundColor -eq -1 )
{
$ForegroundColor = $Host.UI.RawUI.ForegroundColor
}
$lines = $_ -split '\r\n'
foreach( $line in $lines )
{
$ColorMappings.GetEnumerator() | % {
if($line -match $_.Key)
{
$ForegroundColor = $_.Value
}
}
if ($ForegroundColor -eq -1)
{
$ForegroundColor = "Yellow"
}
Write-host $line -ForegroundColor $ForegroundColor
}
}
$resultColorHash = @{
# message regex = display color
"No Upgrade Required|Completed|DEBUG" = "Green"
"Error|Invalid|Failed|Unknown" = "Red"
}
$UpgradeContentDatabaseScriptBlock = {
param([string]$ContentDatabaseName,
[string]$ContentDatabaseId)
$upgradeResultMessage = "Completed"
if($false) # USED FOR TESTING AND DEBUGGING ONLY
{
$randomInt = Get-Random -Minimum 5.0 -Maximum 60.0
Start-Sleep -Seconds $randomInt
return New-Object PSObject -Property @{
ContentDatabaseName = $ContentDatabaseName;
ContentDatabaseId = $ContentDatabaseId;
ExecutionTime = $randomInt;
Result = "DEBUG/TEST EXECUTION, NO UPGRADE PERFORMED";
}
}
try
{
$contentDatabase = Get-SPContentDatabase $ContentDatabaseId
if($contentDatabase -and $contentDatabase.NeedsUpgrade)
{
$upgradeErrorMessage = $upgradeWarningMessage = ""
# we need to stagger start time to prevent upgrades from starting at the same time
# because they will have a session naming conflict and cause the upgrade to fail
Start-Sleep -Seconds $( Get-Random -Minimum 5.0 -Maximum 60.0 )
$databaseUpgradeStopWatch = Measure-Command {
$contentDatabase | Upgrade-SPContentDatabase -Confirm:$false -ErrorVariable upgradeErrorMessage -WarningVariable upgradeWarningMessage
}
}
elseif($contentDatabase -and !$contentDatabase.NeedsUpgrade)
{
# return the object showing the db doesn't need to be upgraded
return New-Object PSObject -Property @{
ContentDatabaseName = $ContentDatabaseName;
ContentDatabaseId = $ContentDatabaseId;
ExecutionTime = "0";
Result = "No Upgrade Required";
}
}
}
catch
{
$upgradeResultMessage = $_.Exception.ToString()
}
if($upgradeWarningMessage)
{
$upgradeResultMessage = $upgradeWarningMessage
}
if($upgradeErrorMessage)
{
$upgradeResultMessage = $upgradeErrorMessage
}
return New-Object PSObject -Property @{
ContentDatabaseName = $ContentDatabaseName;
ContentDatabaseId = $ContentDatabaseId;
ExecutionTime = $databaseUpgradeStopWatch.TotalSeconds.ToString("N1")
Result = $upgradeResultMessage;
}
}
# limit the number of threads to a max of the number of logical processsors on the box
if($maxUpgradeThreads -gt $(Get-ProcessorCount))
{
$maxUpgradeThreads = $logicalProcessorCount
Write-Host "`n`tLimiting max threads to the number of logical processsors ($maxUpgradeThreads).`n" -ForegroundColor Yellow
}
# start the upgrades
$databaseUpgradeJobs = $lastCheckedCompletedUpgradeJobs = $databaseUpgradeResults = @()
$databaseUpgradeProgressCounter = 0
$warning = $null
# create the run space
$runspacePool = New-RunspacePool -MaxRunspaces $maxUpgradeThreads
Write-Host "`n`nCreating Content Database Upgrade Jobs`n" -ForegroundColor Green
# enumerate all the content databases to upgrade
$contentDatabaseNamesToUpgrade | % {
$databaseName = $_
try
{
# Make sure the database exists
$contentDatabase = Get-SPContentDatabase | Where {$_.Name -eq $databaseName}
if($contentDatabase)
{
# added foreach-object to account for cases where more than one db by the same name exists
$contentDatabase | ForEach-Object {
# added to fix bug which happens when more than one database by the same name exists
$contentDatabaseId = $_.Id
Write-Host "`tQueuing upgrade job for database: $databaseName [$contentDatabaseId]"
# don't crush the machine with starting jobs.
Start-Sleep -Seconds 5
# create the upgrade job
$contentDatabaseUpgradeJob = New-ContentDatabaseUpgradeJob -ContentDatabaseName $databaseName -ContentDatabaseId $contentDatabaseId -RunspacePool $runspacePool
# add the job to our upgrade job list and start the job
$databaseUpgradeJobs += New-Object PSObject -Property @{
PowerShell = $contentDatabaseUpgradeJob;
Runspace = $contentDatabaseUpgradeJob.BeginInvoke();
ContentDatabase = $databaseName;
ContentDatabaseId = $contentDatabaseId;
UpgradeJobResult = $null;
NeedsUpgrade = $null;
}
}
}
}
catch
{
Write-Host "`t`tError: Error creating or starting upgrade job for database: $databaseName." -ForegroundColor Red
Write-host "`t`tDetails: $($_.Exception.Message)" -ForegroundColor Red
}
}
if( $databaseUpgradeJobs.Count -gt 0 )
{
Write-Host "`nDatabase Upgrade Progress`n" -ForegroundColor Green
# keep looping until all the installation jobs are complete
do
{
try
{
# pause for a few seconds between checks for completed jobs
Start-Sleep -Seconds 5
# get a list of all the completed database jobs
$completedDatabaseUpgradeJobs = @($databaseUpgradeJobs | ? { $_.Runspace.IsCompleted })
# check if any more jobs completed since last check
if($lastCheckedCompletedUpgradeJobs.Count -ne $completedDatabaseUpgradeJobs.Count)
{
# print out the computer names for each job that completed since the last check
$completedDatabaseUpgradeJobs | ? { $lastCheckedCompletedUpgradeJobs -notcontains $_ } | % {
Write-Host "`t$(Get-Date) - $($_.ComputerName): Upgrade of $($_.ContentDatabase) [$($_.ContentDatabaseId)] has completed."
}
# update the list of completed jobs
$lastCheckedCompletedUpgradeJobs = $completedDatabaseUpgradeJobs
# update the progress bar
Write-Progress `
-Activity "Content Database Upgrade Progress" `
-Status "Progress: $($completedDatabaseUpgradeJobs.Count) of $($databaseUpgradeJobs.Count) Upgrades Completed" `
-PercentComplete $(($($Progress.Count)/$($databaseUpgradeJobs.Count))*100)
}
}
catch
{
Write-Host "`t`tError: Error checking for job completion status" -ForegroundColor Red
Write-host "`t`tDetails: $($_.Exception.Message)" -ForegroundColor Red
}
}
while( $databaseUpgradeJobs | ? { !$_.Runspace.IsCompleted } )
try
{
# collect the job results
$databaseUpgradeJobs | % {
$_.UpgradeJobResult = $_.PowerShell.EndInvoke($_.Runspace)
}</