List installed .NET versions on remote computers

From Svendsen Tech PowerShell Wiki
Jump to: navigation, search

You could find yourself wanting to know which versions of .NET are installed on remote computers or the computer you're on. Here is a PowerShell script that takes a list of computers (or you can use -LocalHost for the current computer), collects data via remote registry access - or PSRemoting as of v2.0 - and the original script creates a CSV file with the data. To be friendly to people used to the old script, I added the parameter -ExportToCSV to the "DotNetVersionLister" module I've since then uploaded. It's a lot more standards-conforming as PowerShell goes. It emits/outputs objects instead of simply writing a CSV file by default, plus I added support for using PSRemoting instead of remote registry access, with the -PSRemoting parameter.

I'm not rewriting all of the documentation, so some of it could be partially outdated if you use the latest v2.x module - but I do recommend that.

The CSV can easily be parsed using PowerShell's usual mechanisms, some of which I demonstrate here. You can also import it in Excel, which there's a screenshot of here.

I have since creating the initial documentation also added support for:

  • .NET 4.5
  • .NET 4.5.1
  • .NET 4.5.2
  • .NET 4.6
  • .NET 4.6.1
  • .NET 4.6.2
  • .NET 4.7
  • .NET 4.7.1

... and greater, but then it will just say "4.7.1 or later". Drop me a mail at svendsentech@gmail.com if a new .NET version is out. I'll try to add support if possible.

The CSV has one field for errors, one for each .NET version from 1.1 till 4.0 (client/full differentiated for v4) and one field with the computer/host name. In v1.1 and later versions of the script there's also a field for 4.x which differentiates between the different 4.x versions above 4.0, from 4.5 and up.

Access to the remote registry service on the target computers is necessary unless you use the module version in v2.x or greater, where you also have a -PSRemoting parameter. There are a couple of example scripts for enabling and disabling the remote registry service remotely using sc.exe at the bottom of the article.

I didn't add detection of .NET v1.0, so you're on your own if you need to look for that. You will also have to rewrite the script if you need to detect service pack levels.

I initially wrote a simple version of this script some time ago, and I've now rewritten it to suit "public" standards a bit more, and it has then been patched with detection of new versions. As the years have passed since inception in 2011, this script has by now been patched numerous times, and rewritten almost completely. The documentation is partially outdated in this article.

Here's what the latest version looks like with the -LocalHost parameter in use:

DotNetVersionLister-example3-localhost-local-computer.png


Used Registry Keys

In the table below you can see the registry keys that are used. If the value is "1", the script reports them as installed. There's an article from Microsoft about this here (previous link: here.)

.NET 1.1 HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322\Install
.NET 2.0 HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727\Install
.NET 3.0 HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.0\Install
.NET 3.5 HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5\Install
.NET 4.0 Client HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client\Install
.NET 4.0 Full HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\Install
.NET >4.5.x HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\Release (parsed)

End Result CSV Displayed In Excel

Here's a screenshot of the CSV imported to Excel.

In Excel 2010 the default delimiter appears to be a tab, not a comma, so you need to go to the ribbon "Data", choose "From Text" under "Get External Data" and specify the correct options when importing. Using either the default encoding ANSI or UTF-8 works for me with the lab data. You need to specify that the data is delimited, and that the delimiter is a comma.

There's more about this in the Excel help if you search for "excel import csv" or similar.

A trick to avoid this, is to use a semicolon as a delimiter in the CSV file, and save it with a .csv extension. That should cause Excel to magically import it correctly when double-clicked (yes, I boldly split infinitives!).

ipcsv file1.csv | epcsv -not -enc utf8 -delim ';' file1semicolon.csv

I've learned this depends on culture settings, so instead of -Delimiter ";" # you can use the parameter -UseCulture, this should make it open magically in Excel on that computer with current culture/locale settings.

Get-DotNet-Version-Excel-CSV.png

Module Parameters

Parameters for Get-DotNetVersion as exported from the DotNetVersionLister module.

ComputerName Alias "Cn". A list of computer names to process. Use "(gc x:\hosts.txt)", without the quotes, to use a file.
PSRemoting Use PowerShell remoting instead of remote registry access. Remote registry access requires RPC, which in turn requires lots of firewall openings.
ExportToCSV Export to a CSV file as well as files containing online and offline computers.
LocalHost Check the local computer. Introduced in version 2.1.
ContinueOnPingFail Try to gather even if the remote computer does not reply to ping.
NoSummary Do not display the end summary with Write-Host.
Clobber Only in use with -ExportToCSV. Overwrite potentially existing files without prompting. Date and time is in the file name by default
Credential Only in use with -PSRemoting. Specify explicit/alternate credentials.

Script Parameters

Parameters for the original script.

ComputerName Required. A list of computer names to process. Use "(gc x:\hosts.txt)", without the quotes, to use a file.
Clobber Overwrite potentially existing files without prompting. Date and time is in the file name by default

Example Use

Here are the results from the home lab. The -ComputerName parameter takes a list of strings; to use a file, use "(gc .\computers.txt)". In the screenshot example I use a subexpression with a pipeline that just gets every computer from Active Directory. See this article for information about how to get computer names from Active Directory.

Test Run

Some errors and some successful checks.

Get-DotNet-Version-lab-run.png

Looking At The CSV Data

For computers where there are no errors, the error field will contain a hyphen/dash ("-"), so you can use -eq '-' or -ne '-' to filter on that using Where-Object. You will get "No ping reply" in the error field for computers that do not respond to ICMP echo / ping - and also for computers which do not resolve via DNS or WINS/NetBIOS.

PS C:\> Import-Csv .\DotNet-Versions-2012-03-18.csv | Select -First 1

Computer   : esxi
v4\Client  : Unknown
v4\Full    : Unknown
v3.5       : Unknown
v3.0       : Unknown
v2.0.50727 : Unknown
v1.1.4322  : Unknown
Error      : No ping reply


PS C:\> Import-Csv .\DotNet-Versions-2012-03-18.csv | Select -Skip 1 -First 1

Computer   : SRV2003R2ESXI
v4\Client  : Installed
v4\Full    : Installed
v3.5       : Installed
v3.0       : Installed
v2.0.50727 : Installed
v1.1.4322  : Not installed (no key)
Error      : -

Inspecting Computers With Errors

To filter out and inspect all errors, you can do it like this:

PS C:\> Import-Csv .\DotNet-Versions-2012-03-18.csv | Where { $_.Error -ne '-' } | ft -auto

Computer    v4\Client v4\Full v3.5    v3.0    v2.0.50727 v1.1.4322 Error
--------    --------- ------- ----    ----    ---------- --------- -----
esxi        Unknown   Unknown Unknown Unknown Unknown    Unknown   No ping reply
WIN2K       Unknown   Unknown Unknown Unknown Unknown    Unknown   No ping reply
SERVER2003  Unknown   Unknown Unknown Unknown Unknown    Unknown   No ping reply
VISTA64ESXI Unknown   Unknown Unknown Unknown Unknown    Unknown   Unable to open remote registry: Exception calling...
WINXPESXI   Unknown   Unknown Unknown Unknown Unknown    Unknown   Unable to open remote registry: Exception calling...
WIN7ESXI    Unknown   Unknown Unknown Unknown Unknown    Unknown   Unable to open remote registry: Exception calling...
SERVER2008  Unknown   Unknown Unknown Unknown Unknown    Unknown   No ping reply
XPTANKET    Unknown   Unknown Unknown Unknown Unknown    Unknown   No ping reply

Filtering On A Certain Installed .NET Version

To filter on which computers have .NET 4.0 Full installed, you can do it like this:

PS C:\> Import-Csv .\DotNet-Versions-2012-03-18.csv |
  Where { $_.'v4\Full' -eq 'Installed' } |
  Select 'v4\Full', Computer | ft -auto

v4\Full   Computer
-------   --------
Installed SRV2003R2ESXI
Installed SIEMENS
Installed 2008R2ESXI2
Installed 2008R2ESXI

You can use "$_.'v4\Full' -ne 'Installed'" to see which clients or servers don't have it installed.

Get-DotNet-Version-Filtering.png

I started the remote registry service on the Vista client and now I got the data from that too:

PS C:\> Import-Csv .\DotNet-Versions-2012-03-18.csv | Where { $_.Computer -ieq 'vista64esxi' }

Computer   : VISTA64ESXI
v4\Client  : Installed
v4\Full    : Not installed (no key)
v3.5       : Installed
v3.0       : Installed
v2.0.50727 : Installed
v1.1.4322  : Not installed (no key)
Error      : -

Summary Of .NET Versions

Here's a screenshot of a summary of my test environment:

Get-DotNet-Version-Summary.png

Here's how the 4.x versions are differentiated:

Get-DotNet-Version-ipcsv-v1.1.png

Here's a screenshot of the DotNetVersionLister module (v2.0 of this thing, you could say):

DotNetVersionLister-example.png


Download

  • Get-DotNet-Version.ps1.txt - frozen version for now, v1.x. Right-click, "save as" and rename to .ps1. Remember to unblock (PSv3 and up have the cmdlet Unblock-File).
  • DotNetVersionLister.zip - v.2.2.4. Download, unblock, unzip, copy to a modules folder. Or download it from the PowerShell gallery (see below).

Version history

  • 2017-12-20: v2.2.4: Corrected a flaw/bug and added support also for .NET 4.6.2 on Server 2016 (in some editions) and on Windows 10 Anniversary Update. Based on feedback from Byron Wright. He had this issue with a Server 2016 server.
  • 2017-12-01: v2.2.3. Added .NET version 4.7.1 detection.
  • 2017-06-18: v2.2.2. Added .NET version 4.7 detection.
  • 2017-06-07: v2.2.1. Added -Credential for use with PSRemoting only for now (needed it ;).
  • 2017-04-20: v2.2. Bug fix. Removed calls to Dispose() causing errors on at least computers with PSv2. Windows 7 with PSv3 worked, with PSv2 it didn't - now both work. This was only in effect with the -PSRemoting flag.
  • 2017-02-12: v2.1.1 uploaded. Documentation fixes/improvements.
  • 2017-02-12: v2.1. Things are looking better. Also fixed the accidental feature loss of 4.x detection on Windows 10 and a few other OSs. Should be OK now.
  • 2017-02-10: Uploading the DotNetVersionLister module. "v2.0" of this stuff. It's a lot better than the stand-alone script, but it became complex and difficult to test, so there are some small oddities/bugs in the error handling in more "obscure" cases. It has an experimental -PSRemoting parameter that uses PSRemoting instead of remote registry access. This became hacky quickly. Should be usable, though. I've been made aware that you can pass String.Empty to access the local registry with OpenRemoteBaseKey(), so I'll fix this mess soon. Just waiting for the next "energy wave" ...
  • 2016-10-10: Uploaded v1.4. Added support for .NET 4.6.2. Thanks to Blake Masri for making me aware of the need.
  • 2016-05-29: Uploaded v1.3. Significant code quality improvements, nothing functionally. Changed the property "Computer" to "ComputerName" (standards-conforming).
  • 2016-01-13: Uploaded v1.2. Added support for .NET 4.6.1. Thanks to Jason Finch for making me aware of the need.
  • 2015-10-31: Uploaded v1.1. Added support for detecting .NET versions 4.5, 4.5.1, 4.5.2 and 4.6, as someone requested it. Old script, needs a rewrite, but I just tacked on support for .NET 4.x (above 4.0 - as per 2015-10-31) for now.

Earlier versions of original script: File:Get-DotNet-Version.ps1.txt.

Earlier versions of DotNetVersionLister module: File:DotNetVersionLister.zip.

If you have Windows Management Framework 5 or higher (WMF 5 is available for Windows 7 and up), you can install my DotNetVersionLister module from the PowerShell gallery, a Microsoft project and online repository for scripts.

To install with WMF 5 and up (to get the latest DotNetVersionLister module version available), simply run this command (requires an internet connection):

Install-Module -Name DotNetVersionLister

Or for your user only (does not require elevation / administrator privileges):

Install-Module -Name DotNetVersionLister -Scope CurrentUser



Get the latest .NET version

"Random" (non-asynchronous) code for getting the latest .NET version in a fairly reliable way using the DotNetVersionLister module.

#$ComputerName = Get-ADGroupMember -Identity SomeComputerObjectGroup | Select-Object -ExpandProperty Name
$ComputerName = Get-ADComputer -Filter * -Propert Name | Select-Object -ExpandProperty Name
Import-Module -Name DotNetVersionLister -ErrorAction Stop
$DotNetVersionsHash = @{}
foreach ($Computer in $ComputerName) {
    $DotNetVersion = Get-DotNetVersion -ComputerName $Computer -PSRemoting -ContinueOnPingFail -NoSummary
    if ($DotNetVersion -and -not $DotNetVersion.Error) {
        if ($DotNetVersion.'>=4.x' -match '\S' -and $DotNetVersion.'>=4.x' -notmatch 'error|not installed|universe') {
            $DotNetVersionsHash[$Computer] = $DotNetVersion.'>=4.x'
        }
        elseif ($DotNetVersion.'v4\Client' -eq 'Installed' -or $DotNetVersion.'v4\Full' -eq 'Installed') {
            $DotNetVersionsHash[$Computer] = '4.0'
        }
        elseif ($DotNetVersion.'v3.5' -eq 'Installed') {
            $DotNetVersionsHash[$Computer] = '3.5'
        }
        elseif ($DotNetVersion.'v3.0' -eq 'Installed') {
            $DotNetVersionsHash[$Computer] = '3.0'
        }
        elseif ($DotNetVersion.'v2.0.50727' -eq 'Installed') {
            $DotNetVersionsHash[$Computer] = '2.0'
        }
        else {
            $DotNetVersionsHash[$Computer] = '<2.0'
        }
    }
    else {
        $DotNetVersionsHash[$Computer] = 'Error'
    }
}
$DotNetVersionsHash.GetEnumerator() | Format-Table -AutoSize

Enabling the Remote Registry Service

I got an inquiry from Anton Ostrovsky, and he had been nice enough to document how to start the remote registry service via sc.exe, by adding a few lines to my script. I took his idea, but feel like this is a separate task entirely, so I put it here in a couple of simple, "throw-away", example scripts at the bottom of the article.

To enable the remote registry service, and set the startup type to automatic, you would ideally use an AD Group Policy (GPO/GP), but if you ghetto it (temporarily), you can use a simple foreach loop that iterates a list of computers, checks the status of the service with Get-Service, and if it's not running, it tries to set the startup type to automatic and then to start it (it seems to get a start signal immediately when you set it to auto).

The scripts were written to be compatible with PowerShell version 2 and up.

The results will look like this for disabling and enabling:

Get-DotNet-Version-EnableOrDisableRemoteRegistryExample.png

Here's my example Enable-RemoteRegistryService.ps1.txt code. Read it, understand it, and adapt as you see fit, or just run it (you will need to populate the $ComputerName variable).

If the service is already running, it'll look like this:

PS C:\temp> C:\temp\Enable-RemoteRegistryService.ps1
server2012: The RemoteRegistry service is already running.
vista64esxi: The RemoteRegistry service is already running.
server2008: The RemoteRegistry service is already running.

Here's a similar, but reversed script, for disabling the RemoteRegistry service, if you want to do that after enabling it and then processing the computers with Get-DotNet-Version.ps1: Disable-RemoteRegistryService.ps1.txt.