Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [5.0.0] - 2026-08-25

### Removed

- **BREAKING** — Support for SharePoint Server 2016 and 2019 (both reached end of support on 14 July 2026). SPSUpdate now targets SharePoint Server Subscription Edition only.
- The deprecated `Microsoft.SharePoint.PowerShell` PSSnapin path: `SPSUpdate.ps1` and `Invoke-SPSCommand` load the `SharePointServer` module only (idempotently, including inside the remoting HERE-STRING).
- The legacy SharePoint 2013 branches (`FileMajorPart`/`ProductMajorPart -eq 15`: `15\BIN`, the `15.0` registry hive and `OSearch15`) and the 2016/2019/SE cumulative-update detection in `Start-SPSConfigExe`, `Start-SPSConfigExeRemote` and `Start-SPSProductUpdate`.
- The `ProductVersion` parameter of `Get-SPSLocalVersionInfo` (Subscription Edition is resolved unconditionally).

### Changed

- **BREAKING** — Major version bump to `5.0.0`. `Start-SPSConfigExe`/`Start-SPSConfigExeRemote` target the `16.0` hive and `16\BIN` and run `Upgrade-SPFarm` unconditionally; `Start-SPSProductUpdate` stops `OSearch16`.

### Migration

- Users still running SharePoint Server 2016 or 2019 must use the previous major release **v4.2.0**. In particular, generating the ContentDatabase inventory (`-Action InitContentDB`) on a 2019 source farm as part of a 2019 → Subscription Edition migration must be done with v4.2.0.

## [4.2.0] - 2026-06-30

### Added
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

**SPSUpdate** is a PowerShell tool that installs SharePoint Server cumulative updates and runs the post-setup Configuration Wizard (PSConfig) across a farm. It installs the binaries, mounts/upgrades content databases in parallel, runs PSConfig on local and remote servers over CredSSP remoting, and configures the side-by-side token for zero-downtime patching.

Compatible with SharePoint Server **2016**, **2019**, and **Subscription Edition**. Requires PowerShell 5.1 or later — no DSC module needed.
Compatible with **SharePoint Server Subscription Edition**. Requires PowerShell 5.1 or later — no DSC module needed.

> SharePoint Server 2016 and 2019 reached end of support on 14 July 2026. If you still run those versions, use the previous major release [v4.2.0](https://github.com/luigilink/SPSUpdate/releases/tag/v4.2.0) — including to generate the ContentDatabase inventory (`-Action InitContentDB`) on a 2019 source farm during a 2019 → Subscription Edition migration.

## Quick links

Expand Down
62 changes: 15 additions & 47 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,24 @@
# SPSUpdate - Release Notes

## [4.2.0] - 2026-06-30

This release adds a near real-time patching dashboard: while a cumulative update is rolled
out across the farm, SPSUpdate records the progress of every phase into a shared status
store and the master assembles a self-contained, auto-refreshing HTML dashboard.

### Added

- Status store and dashboard functions: `Set-SPSUpdateStatus` / `Get-SPSUpdateStatus`
(atomic per-scope JSON store, each writer owns its file), `Get-SPSStatusCampaignPath`
(resolves `<root>\<App>-<Env>-<Farm>`), and `Export-SPSUpdateProgressReport` (renders a
self-contained HTML dashboard with overall state, per-phase sections, colored state
badges, per-item exit codes and per-sequence percentage; meta-refresh while running).
- New optional `StatusStorePath` config key (UNC share) with a local `Results\status`
fallback.
- New `-Action ResetStatus` to clear a campaign before a fresh patching round.
- `SPSUpdate.ps1` is instrumented end-to-end: ProductUpdate per server (per-setup-file
items), the four mount/upgrade sequences (per-database items and a running percentage),
the Configuration Wizard per server (local and remote), and side-by-side. The master
regenerates the dashboard on every wait-loop iteration and writes a final completed
dashboard with auto-refresh off.
- New `Test-SPSUpdateReadiness.ps1` pre-flight check (module, config, DPAPI secret,
elevation, status store write access, per-server CredSSP reachability). The status store
check probes write access **both as the current user and as the InstallAccount**, since the
upgrade sequences run as the service account and only appear on the dashboard when it can
write to the share.
- The dashboard collapses finished scopes (Done/Skipped) by default and keeps active ones
expanded (native `<details>`/`<summary>`, accessible, no JS); each collapsed line still
shows its badge, percentage and an `N/M done` count.
- Real process exit codes are surfaced: the ProductUpdate item's Exit column shows the
setup.exe code (`0`, `17022` = reboot required, ...), and the Configuration Wizard records
the psconfig.exe code in its detail (`PSConfig completed (exit 0)`).

### How to use

1. Set `StatusStorePath` to a UNC share writable by the InstallAccount from every server
(grant it **Modify** on the SMB share and NTFS — the sequence tasks run as that account).
2. Run `Test-SPSUpdateReadiness.ps1` to confirm the environment (both write probes green).
3. `SPSUpdate.ps1 -ConfigFile '<farm>.psd1' -Action ResetStatus` to start a clean campaign.
4. Open `<StatusStorePath>\<App>-<Env>-<Farm>\_dashboard.html` in a browser.
5. Run `-Action ProductUpdate` on each server, then the default master run; watch the
dashboard update itself.
## [5.0.0] - 2026-08-25

This is a **breaking** release: SPSUpdate now targets **SharePoint Server Subscription
Edition only**. SharePoint Server 2016 and 2019 both reached end of support on
14 July 2026.

### Removed

- **BREAKING** — Support for SharePoint Server 2016 and 2019. SPSUpdate now targets SharePoint Server Subscription Edition only.
- The deprecated `Microsoft.SharePoint.PowerShell` PSSnapin path: `SPSUpdate.ps1` and `Invoke-SPSCommand` load the `SharePointServer` module only (idempotently, including inside the remoting HERE-STRING).
- The legacy SharePoint 2013 branches (`FileMajorPart`/`ProductMajorPart -eq 15`: `15\BIN`, the `15.0` registry hive and `OSearch15`) and the 2016/2019/SE cumulative-update detection in `Start-SPSConfigExe`, `Start-SPSConfigExeRemote` and `Start-SPSProductUpdate`.
- The `ProductVersion` parameter of `Get-SPSLocalVersionInfo` (Subscription Edition is resolved unconditionally).

### Changed

- Bumped the module manifest to `4.2.0` and exported the new functions.
- **BREAKING** — Major version bump to `5.0.0`. `Start-SPSConfigExe`/`Start-SPSConfigExeRemote` target the `16.0` hive and `16\BIN` and run `Upgrade-SPFarm` unconditionally; `Start-SPSProductUpdate` stops `OSearch16`.

### Notes
### Migration

- Validated end-to-end on a real three-server Subscription Edition farm with an actual
cumulative update (binary install, parallel content-database upgrade, and the post-setup
Configuration Wizard, all reflected live on the dashboard).
- Users still running SharePoint Server 2016 or 2019 must use the previous major release **v4.2.0**. In particular, generating the ContentDatabase inventory (`-Action InitContentDB`) on a 2019 source farm as part of a 2019 → Subscription Edition migration must be done with v4.2.0.

A full list of changes in each version can be found in the [change log](CHANGELOG.md)
14 changes: 2 additions & 12 deletions src/Modules/SPSUpdate.Common/Private/Get-SPSLocalVersionInfo.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,13 @@
[OutputType([System.Version])]
param
(
# Parameter help description
[Parameter(Mandatory = $true)]
[ValidateSet('2016', '2019', 'SE')]
[System.String]
$ProductVersion,

# SharePoint Server Subscription Edition is the only supported product version.
[Parameter()]
[Switch]
$IsWssPackage
)

if ($ProductVersion -eq 'SE') {
$spVersion = 'Subscription Edition'
}
else {
$spVersion = $ProductVersion
}
$spVersion = 'Subscription Edition'

$productNameRegEx = "Microsoft SharePoint (Foundation|Server) $($spVersion) Core"
if ($IsWssPackage) {
Expand Down
18 changes: 6 additions & 12 deletions src/Modules/SPSUpdate.Common/Private/Invoke-SPSCommand.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,16 @@
)
$VerbosePreference = 'Continue'

# Base script to ensure the SharePoint snap-in is loaded. On SharePoint 2016/2019
# the legacy PSSnapin is required; on Subscription Edition the SharePointServer
# module is auto-loaded, so no base script is prepended.
$installedVersion = Get-SPSInstalledProductVersion
if ($installedVersion.ProductMajorPart -eq 15 -or $installedVersion.ProductBuildPart -le 12999) {
$baseScript = @"
if (`$null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue))
# Base script to ensure the SharePointServer module is loaded in the remote session.
# SharePoint Server Subscription Edition exposes its cmdlets through the SharePointServer
# module; load it idempotently before running the caller's script block.
$baseScript = @"
if (`$null -eq (Get-Module -Name SharePointServer))
{
Add-PSSnapin Microsoft.SharePoint.PowerShell
Import-Module SharePointServer -Verbose:`$false -WarningAction SilentlyContinue
}

"@
}
else {
$baseScript = ''
}

# Prepare the arguments for Invoke-Command
$invokeArgs = @{
Expand Down
22 changes: 5 additions & 17 deletions src/Modules/SPSUpdate.Common/Public/Start-SPSConfigExe.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,9 @@
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param ()

# Check which version of SharePoint is installed
$pathToSearch = 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\*\ISAPI\Microsoft.SharePoint.dll'
$fullPath = Get-Item $pathToSearch -ErrorAction SilentlyContinue | Sort-Object { $_.Directory } -Descending | Select-Object -First 1
$getSPInstalledProductVersion = (Get-Command $fullPath).FileVersionInfo

if ($getSPInstalledProductVersion.FileMajorPart -eq 15) {
$wssRegKey = 'hklm:SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\15.0\WSS'
$binaryDir = Join-Path $env:CommonProgramFiles "Microsoft Shared\Web Server Extensions\15\BIN"
}
else {
$wssRegKey = 'hklm:SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\16.0\WSS'
$binaryDir = Join-Path $env:CommonProgramFiles "Microsoft Shared\Web Server Extensions\16\BIN"
}
# SharePoint Server Subscription Edition installs under the 16.0 hive.
$wssRegKey = 'hklm:SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\16.0\WSS'
$binaryDir = Join-Path $env:CommonProgramFiles "Microsoft Shared\Web Server Extensions\16\BIN"
$psconfigExe = Join-Path -Path $binaryDir -ChildPath "psconfig.exe"

# Read LanguagePackInstalled and SetupType registry keys
Expand Down Expand Up @@ -44,10 +34,8 @@
$count++
}

# Fix for issue with psconfig on SharePoint 2019
if ($getSPInstalledProductVersion.FileMajorPart -eq 16) {
Upgrade-SPFarm -ServerOnly -SkipDatabaseUpgrade -SkipSiteUpgrade -Confirm:$false
}
# Prepare the farm for the in-place build-to-build upgrade before running psconfig.
Upgrade-SPFarm -ServerOnly -SkipDatabaseUpgrade -SkipSiteUpgrade -Confirm:$false

$stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)"
$psconfig = Start-Process -FilePath $psconfigExe `
Expand Down
24 changes: 4 additions & 20 deletions src/Modules/SPSUpdate.Common/Public/Start-SPSConfigExeRemote.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,8 @@
$InstallAccount
)

# Check which version of SharePoint is installed
$pathToSearch = 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\*\ISAPI\Microsoft.SharePoint.dll'
$fullPath = Get-Item $pathToSearch -ErrorAction SilentlyContinue | Sort-Object { $_.Directory } -Descending | Select-Object -First 1
$getSPInstalledProductVersion = (Get-Command $fullPath).FileVersionInfo

if ($getSPInstalledProductVersion.FileMajorPart -eq 15) {
$binaryDir = Join-Path $env:CommonProgramFiles "Microsoft Shared\Web Server Extensions\15\BIN"
}
else {
$binaryDir = Join-Path $env:CommonProgramFiles "Microsoft Shared\Web Server Extensions\16\BIN"
}
# SharePoint Server Subscription Edition installs under the 16.0 hive.
$binaryDir = Join-Path $env:CommonProgramFiles "Microsoft Shared\Web Server Extensions\16\BIN"
$psconfigExe = Join-Path -Path $binaryDir -ChildPath "psconfig.exe"

# Start wizard
Expand All @@ -33,11 +24,6 @@

$psconfigExe = $args[0]

# Check which version of SharePoint is installed
$pathToSearch = 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\*\ISAPI\Microsoft.SharePoint.dll'
$fullPath = Get-Item $pathToSearch -ErrorAction SilentlyContinue | Sort-Object { $_.Directory } -Descending | Select-Object -First 1
$getSPInstalledProductVersion = (Get-Command $fullPath).FileVersionInfo

Write-Verbose -Message "Starting 'Product Version Job' timer job"
$pvTimerJob = Get-SPTimerJob -Identity 'job-admin-product-version'
$lastRunTime = $pvTimerJob.LastRunTime
Expand All @@ -55,10 +41,8 @@
$count++
}

# Fix for issue with psconfig on SharePoint 2019
if ($getSPInstalledProductVersion.FileMajorPart -ne 15) {
Upgrade-SPFarm -ServerOnly -SkipDatabaseUpgrade -SkipSiteUpgrade -Confirm:$false
}
# Prepare the farm for the in-place build-to-build upgrade before running psconfig.
Upgrade-SPFarm -ServerOnly -SkipDatabaseUpgrade -SkipSiteUpgrade -Confirm:$false

$stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)"
$psconfig = Start-Process -FilePath $psconfigExe `
Expand Down
28 changes: 5 additions & 23 deletions src/Modules/SPSUpdate.Common/Public/Start-SPSProductUpdate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,44 +40,26 @@ Setup file is blocked! Please use 'Unblock-File -Path $SetupFile' to unblock the
$fileVersion = $setupFileInfo.VersionInfo.FileVersion
Write-Verbose -Message "Update has version $fileVersion"
$fileVersionInfo = New-Object -TypeName System.Version -ArgumentList $fileVersion
if ($fileVersionInfo.Build.ToString().Length -eq 4) {
$sharePointVersion = '2016'
}
else {
if ($fileVersionInfo.Build -lt 13000) {
$sharePointVersion = '2019'
}
else {
$sharePointVersion = 'SE'
}
}

Write-Verbose -Message "Update is a Cumulative Update."
# For SP 2016 + 2019 Patches
# Subscription Edition cumulative update package.
$setupFileInformation = New-Object -TypeName System.IO.FileInfo -ArgumentList $SetupFile
if ($setupFileInformation.Name.StartsWith("wssloc")) {
Write-Verbose -Message "Cumulative Update is multilingual"
$versionInfo = Get-SPSLocalVersionInfo -ProductVersion $sharePointVersion -IsWssPackage
$versionInfo = Get-SPSLocalVersionInfo -IsWssPackage
}
else {
Write-Verbose -Message "Cumulative Update is generic"
$versionInfo = Get-SPSLocalVersionInfo -ProductVersion $sharePointVersion
$versionInfo = Get-SPSLocalVersionInfo
}

Write-Verbose -Message "The lowest version of any SharePoint component is $($versionInfo)"
if ($versionInfo -lt $fileVersionInfo) {
# Version of SharePoint is lower than the patch version. Patch is not installed.
Write-Verbose -Message "The version of SharePoint installed is lower than the update. Starting update process."
$installedVersion = Get-SPSInstalledProductVersion
if ($ShutdownServices) {
$listOfServices = @("SPSearchHostController", "SPTimerV4", "IISADMIN")
if ($installedVersion.ProductMajorPart -eq 15) {

$listOfServices += "OSearch15"
}
else {
$listOfServices += "OSearch16"
}
# Subscription Edition search service instance is OSearch16.
$listOfServices = @("SPSearchHostController", "SPTimerV4", "IISADMIN", "OSearch16")
Write-Verbose -Message "Gettings services status before stopping services for installation."
$servicesStatusFilePath = Join-Path -Path $PSScriptRoot -ChildPath "ServicesStatus_$($env:COMPUTERNAME)_$(Get-Date -Format 'yyyyMMddHHmmss').json"
Get-Service -Name $listOfServices -ErrorAction SilentlyContinue | Select-Object Name, StartType, Status | ConvertTo-Json | Set-Content -Path $servicesStatusFilePath -Force
Expand Down
2 changes: 1 addition & 1 deletion src/Modules/SPSUpdate.Common/SPSUpdate.Common.psd1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@{
RootModule = 'SPSUpdate.Common.psm1'
ModuleVersion = '4.2.0'
ModuleVersion = '5.0.0'
GUID = 'd6f4e2b7-3a1c-4d8e-9f2a-6c5b7e0a1d34'
Author = 'Jean-Cyril DROUHIN'
CompanyName = 'luigilink'
Expand Down
9 changes: 2 additions & 7 deletions src/SPSUpdate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -347,16 +347,11 @@ Write-Output '-----------------------------------------------'
Write-Verbose -Message "Setting power management plan to 'High Performance'..."
Start-Process -FilePath "$env:SystemRoot\system32\powercfg.exe" -ArgumentList '/s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' -NoNewWindow

# 1. Load SharePoint Powershell Snapin or Import-Module
# 1. Load the SharePointServer module (SharePoint Server Subscription Edition)
try {
$installedVersion = Get-SPSInstalledProductVersion
Write-Output "Installed SharePoint Product Version: $($installedVersion.FileVersion)"
if ($installedVersion.ProductMajorPart -eq 15 -or $installedVersion.ProductBuildPart -le 12999) {
if ($null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)) {
Add-PSSnapin Microsoft.SharePoint.PowerShell
}
}
else {
if ($null -eq (Get-Module -Name SharePointServer)) {
Import-Module SharePointServer -Verbose:$false -WarningAction SilentlyContinue
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/SPSUpdate_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ SharePoint environment.
> This guide ships inside the release package so it is available offline on the server.
> For the full online documentation, see the [SPSUpdate Wiki](https://github.com/luigilink/SPSUpdate/wiki).

> **Subscription Edition only.** SharePoint Server 2016 and 2019 reached end of support on
> 14 July 2026. If you still run those versions (including generating the ContentDatabase
> inventory on a 2019 source farm during a migration), use the previous major release
> v4.2.0.

## 📦 Prerequisites

- SharePoint Server 2016, 2019 or Subscription Edition
- SharePoint Server Subscription Edition
- Administrator privileges on the server
- PowerShell 5.1 or later (no DSC module required)
- A service account (`InstallAccount`) for the scheduled tasks and CredSSP remoting
Expand Down Expand Up @@ -200,8 +205,7 @@ E:\SCRIPT\SPSUpdate.ps1 -Action Uninstall -ConfigFile 'E:\SCRIPT\Config\CONTOSO-

- Creates a `Logs` folder and a per-run transcript (sequence/action-aware naming).
- Verifies the script runs with Administrator rights before proceeding.
- Detects the installed SharePoint version (`Get-SPSInstalledProductVersion`) and loads the
appropriate SharePoint snap-in (2016/2019) or the `SharePointServer` module (SE).
- Loads the `SharePointServer` module (SharePoint Server Subscription Edition).
- Full mode creates four sequence tasks (`SPSUpdate-Sequence1..4`) and starts them in
parallel (with short random sleeps to avoid OWSTimer conflicts).
- Remote operations (PSConfig, side-by-side) use CredSSP and fail with a clear error if the
Expand Down
4 changes: 2 additions & 2 deletions tests/SPSUpdate.Common.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ Describe 'SPSUpdate.Common module' {
{ Test-ModuleManifest -Path $modulePath -ErrorAction Stop } | Should -Not -Throw
}

It 'manifest version is 4.0.0 or higher' {
(Test-ModuleManifest -Path $modulePath).Version | Should -BeGreaterOrEqual ([version]'4.2.0')
It 'manifest version is 5.0.0 or higher' {
(Test-ModuleManifest -Path $modulePath).Version | Should -BeGreaterOrEqual ([version]'5.0.0')
}

It 'exports exactly the expected public functions' {
Expand Down
Loading
Loading