I recently overhauled a script that I wrote to take advantage of the parallel processing functionality that is included in Microsoft Powershell 7. The results have been excellent with script runtimes being reduced from over an hour down to roughly 5 minutes. Learning the ins-and-outs of using parallel processing was a bit of a chore that I will discuss in a later article, however the first hurdle that had to be mounted was simply getting Powershell 7 installed and figuring out how to make use of it. Quickly getting up and running with Powershell 7 is what this article seeks to address.


Why is Moving to a New Version of Powershell a Chore?
Windows 10 ships with Powershell version 5. Powershell 5 is based on the .NET framework. Powershell 7 is based on .NET “Core”. There are a slew of reasons for this shift that I am not going to get into, in short though, .NET core is the apparent future of Powershell as it isn’t constrained to just running on Microsoft platforms. As a result of this fundamental shift in frameworks, one cannot simply “upgrade” from Powershell 5 to Powershell 7 as they are essentially two different applications. Therefore, rather than a simple upgrade, Powershell 7 is installed alongside Powershell 5. After getting my head wrapped around this and dealing with a few bumps, frankly, it’s pretty easy to work with both. I am going to quickly layout how to get up and running with Powershell Version 7, how to actually use Powershell 7, and finally some of the changes I ran into that you should be aware of.


Installing Powershell 7
If you want to read the long Microsoft documentation on installing powershell 7 you can do so right here:
https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-7#installing-the-msi-package

My preference though is to make life easier when possible…

To install Powershell 7 (on Windows 10), you simply get the MSI from GIT and you (most likely) will want the current stable-release version:
GIT HUB PAGE (Search for Powershell-7.0.3-win-x64.msi in the page): https://github.com/PowerShell/PowerShell/releases
–or– If you are feeling particularly lazy you can just direct download…
DIRECT 64-BIT DOWNLOAD LINK (version 7.0.3): https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-x64.msi

Download that and install it. You may have to install some pre-requisites, if so the installer will tell you as much. Getting Powershell 7 installed is a reasonably simple affair.

Onto the next question…


How Does One Actually Use Powershell 7?

First, if you want to just open a Powershell 7 shell, open up a Powershell console. Once it is open you can run:

$PSVersionTable

You will note that the shell opens and defaults to Powershell 5.1.xxx. To switch to a Powershell 7 shell, use the following command:

pwsh

This creates a new shell session in your current window and if you check $PSversiontable again you will note it now shows Powershell version 7.0.3. Simple enough, but wait, there’s more…


Executing Powershell Scripts with Powershell 7
First, if you want to run a powershell script you wrote with Powershell 7 and you are currently in a Powershell 5 shell, that syntax looks like this:

pwsh.exe ./powershell7scriptname.ps1



Running Powershell 5 Scripts From a Powershell 7 Script
This admittedly may be an outside use case but it was a question I ended up having to answer all the same so hopefully I will save you some Google + Trial & Error. Briefly stated, what if your Powershell 7 script calls upon another script that needs to run in Powershell 5? I ran into this situation due to laziness because I needed to call another script I didn’t feel like re-writing for Powershell 7. The answer that I found was that in your Powershell 7 script you can actually call and execute your Powershell 5 script directly under a Powershell 5 session. That syntax looked like this for me (within my Powershell 7 script):

powershell.exe ./powershell5scriptname.ps1 [parameter] [parameter]

You will notice that I included parameters in my example syntax above. Reason being, that Powershell 5 script is running in a completely different shell, so if you need to pass information into it from your Powershell 7 script, you can do so via defining parameters in the Powershell 5 script.

Onto the next question…


How do I use Powershell 7 with ISE?
I should probably be using a tool like Visual Studio Code but honestly I just love ISE. This was probably the first question I ended up asking after installing Powershell 7 and was frustrated as it wasn’t clear how I could switch between version 5 and version 7 within ISE.

Thankfully, someone else wrote a nice bit of code you can run once ISE is open to add some menu items in ISE that allow you to switch between a Powershell 5 and Powershell 7 session. Run this in ISE:

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Clear()
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Switch to PowerShell 7", {
        function New-OutOfProcRunspace {
            param($ProcessId)

            $ci = New-Object -TypeName System.Management.Automation.Runspaces.NamedPipeConnectionInfo -ArgumentList @($ProcessId)
            $tt = [System.Management.Automation.Runspaces.TypeTable]::LoadDefaultTypeFiles()

            $Runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($ci, $Host, $tt)

            $Runspace.Open()
            $Runspace
        }

        $PowerShell = Start-Process PWSH -ArgumentList @("-NoExit") -PassThru -WindowStyle Hidden
        $Runspace = New-OutOfProcRunspace -ProcessId $PowerShell.Id
        $Host.PushRunspace($Runspace)
}, "ALT+F5") | Out-Null

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Switch to Windows PowerShell", {
    $Host.PopRunspace()

    $Child = Get-CimInstance -ClassName win32_process | where {$_.ParentProcessId -eq $Pid}
    $Child | ForEach-Object { Stop-Process -Id $_.ProcessId }

}, "ALT+F6") | Out-Null

After running the above, look under the “Add-ons” menu at the top of ISE and you will see the new options.

I used the phrase “switch between sessions”, that isn’t quite accurate. Every time you click on “Switch to Powershell 7” it creates a new Powershell 7 session. When you click “Switch to Powershell” it does switch you back into your previous Powershell 5 session. But if you then click “Switch to Powershell 7” again, it is a brand new Powershell 7 session. Why does this matter? In short, any variables or functions you loaded into memory during your first Powershell 7 session are lost. Variables and functions loaded into your Powershell 5 session however remain. Not being aware of this could potentially throw you off a bit so I figured I should mention it.


Protecting Your Powershell 7 Scripts from Accidental Execution in Powershell 5
A colleague of mine made me aware of this… At the top of your Powershell 7 scripts put the following line of code (including the “#” hash):

#Requires -version 7

Save your script and then try to execute it under a Powershell 5 session and you will see that it won’t allow you to. This can prevent you or someone else from accidentally executing a script (under the default Powershell 5) written with components and or syntax that only works in Powershell 7.


Finally, Realize That Powershell 7 is Not Always Equal to Powershell 5
Powershell version 7 and Powershell version 5 are largely (deceptively) the same and I made the understandable mistake of thinking I could just take my Powershell 5 script and run it under Powershell 7 and all would work well… Not so… Some common built-in functions have changed. I will provide my full example here, which is using the Test-Connection command:

In my script, I am passing an array of computer names and checking which systems are reachable from the system where the script is running. I then create two new arrays, one with all of the systems that are network reachable, and one array with the systems that are not. I do this in a rather round-about way but all the same here is what that looked like.

Original Powershell 5 syntax:

$output = @($output | ForEach-Object { Test-Connection -ComputerName $_ -Count 1 -AsJob } | Get-Job | Receive-Job -Wait | `
Select-Object @{Name='ComputerName';Expression={$_.Address}}, @{Name='Reachable';Expression={if ($_.StatusCode -eq 0) { echo 1 } else { echo 0 }}})
Reachable = @($output | Where-Object {$_.Reachable -eq'1'} | Select-Object -ExpandProperty ComputerName)
Unreachable = @($output | Where-Object {$_.Reachable -eq'0'} | Select-Object -ExpandProperty ComputerName)

My revised version of the above code so that it would run on Powershell 7:

$outputTest = @()
$outputTest += $output | ForEach-Object -ThrottleLimit 500 -Parallel {
    $item = New-Object PSObject
    $item | Add-Member -type NoteProperty -Name "ComputerName" -Value $_
    If ( Test-Connection -TargetName $_ -count 1 -quiet -ErrorAction SilentlyContinue ) {
        $item | Add-Member -type NoteProperty -Name "Reachable" -Value "1"
        }Else{
        $item | Add-Member -type NoteProperty -Name "Reachable" -Value "0"
    }
    Return $item
}
Reachable = @($output | Where-Object {$_.Reachable -eq'1'} | Select-Object -ExpandProperty ComputerName)
Unreachable = @($output | Where-Object {$_.Reachable -eq'0'} | Select-Object -ExpandProperty ComputerName)

You will note that under Powershell 5 I was using the -AsJob option with Test-Connection which is an easy way to do a type of parallel processing for that particular function in Powershell 5. That option doesn’t exist in Powershell 7. Furthermore, the option “ComputerName” is now “TargetName.” Also, output names also changed but I just did away with them for my purposes anyhow. I ended up using the “ForEachObject -Parallel” approach to solve my issue. The lesson here is that you have to test and then test again and then after that, test some more. Most things are portable, but some seemingly common and basic functions have really changed and porting a script over to Powershell 7 may not be a simple copy-and-paste operation.

All of the above said, was it worth the headache? I would argue that it definitely is for some use cases. My particular script is being used to “scan” systems in the environment and collect metrics. Scanning a handful of systems with the old script (which did one at a time) was fine for a handful of systems, however it scaled terribly. Parallel processing in Powershell 7 means I can scan as many systems as my host computer can establish connections to in the same amount of time it takes me to scan whichever single system takes the longest. For me, this reduced the script run time from well over an hour down to around 5 minutes. But again, I will get into the complexities I ran into with parallel execution in another article.

Cheers!

One comment on: Getting Up and Running with Powershell 7

  1. Vytenis
    Reply

    nice, thanks.

Join the discussion

Your email address will not be published. Required fields are marked *