# encoding: ascii
# api: powershell
# title: Find Installed .NET ver
# description: A script for finding installed .NET versions on remote workstations or servers. See the full documentation at powershelladmin.com ( http://www.powershelladmin.com/wiki/Script_for_finding_which_dot_net_versions_are_installed_on_remote_workstations ).
# version: 3.5
# type: script
# author: Joakim
# license: CC0
# x-poshcode-id: 3297
# x-archived: 2012-03-28T09:05:08
# x-published: 2012-03-25T13:48:00
#
#
# Svendsen Tech's .Net version finding script.
# See the full documentation at:
# http://www.powershelladmin.com/wiki/Script_for_finding_which_dot_net_versions_are_installed_on_remote_workstations
param([Parameter(Mandatory=$true)][string[]] $ComputerName,
[switch] $Clobber)
##### START OF FUNCTIONS #####
function ql { $args }
function Quote-And-Comma-Join {
param([Parameter(Mandatory=$true)][string[]] $Strings)
# Replace all double quotes in the text with single quotes so the CSV isn't messed up,
# and remove the trailing newline (all newlines and carriage returns).
$Strings = $Strings | ForEach-Object { $_ -replace '[\r\n]', '' }
($Strings | ForEach-Object { '"' + ($_ -replace '"', "'") + '"' }) -join ','
}
##### END OF FUNCTIONS #####
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$StartTime = Get-Date
"Script start time: $StartTime"
$Date = (Get-Date).ToString('yyyy-MM-dd')
$OutputOnlineFile = ".\DotNetOnline-${date}.txt"
$OutputOfflineFile = ".\DotNetOffline-${date}.txt"
$CsvOutputFile = ".\DotNet-Versions-${date}.csv"
if (-not $Clobber) {
$FoundExistingLog = $false
foreach ($File in $OutputOnlineFile, $OutputOfflineFile, $CsvOutputFile) {
if (Test-Path -PathType Leaf -Path $File) {
$FoundExistingLog = $true
"$File already exists"
}
}
if ($FoundExistingLog -eq $true) {
$Answer = Read-Host "The above mentioned log file(s) exist. Overwrite? [yes]"
if ($Answer -imatch '^n') { 'Aborted'; exit 1 }
}
}
# Deleting existing log files if they exist (assume they can be deleted...)
Remove-Item $OutputOnlineFile -ErrorAction SilentlyContinue
Remove-Item $OutputOfflineFile -ErrorAction SilentlyContinue
Remove-Item $CsvOutputFile -ErrorAction SilentlyContinue
$Counter = 0
$DotNetData = @{}
$DotNetVersionStrings = ql v4\Client v4\Full v3.5 v3.0 v2.0.50727 v1.1.4322
$DotNetRegistryBase = 'SOFTWARE\Microsoft\NET Framework Setup\NDP'
foreach ($Computer in $ComputerName) {
$Counter++
$DotNetData.$Computer = New-Object PSObject
# Skip malformed lines (well, some of them)
if ($Computer -notmatch '^\S') {
Write-Host -Fore Red "Skipping malformed item/line ${Counter}: '$Computer'"
Add-Member -Name Error -Value "Malformed argument ${Counter}: '$Computer'" -MemberType NoteProperty -InputObject $DotNetData.$Computer
continue
}
if (Test-Connection -Quiet -Count 1 $Computer) {
Write-Host -Fore Green "$Computer is online. Trying to read registry."
$Computer | Add-Content $OutputOnlineFile
# Suppress errors when trying to open the remote key
$ErrorActionPreference = 'SilentlyContinue'
$Registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Computer)
$RegSuccess = $?
$ErrorActionPreference = 'Stop'
if ($RegSuccess) {
Write-Host -Fore Green "Successfully connected to registry of ${Computer}. Trying to open keys."
foreach ($VerString in $DotNetVersionStrings) {
if ($RegKey = $Registry.OpenSubKey("$DotNetRegistryBase\$VerString")) {
#"Successfully opened .NET registry key (SOFTWARE\Microsoft\NET Framework Setup\NDP\$verString)."
if ($RegKey.GetValue('Install') -eq '1') {
#"$computer has .NET $verString"
Add-Member -Name $VerString -Value 'Installed' -MemberType NoteProperty -InputObject $DotNetData.$Computer
}
else {
Add-Member -Name $VerString -Value 'Not installed' -MemberType NoteProperty -InputObject $DotNetData.$Computer
}
}
else {
Add-Member -Name $VerString -Value 'Not installed (no key)' -MemberType NoteProperty -InputObject $DotNetData.$Computer
}
}
}
# Error opening remote registry
else {
Write-Host -Fore Yellow "${Computer}: Unable to open remote registry key."
Add-Member -Name Error -Value "Unable to open remote registry: $($Error[0].ToString())" -MemberType NoteProperty -InputObject $DotNetData.$Computer
}
}
# Failed ping test
else {
Write-Host -Fore Yellow "${Computer} is offline."
Add-Member -Name Error -Value "No ping reply" -MemberType NoteProperty -InputObject $DotNetData.$Computer
$Computer | Add-Content $OutputOfflineFile
}
}
$CsvHeaders = @('Computer') + @($DotNetVersionStrings) + @('Error')
$HeaderLine = Quote-And-Comma-Join $CsvHeaders
Add-Content -Path $CsvOutputFile -Value $HeaderLine
# Process the data and output to manually crafted CSV.
$DotNetData.GetEnumerator() | ForEach-Object {
$Computer = $_.Name
# I'm building a temporary hashtable with all $CsvHeaders
$TempData = @{}
$TempData.'Computer' = $Computer
# This means there's an "Error" note property.
if (Get-Member -InputObject $DotNetData.$Computer -MemberType NoteProperty -Name Error) {
# Add the error to the temp hash.
$TempData.'Error' = $DotNetData.$Computer.Error
# Populate the .NET version strings with "Unknown".
foreach ($VerString in $DotNetVersionStrings) {
$TempData.$VerString = 'Unknown'
}
}
# No errors. Assume all .NET version fields are populated.
else {
# Set the error key in the temp hash to "-"
$TempData.'Error' = '-'
foreach ($VerString in $DotNetVersionStrings) {
$TempData.$VerString = $DotNetData.$Computer.$VerString
}
}
# Now we should have "complete" $TempData hashes.
# Manually craft CSV data. Headers were added before the loop.
# The array is for ordering the output predictably.
$TempArray = @()
foreach ($Header in $CsvHeaders) {
$TempArray += $TempData.$Header
}
$CsvLine = Quote-And-Comma-Join $TempArray
Add-Content -Path $CsvOutputFile -Value $CsvLine
}
@"
Script start time: $StartTime
Script end time: $(Get-Date)
Output files: $CsvOutputFile, $OutputOnlineFile, $OutputOfflineFile
"@