Most admins install PowerShell 7 on a new server expecting it to take over from Windows PowerShell 5.1, and it doesn’t. The two shells sit side by side on Windows Server 2025. Every role module Microsoft ships still lives in the 5.1 module folder. The PowerShell 7 vs Windows PowerShell 5.1 question on a server is really a question about those modules: which ones PowerShell 7 can load into its own process, which ones it quietly hands to a hidden 5.1 process, and which ones it refuses outright.
We answered that on a lab VM with the DHCP Server and Hyper-V roles, the RSAT tools for Active Directory, Group Policy and WSUS, and Windows Server Backup installed, then imported each module in PowerShell 7 and timed it. The article explains how the compatibility layer decides, what the -UseWindowsPowerShell session costs you, and how Microsoft Update keeps PowerShell 7 patched once you opt in. The behavior described here was checked against PowerShell 7.6 LTS on Windows Server 2025 Datacenter (build 26100) in October 2026, starting from 7.6.6 and letting Microsoft Update move it to 7.6.9.
What differs between PowerShell 7 and Windows PowerShell 5.1 on a server
The difference that matters is the runtime. Windows PowerShell 5.1 runs on .NET Framework 4.8.1, ships inside Windows and is patched by the monthly cumulative update. PowerShell 7 runs on modern .NET (the 7.6 LTS line is built on .NET 10), installs separately and has its own support clock. A module that was compiled against .NET Framework types can only run where those types exist, so the runtime split is what drives every compatibility result later in this article.
The two compare like this on the same Server 2025 box, with the startup row measured on our lab VM:
| Item | Windows PowerShell 5.1 | PowerShell 7 (7.6 LTS) |
|---|---|---|
| Executable | powershell.exe | pwsh.exe |
| Runtime | .NET Framework 4.8.1 (CLR 4.0.30319) | .NET 10 |
PSEdition | Desktop | Core |
| Installed on Server 2025 | Yes, part of the OS | No, MSI or MSIX install |
| Patched by | Windows cumulative update | Microsoft Update or WSUS (MSI installs only) |
| Support ends | With the Windows release | 14 November 2028 for 7.6 LTS |
| AllUsers module folder | C:\Program Files\WindowsPowerShell\Modules | C:\Program Files\PowerShell\Modules |
| Startup on our 2 vCPU VM (median of 5) | 2,054 ms | 798 ms |
The startup row surprised us, because the usual complaint is that pwsh is the slow one. On this box powershell.exe -NoProfile -Command exit took 1,947 to 2,437 ms across five runs while pwsh.exe took 570 to 865 ms, with one 1,765 ms outlier. That advantage disappears the moment a script touches a module that has to go through the compatibility layer, which we measured further down. If you’re comparing the two for performance, measure your own module mix rather than an empty shell.
You can check what a server already has before installing anything. A fresh Server 2025 install reports only 5.1, and Get-Command pwsh returns nothing:
$PSVersionTable
Get-Command pwsh -ErrorAction SilentlyContinue
Our VM, a clone of our Windows Server template on Proxmox, printed the 5.1 table, trimmed here to the rows that matter, and nothing for pwsh:
PSVersion 5.1.26100.7462
PSEdition Desktop
CLRVersion 4.0.30319.42000
Windows PowerShell 2.0 is no longer part of Server 2025 either. Microsoft removed it with the September 2025 update, and on our box Get-WindowsFeature PowerShell-V2 returned nothing at all. Microsoft says a script that still calls powershell.exe -Version 2 now gets 5.1 instead, so only scripts that depend on 2.0 behavior break, but the flag is worth removing whichever shell you settle on.
How to install PowerShell 7 so Microsoft Update services it
Use the MSI package on a server. Microsoft’s own install page calls it the best choice for Windows Server, and it’s the only package that registers with Microsoft Update. The winget route installs the MSIX package by default, which Microsoft documents as per-user and unable to act as a remoting endpoint, and the Microsoft Update servicing described below is documented for the MSI only. If you do want winget, our winget guide covers the client, and --installer-type wix forces the MSI. Starting with 7.7 there is no MSI at all, so the 7.6 LTS line is the one to standardise servers on for now.
The PowerShell repo publishes its current release tags in tools/metadata.json, and LTSReleaseTag lists both supported LTS lines. Sort them and take the newest so the command keeps working after the next patch release. Run this in an elevated Windows PowerShell 5.1 session:
$tag = (Invoke-RestMethod https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/metadata.json).LTSReleaseTag | Sort-Object { [version]$_.TrimStart('v') } | Select-Object -Last 1
$ver = $tag.TrimStart('v')
$tag
$ver
On the day we tested, the file held {v7.4.22, v7.6.9} and the sort picked the 7.6 line:
v7.6.9
7.6.9
Download the x64 MSI for that tag. On our lab link the 112 MB file took 9 seconds:
$ProgressPreference = 'SilentlyContinue'
$msi = "C:\lab\PowerShell-$ver-win-x64.msi"
New-Item -ItemType Directory C:\lab -Force | Out-Null
Invoke-WebRequest "https://github.com/PowerShell/PowerShell/releases/download/$tag/PowerShell-$ver-win-x64.msi" -OutFile $msi -UseBasicParsing
Two MSI properties control servicing. USE_MU=1 lets Microsoft Update, WSUS or Configuration Manager deliver PowerShell updates, and ENABLE_MU=1 turns on the Microsoft Update service itself so Automatic Updates checks it. Microsoft documents both as defaulting to 1, but we pass them explicitly in scripts so the intent is recorded in the install command and the MSI log:
Start-Process msiexec.exe -ArgumentList "/i $msi /qn ADD_PATH=1 USE_MU=1 ENABLE_MU=1 /l*v C:\lab\pwsh-msi.log" -Wait
We deliberately installed 7.6.6, three patch releases behind, so we could watch Microsoft Update offer the newer build later; to do the same, set $tag and $ver to an older 7.6 release by hand before the download. The install took 85 seconds. The verbose log shows what ENABLE_MU really does: it schedules a custom action that runs a script shipped inside the package.
Select-String -Path C:\lab\pwsh-msi.log -Pattern 'Adding EnableMU property|Installation success or error status' | ForEach-Object { $_.Line }
The EnableMU action calls RegisterMicrosoftUpdate.ps1 with the freshly installed pwsh.exe, and status 0 means the product installed cleanly. The two matching lines, with the MSI timestamp prefixes trimmed:
PROPERTY CHANGE: Adding EnableMU property. Its value is '"C:\Program Files\PowerShell\7\pwsh.exe" -NoProfile -ExecutionPolicy Bypass -File "C:\Program Files\PowerShell\7\RegisterMicrosoftUpdate.ps1"'
Product: PowerShell 7-x64 ... Product Version: 7.6.6.0 ... Installation success or error status: 0
The MSI also records your choices in the registry, which is the quickest way to audit a fleet later. Every property we passed shows up under InstallerProperties:
Get-ItemProperty HKLM:\SOFTWARE\Microsoft\PowerShellCore\InstallerProperties | Select-Object AddToPath,EnablePSRemoting,UseMU,EnableMU
Both servicing flags read 1. EnablePSRemoting is 0 because we did not ask the installer to register a PowerShell 7 remoting endpoint:
AddToPath : 1
EnablePSRemoting : 0
UseMU : 1
EnableMU : 1
If either servicing flag reads 0 on one of your servers, that install was done without the opt-in, and it’s the first thing to check when one box sits on an older 7.6 build than the rest of the fleet.
Which Server 2025 modules load natively in PowerShell 7
Ten of the sixteen modules we tried loaded straight into the PowerShell 7 process, three went through the compatibility session, and three were refused. The deciding factor is a single manifest key, CompatiblePSEditions. When a module lives in the Windows system module folder and its manifest doesn’t list Core, PowerShell 7 will not load it in-process. It imports the module inside a background 5.1 process instead, unless the module is on a deny list.
To repeat the test, save the script below. It imports each module, records whether PowerShell printed a compatibility warning, and times the import. Open the file in Notepad first:
notepad C:\lab\04-modules.ps1
Paste the script and save it. This is the exact file we ran:
$ProgressPreference='SilentlyContinue'
"PS " + $PSVersionTable.PSVersion.ToString()
$mods = 'ServerManager','DhcpServer','Hyper-V','ActiveDirectory','GroupPolicy','ScheduledTasks','NetAdapter','NetSecurity','Storage','SmbShare','WindowsServerBackup','PKI','Dism','UpdateServices','BestPractices','PSScheduledJob'
foreach ($m in $mods) {
$w = $null
$t = Measure-Command {
try { Import-Module $m -ErrorAction Stop -WarningVariable w -WarningAction SilentlyContinue; $script:err = $null }
catch { $script:err = $_.Exception.Message }
}
if ($script:err) { "{0,-20} FAILED {1,6} ms {2}" -f $m, [int]$t.TotalMilliseconds, $script:err; continue }
$g = Get-Module $m | Select-Object -First 1
$how = if ($w) { 'compat' } else { 'native' }
"{0,-20} {1,-7} {2,6} ms {3} {4}" -f $m, $how, [int]$t.TotalMilliseconds, $g.ModuleType, $g.Path
if ($w) { " WARN: $w" }
}
"=== sessions"
Get-PSSession | Format-Table Id,Name,ComputerName,ConfigurationName,State -AutoSize | Out-String -Width 200
Run it with PowerShell 7 in a fresh process, so no module is already loaded from an earlier session:
pwsh -NoProfile -File C:\lab\04-modules.ps1
Our run, with the repeated warning lines kept and the proxy-module GUIDs shortened:
PS 7.6.6
ServerManager compat 7693 ms Script C:\WINDOWS\SystemTemp\remoteIpMoProxy_ServerManager_2.0.0.0_localhost_53049719-...\remoteIpMoProxy_ServerManager_2.0.0.0_localhost_53049719-....psm1
WARN: Module ServerManager is loaded in Windows PowerShell using WinPSCompatSession remoting session; please note that all input and output of commands from this module will be deserialized objects. If you want to load this module into PowerShell please use 'Import-Module -SkipEditionCheck' syntax.
DhcpServer compat 4314 ms Script C:\WINDOWS\SystemTemp\remoteIpMoProxy_DhcpServer_2.0.0.0_localhost_53049719-...\remoteIpMoProxy_DhcpServer_2.0.0.0_localhost_53049719-....psm1
WARN: Module DhcpServer is loaded in Windows PowerShell using WinPSCompatSession remoting session; please note that all input and output of commands from this module will be deserialized objects. If you want to load this module into PowerShell please use 'Import-Module -SkipEditionCheck' syntax.
Hyper-V native 977 ms Manifest C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Hyper-V\2.0.0.0\Hyper-V.psd1
ActiveDirectory compat 2031 ms Manifest C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\ActiveDirectory\ActiveDirectory.psd1
WARN: Error initializing default drive: 'Unable to find a default server with Active Directory Web Services running.'.
GroupPolicy compat 2096 ms Script C:\WINDOWS\SystemTemp\remoteIpMoProxy_GroupPolicy_1.0.0.0_localhost_53049719-...\remoteIpMoProxy_GroupPolicy_1.0.0.0_localhost_53049719-....psm1
WARN: Module GroupPolicy is loaded in Windows PowerShell using WinPSCompatSession remoting session; please note that all input and output of commands from this module will be deserialized objects. If you want to load this module into PowerShell please use 'Import-Module -SkipEditionCheck' syntax.
ScheduledTasks native 1473 ms Manifest C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\ScheduledTasks\ScheduledTasks.psd1
NetAdapter native 4898 ms Manifest C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\NetAdapter\NetAdapter.psd1
NetSecurity native 4335 ms Manifest C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\NetSecurity\NetSecurity.psd1
Storage native 6272 ms Manifest C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Storage\Storage.psd1
SmbShare native 2961 ms Manifest C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\SmbShare\SmbShare.psd1
WindowsServerBackup native 702 ms Binary C:\WINDOWS\Microsoft.NET\assembly\GAC_MSIL\wsbcmdlet\v4.0_10.0.0.0__31bf3856ad364e35\wsbcmdlet.dll
PKI native 1071 ms Manifest C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\PKI\PKI.psd1
Dism native 1481 ms Script C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Dism\Dism.psm1
UpdateServices FAILED 48 ms Module 'UpdateServices' is blocked from loading using Windows PowerShell compatibility feature by a 'WindowsPowerShellCompatibilityModuleDenyList' setting in PowerShell configuration file.
BestPractices FAILED 56 ms Module 'BestPractices' is blocked from loading using Windows PowerShell compatibility feature by a 'WindowsPowerShellCompatibilityModuleDenyList' setting in PowerShell configuration file.
PSScheduledJob FAILED 121 ms Module 'PSScheduledJob' is blocked from loading using Windows PowerShell compatibility feature by a 'WindowsPowerShellCompatibilityModuleDenyList' setting in PowerShell configuration file.
=== sessions
Id Name ComputerName ConfigurationName State
-- ---- ------------ ----------------- -----
2 WinPSCompatSession localhost Opened
Read the path column, not our compat label. A compatibility module’s path points at a generated remoteIpMoProxy_*.psm1 file in the temp folder (C:\WINDOWS\SystemTemp here, because the test ran as SYSTEM; an interactive admin sees $Env:TEMP), and its type changes to Script, because what PowerShell 7 loaded is a proxy, not the module. ActiveDirectory is a false positive in our script: it printed a warning because the lab VM is not a domain member and found no Active Directory Web Services, but its path is the real ActiveDirectory.psd1, so it loaded natively. The import times come from a 2 vCPU guest on a busy host, so treat them as relative rather than absolute.
The manifests explain the split. We asked PowerShell 7 for each module’s CompatiblePSEditions and path with Get-Module -ListAvailable, which is also a quick way to predict how a module will load before you import it:
foreach ($m in 'ServerManager','DhcpServer','GroupPolicy','Hyper-V','ActiveDirectory','ScheduledTasks','NetAdapter','Storage','UpdateServices','PKI') {
$mod = Get-Module -ListAvailable $m | Select-Object -First 1
"{0,-16} {1,-12} {2}" -f $m, (($mod.CompatiblePSEditions) -join ','), $mod.Path
}
Modules that declare Desktop,Core come back with their real path and load natively. The other four come back with neither an edition nor a path, because PowerShell 7 leaves a module in the Windows system folder out of -ListAvailable when its manifest doesn’t list Core. Those four are exactly the ones it proxied or blocked:
ServerManager
DhcpServer
GroupPolicy
Hyper-V Desktop,Core C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Hyper-V\2.0.0.0\Hyper-V.psd1
ActiveDirectory Desktop,Core C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\ActiveDirectory\ActiveDirectory.psd1
ScheduledTasks Desktop,Core C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\ScheduledTasks\ScheduledTasks.psd1
NetAdapter Desktop,Core C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\NetAdapter\NetAdapter.psd1
Storage Desktop,Core C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Storage\Storage.psd1
UpdateServices
PKI Desktop,Core C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\PKI\PKI.psd1
The screenshot shows the same check without the path column, run after Microsoft Update moved the box to 7.6.9, with the error an explicit -UseWindowsPowerShell import of UpdateServices returned in our tests. The deny list behind that error is covered in the next section:

Our results line up with Microsoft’s PowerShell 7 module compatibility list on most rows and disagree on one. That page lists ServerManager as natively compatible, yet on build 26100 the ServerManager manifest declares no edition and PowerShell 7 loaded it through the proxy every time. DhcpServer and GroupPolicy are marked as untested with the compatibility layer. Both imported through it here; we exercised DhcpServer with real scope reads, while GroupPolicy only got as far as the import because the VM wasn’t in a domain. The summary below is what we observed:
| Module | How PowerShell 7 loaded it | With -SkipEditionCheck |
|---|---|---|
| Hyper-V, ActiveDirectory, ScheduledTasks, NetAdapter, NetSecurity, Storage, SmbShare, PKI, Dism, WindowsServerBackup | Native, in-process | Not needed |
| DhcpServer | WinPSCompatSession proxy | Loaded natively, Get-DhcpServerv4Scope worked |
| GroupPolicy | WinPSCompatSession proxy | Loaded natively, 28 commands (not exercised without a domain) |
| ServerManager | WinPSCompatSession proxy | Failed: type load error |
| UpdateServices, BestPractices, PSScheduledJob | Blocked by the deny list | UpdateServices loaded; others not tried |
How Import-Module -UseWindowsPowerShell works behind the warning
The compatibility feature is implicit remoting to your own machine. When PowerShell 7 meets a module it cannot load, it does not fail; it starts a Windows PowerShell 5.1 process, imports the module there and generates a local proxy whose functions forward every call. Import-Module -UseWindowsPowerShell forces the same path for a module that would otherwise load natively, and an ordinary Import-Module or command autoload triggers it automatically. In order, this is what happened on our server:
- Edition check: PowerShell 7 finds DhcpServer under
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules, which is on itsPSModulePath, and sees noCorein the manifest. - Child process: it launches
powershell.exe -Version 5.1 -s -NoLogo -NoProfileand opens a remoting session to it calledWinPSCompatSession. - Proxy module: it writes
remoteIpMoProxy_DhcpServer_*.psm1to$Env:TEMP(C:\WINDOWS\SystemTemphere, because our script ran as SYSTEM) and imports that file into your session. - Serialized calls: each command sends its parameters to the child and gets back serialized results, which is what the warning means by deserialized objects.
- Teardown: removing the last proxied module closes the session; exiting
pwshdoes the same.
You can watch steps 1 to 3 from a PowerShell 7 prompt. Import the module, then look for the session and the child process:
Import-Module DhcpServer
Get-PSSession | Format-Table Id,Name,ComputerName,ConfigurationName,State -AutoSize
Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | Select-Object ProcessId,ParentProcessId,CommandLine | Format-List
The warning, the session and the 5.1 child all appeared within a few seconds:
WARNING: Module DhcpServer is loaded in Windows PowerShell using WinPSCompatSession remoting session; please note that all input and output of commands from this module will be deserialized objects. If you want to load this module into PowerShell please use 'Import-Module -SkipEditionCheck' syntax.
Id Name ComputerName ConfigurationName State
-- ---- ------------ ----------------- -----
2 WinPSCompatSession localhost Opened
ProcessId : 3204
ParentProcessId : 4776
CommandLine : "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Version 5.1 -s -NoLogo -NoProfile
After Remove-Module DhcpServer, Get-PSSession came back empty, which confirms step 5. That matters on a long-running automation host, because each proxied module keeps a whole 5.1 process alive until the last one is removed.
What deserialized objects cost you
Objects that cross the session lose their methods and some formatting, and how much depends on the type. ServerManager shows it plainly. In PowerShell 7, Get-WindowsFeature DHCP,Hyper-V printed an empty Display Name column, and the object type tells you why:
$f = Get-WindowsFeature DHCP
$f.pstypenames
"DisplayName=[" + $f.DisplayName + "]"
$f | Get-Member -MemberType Method | Measure-Object | ForEach-Object { "methods=" + $_.Count }
The object is a Deserialized.* copy with two methods left, and the property is still there even though the table view didn’t render it:
Deserialized.Microsoft.Windows.ServerManager.Commands.Feature
Deserialized.System.Object
DisplayName=[DHCP Server]
methods=2
CIM-based modules fare better. A DHCP scope fetched through the proxy kept its CimInstance#root/Microsoft/Windows/DHCP/DhcpServerv4Scope type name and seven of its nine methods (only Clone and GetObjectData were missing), so pipelines that read properties work unchanged. Scripts that call methods on ServerManager, Group Policy or other .NET-typed results are the ones that break, and they break at run time, not at import.
The proxy is also slower per process. We timed a first Get-DhcpServerv4Scope call in a new process, three times each:
| Run | Windows PowerShell 5.1 (native) | PowerShell 7 via WinPSCompatSession |
|---|---|---|
| 1 | 5,813 ms | 13,536 ms |
| 2 | 4,569 ms | 11,916 ms |
| 3 | 5,021 ms | 9,655 ms |
PowerShell 7 started faster but finished the DHCP job roughly two to two and a half times slower, because it paid for its own startup, a second 5.1 startup and the proxy generation. A scheduled task that runs a short DHCP or ServerManager script every few minutes is better off in powershell.exe.
When -SkipEditionCheck is the better fix
-SkipEditionCheck tells PowerShell 7 to ignore the empty manifest key and load the module in-process. It’s a bet that the module’s code doesn’t use .NET Framework types missing from modern .NET, and we got both outcomes. DhcpServer loaded from its real DhcpServer.psd1, returned the scope and opened no compatibility session. ServerManager failed immediately:
Import-Module ServerManager -SkipEditionCheck
The missing type lives in .NET Framework’s System.Core, which modern .NET does not carry:
Could not load type 'System.Diagnostics.Eventing.EventDescriptor' from assembly 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
We’d use -SkipEditionCheck only for a module we’ve exercised end to end in PowerShell 7, DhcpServer being the clear candidate from this test. A clean import proves nothing, because the type load can happen on the first call to a particular cmdlet rather than at import time.
Why UpdateServices, BestPractices and PSScheduledJob refuse to load
Those three are on a deny list that ships in $PSHOME\powershell.config.json, and Microsoft’s compatibility list marks all three as not supported by the compatibility layer. The block applies to the compatibility path only, so both a plain import and an explicit Import-Module UpdateServices -UseWindowsPowerShell returned the same error on our server. The config file is short:
Get-Content "$PSHOME\powershell.config.json"
It sets the execution policy and names the three modules:
{
"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned",
"WindowsPowerShellCompatibilityModuleDenyList": [
"PSScheduledJob",
"BestPractices",
"UpdateServices"
]
}
Editing that list is not the fix, since Microsoft marks all three as not supported by the compatibility layer. For WSUS administration, the module that hurts most on a WSUS server, the dependable route is to run the WSUS part of a PowerShell 7 script in Windows PowerShell directly. Passing a script block to powershell.exe runs it in a real 5.1 process and returns serialized results:
powershell.exe -NoProfile -Command { Import-Module UpdateServices; (Get-Command -Module UpdateServices).Count; $PSVersionTable.PSVersion.ToString() }
All 16 WSUS cmdlets loaded inside 5.1:
16
5.1.26100.7462
Import-Module UpdateServices -SkipEditionCheck did load in PowerShell 7, but our box had only the WSUS tools and no WSUS server role, so Get-WsusServer threw WsusInvalidServerException in both shells and we couldn’t prove the cmdlets work natively. Treat that path as untested. PSScheduledJob has a better replacement anyway: the native ScheduledTasks module covers the same ground and loaded in-process.
How Microsoft Update services PowerShell 7
Once the MSI has registered it, PowerShell 7 arrives like any other Microsoft product: through Windows Update against Microsoft Update, through WSUS, or through Configuration Manager. Microsoft’s Microsoft Update FAQ for PowerShell says updates stay inside the installed release line, so 7.6 moves to newer 7.6 builds and never jumps to 7.7. The installer’s registration step shows up as Microsoft Update being the default Automatic Updates service:
$sm = New-Object -ComObject Microsoft.Update.ServiceManager
$sm.Services | Select-Object Name,ServiceID,IsDefaultAUService,IsRegisteredWithAU | Format-Table -AutoSize
Microsoft Update, service ID 7971f918-a847-4430-9279-4a52d1efe18d, is registered and default:
Name ServiceID IsDefaultAUService IsRegisteredWithAU
---- --------- ------------------ ------------------
Microsoft Update 7971f918-a847-4430-9279-4a52d1efe18d True True
DCat Flighting Prod 8b24b027-1dee-babb-9a95-3517dfb9c552 False False
Windows Store (Prod) 855e8a7c-ecb4-4ca3-b045-1dfa50104289 False False
Windows Update 9482f4b4-e343-43b6-b170-9a65bc822c77 False False
To see what Microsoft Update would offer without waiting for the next scheduled scan, query it through the same COM API that Windows Update uses. This search took 12 seconds on our VM:
$s = New-Object -ComObject Microsoft.Update.Session
$q = $s.CreateUpdateSearcher()
$q.ServerSelection = 3
$q.ServiceID = '7971f918-a847-4430-9279-4a52d1efe18d'
$r = $q.Search("IsInstalled=0 and Type='Software'")
foreach ($u in $r.Updates) {
"TITLE: " + $u.Title
" BrowseOnly=" + $u.BrowseOnly + " MaxDownloadMB=" + [int]($u.MaxDownloadSize/1MB)
" Categories: " + (($u.Categories | ForEach-Object { $_.Name + ' [' + $_.Type + ']' }) -join '; ')
" KB: " + ($u.KBArticleIDs -join ',') + " Released: " + $u.LastDeploymentChangeTime
}
Among the October cumulative update and Defender items, trimmed here, was the PowerShell entry:
TITLE: PowerShell v7.6.9 (x64)
BrowseOnly=False MaxDownloadMB=111
Categories: PowerShell - x64 [Product]; Security Updates [UpdateClassification]
KB: 5021051 Released: 10/25/2026 00:00:00
Three details in that entry are worth acting on. The product category is PowerShell - x64, which is the product you tick in WSUS to approve these updates. The classification is Security Updates and BrowseOnly is False. Microsoft’s FAQ says PowerShell updates arrive as optional updates and can take up to two weeks to appear, but this one was offered as a normal update and carried the same 25 October 2026 date as the GitHub release.
We installed only that update with the same API, which is how you’d patch PowerShell 7 alone without pulling the cumulative update. Run it in the same elevated powershell.exe window as the search, since it reuses $s and $r and the MSI replaces pwsh.exe:
$t0 = Get-Date
$c = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($u in $r.Updates) { if ($u.Title -like 'PowerShell v7*') { $u.AcceptEula(); [void]$c.Add($u); "selected: " + $u.Title } }
$d = $s.CreateUpdateDownloader(); $d.Updates = $c
$dr = $d.Download()
"download result=" + $dr.ResultCode + " secs=" + [int]((Get-Date)-$t0).TotalSeconds
$i = $s.CreateUpdateInstaller(); $i.Updates = $c
$ir = $i.Install()
"install result=" + $ir.ResultCode + " reboot=" + $ir.RebootRequired + " secs=" + [int]((Get-Date)-$t0).TotalSeconds
& "$env:ProgramFiles\PowerShell\7\pwsh.exe" -NoProfile -Command '$PSVersionTable | Format-Table -AutoSize | Out-String -Width 200'
Result code 2 means succeeded. The download took 22 seconds, the whole job 142 seconds, no reboot was needed, and the new pwsh reports the build Microsoft Update delivered:
selected: PowerShell v7.6.9 (x64)
download result=2 secs=22
install result=2 reboot=False secs=142
Name Value
---- -----
PSVersion 7.6.9
PSEdition Core
GitCommitId 7.6.9
OS Microsoft Windows 10.0.26100
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0.}
PSRemotingProtocolVersion 2.4
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
One support date is worth putting in your calendar. PowerShell 7.4 LTS and 7.5 both reach end of support on 10 November 2026, according to Microsoft’s lifecycle page. Microsoft Update never moves an LTS release to a newer line, so a server on 7.4 needs a fresh 7.6 MSI install. The Microsoft Update FAQ says a 7.5 install is upgraded to the next LTS once 7.5 support ends, but we did not test that and would not wait on its timing: install the 7.6 MSI before 10 November, after which the servicing described here takes over.
Where PowerShell 7 still hands the job back to 5.1
The common belief is that PowerShell 7 replaces Windows PowerShell on a server. It doesn’t, and on Server 2025 it can’t, because several role modules still declare no Core edition and Windows itself keeps shipping 5.1. The practical model is two shells with a clear split, and these are the cases where we’d keep a script on powershell.exe:
- WSUS automation: UpdateServices is denied in the compatibility layer, so cleanup and approval scripts belong in 5.1 or in a
powershell.exescript block. - ServerManager-heavy scripts: the module only runs through the proxy, returns deserialized feature objects and fails outright with
-SkipEditionCheck. - Short, frequent scheduled tasks: anything that loads a proxied module pays for two process starts every run, which was about twice as slow in our DHCP timing.
- Best Practices Analyzer and PSScheduledJob: both are denied, and only 5.1 runs them.
Everything else on the list moved to PowerShell 7 cleanly. Hyper-V returned live Microsoft.HyperV.PowerShell.VirtualMachine objects in-process, and ActiveDirectory, Storage, networking, scheduled tasks, PKI and Windows Server Backup all loaded natively. That covers most day-to-day server automation, including the Active Directory build-out with PowerShell we’ve written about before. If you also manage Linux boxes, PowerShell 7 is the version you can run there too, on Ubuntu and Debian or RHEL-family systems, and that is the strongest reason to write new scripts for it. Keep the 5.1 exceptions above where they are.