Prof. Powershell
Practical PowerShell Part 5: Remoting
Here's how to query info on a remote computer.
- By Jeffery Hicks
- 08/05/2014
More on this topic:
Our journey is almost at an end. What I've shown you is handy, but it only works on the local machine. Wouldn't it be helpful to query the same type of information on a remote computer? As you probably know, you can only access the registry PSDrive locally. But you can use PowerShell remoting.
PS C:\> invoke-command { Get-ItemProperty -Path HKLM:\SOFTWARE\RegisteredApplications } -comp chi-win81 -Credential globomantics\jeff
This also means I can run my script remotely as well.
PS C:\> invoke-command -FilePath C:\scripts\get-registeredapps.ps1 -ComputerName chi-win81 -Credential globomantics\jeff
Remember, when using this technique the script does NOT have to exist remotely. In fact the remote computer can still have a Restricted execution policy since it is technically not invoking a script but rather the contents of the script. Here's my result:
Hmmmmm….where's my default display set? This is where putting the Update-TypeData command in the script is of no use. The results are coming back to my computer. That is where I need the type data information.
PS C:\> $ddps = "Computername","RegisteredApp","RegisteredPath","AppDescription","AppName"
PS C:\> $myType = "My.RegisteredApp"
PS C:\> Update-TypeData -TypeName $mytype -DefaultDisplayPropertySet $ddps -Force
To be clear, I still need to add the type name in the script. Now when I run the command remotely, I get the results I expect.
PS C:\> invoke-command -FilePath C:\scripts\get-registeredapps.ps1 -ComputerName chi-win81 -Credential globomantics\jeff –HideComputerName
For this particular scenario, I'm hiding the computername from Invoke-Command since my default display property set already shows that piece of information. Checking type names you can see that PowerShell knows how to use my type extension.
Even though the result is deserialized, PowerShell makes it work.
The final step is to turn this into a re-usable tool by wrapping the code in a function.
#requires -version 3.0
Function Get-RegisteredApp {
[cmdletbinding()]
Param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:computername,
[System.Management.Automation.Credential()]$Credential=[PSCredential]::Empty
)
Begin {
Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"
#define a scriptblock to run
$sb = {
#define registry property values to ignore
$exclude = "PSParentPath","PSChildName","PSDrive","PSProvider","PSPath"
$regentry = Get-ItemProperty -Path HKLM:\SOFTWARE\RegisteredApplications | Select -Property * -ExcludeProperty $exclude
#get property names
$props = $regentry.psobject.properties.name
#enumerate properties
foreach ($entry in $props) {
#create the registry path to check
$valuepath = Join-Path -path "HKLM:" -child $regentry.$entry
Try {
#follow the value to the corresponding registry key
$app = Get-ItemProperty -path $valuepath -ErrorAction Stop |
Select ApplicationDescription,ApplicationName
}
Catch {
$app=$Null
}
#write an object to the pipeline
$obj = New-Object -TypeName psobject -Property @{
Computername = $env:computername
RegisteredApp = $entry
RegisteredPath = $regentry.$entry
AppDescription = $app.ApplicationDescription
AppName = $app.ApplicationName
}
#add the custom type name
$obj.psobject.TypeNames.Insert(0,"My.RegisteredApp")
#write result to the pipeline
$obj
}
} #close scriptblock
#define a set of parameters to splat to Invoke-Command
$paramHash = @{
Scriptblock = $sb
HideComputername = $True
Computername = $Null
}
#add credential if specified
if ($credential) {
$paramHash.add("Credential",$credential)
}
} #begin
Process {
foreach ($computer in $computername) {
#add the computer to the parameter hashtable
$paramHash.Computername=$computer
Write-Verbose "Connecting to $computer"
Invoke-Command @paramHash
} #foreach computer
} #process
End {
Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
} #end
} #close function
I took all of the registry query code and put it inside a scriptblock. The function uses Invoke-Command to run the scriptblock against one or more remote computers. I'm dynamically building an array of parameters for Invoke-Command in a hash table that I can splat. The other interesting scripting technique is my Credential parameter. I can pass a saved credential object, or a string in which case I will be prompted for the password.
I can now use my command like any other PowerShell command.
PS C:\> "jh-win81-ent","win81-ent-01" | get-registeredapp -verbose | out-gridview
VERBOSE: Starting Get-RegisteredApp
VERBOSE: Connecting to jh-win81-ent
VERBOSE: Connecting to win81-ent-01
VERBOSE: Ending Get-RegisteredApp
If I had tried to write this function from the very beginning and in one sitting, it would have taken me quite a bit of time. But by following the journey and building up my final result as I went, this was a much easier and rewarding project.
About the Author
Jeffery Hicks is an IT veteran with over 25 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis in automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award in Windows PowerShell. He works today as an independent author, trainer and consultant. Jeff has written for numerous online sites and print publications, is a contributing editor at Petri.com, and a frequent speaker at technology conferences and user groups.