203

What would a PowerShell script be to return versions of the .NET Framework on a machine?

My first guess is something involving WMI. Is there something better?

It should be a one-liner to return only the latest version for each installation of .NET [on each line].

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
MattUebel
  • 2,457
  • 4
  • 17
  • 20
  • 7
    A machine can (and will) have _multiple_ versions of the Fx. How do you want to handle that? And then there is the Fx2 .. Fx3.5SP1 mess. What version do you want to hear? – Henk Holterman Aug 15 '10 at 12:07
  • I suppose it would be necessary to return the full version number for each install. – MattUebel Aug 16 '10 at 14:39
  • 1
    Isn't there a way to do this via WMI? – Mark Richman Jul 11 '14 at 13:53
  • You asked for PowerShell, I made something for C# (console application). If you're interested, **[here](https://stackoverflow.com/a/46826686/1016343)** it is... – Matt Oct 19 '17 at 09:26
  • It's really incredible that there isn't something like: `asp.net -v` – Altimus Prime Aug 25 '19 at 03:17
  • Why MS made it such a pain in the butt to look this up, I will never know... I will just leave it there because if I say anything more, it will mostly consist of expletives. – Marc Everlove Jul 10 '20 at 16:39
  • I've made a script that runs through each computer in a specific OU. Outputs their OS, all their versions of DOTNET and their vulnerability status to a specific windows security vulnerability which I've built this to detect. Requirements: Install RSAT to get the AD module. Install "Visual Studio Code" and get the "Powershell 7.x Extension" Notes: if the "CompromisedCheck returns a value of =0, it means someone (a supplier or an admin, or a hacker) has set a file to be dangerously insecure. I'll post this as an answer below, anyone can feel free to find the script there. – Mark Purnell Nov 13 '20 at 14:47

17 Answers17

384

If you're going to use the registry you have to recurse in order to get the full version for the 4.x Framework. The earlier answers both return the root number on my system for .NET 3.0 (where the WCF and WPF numbers, which are nested under 3.0, are higher -- I can't explain that), and fail to return anything for 4.0 ...

EDIT: For .Net 4.5 and up, this changed slightly again, so there's now a nice MSDN article here explaining how to convert the Release value to a .Net version number, it's a total train wreck :-(

This looks right to me (note that it outputs separate version numbers for WCF & WPF on 3.0. I don't know what that's about). It also outputs both Client and Full on 4.0 (if you have them both installed):

Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse |
Get-ItemProperty -name Version,Release -EA 0 |
Where { $_.PSChildName -match '^(?!S)\p{L}'} |
Select PSChildName, Version, Release

Based on the MSDN article, you could build a lookup table and return the marketing product version number for releases after 4.5:

$Lookup = @{
    378389 = [version]'4.5'
    378675 = [version]'4.5.1'
    378758 = [version]'4.5.1'
    379893 = [version]'4.5.2'
    393295 = [version]'4.6'
    393297 = [version]'4.6'
    394254 = [version]'4.6.1'
    394271 = [version]'4.6.1'
    394802 = [version]'4.6.2'
    394806 = [version]'4.6.2'
    460798 = [version]'4.7'
    460805 = [version]'4.7'
    461308 = [version]'4.7.1'
    461310 = [version]'4.7.1'
    461808 = [version]'4.7.2'
    461814 = [version]'4.7.2'
    528040 = [version]'4.8'
    528049 = [version]'4.8'
}

# For One True framework (latest .NET 4x), change the Where-Object match 
# to PSChildName -eq "Full":
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
  Get-ItemProperty -name Version, Release -EA 0 |
  Where-Object { $_.PSChildName -match '^(?!S)\p{L}'} |
  Select-Object @{name = ".NET Framework"; expression = {$_.PSChildName}}, 
@{name = "Product"; expression = {$Lookup[$_.Release]}}, 
Version, Release

In fact, since I keep having to update this answer, here's a script to generate the script above (with a little extra) from the markdown source for that web page. This will probably break at some point, so I'm keeping the current copy above.

# Get the text from github
$url = "https://raw.githubusercontent.com/dotnet/docs/master/docs/framework/migration-guide/how-to-determine-which-versions-are-installed.md"
$md = Invoke-WebRequest $url -UseBasicParsing
$OFS = "`n"
# Replace the weird text in the tables, and the padding
# Then trim the | off the front and end of lines
$map = $md -split "`n" -replace " installed [^|]+" -replace "\s+\|" -replace "\|$" |
    # Then we can build the table by looking for unique lines that start with ".NET Framework"
    Select-String "^.NET" | Select-Object -Unique |
    # And flip it so it's key = value
    # And convert ".NET FRAMEWORK 4.5.2" to  [version]4.5.2
    ForEach-Object { 
        [version]$v, [int]$k = $_ -replace "\.NET Framework " -split "\|"
        "    $k = [version]'$v'"
    }

# And output the whole script
@"
`$Lookup = @{
$map
}

# For extra effect we could get the Windows 10 OS version and build release id:
try {
    `$WinRelease, `$WinVer = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" ReleaseId, CurrentMajorVersionNumber, CurrentMinorVersionNumber, CurrentBuildNumber, UBR
    `$WindowsVersion = "`$(`$WinVer -join '.') (`$WinRelease)"
} catch {
    `$WindowsVersion = [System.Environment]::OSVersion.Version
}

Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
    Get-ItemProperty -name Version, Release -EA 0 |
    # For The One True framework (latest .NET 4x), change match to PSChildName -eq "Full":
    Where-Object { `$_.PSChildName -match '^(?!S)\p{L}'} |
    Select-Object @{name = ".NET Framework"; expression = {`$_.PSChildName}}, 
                @{name = "Product"; expression = {`$Lookup[`$_.Release]}}, 
                Version, Release,
    # Some OPTIONAL extra output: PSComputerName and WindowsVersion
    # The Computer name, so output from local machines will match remote machines:
    @{ name = "PSComputerName"; expression = {`$Env:Computername}},
    # The Windows Version (works on Windows 10, at least):
    @{ name = "WindowsVersion"; expression = { `$WindowsVersion }}
"@
SSS
  • 4,877
  • 1
  • 19
  • 39
Jaykul
  • 14,264
  • 7
  • 55
  • 67
  • This is exactly what I am looking for as well, but I am having a hard time wrapping my mind around what exactly this is doing. From what I understand it is going out to the NDP registry and recursively searching through each folder that fits the `'^(?!S)\p{L}'` regex and getting the Version and Release info. What exactly is that regular expression trying to qualify? – Johnrad Jan 12 '16 at 20:12
  • 2
    @Johnrad `PSChildName` is the leaf name of the registry key. `\p{L}` is the any character in the Unicode category "letter". `(?!S)` is a negative look around, and `^` is the start of the string. So it has to start with a letter other than `S`. So if you consider only ASCII, it's the same as `$_.PSChildName -cmatch '^[A-RT-Za-z]'` (note the `-cmatch`). So it finds keys where the name starts with a letter other than `S`. I have no idea why you'd care about non-ASCII if you're filtering out names starting with `S`... Definitely with you on it being so confusing. – jpmc26 Jan 29 '16 at 01:01
  • 1
    Now I'm more confused about what the heck `Get-ItemProperty -name Version,Release -EA 0` is doing. I know `-EA 0` is the same as `-ErrorAction SilentlyContinue`, but what effect would `Get-ItemProperty -name Version,Release` have when piping all the results to it? It doesn't seem to strip off any variables from the object, as others are used in later commands in the pipeline. Does it run, error out when the `Version` or `Release` name is missing from the key, and then pass objects where it succeeded on into the next command in the pipeline? – jpmc26 Jan 29 '16 at 17:24
  • 3
    The Get-ChildItem returns ALL the registry subkeys (the subfolders, if you will). Get-ItemProperty returns the values (specifically: "Version" and "Release") -- we ignore the errors because we don't care about folders that don't have those values. So yes, basically we find EVERY subfolder, and then look for Version or Release (any folders without one or both of those is ignored). – Jaykul Feb 19 '16 at 05:04
  • The regular expression is mostly just removing all the language-code folders (e.g. "1033" for English) -- I honestly don't remember why there's an exclusion for S :-/ – Jaykul Feb 19 '16 at 05:07
  • The `S` filter removes a subkey named `Setup` for me. I can see that the `Get-ItemProperty` acts a filter on keys with those values, but then where does the `PSChildName` come from? I checked and it does return a `PSCustomObject` type, so this is really confusing. – jpmc26 Mar 03 '16 at 01:48
  • Items from PowerShell Providers always have automatic NoteProperties added for PSChildName, PSParentPath, PSPath, PSProvider and PSDrive. – Jaykul Mar 14 '16 at 16:49
  • Properties from PowerShell Providers always have automatic NoteProperties added for PSChildName, PSParentPath, PSPath, PSProvider but NOT PSDrive. In the registry, we have to use Get-ItemProperty to read the property values. That is, get-item or get-childitem returns the keys (the folders in regedit), and Get-ItemProperty returns the property names and values (in a PSCustomObject, which is PowerShell's dynamic object property bag). – Jaykul Mar 14 '16 at 16:55
  • The filtering is done with Where-Object, obviously, and could be done before Get-ItemProperty instead of after (it would probably be slightly faster to do it that way, since you'd be calling Get-ItemProperty on less things). – Jaykul Mar 14 '16 at 16:57
  • 3
    Awesome! I only modified the `(?!S)` clause to be `(?![SW])` to further exclude the "Windows*" entries. This can also be done with `(?=[vCF])` since the only keys we really care about are the Version roots and the "Full" and "Client" keys for .NET 4.0+. ;) – Chiramisu Jul 05 '16 at 22:46
  • I updated it with 4.6.2 numbers, but with Windows 10 insider build 14366 I have an undocumented `394800` release tag which probably also means `4.6.2` ;-) – Jaykul Jul 06 '16 at 05:56
  • in the second script there is a `;` missing in the hash table after the line `name="Product"` – inwenis Oct 26 '16 at 13:49
  • semi-colons are so last language, @inwenis ;-) You don't need them in PowerShell if you have a newline instead. – Jaykul Oct 31 '16 at 23:09
  • There is now the **.NET Framework 4.7.1** released, and [according to Microsoft](https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#ps_a) it has the build number 461308 - but on my machine the tool above reports Version 4.7.02558, Release 461310. Tried to update the switch in the script but it didn't work. It still claims to be 4.7.02558. – Matt Oct 19 '17 at 08:27
  • @matt update code for supports ***.NET framework 4.7.1*** – Kiquenet Dec 20 '17 at 14:55
  • @Matt I have updated the script according to the documentation -- Note that in the first table on the page it lists DIFFERENT release codes for Windows 10 and non-Windows 10... – Jaykul Dec 20 '17 at 20:19
  • @Jaykul - Thanks for updating! Now you can already add Version 4.7.2 since that is out, too ;-) Btw. I have one question - what are you downloading in the 2nd script? Why would you need a URL for determining the .NET version? – Matt Dec 21 '17 at 08:17
  • That second script is downloading the markdown source for the docs.microsoft.com page (from the dotnet/docs repo on github) to generate the table you see in the first script. But the table doesn't have 4.7.2 in it, so I have no official source for the release number. I'll file (another) bug on github to get them to update it. – Jaykul Dec 22 '17 at 16:53
  • @Matt you can follow this issue https://github.com/dotnet/docs/issues/4008 if you want. I'll update it after they close that. Otherwise, I'd just be putting in "461310" because you said so. – Jaykul Dec 22 '17 at 17:03
  • I've added 461310 ... and the new 461808 and 461814 for 4.7.2 as well now. My personal copy is a little more verbose: https://gist.github.com/Jaykul/a1e448d982d469e82d9a4244585d85f2 – Jaykul May 17 '18 at 20:10
  • the *DotNetVersionLister* module (https://github.com/EliteLoser/DotNetVersionLister) basically wraps this functionality and more as explained also here: https://stackoverflow.com/a/52078523/1915920 – Andreas Covidiot Sep 03 '18 at 07:42
28
gci 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' |
sort pschildname -des                                  |
select -fi 1 -exp pschildname

This answer doesn't return 4.5 if that is installed. The answer below from @Jaykul and using recurse does.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Jason Stangroome
  • 4,291
  • 3
  • 31
  • 38
  • 5
    gci 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' | sort pschildname -des | foreach-object {$_.name; $_.GetValue("Version");} – MattUebel Aug 16 '10 at 15:30
  • for me the answer is now on top so here a link to it :-): http://stackoverflow.com/a/3495491/1747983 – Tilo Nov 20 '15 at 22:28
  • 2
    Having installed .NET 4.7.1 on Windows 10, this still returns v4.0. – Matt Oct 19 '17 at 08:35
28

Added v4.8 support to the script:

Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse |
Get-ItemProperty -name Version,Release -EA 0 |
Where { $_.PSChildName -match '^(?![SW])\p{L}'} |
Select PSChildName, Version, Release, @{
  name="Product"
  expression={
      switch -regex ($_.Release) {
        "378389" { [Version]"4.5" }
        "378675|378758" { [Version]"4.5.1" }
        "379893" { [Version]"4.5.2" }
        "393295|393297" { [Version]"4.6" }
        "394254|394271" { [Version]"4.6.1" }
        "394802|394806" { [Version]"4.6.2" }
        "460798|460805" { [Version]"4.7" }
        "461308|461310" { [Version]"4.7.1" }
        "461808|461814" { [Version]"4.7.2" }
        "528040|528049" { [Version]"4.8" }
        {$_ -gt 528049} { [Version]"Undocumented version (> 4.8), please update script" }
      }
    }
}
Jaquez
  • 910
  • 1
  • 13
  • 20
AlexBar
  • 1,748
  • 17
  • 13
24
[environment]::Version

Gives you an instance of Version for the CLR the current copy of PSH is using (as documented here).

Richard
  • 100,436
  • 21
  • 189
  • 251
  • 3
    I have .NET 4 installed but PowerShell will only use the 2.0 runtime. So that's not really of help here. – Joey Aug 15 '10 at 13:41
  • @Johannes: See comment on your Q, you need to be explicit about what you want. – Richard Aug 15 '10 at 13:52
  • 10
    For Powershell 2.0, you can also use `$PSVersionTable` to find the version of the CLR PowerShell is running on. – Keith Hill Aug 15 '10 at 18:07
  • 6
    How about higher versions? I have **.NET 4.7.1** now, and the script **always returns 4.0.30319 Rev. 42000.** – Matt Oct 19 '17 at 08:34
  • @Matt you'll need to translate the minor part of the version... and note depending on what is set within Powershell's config it may not be using the latest minor/patch version. – Richard Oct 19 '17 at 08:43
15

Correct syntax:

[System.Runtime.InteropServices.RuntimeEnvironment]::GetSystemVersion()
#or
$PSVersionTable.CLRVersion

The GetSystemVersion function returns a string like this:

v2.0.50727        #PowerShell v2.0 in Win 7 SP1

or like this

v4.0.30319        #PowerShell v3.0 (Windows Management Framework 3.0) in Win 7 SP1

$PSVersionTable is a read-only object. The CLRVersion property is a structured version number like this:

Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      30319  18444   
Iain Samuel McLean Elder
  • 16,665
  • 10
  • 59
  • 76
Desmond Lee
  • 167
  • 1
  • 2
  • 1
    I tried this on win8, it returns nothing. On windows 7, it returns 2 while 4.5.1 is already installed. I do not know why this is not usable on new platforms. On win sesrver 2008, it works. – max Feb 25 '14 at 02:05
  • The first option works on my Windows 8, 64-bit environment. The second option works, but I think that just shows the .NET version that the current instance of PowerShell is running in, which is almost always the latest. (Edit: Maybe they both do.) – Vimes Aug 01 '14 at 21:14
  • same here. on windows 7, I have both .net 2.0 and 4.0 but the command only shows v2.0.50727. Use Jaykul's approach. – max Oct 01 '14 at 19:04
  • Clr version does not equal the framework version, 4+ frameworks are all based on 4 clr – janv8000 Apr 15 '16 at 09:07
  • How about higher versions? I have **.NET 4.7.1** now, and the script **always returns 4.0.30319 Rev. 42000.** – Matt Oct 19 '17 at 08:33
  • `$PSVersionTable.CLRVersion` is empty for PowerShell 6 (a.k.a. PowerShell Core). – Franklin Yu Sep 20 '18 at 14:50
13

I found this through tab completion in powershell for osx:

[System.Runtime.InteropServices.RuntimeInformation]::get_FrameworkDescription() .NET Core 4.6.25009.03

js2010
  • 13,551
  • 2
  • 28
  • 40
  • 1
    Yes, it returns **.NET Framework 4.7.2558.0** - but how can one distinguish 4.7 from 4.7.1 (I have 4.7.1 on my Windows 10 machine). – Matt Oct 19 '17 at 08:38
  • 1
    `[version]([Runtime.InteropServices.RuntimeInformation]::FrameworkDescription -replace '^.[^\d.]*','')` – Rabash Mar 16 '19 at 14:28
3

There's no reliable way to do this for all platforms and architectures using a simple script. If you want to learn how to do it reliably, start at the blog post Updated sample .NET Framework detection code that does more in-depth checking.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
x0n
  • 47,695
  • 5
  • 84
  • 110
2

Refer to the page Script for finding which .NET versions are installed on remote workstations.

The script there might be useful to find the .NET version for multiple machines on a network.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
kalidoss
  • 451
  • 6
  • 13
2

Nice solution

Try using the downloadable DotNetVersionLister module (based on registry infos and some version-to-marketing-version lookup table).

Which would be used like this:

PS> Get-DotNetVersion -LocalHost -nosummary


ComputerName : localhost
>=4.x        : 4.5.2
v4\Client    : Installed
v4\Full      : Installed
v3.5         : Installed
v3.0         : Installed
v2.0.50727   : Installed
v1.1.4322    : Not installed (no key)
Ping         : True
Error        :

Or like this if you just want to test it for some .NET framework >= 4.*:

PS> (Get-DotNetVersion -LocalHost -nosummary).">=4.x"
4.5.2

But it will not work (install/import) e.g. with PS v2.0 (Win 7, Win Server 2010 standard) due to incompatibility...

Motivation for "legacy" functions below

(You could skip reading this and use code below)

We had to work with PS 2.0 on some machines and could not install/import the above DotNetVersionLister.
On other machines we wanted to update (from PS 2.0) to PS 5.1 (which in turn needs .NET Framework >= 4.5) with the help of two company-custom Install-DotnetLatestCompany and Install-PSLatestCompany.
To guide admins nicely through the install/update process we would have to determine the .NET version in these functions on all machines and PS versions existing.
Thus we used also the below functions to determine them more safely in all environments...

Functions for legacy PS environments (e.g. PS v2.0)

So the following code and below (extracted) usage examples are useful here (based on other answers here):

function Get-DotNetVersionByFs {
  <#
    .SYNOPSIS
      NOT RECOMMENDED - try using instead:
        Get-DotNetVersion 
          from DotNetVersionLister module (https://github.com/EliteLoser/DotNetVersionLister), 
          but it is not usable/importable in PowerShell 2.0 
        Get-DotNetVersionByReg
          reg(istry) based: (available herin as well) but it may return some wrong version or may not work reliably for versions > 4.5 
          (works in PSv2.0)
      Get-DotNetVersionByFs (this):  
        f(ile) s(ystem) based: determines the latest installed .NET version based on $Env:windir\Microsoft.NET\Framework content
        this is unreliable, e.g. if 4.0* is already installed some 4.5 update will overwrite content there without
        renaming the folder
        (works in PSv2.0)
    .EXAMPLE
      PS> Get-DotnetVersionByFs
      4.0.30319
    .EXAMPLE
      PS> Get-DotnetVersionByFs -All
      1.0.3705
      1.1.4322
      2.0.50727
      3.0
      3.5
      4.0.30319
    .NOTES
      from https://stackoverflow.com/a/52078523/1915920
  #>
    [cmdletbinding()]
  param(
    [Switch]$All  ## do not return only latest, but all installed
  )
  $list = ls $Env:windir\Microsoft.NET\Framework |
    ?{ $_.PSIsContainer -and $_.Name -match '^v\d.[\d\.]+' } |
    %{ $_.Name.TrimStart('v') }
  if ($All) { $list } else { $list | select -last 1 }
}


function Get-DotNetVersionByReg {
  <#
    .SYNOPSIS
      NOT RECOMMENDED - try using instead:
        Get-DotNetVersion
          From DotNetVersionLister module (https://github.com/EliteLoser/DotNetVersionLister), 
          but it is not usable/importable in PowerShell 2.0. 
          Determines the latest installed .NET version based on registry infos under 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP'
    .EXAMPLE
        PS> Get-DotnetVersionByReg
        4.5.51209
    .EXAMPLE
        PS> Get-DotnetVersionByReg -AllDetailed
        PSChildName                                          Version                                             Release
        -----------                                          -------                                             -------
        v2.0.50727                                           2.0.50727.5420
        v3.0                                                 3.0.30729.5420
        Windows Communication Foundation                     3.0.4506.5420
        Windows Presentation Foundation                      3.0.6920.5011
        v3.5                                                 3.5.30729.5420
        Client                                               4.0.0.0
        Client                                               4.5.51209                                           379893
        Full                                                 4.5.51209                                           379893
    .NOTES
      from https://stackoverflow.com/a/52078523/1915920
  #>
    [cmdletbinding()]
    param(
        [Switch]$AllDetailed  ## do not return only latest, but all installed with more details
    )
    $Lookup = @{
        378389 = [version]'4.5'
        378675 = [version]'4.5.1'
        378758 = [version]'4.5.1'
        379893 = [version]'4.5.2'
        393295 = [version]'4.6'
        393297 = [version]'4.6'
        394254 = [version]'4.6.1'
        394271 = [version]'4.6.1'
        394802 = [version]'4.6.2'
        394806 = [version]'4.6.2'
        460798 = [version]'4.7'
        460805 = [version]'4.7'
        461308 = [version]'4.7.1'
        461310 = [version]'4.7.1'
        461808 = [version]'4.7.2'
        461814 = [version]'4.7.2'
    }
    $list = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
        Get-ItemProperty -name Version, Release -EA 0 |
        # For One True framework (latest .NET 4x), change match to PSChildName -eq "Full":
        Where-Object { $_.PSChildName -match '^(?!S)\p{L}'} |
        Select-Object `
           @{
               name = ".NET Framework" ; 
               expression = {$_.PSChildName}}, 
           @{  name = "Product" ; 
               expression = {$Lookup[$_.Release]}}, 
           Version, Release
    if ($AllDetailed) { $list | sort version } else { $list | sort version | select -last 1 | %{ $_.version } }
}

Example usage:

PS> Get-DotNetVersionByFs
4.0.30319

PS> Get-DotNetVersionByFs -All
1.0.3705
1.1.4322
2.0.50727
3.0
3.5
4.0.30319

PS> Get-DotNetVersionByReg
4.5.51209

PS> Get-DotNetVersionByReg -AllDetailed

.NET Framework                   Product Version        Release
--------------                   ------- -------        -------
v2.0.50727                               2.0.50727.5420
v3.0                                     3.0.30729.5420
Windows Communication Foundation         3.0.4506.5420
Windows Presentation Foundation          3.0.6920.5011
v3.5                                     3.5.30729.5420
Client                                   4.0.0.0
Client                           4.5.2   4.5.51209      379893
Full                             4.5.2   4.5.51209      379893
Andreas Covidiot
  • 3,247
  • 5
  • 42
  • 81
1

Not pretty. Definitely not pretty:

ls $Env:windir\Microsoft.NET\Framework | ? { $_.PSIsContainer } | select -exp Name -l 1

This may or may not work. But as far as the latest version is concerned this should be pretty reliable, as there are essentially empty folders for old versions (1.0, 1.1) but not newer ones – those only appear once the appropriate framework is installed.

Still, I suspect there must be a better way.

Joey
  • 316,376
  • 76
  • 642
  • 652
  • You need to filter a little more, "V[.0-9]+" should limit the match to the .NET folders (I have some other folders there). And then check there is a real install... WMI on installed components might be easier. – Richard Aug 15 '10 at 13:55
  • Hm, right ... on this machine there are a few other folders as well – I had only a bunch of other files on my other machine. This whole answer was more of a case of »works for me«, though. I'm sure there is a reliable and intended way of getting that information. – Joey Aug 15 '10 at 15:56
  • 6
    psake (build automation tool) takes a similar approach and uses it successfully (or at least nobody changed it because of an issue). But it's true that they don't need full framework version... For my computer this gets closer: `ls $Env:windir\Microsoft.NET\Framework | ? { $_.PSIsContainer -and $_.Name -match '^v\d.[\d\.]+' } | % { $_.Name.TrimStart('v') }` – stej Aug 17 '10 at 05:07
  • Of all the one-liners in the answers, the one provided by stej is the cleanest and works as expected. If it was answer I would have voted for it. – Bratch Aug 21 '10 at 16:09
  • Unfortunately, it is not reliable. I have **.NET 4.7.1** now, and the script always **returns v4.0.30319.** – Matt Oct 19 '17 at 08:37
  • @Matt: every .NET version after 4.0 is an *in-place upgrade* and doesn't change the version number on disk. Also keep in mind that this answer has been written quite a while ago when it was still accurate. – Joey Oct 19 '17 at 08:50
  • @Joey - I know, I was looking for a simple way to get the .NET version - but as it seems it is getting more and more complicated. I am putting a C# script together from 2 Microsoft examples, hopefully this will help. – Matt Oct 19 '17 at 09:20
  • @Matt: There is an accepted answer somewhere above, which does do what you want and works. – Joey Oct 19 '17 at 09:30
  • @Joey - Yes, but the accepted answer doesn't print out 4.7.1 (I have tried to update the script). Hence, I was looking into other alternatives. And I have now made a C# script which does it. – Matt Oct 19 '17 at 10:35
0

Here's my take on this question following the msft documentation:

$gpParams = @{
    Path        = 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full'
    ErrorAction = 'SilentlyContinue'
}
$release = Get-ItemProperty @gpParams | Select-Object -ExpandProperty Release

".NET Framework$(
    switch ($release) {
        ({ $_ -ge 528040 }) { ' 4.8'; break }
        ({ $_ -ge 461808 }) { ' 4.7.2'; break }
        ({ $_ -ge 461308 }) { ' 4.7.1'; break }
        ({ $_ -ge 460798 }) { ' 4.7'; break }
        ({ $_ -ge 394802 }) { ' 4.6.2'; break }
        ({ $_ -ge 394254 }) { ' 4.6.1'; break }
        ({ $_ -ge 393295 }) { ' 4.6'; break }
        ({ $_ -ge 379893 }) { ' 4.5.2'; break }
        ({ $_ -ge 378675 }) { ' 4.5.1'; break }
        ({ $_ -ge 378389 }) { ' 4.5'; break }
        default { ': 4.5+ not installed.' }
    }
)"

This example works with all PowerShell versions and will work in perpetuity as 4.8 is the last .NET Framework version.

Maximilian Burszley
  • 15,132
  • 3
  • 27
  • 49
0

This is purely because I had to spend time making/editing this when it should be widely available, so I'm providing it to everyone else.

The below script will Output a couple of CSV files to TEMP with the versions and vulnerability status of each machine in a selected (in the code) OU. You'll be able to remotely "security audit" an OU of machines.

Powershell 7.0 needed for the connection test line RSAT needed to get the AD module Visual Studio Code needed to get powershell 7.0 (on win7)

By the time you read this, the version list will probably be out of date within the file. Use this website https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/versions-and-dependencies to add newer dotnet entries. It's just a bunch of key values in "DotNet4Builds"

If within CompromisedCheck.csv a machine shows as =0, it's had it's security turned off manually, and you should raise whether the supplier did it, or a suspect employee.

I hope this helps someone searching for it for their business.

     <#
        Script Name : Get-DotNetVersions_Tweaked.ps1
        Description : This script reports the various .NET Framework versions installed on the local or a remote set of computers
        Author      : Original by Martin Schvartzman - Edited by Mark Purnell
        Reference   : https://msdn.microsoft.com/en-us/library/hh925568
#>

$ErrorActionPreference = "Continue”
import-module ActiveDirectory
$searchOU = "OU=OU LEVEL 1,OU=OU LEVEL 2,OU=MACHINES,OU=OUR LAPTOPS,DC=PUT,DC=MY,DC=DOMAIN,DC=CONTROLLER,DC=HERE,DC=OK"
$computerList = Get-ADComputer -searchbase $searchOU -Filter *


function Get-DotNetFrameworkVersion($computerList)
{
    $dotNetter = @()
    $compromisedCheck = @()
    
    $dotNetRoot = 'SOFTWARE\Microsoft\.NETFramework'
    $dotNetRegistry  = 'SOFTWARE\Microsoft\NET Framework Setup\NDP'
    $dotNet4Registry = 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full'
    $dotNet4Builds = @{
        '30319'  = @{ Version = [System.Version]'4.0'                                                     }
        '378389' = @{ Version = [System.Version]'4.5'                                                     }
        '378675' = @{ Version = [System.Version]'4.5.1'   ; Comment = '(8.1/2012R2)'                      }
        '378758' = @{ Version = [System.Version]'4.5.1'   ; Comment = '(8/7 SP1/Vista SP2)'               }
        '379893' = @{ Version = [System.Version]'4.5.2'                                                   }
        '380042' = @{ Version = [System.Version]'4.5'     ; Comment = 'and later with KB3168275 rollup'   }
        '393295' = @{ Version = [System.Version]'4.6'     ; Comment = '(Windows 10)'                      }
        '393297' = @{ Version = [System.Version]'4.6'     ; Comment = '(NON Windows 10)'                  }
        '394254' = @{ Version = [System.Version]'4.6.1'   ; Comment = '(Windows 10)'                      }
        '394271' = @{ Version = [System.Version]'4.6.1'   ; Comment = '(NON Windows 10)'                  }
        '394802' = @{ Version = [System.Version]'4.6.2'   ; Comment = '(Windows 10 Anniversary Update)'   }
        '394806' = @{ Version = [System.Version]'4.6.2'   ; Comment = '(NON Windows 10)'                  }
        '460798' = @{ Version = [System.Version]'4.7'     ; Comment = '(Windows 10 Creators Update)'      }
        '460805' = @{ Version = [System.Version]'4.7'     ; Comment = '(NON Windows 10)'                  }
        '461308' = @{ Version = [System.Version]'4.7.1'   ; Comment = '(Windows 10 Fall Creators Update)' }
        '461310' = @{ Version = [System.Version]'4.7.1'   ; Comment = '(NON Windows 10)'                  }
        '461808' = @{ Version = [System.Version]'4.7.2'   ; Comment = '(Windows 10 April & Winserver)'    }
        '461814' = @{ Version = [System.Version]'4.7.2'   ; Comment = '(NON Windows 10)'                  }
        '528040' = @{ Version = [System.Version]'4.8'     ; Comment = '(Windows 10 May 2019 Update)'  }
        '528049' = @{ Version = [System.Version]'4.8'     ; Comment = '(NON Windows 10)'  }
    }

    foreach($computerObject in $computerList)
    {
        $computerName = $computerObject.DNSHostName
        write-host("PCName is " + $computerName)

        if(test-connection -TargetName $computerName -Quiet -TimeOutSeconds 1 -count 2){
            if($regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computerName))           
            {
                $os = (Get-WMIObject win32_operatingsystem -ComputerName SPL305350).Name
                if(!$?){
                    write-host("wim not available")
                    $dotNetter += New-Object -TypeName PSObject -Property @{
                        'ComputerName' = $computerName
                        'OS' = "WIM not available"
                        'Build' = "WIM not available"
                        'Version' = "WIM not available"
                        'Comment' = "WIM not available"
                    }
                }
                else{
                    if ($netRegKey = $regKey.OpenSubKey("$dotNetRegistry"))
                    {
                        foreach ($versionKeyName in $netRegKey.GetSubKeyNames())
                        {
                            if ($versionKeyName -match '^v[123]') {
                                $versionKey = $netRegKey.OpenSubKey($versionKeyName)
                                $version = [System.Version]($versionKey.GetValue('Version', ''))
                                
                                write-host("adding old dotnet")
                                $dotNetter += New-Object -TypeName PSObject -Property @{
                                        ComputerName = $computerName
                                        OS = $os
                                        Build = $version.Build
                                        Version = $version
                                        Comment = ''
                                }
                            }
                        }
                    }
                    if ($net4RegKey = $regKey.OpenSubKey("$dotNet4Registry"))
                    {
                        if(-not ($net4Release = $net4RegKey.GetValue('Release')))
                        {
                            $net4Release = 30319
                        }
                        
                        write-host("adding new dotnet")
                        $dotNetter += New-Object -TypeName PSObject -Property @{
                                'ComputerName' = $computerName
                                'OS' = $os
                                'Build' = $net4Release
                                'Version' = $dotNet4Builds["$net4Release"].Version
                                'Comment' = $dotNet4Builds["$net4Release"].Comment
                        }
                    }
                    if ($netRegKey = $regKey.OpenSubKey("$dotNetRoot")){
                        write-host("Checking for hacked keys")
                        foreach ($versionKeyName in $netRegKey.GetSubKeyNames())
                        {
                            if ($versionKeyName -match '^v[1234]') {
                                $versionKey = $netRegKey.OpenSubKey($versionKeyName)
                                write-host("versionKeyName is" + $versionKeyName)
                                write-host('ASPNetEnforceViewStateMac = ' + $versionKey.GetValue('ASPNetEnforceViewStateMac', ''))
                                $compromisedCheck += New-Object -TypeName PSObject -Property @{
                                    'ComputerName' = $computerName
                                    'version' = $versionKeyName
                                    'compromisedCheck' = ('ASPNetEnforceViewStateMac = ' + $versionKey.GetValue('ASPNetEnforceViewStateMac', ''))
                                }
                            }
                        }
                    }
                }
            }
        }
        else{
            write-host("could not connect to machine")
            $dotNetter += New-Object -TypeName PSObject -Property @{
                    'ComputerName' = $computerName
                    'OS' = $os
                    'Build' = "Could not connect"
                    'Version' = "Could not connect"
                    'Comment' = "Could not connect"
            }
        }
    }
    $dotNetter | export-CSV c:\temp\DotNetVersions.csv
    $compromisedCheck | export-CSV C:\temp\CompromisedCheck.csv
}
get-dotnetframeworkversion($computerList)
Mark Purnell
  • 171
  • 2
  • 10
-1

Here's the general idea:

Get child items in the .NET Framework directory that are containers whose names match the pattern v number dot number. Sort them by descending name, take the first object, and return its name property.

Here's the script:

(Get-ChildItem -Path $Env:windir\Microsoft.NET\Framework | Where-Object {$_.PSIsContainer -eq $true } | Where-Object {$_.Name -match 'v\d\.\d'} | Sort-Object -Property Name -Descending | Select-Object -First 1).Name
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
doer
  • 563
  • 6
  • 5
-1

I would try this one in PowerShell: Worked for me!

(Get-ItemProperty "HKLM:Software\Microsoft\NET Framework Setup\NDP\v4\Full").Version

G.B.
  • 11
  • That doesn't tell you the truth. The version number there will say, for instance 4.7.03056 when the product version is 4.7.2 – Jaykul Sep 04 '18 at 14:50
-1

If you have installed Visual Studio on your machine then open the Visual Studio Developer Command Prompt and type the following command: clrver

It will list all the installed versions of .NET Framework on that machine.

Abdul Rauf
  • 772
  • 1
  • 8
  • 18
-2

I'm not up on my PowerShell syntax, but I think you could just call System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion(). This will return the version as a string (something like v2.0.50727, I think).

Andry
  • 14,281
  • 23
  • 124
  • 216
Andy
  • 28,217
  • 5
  • 75
  • 86
  • 2
    For the currently executing runtime, not necessarily the latest installed one. – Joey Aug 15 '10 at 13:47
  • For the powershell, the correct syntax is: `[System.Runtime.InteropServices.RuntimeEnvironment]::GetSystemVersion()`, but it just returns v4.0.30319, even though v4.6 is installed in my case. – Matt Mar 10 '16 at 14:41
  • @matt 4.0.30319 is the CLR version from .Net Framework 4.0 to .Net Framework 4.7.1. So your v4.6 framework actually use 4.0.30319 as its CLR version. Notice that only the Revision part of the Version is difference between all the .Net Frameworks. See Also: [.NET Framework Versions and Dependencies - Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/versions-and-dependencies#targeting-and-running-net-framework-apps-for-version-45-and-later) – walterlv Sep 27 '17 at 16:22
  • @walterlv - Thank you for the link. Yes, I am aware of that. Microsoft made a big mistake to do that, it is not easy to remote connect to a server and find out which .net version exactly is actually installed there. Another big headache for admins and developers. – Matt Sep 27 '17 at 16:44
  • And this might help as well: [Microsoft: How to determine versions and service pack levels of .NET Framework](https://support.microsoft.com/en-us/help/318785/how-to-determine-which-versions-and-service-pack-levels-of-the-microso). It also shows how complicated it became to find out what exactly is installed on your machine ... :-( – Matt Sep 28 '17 at 10:43
  • @matt I've read this article before in another link [How to: Determine which .NET Framework versions are installed - Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed). But I can't understand what the numbers mean for. Do you have any ideas? – walterlv Sep 28 '17 at 15:09
  • @walterlv - If you are referring to "Value of the release DWORD", as I understand it, they are build numbers. Unfortunately they can differ, for example 460798, 460805 are both .NET 4.7 depending on the operating system (Windows 10 creators update vs."all other" Windows versions). *Really* confusing IMHO. – Matt Sep 28 '17 at 15:40
  • @matt Since .Net Framework 4.6.1, all .Net Frameworks have two different versions. 4.6.1 has November update and other. 4.6.2 has Anniversary update and other. 4.7 has Creators Update and other. The codes are different and you can try to goto the download link of [.Net Framework Reference Source](http://referencesource.microsoft.com/) to find the difference. – walterlv Sep 28 '17 at 16:00
  • @walterlv - so one number is for an update (like the aniversary update Win X) including .net and the other one for a redistributable .net package? I agree, if the code is different this makes sense. – Matt Sep 28 '17 at 19:40
-2

This is a derivite of previous post, but this gets the latest version of the .net framework 4 in my tests.

get-itemproperty -name version,release "hklm:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\FULL"

Which will allow you to invoke-command to remote machine:

invoke-command -computername server01 -scriptblock {get-itemproperty -name version,release "hklm:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\FULL" | select pscomputername,version,release} 

Which sets up this possibility with ADModule and naming convention prefix:

get-adcomputer -Filter 'name -like "*prefix*"' | % {invoke-command -computername $_.name -scriptblock {get-itemproperty -name version,release "hklm:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\FULL" | select pscomputername,version,release} | ft