Remoting Using PowerShell V1

Remoting Using PowerShell V1

  • Comments 18

As you probably know by now, "remoting" is one of the cornerstone features of the next version of PowerShell.  I am absolutely thrilled with the stuff we are doing here and the benefits that come from that approach.  That said, there are lots of things that need to be buttoned up before this is ready: the right user experience, proper serialization, where and how much throttling to do, how to maximize bandwidth utilization and of course our primary concern: how to make remoting safe and secure.  We are working closing with some of the companies top security people reviewing what we are doing to ensure that we do it right.  (Last week we were in a conference room with about 20 people walking through our scenarios.  It became very clear to me that Microsoft's investments and prioritization in security had really paid off as these folks were just awesome.)

So the bottom line is that while I'm optimistic about the remoting, the reality is that is going to take a while before it is cooked.  If I was an IT Pro, I can imagine saying something to the effect of, "That is fine but my hair is on fire - what can I do NOW?"

The answer to that is:  Cheat.

 

Yup - you can do PowerShell remoting with V1.  It's not going to be pretty but it can be done and hey - when your hair is on fire, you'll stick your head in a bucket of #$@! to put it out right?  (this approach isn't a bucket of @#%!, I just wanted to use that phrase :-))

Let's walk through the basics and then compose them into a solution.

Type "Powershell -?" and you'll see the command line parameters you can use to start PowerShell.  There are a couple that are interesting here.  Notice that there is a -Command parameter.  Give it a try by typing

Powershell -Command Get-Process *ss

 

 

You might notice a bunch of cruft before the output, this is  because we are running your profile and that might generate output.  We say -NoProfile to stop that.  We'll also add -NonInteractive so that we don't get prompted for any input.  So now try

PowerShell -NoProfile -NonInteractive -Command Get-Process *ss

 

 

If you plan with this for more than a little bit, you are going to find that you want to run a command that requires a ton of escaping and quoting to work properly (think through a pipeline - it could happen in either process). 

PowerShell -NoProfile -NonInteractive -Command "Get-Process *ss |sort handles"

 

 

What you will find is that at some point, the escaping can become incomprehensible.  That's where the hidden parameter comes in.  I don't know why we decided not to document this or who made that decision but we have a -EncodedCommand parameter that does not show up in our help (I suspect it's because we would have to explain how to encode it and people felt that most people would be too confused by this [you'll see what I mean] and therefore documenting it would do more harm than good.)  I blogged this parameter a while ago HERE.

Here is how you take a scriptblock, turn it into string and then encode it and invoke it:

$script = {gps |where {$_.handles -ge 900}|sort handles}.ToString() 
$encodedScript = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))
PowerShell -NoProfile -NonInteractive -EncodedCommand $encodedScript

 

 

Now it is time to remote it!

WINRS is a commandline interface to invoke a command on a remote machine via WS-MGMT.  You can now run this remotely by doing the following:

Winrs "-r:localhost" PowerShell -NoProfile -NonInteractive -EncodedCommand $encodedScript

And there you have it REMOTING using PowerShell V1.   (Notice that WINRS allows you to specify UserName and Password as well)

 

 

Not it is time to put a bow on it and go to hit the gym.

function Invoke-RemoteCommand
{
param(
$ComputerName,
[SCRIPTBLOCK]$script
)
    $encodedScript = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))
    Winrs "-r:$ComputerName" PowerShell -NoProfile -NonInteractive -EncodedCommand $encodedScript
}

Invoke-RemoteCommand localhost {gps |where {$_.handles -ge 500} |sort handles}

 

 

It's a beautiful thing!  Oh wait a minute.  That almost works but not really.  Try this and look what happens when you try to do the filtering and sorting locally:

Invoke-RemoteCommand localhost {gps} |where {$_.handles -ge 500} |sort handles

Complete crap.

 

 

If only there was a way I could get objects back instead of text.  That's sorta the point of PowerShell right?  Hmmm, if you go back to the beginning and look at the parameters you can provide PowerShell, you'll see -OutputFormat and that can take the value "XML".  This will produce CLIXML and PowerShell native command processor (the thing that invokes processes) looks at the output of a command for something we call a STRUCTURED DataStream and autoconverts that datastream into objects.  Thus the final script looks like this:

function Invoke-RemoteCommand
{
param(
$ComputerName,
[SCRIPTBLOCK]$script
)
    $encodedScript = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))
    $objects = Winrs "-r:$ComputerName" PowerShell -OutputFormat XML -NoProfile -NonInteractive -EncodedCommand $encodedScript
    Write-Output $objects
}

Invoke-RemoteCommand localhost {gps} |where {$_.handles -ge 500} |sort handles

Now it's time to hit the gym. 

Enjoy!

 

Jeffrey Snover [MSFT]
Windows Management Partner Architect
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Leave a Comment
  • Please add 2 and 6 and type the answer here:
  • Post
  • Base64 sometimes uses the = sign for padding.  When it does this, you cannot pass the encoded string on the command line because the CMD.exe uses the = character as a delimiter and it gets dropped.

    As a work-around, I've created this function to create encoded strings:

    Function global:Get-EncodedScript([string] $script) {

     $encodedScript = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))

     while ($encodedScript.Contains("=")) {

       $script = $script + " "

       $encodedScript = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))

     }

     return $encodedScript

    }

  • Base64 sometimes uses the = sign for padding.  When it does this, you cannot pass the encoded string on the command line because the CMD.exe uses the = character as a delimiter and it gets dropped.

    As a work-around, I've created this function to create encoded strings:

    Function global:Get-EncodedScript([string] $script) {

     $encodedScript = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))

     while ($encodedScript.Contains("=")) {

       $script = $script + " "

       $encodedScript = [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($script))

     }

     return $encodedScript

    }

  • how can i check if a server really exists ?

    i mean ... i'm connecting to a number of servers, and using the get-wmiobject..

    if a servers doesn't exist, then the whole task gets stuck, with no error action... can u help

Page 2 of 2 (18 items) 12