Skip to main content

Powershell v2 Remoting

In my quest to make my life easier, I have been waiting with bated breath to get my hands on the remoting features of PS v2.  Now that I have access to windows 7 and Windows Server 2008 R2, let the fun begin!

When looking for information about these new features, where better to go than the source.  I found this video on Tech.ED where Jeffery Snover (Powershell Godfather as he is introduced!) co presents using Powershell v2 in large Environments. 

From my findings (mainly from this video) there are primarily 2 new ways of gathering information from remote computers in PS v2, the all singing all dancing big brother that needs all the options installed and configured, and the little brother version which has less pre-requisites for the remote client, but does not do quite as much.

The -computername switch (Little Brother) 

One of the biggest things missing in powershell 1 was the ability to run a cmdlet agains a remote machine.  With PS v2 this has changed. Suddenly, I can get the event logs from my DC from my workstation without too much fuss :  
get-eventlog -ComputerName dc01 -logname "Directory Service" -Newest 5.

This will get information from Server 2003, Server 2008 as well as 2008 R2 so very useful while there is a wait for either all computers that you manage become windows 7 or 2008 R2 or PS v2 becomes available for the earlier OS's.

Sessions and jobs (All Singing All dancing Big Brother)

When you have your server estate (and workstations too) to PS v2, nice things become available. 

The key components to Remoting are as follows :

WinRM service on the Remote Computer.

The service needs to be running and actively listening for connections.  The simplest way of configuring this is running 'winrm qc' at an elevated command prompt.

winrm qc does the following :

set the winrm service to auto start
start the service
create a winrm listener
add firewall exceptions to allow the service to communicate.

Powershell v2 installed

To process the commands, PS v2 need to be installed on the remote machine. (no real suprise there really!)

Permissions!

Even if you are a local admin on the server, you need to be part of the Remote Desktop Users group on the server.  It does seem a bit weird as you are not actually logging in interactivly, but I can only assume it assigns a range of remote access permissions (assuming is a dangerous thing I know - I will try to find out what exact permission is required).


I started with the idea that I would have to manage all my jobs independantly of each other.  By this i mean start all jobs seperatly, check to see if all jobs are still running and once all complete, get the results. 

Here's what I created :

$sb = [scriptblock]{ipconfig;get-service}
$collection = @()
$ComputerArray = "dc01","dc02"
foreach ($Computer in $ComputerArray){
  $store = "" | select "name","job","return"
  $store.name = $Computer
  $store.job = Invoke-command -computername $Computer -scriptblock $sb -asjob
  $collection += $store
}
$count = 0
:main while ($collection.length -gt $count){
  foreach ($col in $collection){
    if ($col.return -eq $null){
      switch ($(get-job $col.job.name).state){
        "Completed" {$col.return = receive-job $col.job -keep;$count++;if ($collection.length -eq $count){break main}}
        "Failed" {$col.return = "job failed";$count++;if ($collection.length -eq $count){break main}}
      }
    }

  }
  "waiting 5 seconds"
  Start-Sleep 5
}


foreach ($col in $collection){
  $col.return
}
I won't go into too much detail about this as there is a much better way script coming up.  But the key lines are as follows:

Define the commands that you want to run on the remote host
$sb = [scriptblock]{ipconfig;get-service}


Run the command using invoke command
$store.job = Invoke-command -computername $Computer -scriptblock $sb -asjob

Grab the results

$col.return = receive-job $col.job -keep

 
After setting up the inital script block - ie the commands that you want to run on the remote machine, I set up individual jobs for each computer in the array and stored them in my $store Array
 
The while loop then checked the job status for all jobs, and when all complete, output all the results.
 
I was infact quite proud of this, until I found out that PowerShell can do all this for you!
 
Remoting script version 2.


$sb = [scriptblock]{ipconfig;get-service}

$ComputerArray = "dc01","dc02"
$Job = invoke-command -computername $ComputerArray -scriptblock $sb -asjob
$Job.childjobs
$Job | wait-job
$results = receive-job $Job -keep
$results

Thats a bit cleaner, Ta very much Mr Snover! Heres the same script with comments :


#Set up the commands you want to run

$sb = [scriptblock]{ipconfig;get-service}


#setup the list of servers you want to target
$ComputerArray = "dc01","dc02"

#run the invoke-command as a job on the whole array. This creates 1 job with multiple child jobs
$Job = invoke-command -computername $ComputerArray -scriptblock $sb -asjob

#view those child jobs
$Job.childjobs

#wait for all the child jobs to complete
$Job | wait-job

#get all the results in one go
$results = receive-job $Job -keep

#display results
$results
Some notes

You can add -throttle to the invoke-command cmdlet to limit the ammount of jobs that are run at once. EG : invoke-command -computername $ComputerArray -scriptblock $sb -asjob -throttle 3

Instead of using a scriptblock, you can use any local ps1 file.  Here is the same code using a file as the source code :

$script = "c:\scripts\example.ps1"

$ComputerArray = "dc01","dc02"
$Job = invoke-command -computername $ComputerArray -filepath $script -asjob
$Job.childjobs
$Job | wait-job
$results = receive-job $Job -keep
$results

If you don't use the -keep option for receive-job, the results are purged (deleted) from the job

To be clean and tidy, add $job | remove-job when finished

That's about as far as i have got with remoting, just got to wait for PowerShell v2 to be available on all my OS's!

Comments

Popular posts from this blog

PowerShell 3 behavioural change

It's taken me way too long to get into PowerShell 3, I guess opportunity hasn't shown it's self until now and so, here, my V3 journey begins.

I was asked to debug a script that would run fine in PS v2 and not in v3.  The issue was a that a variable length was being checked and was failing in v3.  This is why...

In v2 if a variable is undefined, this test returns false

PS C:\windows\system32> $var.length -eq 0
False

In v3 the same test returns true....

PS C:\windows\system32> $var.length -eq 0
True

Not a biggie, but as in this case, a script has broken so something to consider!

cheers

Adam

Enable Powershell Remoting (WinRM) via Group Policy

I have been doing some testing on enabling WinRM via group policy, being that WinRM is the service that Powershell v2 sets up it remoting capabilities. Here are the GPO settings that you need to configure WinRM ....


set the winrm service to auto start


Computer Configuration \ Policies \ Windows Settings \ Security Settings \ System Services


Windows Remote Management (WS-Management)  set Startup Mode to Automatic

start the service


incorporated in to the above - you may need a restart.


create a winrm listener


Computer Configuration / Policies / Administrative Templates / Windows Components / Windows Remote Management (WinRM) / WinRM Service / Allow automatic configuration of listeners


IPv4 filter: *


* is listen on all addresses, or if you only want a particular IP address to respond use an iprange eg 10.1.1.1-10.1.1.254 - don't forget that this IP range has to be valid for all hosts that fall in the scope of the GPO you are creating.  You can use 10.1.1.1 - 10.1.1.254,10.1.1.3 - 10.1.4.254 …

compare-object in Powershell - comparing mulitple values

I'm starting to use compare-object more and more, and one thing I noticed, is that you can compare 2 objects based on multiple attributes. here is how it is constructed...
Compare-Object -ReferenceObject $object1 -DifferenceObject $object2 -Property a,b,c,d,eIf a,b,c and d are the same, but e is different, compare object will return a difference. In the following example, I use "-eq $null" as a check because by default compare-object returns $null if the objects are the same.
#create an array of objects to check against

$collection = @()
foreach ($entry in ("aaaaa","bbbbb","ccccc","ddddd")){
   $store = "" | select "a","b","c","d","e"
   $store.a = $entry*1
   $store.b = $entry*2
   $store.c = $entry*3
   $store.d = $entry*4
   $store.e = $entry*5
   $collection += $store
}

#create an object similar to those in the array
$object = "" | select "a","b…