get-dotnetversion.ps1
<#PSScriptInfo
.VERSION 1.1 .GUID db8a42fd-c58d-4d04-936e-3e347d79996d .AUTHOR Nimal Raj .TAGS .NET,dotNET,Framework .PROJECTURI https://github.com/Raj-GT/Windows-dotNet-Version #> <# .SYNOPSIS List versions of .NET Frameworks installed .DESCRIPTION List versions of .NET Frameworks installed on a machine by reading from the registry keys. The script utilises WinRM to enumerate data from remote machines. .PARAMETER Server Single hostname or textfile with multiple hosts to retrieve .NET versions information from. When this parameter is omitted the script will run against the localhost. .PARAMETER Credential Administrator credentials for the servers to allow remote powershell access. .EXAMPLE To get the .NET Framework information from the localhost PS C:\>Get-dotNETversion.ps1 .EXAMPLE To get the .NET Framework information from a remote server PS C:\>Get-dotNETversion.ps1 -Server MyServer1 -Credential DOMAIN\Administrator .EXAMPLE To get the .NET Framework information from a list of servers PS C:\>Get-dotNETversion.ps1 -Server MyServerList.txt -Credential DOMAIN\Administrator .INPUTS System.String, System.Management.Automation.PSCredential .OUTPUTS System.String. Returns the following string objects - Hostname, Framework, Version, Release, Title In addition, a list of failed hosts are returned inside $failed global variable .NOTES Version: 1.1 Author: Nimal Raj Revisions: 20/05/2018 Initial draft (1.0) 15/06/2018 Bugfix for parsing remote data Added additional verbose outputs Added numeric Release field and moved ReleaseTitle to Title .LINK https://github.com/Raj-GT/Windows-dotNet-Version #> #Requires -Version 3.0 #--------------------------------------------------------[Parameters]------------------------------------------------------- [CmdletBinding()] param ( [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][Alias('Cn', 'PSComputerName','Servers','Computer')][String[]]$Server, [Parameter(Mandatory=$false)][Alias('Creds','Admin','User')][PSCredential] $Credential ) Begin { # Start of Begin #---------------------------------------------------------[Modules]--------------------------------------------------------- #--------------------------------------------------------[Variables]-------------------------------------------------------- $script:regkey = "HKLM:SOFTWARE\Microsoft\NET Framework Setup\NDP\" $script:creds = $Credential $global:failed =@() $usage = @' .\Get-dotNETversion.ps1 -server <hostname or list.txt> -Credential <admin credentials> '@ #--------------------------------------------------------[Functions]-------------------------------------------------------- Function ReadNetVersion ($node) { $results = @() Write-Verbose -Message "Processing $node" # Read from local registry if scanning localhost If ($node -eq $env:computername) { Write-Verbose -Message "Reading localhost [$($node)]$($script:regkey)" Try { $keydump = Get-ChildItem "$script:regkey" -Recurse -ErrorAction Stop| Get-ItemProperty -Name Version,Release -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^[vCF]'} | Sort-Object Version} Catch { Write-Host "ERROR: $($Error[0])" -ForegroundColor Red; Exit } } # Read remote hosts using WinRM Else { Write-Verbose -Message "Reading remote host [$($node)]$($script:regkey)" Try { $keydump = Invoke-Command -ComputerName $node -Credential $script:creds -ScriptBlock { Get-ChildItem "$using:regkey" -Recurse | Get-ItemProperty -Name Version,Release -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^[vCF]'} | Sort-Object Version } -ErrorAction Stop } Catch { Write-Host "ERROR: $($Error[0])" -ForegroundColor Red; $global:failed += $node; Return } } # Hopefully should have data in $keydump now, but just in case... If (!($keydump)) { Write-Host "ERROR: No usable data returned by $($node)" -ForegroundColor Red; $global:failed += $node; Return } Write-Verbose -Message "Raw data from $($node): $keydump" # Let's parse the data and build our ResultObject $i = 0 While ($i -lt $keydump.Count) { $ResultObject = New-Object System.Object $ResultObject | Add-member -Name Hostname -Type NoteProperty -Value $node $ResultObject | Add-member -Name Framework -Type NoteProperty -Value ((Split-Path $keydump[$i].PSPath -NoQualifier).replace((Convert-Path "$script:regkey"),"")) $ResultObject | Add-member -Name Version -Type NoteProperty -Value $keydump[$i].Version If ($keydump[$i] | Get-ItemProperty -Name Release -ErrorAction SilentlyContinue) { $ResultObject | Add-member -Name Release -Type NoteProperty -Value $keydump[$i].Release $ResultObject | Add-member -Name Title -Type NoteProperty -Value (FindRelease($keydump[$i].Release)) } Else { $ResultObject | Add-member -Name Release -Type NoteProperty -Value "N/A" $ResultObject | Add-member -Name Title -Type NoteProperty -Value "N/A" } $results += $ResultObject $i = $i+1 } return($results) } Function FindRelease ($release) { # Check and update as new releases are added... # https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed Switch ($release) { "378389" {$release = ".NET Framework 4.5";break} "378675" {$release = ".NET Framework 4.5.1";break} "378758" {$release = ".NET Framework 4.5.1";break} "379893" {$release = ".NET Framework 4.5.2";break} "393295" {$release = ".NET Framework 4.6";break} "393297" {$release = ".NET Framework 4.6";break} "394254" {$release = ".NET Framework 4.6.1";break} "394271" {$release = ".NET Framework 4.6.1";break} "394802" {$release = ".NET Framework 4.6.2";break} "394806" {$release = ".NET Framework 4.6.2";break} "460798" {$release = ".NET Framework 4.7";break} "460805" {$release = ".NET Framework 4.7";break} "461308" {$release = ".NET Framework 4.7.1";break} "461310" {$release = ".NET Framework 4.7.1";break} "461808" {$release = ".NET Framework 4.7.2";break} "461814" {$release = ".NET Framework 4.7.2";break} default {} } return($release) } } # End of Begin #--------------------------------------------------------[Execution]-------------------------------------------------------- Process { # Validate credential argument - mandatory when a servername or list is specified If (($server) -AND !($credential)) { Write-Host "ERROR: Credential is mandatory to scan remote servers." -ForegroundColor Red; Exit } # Print usage when called without arguments and proceed to output local machine If (!($server)) { Write-Host "USAGE: " -ForegroundColor Yellow -NoNewline Write-Host "$usage" -ForegroundColor Green $server = $env:computername } # Validate server argument and create list of servers to scan If ($server -match '.txt$') { Write-Verbose -Message "Reading content from file $server" Try { $server = Get-Content -Path $server -ErrorAction Stop } Catch { Write-Host "ERROR: Unable to read file $server. Check file path and permissions and try again." -ForegroundColor Red; Exit } } # Main loop ForEach ($node in $server) { ReadNetVersion ($node) } } # End of Process #---------------------------------------------------------[Cleanup]--------------------------------------------------------- End { If ($failed.Count -gt 0) { Write-Host ""; Write-Warning -Message "Errors encountered while scanning some hosts; Check `$failed global variable for a list." } } |