Add PowerShell-7 preview to Windows Terminal in 30 seconds

Edit: The stuff below doesn’t work any more. But this new stuff works.

Fun times in Windows land over the last few days with the release of new previews of Windows Terminal (v0.4) and PowerShell 7 (Preview 3).

To celebrate, here’s a simple script that you can copy-and-paste into a PowerShell window to add PowerShell 7 to Windows Terminal. (You can even use the Windows Terminal PowerShell terminal. Wow!)

The whole thing might take 30 seconds, which is a significant improvement on my last effort of 2 minutes. Why not take 1 minutes 30 seconds to make yourself a drink?

Pre-requisites:

  • PowerShell 7-preview x64 is installed
  • Windows Terminal v0.4 is installed and has been run

Here’s what it looks like:

Here’s the code:

$terminalFolderPath = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState"
# Get Windows Terminal settings file
$settingsFilePath = Join-Path $terminalFolderPath 'profiles.json'
$json = Get-Content $settingsFilePath | ConvertFrom-Json
# Get profiles
$profiles = $json.profiles
# Make a copy of first profile and configure for PS7 x64
$ps7 = $profiles[0].psobject.Copy()
$ps7.name = 'PowerShell 7-preview (x64)'
$ps7.commandline = 'C:\Program Files\PowerShell\7-preview\pwsh.exe'
$ps7.guid = '{' + (New-Guid).ToString() + '}'
# Download and set icon
$pwsh7IconPath = Join-Path $terminalFolderPath 'pwsh7.ico'
Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/weebsnore/Add-PS7ToWindowsTerminal/master/pwsh7.ico' -OutFile $pwsh7IconPath
$ps7.icon = $pwsh7IconPath
# Write updated settings file to disk
$json.profiles = $profiles + $ps7
$json | ConvertTo-Json | Out-File $settingsFilePath

And here it is on GitHub.

Tip: avoid Wait-Debugger gotcha on Azure Functions

My biggest gotcha with Azure Functions is that you need to put Wait-Debugger in your script for local debugging, and it’s easy to forget to remove it when you deploy to Azure.

My last post talked about exploring Azure Functions’ environment, and I mentioned that you could compare the cloud version with its locally-run approximation.

Well, when you’re running your function locally, the variable $env:AZURE_FUNCTIONS_ENVIRONMENT is set to Development.

This means you can ensure that you never leave a function hanging at Wait-Debugger in the cloud by wrapping it like this:

if ($env:AZURE_FUNCTIONS_ENVIRONMENT -eq 'Development') {
    Wait-Debugger
}

Another solution is to wrap your debug command like this:

if ($Request.Query.Debug -eq 'True') {
    Wait-Debugger
}

And then invoke your function with &Debug=True when you want to debug it.

How do you handle this problem?

Resize browser window with PowerShell

I like to make little videos to go with my blog posts, and today I’ve been looking for a way to resize my browser window to a consistent size for recording.

Google immediately turned up this little beauty in the TechNet Script Center:

But when I tried it out:

Set-Window -ProcessName msedge -Width 1024 -Height 768

…I got a scary error:

Cannot convert argument "hWnd", with value: "System.Object[]", for "GetWindowRect" to type "System.IntPtr": "Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.IntPtr"."
At C:\Git\PowerShell\Set-Window.ps1:91 char:9
+         $Return = [Window]::GetWindowRect($Handle,[ref]$Rectangle)
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

Here are the relevant lines from the function:

$Handle = (Get-Process -Name $ProcessName).MainWindowHandle
$Return = [Window]::GetWindowRect($Handle,[ref]$Rectangle)

A bit of exploring shows that one of the msedge processes is very different from the others:

$msedge = Get-Process msedge

$msedge

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
     10     1.77       2.64       0.03     904   1 msedge
     19    17.69      30.85       0.34    4044   1 msedge
     57   128.71     150.07      57.81    5000   1 msedge
     83   185.82     182.69      38.77    8840   1 msedge
     29    47.08      75.16       9.78    9548   1 msedge
     83    82.12     128.55     171.92   11304   1 msedge
     22    27.96      41.45       0.41   11596   1 msedge
     20    21.31      38.14       2.47   12664   1 msedge
     49   303.89     156.82     116.14   12828   1 msedge
     24    29.71      48.62       1.52   14788   1 msedge
     26    21.57      31.14      25.20   15904   1 msedge
     56   132.80     157.91      76.39   15976   1 msedge
     20    19.50      33.82       0.27   16360   1 msedge
     16    11.88      19.46       0.05   16416   1 msedge
     34    59.82      89.91       3.55   17300   1 msedge
     29    48.37      76.49       3.34   17884   1 msedge
     22    25.07      42.07       0.41   18408   1 msedge
     27    51.70      78.73       5.69   18492   1 msedge
     26    39.59      60.88       0.81   18520   1 msedge
     28    54.87      73.89      18.56   19080   1 msedge
     47   108.27     135.33      26.95   19888   1 msedge

$msedge.MainWindowHandle

0
0
0
0
0
4326398
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0

This was my first solution, which works fine:

# Only get non-zero handles
$Handle = (Get-Process -Name $ProcessName).MainWindowHandle | Where-Object {$_.ToInt32() -gt 0}
# The Handles have type IntPtr. Without .ToInt32() you get this error:
# Cannot compare "0" because it is not IComparable

But after a bit of poking around, I think this is a little better:

# Only get process whose parent is explorer
$Handle = (Get-Process -Name $ProcessName | Where-Object {$_.Parent.ProcessName -eq 'explorer'}).MainWindowHandle