Got-PowerShell Revisited

Last week I showed a PowerShell function that used WMI to remotely query a machine to determine if PowerShell was installed. Well, like most things Microsoft there’s often more than one way to accomplish a task. Here is a different version of essentially the same function. This version, however, uses the .NET framework and the Microsoft.Win32.Registry class:

Function Get-PowerShellInstall {
Param ([string]$computer="localhost")

$ErrorActionPreference="SilentlyContinue"
$DebugPreference="SilentlyContinue"

#provide some traps to handle connection errors
Trap [system.IO.IOException] {
Write-Warning "Failed to connect to $computer"
Write-Debug "IOException with $computer"
$script:failed=$True
continue
}

Trap [system.UnauthorizedAccessException] {
Write-Warning "Access violation on $computer"
Write-Debug "UnauthorizedAccessException on $computer"
$script:denied=$True
continue
}

#The registry class doesn’t accept localhost as
#a computer name so if passed, get the actual
#computer name

if ($computer -match "localhost") {
$computer = $env:computername
}

$computer=$computer.ToUpper()
Write-Debug "Connecting to $computer"

$regbase=[Microsoft.Win32.RegistryKey]::`
OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::`
LocalMachine,$computer)

Write-Debug "Continuing with function"
If ($script:denied) {
Write-Debug "access denied"
Remove-Variable denied -Scope Script
Return
}

If ($script:failed) {
Write-Debug "failed to connect"
Remove-Variable failed -Scope Script
Return
}

#bail out if the OpenRemoteBaseKey method failed.
if (!$regbase) {
Write-Debug "regbase not found"
return
}

$basePath="software\microsoft\powershell\1"
$ShellPath="shellids\Microsoft.PowerShell"
$EnginePath="PowerShellEngine"

Write-Debug "basepath=$basepath"
Write-Debug "shellpath=$shellpath"
Write-Debug "enginepath=$enginepath"

Write-Debug "Opening (Join-Path $basePath $ShellPath)"
$regkey=$regbase.opensubkey((Join-Path $basePath $ShellPath))

if ($regkey) {
Write-Debug "`$regkey is found on $computer"
$InstallPath=$regkey.GetValue("path")
Write-Debug "Install path is $InstallPath"
#only get other values if able to get a value for $InstallPath
if ($InstallPath) {
$Installed=$True
$ExecutionPolicy=$regkey.GetValue("ExecutionPolicy")
Write-Debug "found execution policy of $ExecutionPolicy"
#if ExecutionPolicy key is missing then assume Restricted
if (-not $ExecutionPolicy) {
Write-Debug "Execution policy is missing"
$ExecutionPolicy="Restricted"
}
$regkey=$regbase.opensubkey((Join-Path $basePath $EnginePath))
$PowerShellVersion=$Regkey.GetValue("PowerShellVersion")
Write-Debug "Found version of $PowerShellVersion"
$RunTimeVersion=$RegKey.GetValue("RunTimeVersion")
Write-Debug "Found RunTimeVersion of $RunTimeVersion"
} #end If ($InstallPath)
}
else {
Write-Debug "`$regkey is NOT found on $computer "
$InstallPath=$Null
$Installed=$False
$ExecutionPolicy=$Null
$PowerShellVersion=$Null
$RunTimeVersion=$Null
} #end if ($regkey)

Write-Debug "Creating custom object"

$obj=New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "Computer" -Value $computer.ToUpper()
$obj | Add-Member -MemberType NoteProperty -Name "Installed" -Value $Installed
$obj | Add-Member -MemberType NoteProperty -Name "InstallPath" -Value $InstallPath
$obj | Add-Member -MemberType NoteProperty -Name "ExecutionPolicy" -Value $ExecutionPolicy
$obj | Add-Member -MemberType NoteProperty -Name "Version" -Value $PowerShellVersion
$obj | Add-Member -MemberType NoteProperty -Name "RunTimeVersion" -Value $RunTimeVersion

write $obj
Write-Debug "end of function"
}

Again, don’t try to copy and paste. Grab the attached file instead. There are some noticeable differences between this version and the one using WMI. This version has a few traps to handle different types of connection errors. If you attempt to connect to a machine that isn’t available, the function uses the Warning pipeline to display a message. You’ll get a slightly different error message if there is a permissions problem. By using Write-Warning, assuming your shell’s $WarningPreference is still set the to default of Continue, you can run an expression like this:

get-content c:\servers.txt | foreach-object {get-powerShellinstall $_} | Select computer,Installed,Version | convertto-html | out-file c:\test\psh.html

You can still see the warnings, but it they won’t be a part of the end result. I’ve added a few other sample usages in the attached file.

I’ve also added some debug lines for tracing code execution. If you need to troubleshoot, change $debugpreference in the function to Continue and you’ll the debug lines written to the debug pipeline.

One last major advantage to this version is that even though it doesn’t explicitly allow for alternate credentials, as long as a I have a secure channel to the remote computer, this script will work. What this means is I can map a drive to a server like this:

net use * \server01\c$ /user:mydom\auser P@ssw0rd

Once the drive is mapped, I can use the PowerShell function:

Get-powershellinstall server01

And it will work.

If you come up with any interesting ways to use this function, please let me know.

 

Technorati Tags: , , ,