# PowerShell 7.7 Is Out — Upgrade, or Stay on 7.6 LTS?


On November 10 the PowerShell team released [7.7.0](https://github.com/PowerShell/PowerShell/releases/tag/v7.7.0), built on .NET 11, together with [7.6.10](https://github.com/PowerShell/PowerShell/releases/tag/v7.6.10) for the 7.6 LTS line. The same day, 7.4 and 7.5 went out of support (see [PowerShell 7.4 and 7.5 Retire on November 10](/2026-10-30-powershell-7-4-7-5-end-of-support/)).

<!--more-->

My own machines always run the latest version, so 7.7 is already installed there. Work is a different story: our Function apps and Automation runbooks run on the PowerShell 7.6 runtime, so anything I write for those has to work on 7.6. That makes the useful question less "should I upgrade?" and more "what am I missing on 7.6, do I actually need it, and is there a workaround that runs on both?"

That's what this post is about. For what 7.6 itself brought, see [What's New in PowerShell 7.6](/2026-03-16-whats-new-in-powershell-76/).

## Support: closer than you'd think

The usual reason to stay on an LTS release is a longer support window. This time that difference is mostly gone. PowerShell's support follows the .NET version underneath it ([support lifecycle](https://learn.microsoft.com/en-us/powershell/scripting/install/powershell-support-lifecycle)), and since Microsoft stretched STS releases to 24 months, .NET 11 ends on [the same day as .NET 10](https://github.com/dotnet/core/blob/main/releases.md):

| Version | Built on | .NET end of support |
|---|---|---|
| PowerShell 7.6 (LTS) | .NET 10 (LTS) | November 14, 2028 |
| PowerShell 7.7 | .NET 11 (STS) | November 14, 2028 |

The PowerShell lifecycle page doesn't list a date for 7.7 yet, so treat that second row as "if it follows .NET, like 7.5 did".

The real difference is what "supported" means in practice. An LTS release only gets security and servicing fixes, so 7.6 stays put. A stable release like 7.7 is where new features and behaviour changes land. And only the latest patch of a release is supported, on both lines.

## What 7.6.10 brings

If you're staying on 7.6, update to 7.6.10. It's a small release:

- A warning when you create or validate a file catalog with the legacy SHA-1 algorithm (`New-FileCatalog` / `Test-FileCatalog`). It recommends `-CatalogVersion 2`, which uses SHA-256. Hashing and validation work exactly as before. ([#28311](https://github.com/PowerShell/PowerShell/pull/28311))
- A fix for a `Test-Json` regression from 7.6.7: when a schema violation sat deeper than the top level (an array item, for example), `Test-Json` returned `False` but no longer told you what was wrong or where ([#28218](https://github.com/PowerShell/PowerShell/issues/28218), fixed by [#28273](https://github.com/PowerShell/PowerShell/pull/28273)). If you validate JSON in a pipeline, this one is worth having.
- Better detection of the MSIX (Store) install. ([#28313](https://github.com/PowerShell/PowerShell/pull/28313))
- An updated .NET 10 SDK underneath.

## You're missing less than you think

Several features that started in 7.7 previews were backported to the 7.6 line during the autumn. If you're on the latest 7.6, you already have these:

| Feature | In 7.6 since |
|---|---|
| `$PSStyle.AutoSizeDefaultFormatting` — make default table/wide output behave like `-AutoSize` | 7.6.7 |
| A broken per-user `powershell.config.json` gives a warning instead of stopping `pwsh` from starting | 7.6.7 |
| No more update notification in the startup banner (the feature was removed) | 7.6.8 |
| SHA-1 file catalog warning | 7.6.10 |

The tolerant config one was triggered by OneDrive: when the file sits in a OneDrive folder that isn't available yet (for example right after logging in), PowerShell used to refuse to start ([#27373](https://github.com/PowerShell/PowerShell/pull/27373)).

The update notification removal is worth knowing if, like me, you rely on it to tell you a new version is out. It's gone on both versions, so update through your package manager instead (`winget upgrade Microsoft.PowerShell`, `brew upgrade`, or the Store).

> [!WARNING] Hosted runtimes lag behind
> A 7.6 runtime in Azure Functions or Azure Automation isn't necessarily on 7.6.10. Before relying on a backported feature there, check `$PSVersionTable.PSVersion` from inside the function or runbook.

## What you miss on 7.6, and the workaround

These are the 7.7 additions I'd actually use, each with a version that works on both. I ran every snippet on 7.6.10 and 7.7.0 side by side.

### New-TemporaryDirectory

7.7 adds `New-TemporaryDirectory`, the folder counterpart of `New-TemporaryFile`. It returns a `DirectoryInfo` for a new, uniquely named folder under the system temp path.

```powershell
$dir = New-TemporaryDirectory
$dir.GetType().FullName
```

On 7.7.0:

```text
System.IO.DirectoryInfo
```

On 7.6.10 you get `The term 'New-TemporaryDirectory' is not recognized as a name of a cmdlet, function, script file, or executable program.` But .NET has had the same thing since .NET 7, and this works on both:

```powershell
$dir = [System.IO.Directory]::CreateTempSubdirectory('blog-')
$dir.GetType().FullName
$dir.Name.StartsWith('blog-')
```

Same output on 7.6.10 and 7.7.0:

```text
System.IO.DirectoryInfo
True
```

**Do you need the cmdlet?** Only in Constrained Language Mode, where calling .NET types directly isn't allowed. That was the [reason it was added](https://github.com/PowerShell/PowerShell/pull/27549). Everywhere else, the .NET method does the job and even lets you set a prefix.

### Join-Path -Extension

`Join-Path` can now change (or add, or remove) the extension of the path it builds:

```powershell
Join-Path -Path '/reports' -ChildPath 'export.csv' -Extension '.json'
```

On 7.7.0:

```text
/reports/export.json
```

On 7.6.10 I expected a "parameter not found" error. Instead I got this:

```text
/reports/export.csv/-Extension/.json
```

> [!WARNING] No error on 7.6
> `Join-Path` has a parameter, `-AdditionalChildPath`, that collects any leftover arguments as extra child paths. On 7.6, `-Extension` and `.json` end up there as two more folder names, so you get a wrong path and no error. If 7.7 code like this ends up on a 7.6 runtime, nothing tells you.

The way that works on both is `[System.IO.Path]::ChangeExtension()`, which is what the cmdlet uses under the hood:

```powershell
[System.IO.Path]::ChangeExtension((Join-Path -Path '/reports' -ChildPath 'export.csv'), '.json')
```

Same output on 7.6.10 and 7.7.0:

```text
/reports/export.json
```

**Do you need it?** No. Nicer to read, nothing more. But given the warning above, I'd stay away from it in any code that might run on 7.6.

### Format-* -ExcludeProperty

All four `Format-*` cmdlets get `-ExcludeProperty`, so you can drop a noisy column without an extra `Select-Object`:

```powershell
$server = [pscustomobject]@{ Name = 'web01'; Role = 'IIS'; Notes = 'Long text nobody needs in a table' }
$server | Format-Table -ExcludeProperty Notes
```

On 7.7.0:

```text
Name  Role
----  ----
web01 IIS
```

On 7.6.10 this one does fail properly: `A parameter cannot be found that matches parameter name 'ExcludeProperty'.` The way that works on both gives the same table:

```powershell
$server | Select-Object -Property * -ExcludeProperty Notes | Format-Table
```

```text
Name  Role
----  ----
web01 IIS
```

**Do you need it?** No. It's for the console, and formatted output doesn't belong in a Function app or runbook anyway.

### $PSApplicationOutputEncoding

When PowerShell reads the output of an external program, it decodes the bytes with `[Console]::OutputEncoding`. If the program writes in a different encoding, you get mangled text. `wsl.exe` is the usual example, because it writes UTF-16 (two bytes per character). `cmd /u` does the same for its built-in commands, which makes it an easy test on any Windows machine.

I ran this on Windows with both versions. The console was on code page 850, the default for a Dutch Windows install, and my only WSL distro is `docker-desktop`:

```powershell
$distros = wsl.exe --list --quiet
$distros -contains 'docker-desktop'
```

Instead of one line with `docker-desktop`, `$distros` held three lines: the name with a null character after every letter (28 characters instead of 14), followed by two lines that held nothing but a null character. Same on 7.6.10 and 7.7.0.

I expected the `-contains` check to fail on that, but it returned `True`. PowerShell's comparison operators are culture-aware and apparently skip the null characters. So the check you'd write first looks fine, while the data itself is broken: it has the wrong length, extra lines, and nulls in anything you write to a file or pass on.

The way that works on both is to change `[Console]::OutputEncoding` for the duration of the call, and change it back afterwards:

```powershell
$previous = [Console]::OutputEncoding
try {
    [Console]::OutputEncoding = [System.Text.Encoding]::Unicode
    $distros = wsl.exe --list --quiet
}
finally {
    [Console]::OutputEncoding = $previous
}
```

That returned one clean `docker-desktop` (14 characters) on both versions. The downside is that `[Console]::OutputEncoding` is process-wide: while it's changed, everything else running in the same process sees the change too, such as `ForEach-Object -Parallel` and other runspaces.

7.7 adds `$PSApplicationOutputEncoding` ([#21219](https://github.com/PowerShell/PowerShell/pull/21219)). It only affects how PowerShell reads output from external programs, and it's an ordinary variable, so it only applies in the scope where you set it:

```powershell
function Get-WslDistro {
    $PSApplicationOutputEncoding = [System.Text.Encoding]::Unicode
    wsl.exe --list --quiet
}
Get-WslDistro
```

On 7.7.0 that returned the same clean `docker-desktop`. Calling `wsl.exe` directly again after the function gave the mangled output again, and `[Console]::OutputEncoding` was still on code page 850. The setting stayed inside the function.

> [!TIP] For wsl.exe specifically
> WSL has its own switch: set the environment variable `WSL_UTF8=1` and `wsl.exe` writes UTF-8 instead. With WSL 2.7.14 that also returned a clean `docker-desktop` on both versions, without touching PowerShell's encoding at all.

**Do you need it?** Only if you call programs that don't write in the encoding PowerShell expects. On 7.6, the try/finally or (for WSL) `WSL_UTF8` does the job. The variable is mostly nicer in parallel code.

### The rest

Smaller additions without a real workaround, and nothing I'd miss on a server: tab completion for `$PSBoundParameters.Keys`, a `SubjectAlternativeName` property on `Get-AuthenticodeSignature` output, `WildcardPattern.ToRegex()`, and a DSC v3 resource for your PowerShell profile (experimental). The [What's New in PowerShell 7.7](https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-77) page has the full list.

## Same script, different result

This is the part that matters if you write code that runs on both versions. Apart from `Join-Path -Extension` above, a few things behave differently on 7.7, mostly without an error to tell you.

### New-Guid now makes version 7 GUIDs

`New-Guid` now creates a version 7 UUID instead of a random version 4 ([#27033](https://github.com/PowerShell/PowerShell/pull/27033)). Version 7 GUIDs start with a timestamp, so they sort by creation time. That's nice for database keys. But it also means anyone who sees the GUID can read roughly when it was created, and the first part is predictable.

```powershell
(New-Guid).Version
[guid]::CreateVersion7().Version
[guid]::NewGuid().Version
```

| Line | 7.6.10 | 7.7.0 |
|---|---|---|
| `(New-Guid).Version` | 4 | 7 |
| `[guid]::CreateVersion7().Version` | 7 | 7 |
| `[guid]::NewGuid().Version` | 4 | 4 |

**Works on both:** say which one you want. `[guid]::NewGuid()` is always random (v4), and `[guid]::CreateVersion7()` is always v7. Both are .NET methods, so they behave the same on 7.6 and 7.7.

### Switches set to $false now mean $false

A long list of cmdlets used to check whether a switch was *passed*, not what its value was. So `-Shuffle:$false` behaved exactly like `-Shuffle`. 7.7 fixes this for `Get-Random`, `Split-Path`, `Where-Object`, `Test-Connection`, `New-PSSession`, the CSV cmdlets and a few more ([full list](https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-77#cmdlet-improvements)).

You rarely type `:$false` yourself. Where you do hit it is a wrapper function that passes its own switch on:

```powershell
function Get-Sample {
    param([int[]]$Item, [switch]$All)
    $Item | Get-Random -Shuffle:$All
}
(Get-Sample -Item (1..5)).Count
```

Called without `-All`, this should return one random item. 7.7.0 returns `1`. 7.6.10 returns `5`: it shuffled the whole list anyway.

Same with `Split-Path`:

```powershell
Split-Path -Path '/reports/2026/export.csv' -Leaf:$false
```

7.7.0 returns the parent folder, `/reports/2026`. 7.6.10 returns `export.csv`, the leaf you just said you didn't want.

**Works on both:** don't pass a switch as `:$false` to these cmdlets. Only add it when it's on, for example with a splat:

```powershell
function Get-Sample {
    param([int[]]$Item, [switch]$All)
    $randomParams = @{}
    if ($All) { $randomParams['Shuffle'] = $true }
    $Item | Get-Random @randomParams
}
(Get-Sample -Item (1..5)).Count
(Get-Sample -Item (1..5) -All).Count
```

Same output on 7.6.10 and 7.7.0:

```text
1
5
```

### Export-Csv -NoTypeInformation is obsolete

`-NoTypeInformation` has been the default behaviour since PowerShell 6, but it used to be something you added to every `Export-Csv` out of habit. I stopped using it a while ago, but if you haven't, search your scripts and runbooks for it before they run on 7.7. In 7.7 it's marked obsolete: it does nothing, but you get a warning every time ([#26719](https://github.com/PowerShell/PowerShell/pull/26719)).

```powershell
[pscustomobject]@{ Name = 'web01'; Role = 'IIS' } | ConvertTo-Csv -NoTypeInformation
```

On 7.6.10:

```text
"Name","Role"
"web01","IIS"
```

On 7.7.0:

```text
WARNING: Parameter 'NoTypeInformation' is obsolete. This parameter is obsolete and has no effect. The default behavior is to not include type information. Use -IncludeTypeInformation to include type information.
"Name","Role"
"web01","IIS"
```

**Works on both:** remove `-NoTypeInformation`. The output is the same on 6.0 and later, without the warning.

### Export-Csv -Append -NoHeader is now an error

That combination used to create a CSV without a header row, which then broke the next append ([#26472](https://github.com/PowerShell/PowerShell/pull/26472)):

```powershell
$csv = Join-Path ([System.IO.Path]::GetTempPath()) 'append-test.csv'
[pscustomobject]@{ Name = 'web01' } | Export-Csv -Path $csv -Append -NoHeader
```

7.6.10 says nothing and writes the file without a header. 7.7.0 stops with `You must specify either the -Append or -NoHeader parameters, but not both.`

### Smaller breaking changes

- `Test-Json` only resolves external `$ref` schema references when you add `-FetchExternalReference` ([#28205](https://github.com/PowerShell/PowerShell/pull/28205)).
- Empty or null arguments to parameters with not-null-or-empty validation now throw `System.ArgumentException` instead of `PSArgumentNullException`. This only matters if you catch that specific exception type ([#26668](https://github.com/PowerShell/PowerShell/pull/26668)).

## No more MSI

7.7.0 ships no MSI installer for Windows: look at the [release assets](https://github.com/PowerShell/PowerShell/releases/tag/v7.7.0) and you'll find the MSIX bundle, ZIPs, and winget/Store. 7.6.10 still has MSIs.

From a packaging point of view I still prefer MSI over MSIX. I don't do packaging professionally anymore, so that's more of a feeling than a strong argument. If you deploy PowerShell to servers or through Intune with an MSI today, that's a reason to stay on 7.6 for now, and to test how MSIX fits your deployment before the next LTS.

## So, upgrade or not?

| You are… | My advice |
|---|---|
| Running scripts on your own machine | Upgrade. My machines are on 7.7 already. |
| Writing code for Azure Functions or Automation | Stay on 7.6. 7.7 isn't available there, and since the portal normally only gets the LTS versions, I don't expect it to be. |
| Writing a module others install | Target 7.6, and use the workarounds above instead of the 7.7-only features. |
| Deploying PowerShell to servers with an MSI | Stay on 7.6 until you've sorted out MSIX. |

All my 7.6 code runs in pipelines, Automation runbooks and Function apps, and it goes through a test environment before production. If I use something that doesn't exist on 7.6, that shows up quickly there. The exceptions are the ones from this post that don't throw an error: `Join-Path -Extension`, switches passed as `:$false`, and `New-Guid`. Those only show up if something actually checks the result, so they're the ones to keep in mind while writing code on 7.7.

Happy scripting 😊

