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

Revisiting PowerShell Version Inventory

Posted on July 13, 2021July 13, 2021
https://www.pexels.com/photo/black-binocular-on-round-device-63901/

In the past, I've shared a variety of PowerShell approaches that you can use to inventory what versions of PowerShell are installed. But I think I now have the best approach short of searching the hard drive for powershell.exe and pwsh.exe, which I suppose is still a possibility and something I should write.

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!

Instead, I'm relying on the fact that if PowerShell or some version of PowerShell 7 was installed, the executable should be found with Get-Command. By the way, the code I came up with is designed to be run on a Windows platform. On non-Windows systems, PowerShell 7 or preview are the only version you can install and those operating systems already have proper tooling for checking installed packages.

Using Get-Command

In Windows, I can start with Get-Command.

$cmd = Get-Command -name powershell.exe -commandtype Application

If the file is found, I can run it and extract version information from the host. This will be in addition to version information available from Get-Command.

&$cmd.path -nologo -noprofile -command { Get-Host}

With is information I can create a custom object.

[pscustomobject]@{
    PSTypeName      = "PSInstallInfo"
    Name            = $cmd.Name
    FileVersion     = $cmd.Version
    PSVersion       = $psh.Version
    Comments        = $null
    Computername    = [System.Environment]::MachineName
    OperatingSystem = $os
}

I'm including the computer name which will be helpful later when I'm querying a bunch of remote machines. I could have used $env:Computername since I expect this will only be used on Windows platforms, but it might not. In which case, I need to use the .NET Framework since $env:Computername isn't available on non-Windows platforms. I'm also grabbing the operating system using Get-CimInstance.

$os = (Get-CimInstance -ClassName Win32_OperatingSystem -Property Caption).caption

And you'll also see that I am creating a custom object with a defined typename of 'PSInstalledInfo'.

The Inventory Script

Of course, you want to see the code.

#requires -version 5.1

#Use Get-Command to test for installed versions of PowerShell

[cmdletbinding()]
Param()

Write-Verbose "Searching for PowerShell installations on $([System.Environment]::MachineName)"

#build a list to hold the results
$list = [System.Collections.Generic.list[object]]::New()

#get the operating system
$os = (Get-CimInstance -ClassName Win32_OperatingSystem -Property Caption).caption

#Windows Powershell
Try {
    Write-Verbose "Testing for Windows PowerShell"
    $cmd = Get-Command -Name powershell.exe -ErrorAction stop
    if ($cmd) {
        Write-Verbose "Using $($cmd.path)"
        $psh = &$cmd.path -nologo -noprofile -command { Get-Host }

        $result = [pscustomobject]@{
            PSTypeName      = "PSInstallInfo"
            Name            = $cmd.Name
            FileVersion     = $cmd.Version
            PSVersion       = $psh.Version
            Comments        = $null
            Computername    = [System.Environment]::MachineName
            OperatingSystem = $os
        }
        Remove-Variable cmd
        $list.Add($result)
    }
}
Catch {
    Write-Verbose "Windows PowerShell not installed on $([Environment]::MachineName)."
}

#test for PowerShell 2.0 engine or feature

Write-Verbose "Testing for Windows PowerShell 2.0 engine or feature"

Try {
    #get computersystem roles to determine if running on a server or client
    $cs = Get-CimInstance -ClassName win32_Computersystem -Property Roles -ErrorAction Stop
    $rolestring = $cs.roles -join ","
    Write-Verbose "Detected roles $rolestring"
    if ($rolestring -match 'Server_NT|Domain_Controller') {
        Write-Verbose "Running Get-WindowsFeature"
        $f = Get-WindowsFeature PowerShell-V2
    }
    else {
        Write-Verbose "Running Get-WindowsOptionalFeature"
        $f = Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
    }

    if ($f.installed -OR $f.State -eq 'Enabled') {
        $result = [pscustomobject]@{
            PSTypeName      = "PSInstallInfo"
            Name            = "powershell.exe"
            FileVersion     = $null
            PSVersion       = "2.0"
            Comments        = "Windows PowerShell 2.0 feature enabled or installed"
            Computername    = [System.Environment]::MachineName
            OperatingSystem = $os
        }
        $list.Add($result)
    }
}
Catch {
    Write-Verbose "Windows PowerShell 2.0 not installed on $([Environment]::MachineName)."
}
#PowerShell 7

Write-Verbose "Testing for PowerShell 7"

Try {
    $cmd = Get-Command -Name "pwsh.exe" -CommandType Application -ErrorAction Stop

    if ($cmd) {
        foreach ($item in $cmd) {
            Write-Verbose "Using $($item.path)"
            $psh = &$item.path -nologo -noprofile -command { Get-Host }
            $PSVer = $psh.version

            #test fpr SSH
            Write-Verbose "Testing for SSH on $([Environment]::MachineName)"
            $ProgressPreference = "SilentlyContinue"
            $ssh = Test-NetConnection -ComputerName ([Environment]::MachineName) -Port 22 -WarningAction SilentlyContinue -InformationLevel Quiet
            If ($ssh) {
                $note = "SSH detected"
            }
            else {
                $note = ""
            }

            $result = [pscustomobject]@{
                PSTypeName      = "PSInstallInfo"
                Name            = $item.Name
                FileVersion     = $item.Version
                PSVersion       = $PSVer
                Comments        = $note.Trim()
                Computername    = [System.Environment]::MachineName
                OperatingSystem = $os
            }

            $list.Add($result)
        } #foreach item
        Remove-Variable cmd
    }
}
Catch {
    Write-Verbose "PowerShell 7 not installed on $([Environment]::MachineName)."
}

Write-Verbose "Testing for PowerShell 7 preview"
#filter out preview if running this command IN a preview
if ($host.version.PSSemVerPreReleaseLabel) {
    Write-Verbose "PowerShell preview detected"
    $n = "pwsh.exe"
}
else {
    $n = "pwsh-preview.cmd"
}
Try {
    $cmd = Get-Command -Name $n -CommandType Application -ErrorAction Stop

    if ($cmd) {
        foreach ($item in $cmd) {
            Write-Verbose "Using $($item.path)"
            $psh = &$item.path -nologo -noprofile -command { Get-Host }
            $PSVer = $psh.version
            if ($psver.PSSemVerPreReleaseLabel) {
                [string]$note = $psver.PSSemVerPreReleaseLabel
            }
            else {
                [string]$note = ""
            }

            #test fpr SSH
            Write-Verbose "Testing for SSH on $([Environment]::MachineName)."
            $ProgressPreference = "SilentlyContinue"
            $ssh = Test-NetConnection -ComputerName ([Environment]::MachineName) -Port 22 -WarningAction SilentlyContinue -InformationLevel Quiet
            if ($ssh) {
                $note += " SSH detected"
            }
            $result = [pscustomobject]@{
                PSTypeName      = "PSInstallInfo"
                Name            = $item.Name
                FileVersion     = $item.Version
                PSVersion       = $PSVer
                Comments        = $note.Trim()
                Computername    = [System.Environment]::MachineName
                OperatingSystem = $os
            }

            if ($list.Psversion -notcontains $result.PSVersion) {
                $list.Add($result)
            }
            else {
                Write-Verbose "Skipping duplicate version $($result.version)"
            }
        } #foreach item
    }
}
Catch {
    Write-Verbose "PowerShell 7 preview not installed on $([Environment]::MachineName)."
}

#write the results to the pipeline
$list | Sort-Object -Property PSVersion

As you look through the code I'm searching for PowerShell and pwsh versions. I'm also testing if ssh is enabled on systems running PowerShell 7. I didn't want to rely on checking the sshd service because I didn't want to make an assumption. Although I am assuming port 22. You'll also see that I am specifying a computername for Test-NetConnection. In my testing, if I don't specify a computername, PowerShell tries to connect to a public IP address.

Here's how it looks on my local machine.

Because this is a script, I can use it with Invoke-Command to query remote Windows computers, assuming of course that PowerShell remoting is enabled.

Doing More with Type

Of course, since I have a custom object, I can do more. Such as define a setup of default properties.

Update-TypeData -TypeName PSInstallInfo -DefaultDisplayPropertySet "Name","FileVersion","PSVersion","Comments","ComputerName" -force

This gets rid of the pesky RunspaceID property you get when using Invoke-Command.

Or, I can really get going and define a custom format file which I did using my trusty New-PSFormatXML command.

<!--
Format type data generated 07/13/2021 12:14:45 by PROSPERO\Jeff

This file was created using the New-PSFormatXML command that is part
of the PSScriptTools module.

https://github.com/jdhitsolutions/PSScriptTools
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 07/13/2021 12:14:45 by PROSPERO\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>PSInstallInfo</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <ScriptBlock>"{0} [{1}]" -f $_.Computername,$_.OperatingSystem</ScriptBlock>
        <Label>Computername</Label>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths
        <AutoSize />.-->
        <TableHeaders>
          <TableColumnHeader>
            <Label>Name</Label>
            <Width>19</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>FileVersion</Label>
            <Width>15</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>PSVersion</Label>
            <Width>15</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Comments</Label>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>FileVersion</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                if (($host.name -match "console|code|serverremotehost") -AND ($_.name -match "preview")) {
                  "$([char]27)[38;5;219m$($_.PSVersion)$([char]27)[0m"
                }
                else {
                  $_.PSVersion
                }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
               <ScriptBlock>
                if (($host.name -match "console|code|serverremotehost") -AND ($_.comments-match "SSH detected")) {
                  <!-- replace SSH Detected with an ANSI sequence-->
                  $_.comments -replace "SSH detected","$([char]27)[1;38;5;155mSSH detected$([char]27)[0m"
                }
                else {
                  $_.comments
                }
                </ScriptBlock>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

I'm using ANSI sequences to highlight PowerShell previews and SSH. All I need to do is import it into my session.

Update-Formatdata c:\scripts\psinstalledinfo.format.ps1xml

Now, I get very meaningful output.

Your Turn

I hope you'll give this a try. There's certainly room for you to add your own touches. I left the Comments property mostly unused for future use. Or you may want to grab additional information like file timestamps. Have fun.


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 “Revisiting PowerShell Version Inventory”

  1. Thomas Lee says:
    July 15, 2021 at 7:21 am

    The inventory script works well for versions of PowerShell installed by Windows Installer. However, it does not find the side by side installations of PowerShell 7. Personally. I have three versions of pwsh – 7.1.x (installed via MSI) as well as the latest 7.2 preview and the build of the day. These last two are NOT found by this script sadly. Get-Command does not find these versions – you would need to use something like Get-ChildItem -Path c:\pwsh.exe -Recurse -EA 0

    1. Jeffery Hicks says:
      July 15, 2021 at 9:09 am

      You are correct. No, it won’t. But I’m working on the assumption that someone like you is an outlier. I would envision this script as being useful to inventory servers.

    2. Jeffery Hicks says:
      July 15, 2021 at 1:40 pm

      See if this is any better for you: https://jdhitsolutions.com/blog/powershell/8492/searching-for-powershell-with-cim/

  2. Pingback: Searching for PowerShell with CIM • The Lonely Administrator

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