Windows ships with Windows PowerShell 5.1 and always will. PowerShell 7 is a separate product with its own executable, pwsh.exe, and it installs alongside 5.1 rather than replacing it. Upgrading therefore means one of two things: moving an existing PowerShell 7 install to a newer 7.x release, or installing 7 for the first time on a machine that only has 5.1.
This guide covers both, on Windows 10 and Windows 11, with four of the five installation methods Microsoft documents for Windows (the fifth, the .NET global tool, is aimed at developers). The current release at the time of writing (September 2026) is 7.6.6, the current LTS release. Two recent changes decide which method is right for you: since the 7.6.0 package, winget installs the MSIX package by default, and Microsoft states there will be no MSI package from PowerShell 7.7.0 onwards.
Quick answer
If PowerShell 7 is already installed and winget can see it, one command upgrades it:
winget upgrade --id Microsoft.PowerShell
Winget keeps the package format you already have, MSI or MSIX. For a first install on a machine that runs scripts, ask for the MSI explicitly, because without --installer-type wix winget now installs the per-user MSIX package:
winget install --id Microsoft.PowerShell --source winget --installer-type wix
Close and reopen the terminal afterwards, then confirm with $PSVersionTable.PSVersion.
powershell.exe keeps working exactly as before.
Check what you are actually running
Before you upgrade anything, find out which shell the window in front of you is. The blue Windows PowerShell icon and the black PowerShell 7 icon look similar enough that people upgrade the wrong thing. One line prints both the version and the edition:
"$($PSVersionTable.PSVersion) $($PSVersionTable.PSEdition)"
Core means PowerShell 7. Desktop with a version starting 5.1 means Windows PowerShell, and no amount of upgrading will change that number, because 5.1 is a Windows component.
Next, find out how PowerShell 7 was installed, because Microsoft advises upgrading with the same method. Microsoft’s documented way to tell is to read $PSHOME from inside pwsh:
$PSHOME
$PSHOME is | Installed with |
|---|---|
$Env:ProgramFiles\PowerShell\7 | Probably the MSI (confirm in Programs and Features) |
Starts with $Env:ProgramFiles\WindowsApps\ | The MSIX package (Microsoft Store, winget default since 7.6.0, or a manual msixbundle) |
$HOME\.dotnet\tools | The .NET global tool |
| Anything else | Most likely the ZIP package |
To see what winget knows about, and whether it has an upgrade to offer:
winget list --id Microsoft.PowerShell --upgrade-available
Method 1: winget, for desktops
Microsoft recommends winget for Windows clients. It is included in Windows 11 and in Windows Server 2025 as part of the App Installer, and on Windows 10 it arrives with the same App Installer package. It is not available on Windows Server 2022 or earlier, and Windows Server 2025 has it only with the Desktop Experience.
First install, MSIX package (the default since 7.6.0):
winget install --id Microsoft.PowerShell --source winget
First install, MSI package, installed for all users:
winget install --id Microsoft.PowerShell --source winget --installer-type wix
Later upgrades, whichever format you chose:
winget upgrade --id Microsoft.PowerShell
To pick up preview builds for testing, the package identifier is different. Previews install side by side with the stable version:
winget install --id Microsoft.PowerShell.Preview --source winget
pwsh window first, including the one embedded in VS Code and any Windows Terminal tab. Run the upgrade from Windows PowerShell or cmd.exe rather than from the pwsh you are replacing.
Method 2: the MSI, for servers and fleets
Microsoft calls the MSI the best choice for Windows Server and enterprise deployment. It installs for all users and accepts remote sessions, which the MSIX package cannot. Download it from the PowerShell releases page, choosing the x64 or arm64 build to match the machine, then install it silently. This is Microsoft’s own example with every option switched on:
$msiParams = @(
'/package'
'PowerShell-7.6.6-win-x64.msi'
'/quiet'
'ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1'
'ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1'
'ENABLE_PSREMOTING=1'
'REGISTER_MANIFEST=1'
'USE_MU=1'
'ENABLE_MU=1'
'ADD_PATH=1'
)
msiexec.exe @msiParams
| Property | What it does |
|---|---|
USE_MU | Opts into updates through Microsoft Update, WSUS or Configuration Manager. Default 1. |
ENABLE_MU | Opts into Microsoft Update for Automatic Updates. Default 1. Setting it to 0 does not remove a setting made earlier, and Group Policy can overrule it. |
ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL | Adds the Open PowerShell item to the Explorer context menu. |
ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL | Adds the Run with PowerShell item to the Explorer context menu. |
ENABLE_PSREMOTING | Enables PowerShell remoting during installation. |
REGISTER_MANIFEST | Registers the Windows event logging manifest for PowerShell 7. |
ADD_PATH | Adds PowerShell to the PATH environment variable. |
DISABLE_TELEMETRY | Opts out of telemetry by setting POWERSHELL_TELEMETRY_OPTOUT. |
Installing a newer MSI replaces the previous PowerShell 7 version, so there is no need to uninstall 7.5 before installing 7.6. The install folder is $Env:ProgramFiles\PowerShell\7 for every release, which is why a newer version cannot sit next to an older one this way.
Method 3: the ZIP, for side-by-side testing
The ZIP package installs nothing. You extract it and run pwsh.exe from wherever you put it, which makes it the right choice when you need to test a script against another build without changing what the machine runs by default. Depending on how you downloaded it, unblock the file first:
Unblock-File -Path .\PowerShell-7.6.6-win-x64.zip
Expand-Archive -Path .\PowerShell-7.6.6-win-x64.zip -DestinationPath C:\PS766
C:\PS766\pwsh.exe -NoProfile -Command '$PSVersionTable.PSVersion'
Nothing is added to PATH, no Start menu shortcut is created and the installer’s prerequisite checks do not run, so WSMan remoting needs its prerequisites met by hand. Delete the folder and the build is gone. The trade-off is that it receives no updates, so treat a ZIP copy as disposable rather than as an install.
Method 4: the MSIX package
This is the Microsoft Store package, the same package winget now installs by default, and the one you get from a manual .msixbundle download with Add-AppxPackage. The Store copy updates itself through Windows, which is convenient on a personal machine. Its limits come from the application sandbox, and Microsoft lists them plainly:
- It installs for a single user. There is no all-users option.
- The application folder,
$PSHOME, is read-only. That blocksRegister-PSSessionConfiguration,Set-ExecutionPolicy -Scope LocalMachine,Update-Help -Scope AllUsersandEnable-ExperimentalFeature -Scope AllUsers, and the all-users profile scripts cannot be created. - Remote sessions cannot connect to it, because the WSMan configuration lives in that folder. Outbound SSH remoting and user-level settings work.
The PowerShell package is exempt from the file and registry virtualisation that other packaged apps get, so ordinary writes from your scripts land where you expect, on Windows 10 version 1903 or later.
Which method to use
| Method | Best for | Updates | Side by side |
|---|---|---|---|
| winget | Windows 10 and 11 desktops | Follows the package it installed, or winget upgrade | No |
| MSI | Servers, imaging, group deployment | Microsoft Update, on by default | No |
| ZIP | Testing a build, Server Core, portable use | None | Yes |
| MSIX (Store) | Personal, interactive use | Automatic | No |
Verify the upgrade and your scripts
Open a new terminal, because the old one still holds the previous PATH, and check the version numerically rather than as text:
if ($PSVersionTable.PSVersion -ge [version]'7.6.0') {
Write-Host "Running $($PSVersionTable.PSVersion)" -ForegroundColor Green
} else {
Write-Warning "Still on $($PSVersionTable.PSVersion), the new build is not the default yet"
}
'7.10.0' -ge '7.6.0' returns False, because strings compare character by character, while [version]'7.10.0' -ge [version]'7.6.0' returns True. In PowerShell 7, $PSVersionTable.PSVersion is a SemanticVersion, and comparing it with a [version] works as shown above.
Then check that the modules your scripts rely on still load. PowerShell 7 searches its own module folders first: Documents\PowerShell\Modules for the current user and $Env:ProgramFiles\PowerShell\Modules for all users. Modules you installed for the current user under 5.1, in Documents\WindowsPowerShell\Modules, are not in that list. List what 7 can actually see:
Get-Module -ListAvailable | Sort-Object Name | Select-Object Name, Version, ModuleBase
For a Windows-only module that has no PowerShell 7 build, load it through the Windows PowerShell compatibility feature instead of rewriting it:
Import-Module ServerManager -UseWindowsPowerShell
That runs the module in a background Windows PowerShell 5.1 session named WinPSCompatSession and proxies the cmdlets, so objects come back deserialised. Three modules are on the default deny list and will not load this way: PSScheduledJob, BestPractices and UpdateServices. It is a bridge, not a fix, but it keeps existing scripts working while you migrate them.
Problems you will actually hit
pwsh is not recognised after installing
The installer updated the machine PATH, but your open shell captured the old one at launch. Close every terminal and open a new one. A ZIP copy is never on PATH. If it still fails, confirm the install folder exists and check the current PATH:
Get-ChildItem 'C:\Program Files\PowerShell'
$env:PATH -split ';' | Select-String PowerShell
winget reports no applicable upgrade
Check where the install came from with $PSHOME, as shown above. A ZIP copy is invisible to winget, because nothing was registered. Run winget list --id Microsoft.PowerShell --upgrade-available; if the install is not listed, upgrade it the way it was installed, or uninstall it and reinstall through winget.
Scheduled tasks still run the old version
A task whose action is powershell.exe runs Windows PowerShell 5.1, no matter what you installed. Change the action to pwsh.exe with its full path. Tasks also fail for reasons that have nothing to do with the version, which is a separate problem worth ruling out.
The profile you spent hours on is gone
It is not gone, it is in the other folder. The two editions keep separate profiles:
| Edition | Current user profile |
|---|---|
| Windows PowerShell 5.1 | Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 |
| PowerShell 7 | Documents\PowerShell\Microsoft.PowerShell_profile.ps1 |
Run $PROFILE in each to see the exact path on your machine, then copy across what you still want.
Scripts fail with an execution policy error
PowerShell 7 keeps its execution policy in its own powershell.config.json files: the current user setting in a user-specific file and the machine setting in $PSHOME. A policy set with Set-ExecutionPolicy in 5.1 does not carry over. Check and set it in the new shell:
Get-ExecutionPolicy -List
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
On the MSIX package the LocalMachine scope cannot be set, because $PSHOME is read-only.
Keep it updated without thinking about it
Each package has its own update path, and the hands-off one for servers is Microsoft Update. The MSI opts into it by default (USE_MU=1 and ENABLE_MU=1), after which new 7.x releases arrive through Windows Update for Business, WSUS, Configuration Manager or the Windows Update page in Settings, subject to your Group Policy. The Microsoft Store package updates itself through Windows. A winget install is updated when someone runs winget upgrade.
PowerShell also tells you itself. When update notification is on, it checks whether more than 24 hours have passed since the last check and, if a newer release exists, shows a notice with the startup banner. The environment variable POWERSHELL_UPDATECHECK controls it: Off turns it off, LTS notifies only about LTS releases, and Default is the same as not setting it. It must be set before PowerShell starts, so set it at machine level, from an elevated prompt, on servers where the notice clutters logs:
[Environment]::SetEnvironmentVariable('POWERSHELL_UPDATECHECK', 'Off', 'Machine')
pwsh opens on the current release, 5.1 still runs everything that depended on it, and your scheduled jobs point at the executable you meant.
Official documentation
- Install PowerShell 7 on Windows: PowerShell | Microsoft Learn
- about_Update_Notifications: PowerShell | Microsoft Learn
- about_PSModulePath: PowerShell | Microsoft Learn
- about_Windows_PowerShell_Compatibility: PowerShell | Microsoft Learn
- about_Execution_Policies: PowerShell | Microsoft Learn
- PowerShell releases on GitHub
Related tools
- cURL converter turns a copied curl command into a PowerShell
Invoke-RestMethodsnippet, ready to run in thepwshyou just installed.
Related guides
- PowerShell commands cheat sheet: the cmdlets worth keeping within reach once you are on 7.
- PowerShell script works manually but fails in Task Scheduler: the other reason a task does not do what your shell does.
- Read Windows event logs with Get-WinEvent: querying the event logs from the shell you just upgraded.
- Test-NetConnection in PowerShell: checking connectivity from the new shell.
- Command aliases in CMD and PowerShell: aliases live in the profile, so they need the same folder fix.
Five cheat sheets, one PDF
Subnet masks, PowerShell, Linux commands, HTTP status codes and the ESXi command line - one page each, free to keep. Leave an address and it arrives in a minute.