Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Get Installed PowerShell Versions

Posted on May 7, 2020May 7, 2020

As is the norm for a typical day, I was working on one thing when I was distracted by a shiny rabbit hole (to mix some metaphors). Half a day later I have a new PowerShell function that not only might you find useful, but I think it has some nice scripting features you might want to try yourself.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

The problem I had was to remotely query a Windows system and determine what versions of PowerShell were installed. For servers, you can use Get-WindowsFeature to check if PowerShell-V2 is installed. But what about PowerShell Core, PowerShell 7, or even a preview build? My solution was to query the registry.

ps-registry

Windows PowerShell uses different keys and property names than PowerShell Core, which includes PowerShell 7. Once I knew where to look, I could write a function. Because you can only query the registry on the "local" machine, my function would need to use PowerShell remoting. Here's the code, which is up on Github.

The function includes comment-based help.

get-psinstalled-helpI wrote the function to accept computer names and credentials or an existing PSSession object. However, under the hood I'm only using PSSessions. If computer names are used, I create a temporary PSSession and add it to an array.

Process {
        if ($PSCmdlet.ParameterSetName -eq 'computer') {
            foreach ($computer in $Computername ) {
                $PSBoundParameters["Computername"] = $Computer
                #create a session
                Write-Verbose "[$(New-TimeSpan -start $start) PROCESS] Creating a temporary PSSession to $($computer.toUpper())"
                If ($Credential.username) {
                    Write-Verbose "[$(New-TimeSpan -start $start) PROCESS] Using credential for $($credential.username)"
                }
                Try {
                    #save each created session to $tmp so it can be removed at the end
                    $all += New-PSSession @PSBoundParameters -ErrorAction Stop -OutVariable +tmp
                }
                Catch {
                    Write-Error $_
                }
            } #foreach computer
        } #if computer parameterset

Note that when I create the session, I am saving it $tmp using Out-Variable. This is so that at the end of the command I can remove any sessions that I created. If a PSSession is passed, I add it to the array.

foreach ($sess in $session) {
                if ($sess.state -eq 'opened') {
                    Write-Verbose "[$(New-TimeSpan -start $start) PROCESS] Using session for $($sess.computername.toUpper())"
                    $all +=$sess
                }
            }

All of the sessions are added to an array of parameters which I splat to Invoke-Command.

 Invoke-Command @icmParams

One thing that is different than what you might expect, is that this statement happens in the function's End scriptblock.  This is because I want to rely on PowerShell to best manage all the connections. That's why I include the ThrottleLimit parameter which gets passed along to Invoke-Command. In earlier versions, I was piping PSSessions and running Invoke-Command in the Process scriptblock. This meant sessions were being processed one at a time. By moving the command to the End scriptblock, total processing time was cut almost in half. To summarize this point: the Process scriptblock is used for building a list of PSSessions. That's it.

The Begin scriptblock is where I define the scriptblock to run remotely. I'm a big fan of using Write-Verbose in my commands. The potential obstacle though, is that if I run the function with -Verbose, when the scriptblock runs remotely, it doesn't know anything about my verbose preferences. Here's how I handle that.

In the scriptblock, I add a parameter to indicate my Verbose preference.

$sb = {
            param([string]$VerbPref = "SilentlyContinue")

            $VerbosePreference = $VerbPref

In my function, I added a value to -Argumentlist that shows the verbose preference on my side.

$icmParams = @{
            Scriptblock      = $sb
            Argumentlist     = $VerbosePreference
            HideComputerName = $True
            ThrottleLimit    = $ThrottleLimit
            ErrorAction      = "Stop"
            Session          = $null
        }

The last part of the verbose feature is that I'm using a running time as part of the message. I also indicate where in the function the statement is happening. In the scriptblock, my verbose messages employ $using to get the $start variable which is defined on my computer. I also use REMOTE to indicate the verbose message is happening elsewhere.

Write-Verbose "[$(New-TimeSpan -start $using:start) REMOTE ] Querying \\$($env:computername)\$pskey"

Here's what it all looks like.

get-psinstalled-verbose

Now I can tell what is happening and where. Note that I aligned the leading portion of the verbose message. I find this makes it much easier to read.

As for the result: nice and simple.

get-psinstalled-result

Or by specifying computer names.

get-psinstalled-result2

I have to say I'm pleased with the results. I hope you'll give it a try and let me know what you think. It should work in both Windows PowerShell and PowerShell 7.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

4 thoughts on “Get Installed PowerShell Versions”

  1. Matt says:
    May 8, 2020 at 6:55 am

    I like your solution.
    Regarding PScore. PS7 replaces previous installations but not completely and registry has leftovers like 7.0.0-rc.3 or -preview which are not actually installed.

    1. Jeffery Hicks says:
      May 8, 2020 at 12:04 pm

      That is good to know. In my tests, the registry is accurately reflecting what is installed. But yiou make a good point. This data is only as good as much as you trust it.

  2. Thomas Lee says:
    May 10, 2020 at 10:50 am

    This does not show side by side builds such as daily build or preview build that are not installed using the MSI. IIRC, the registry is only updated via MSI’s. Just tested and this script does not find 7.1 preview or build of the day not installed via MSI

    1. Jeffery Hicks says:
      May 10, 2020 at 11:33 am

      This function is only as good as you trust your registry. Or your users.

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d