Passing Variables to a Remote PSSession in CTP 3

PowerShell Sessions are what are used to connect to remote computers using PowerShell. There are a bunch of new Cmdlets that use the noun PSSession. One big change from CTP2 to CTP3 is that we are now using PSSessions rather than Runspaces. I think this is great, as most of the ops/admin folks I work with don’t have a clue what a runspace is or why they would need one to connect to a remote computer. Session seems to be a much better noun. I think its right on par with a telnet session or an RDP session. This makes a lot sense from an Admin’s point of view.

Anyway, the way we use PSSessions is through the invoke-command Cmdlet. At a very basic level, invoke-command takes two parameters, a ComputerName and a scriptblock to execute on that remote computer as shown in the example below

   1: PS C:> invoke-command -computer win7andys -scriptblock {cat env:computername}
   2: win7andys
   3: PS C:>

I came across a situation where I needed a variable (lets call it $a) that I was using in my local PSSession to be available in my remote PSSession. Let’s try this and see what happens.

   1: PS C:> "Hello $a"
   2: Hello Foo
   3: PS C:> invoke-command -computer win7andys -scriptblock {"Hello $a"}
   4: Hello

Notice that $a was there locally but not when we tried to use it remotely in our scriptblock.

Lucky for us, there is a –ArgumentList parameter that we can use with invoke-command. This looks promising.

   1: PS C:> invoke-command -computer win7andys -ArgumentList $a -scriptblock {"Hello $a"}
   2: Hello
   3: PS C:>

Still no dice, but we are close. Turns out we need to declare $a as a parameter in our scriptblock.

   1: PS C:> invoke-command -computer win7andys -Argu $a -scriptblock {param ($a) "Hello $a"}
   2: Hello Foo
   3: PS C:>

And now we have it!

Passing Variables to a Remote PSSession in CTP 3

PowerShell Sessions are what are used to connect to remote computers using PowerShell. There are a bunch of new Cmdlets that use the noun PSSession. One big change from CTP2 to CTP3 is that we are now using PSSessions rather than Runspaces. I think this is great, as most of the ops/admin folks I work with don’t have a clue what a runspace is or why they would need one to connect to a remote computer. Session seems to be a much better noun. I think its right on par with a telnet session or an RDP session. This makes a lot sense from an Admin’s point of view. Anyway, the way we use PSSessions is through the invoke-command Cmdlet. At a very basic level, invoke-command takes two parameters, a ComputerName and a scriptblock to execute on that remote computer as shown in the example below
   1: PS C:\> invoke-command -computer win7andys -scriptblock {cat env:\computername}
   2: win7andys
   3: PS C:\>
I came across a situation where I needed a variable (lets call it $a) that I was using in my local PSSession to be available in my remote PSSession. Let’s try this and see what happens.
   1: PS C:\> "Hello $a"
   2: Hello Foo
   3: PS C:\> invoke-command -computer win7andys -scriptblock {"Hello $a"}
   4: Hello
Notice that $a was there locally but not when we tried to use it remotely in our scriptblock. Lucky for us, there is a –ArgumentList parameter that we can use with invoke-command. This looks promising.
   1: PS C:\> invoke-command -computer win7andys -ArgumentList $a -scriptblock {"Hello $a"}
   2: Hello
   3: PS C:\>
Still no dice, but we are close. Turns out we need to declare $a as a parameter in our scriptblock.
   1: PS C:\> invoke-command -computer win7andys -Argu $a -scriptblock {param ($a) "Hello $a"}
   2: Hello Foo
   3: PS C:\>
And now we have it!

Encrypting and Decrypting Strings with a Key in PowerShell

Note:  After writing up this post,  I found a great script by Oisin up on Poshcode that does all of this using System.Security.Cryptography.RijndaelManaged in a much more elegant way, but I had fun hammering through some of this stuff so I thought I would share. I was recently working on a script that required encryption for some of the data. This has been dealt with quite a bit in the PowerShell Community. There are some great posts here (Lee Holmes Precision Computing) and here (/\/\o\/\/'s old blog). I had the unique constraint that the user who encrypts the data may not necessarily be the same user who decrypts it. The way I chose to solve this was with a Shared key, that both users would know. Here's where it gets a little tricky. All the cmdlets for managing Secure Strings seem to encrypt using some sort of hash from the given logged on user. This obviously would not work for use with a shared key. It turns out that there is a -key parameter on the ConvertTo-SecureString. There is also a -asPlainText parameter. But you can't use them both at the same time. They are in different parameter sets. This poses the question, How can you encrypt a plainText string with a key in PowerShell? Well here is what I came up with. First of all, we need to generate a key. Wouldn't it be nice if you could just use an alphanumeric string? But that wouldn't have been nearly as much fun. It turns out the key needs to be a 16,24, or 32 byte array. That made me come up with this handy little function:
function Set-Key {
param([string]$string)
$length = $string.length
$pad = 32-$length
if (($length -lt 16) -or ($length -gt 32)) {Throw "String must be between 16 and 32 characters"}
$encoding = New-Object System.Text.ASCIIEncoding
$bytes = $encoding.GetBytes($string + "0" * $pad)
return $bytes
}
It takes a string, forces the length, and then returns an appropriate byte array that we can use generate a key to encrypt stuff with. So the next hurdle is to actually encrypt a string of plain text with they key we generated. Even though you cannot encrypt a whole string in a secureString, there is a method on System.SecureString called AppendChar. So, if we break our string out in chars, we can build our SecureString.  
function Set-EncryptedData {
param($key,[string]$plainText)
$securestring = new-object System.Security.SecureString
$chars = $plainText.toCharArray()
foreach ($char in $chars) {$secureString.AppendChar($char)}
$encryptedData = ConvertFrom-SecureString -SecureString $secureString -Key $key
return $encryptedData
}
Finally, to decrypt everything, we can use a technique that /\/\o\/\/\ came up with a long time ago using [Runtime.InteropServices.Marshal]
function Get-EncryptedData {
param($key,$data)
$data | ConvertTo-SecureString -key $key |
ForEach-Object {[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($_))}
}
Here is how you could use these functions together to encrypt and decrypt strings to your heart's content: image  
187 >  $plainText = "Some Super Secret Password"
188 >  $key = Set-Key "AGoodKeyThatNoOneElseWillKnow"
189 >  $encryptedTextThatIcouldSaveToFile = Set-EncryptedData -key $key -plainText $plaintext
190 >  $encryptedTextThatIcouldSaveToFile
507964ed3a197b26969adead0212743c378a478c64007c477efbb21be5748670a7543cb21135ec324e37f80f66d17c76c4a75f6783de126658bce09ef19d50da
191 >  $DecryptedText = Get-EncryptedData -data $encryptedTextThatIcouldSaveToFile -key $key
192 >  $DecryptedText
Some Super Secret Password

Customizing Graphical PowerShell/ISE

Graphical PowerShell, also known as the PowerShell Scripting Environment (ISE) can be customized using an object model.

There is a variable called $psISE in the ISE.

   1: PS >$psise | gm | select Name
   2:  
   3: Name                                                                           
   4: ----                                                                           
   5: Equals                                                                         
   6: GetHashCode                                                                    
   7: GetType                                                                        
   8: ToString                                                                       
   9: CurrentOpenedFile                                                              
  10: CurrentOpenedRunspace                                                          
  11: CustomMenu                                                                     
  12: OpenedRunspaces                                                                
  13: Options              

In there we have options, customMenus, and a number of other cool things. Here we can change the Scripting Pane color to “black”

image

ConvertTo-ScriptBlock

Out of the box in PowerShell, you cannot cast a string to a scriptblock.

   1: 81# [scriptblock]"A String that can't be a SB"
   2: Cannot convert value "A String that can't be a SB" to type "System.Management.Automation.ScriptBlock". Error: "Invalid
   3: cast from 'System.String' to 'System.Management.Automation.ScriptBlock'."
   4: At line:1 char:14
   5: + [scriptblock] <<<< "A String that can't be a SB"

This is actually a good thing. To be able to take a string and arbitrarily execute it could definitely be a security risk. However, there are definitely times when you need to do this.

In my case, I am parsing a Windows Sharepoint list that has a set of ScriptBlocks and our code snippets that I need to execute. However, when I pull them from SharePoint, they are strings.

Originally, I was using Invoke-Expression because everything was running locally. But now, with PowerShell remoting becoming available with V2, I wanted to start using invoke-command. Invoke-Expression takes a string for the –command parameter. Invoke-Command takes a ScriptBlock as a –ScriptBlock paramater

Using an old V1 method, you can use the following function to convert a string to a script block.

   1: function ConvertTo-ScriptBlock {
   2: param ([string]$string)
   3: $scriptblock = $executioncontext.invokecommand.NewScriptBlock($string)
   4: return $scriptblock
   5: }

Below is a screen shot of using invoke-command with a string vs the scriptblock that we get back from this function.

image

As you  know, Windows 7 (Demoed at PDC) has PowerShell enabled out of the box. Turns out there is a slightly easier way to do this in the new win7 build and it  will very likely be here in CTP3 as well.

There is now a static method in the ScriptBlock class called Create().

image

So we can write a new V2 function (lets make it an advanced function for fun) using this new method.

   1: function Convert-StringToScriptBlock {
   2: param(
   3: [parameter(ValueFromPipeline=$true,Position=0)]
   4: [string]
   5: $string
   6: )
   7: $sb = [scriptblock]::Create($string)
   8: return $sb
   9: }

Now with the ability to specify the [parameter] attribute and pass it in parameters like ValueFromPipeline we can easily pass in our string from the pipeline or directly via the parameter like we did in the V1 version.

image

UPDATE:

Oisin Grehan has meticulously pointed out that the original name of my function, convert-stringToScriptBlock did not adhere to the naming standard for Cmdlets. Note the following examples:

   1: PS C:\> gcm convert* -CommandType cmdlet | ft Name
   2:  
   3: Name
   4: ----
   5: ConvertFrom-Csv
   6: ConvertFrom-SecureString
   7: ConvertFrom-StringData
   8: Convert-Path
   9: ConvertTo-Csv
  10: ConvertTo-Html
  11: ConvertTo-SecureString
  12: ConvertTo-Xml

For convert functions, we should use ConvertTo and ConvertFrom as the “verb”. I wanted to point this out and thank Osin because I completely acknowledge that I am guilty as charged and I think it is a BIG DEAL to stick with standards. Consistency is imperative as PS Adoption rates increase.

In my humble opinion, I  say functions should use the standard verbs just like Cmdlets. You can create shortcuts all day long with aliases. This will become even more important with the introduction of Modules and advanced functions

SharePoint, CAML, and PowerShell

A while ago I wrote about getting a list of items from a SharePoint list. I had mentioned that you can do some clever things to select which list items you want without having to have all the items returned. Basically, this will allow you pass in a filter to the web service call so it will only return the items you care about.

This ability comes from a Query Language called CAML that is used pretty heavily in SharePoint. It definitely looks a little odd at first glance but essentially it is like SQL in an XML syntax. I think the easiest way to describe this is with an example.

<Query>
  <Where>
    <And>
      <Eq>
        <FieldRef Name="SomeColumnName"></FieldRef>
        <Value Type="Text">data1</Value>
      </Eq>
      <Eq>
        <FieldRef Name="aDifferentColumn"></FieldRef>
        <Value Type="Text">data2</Value>
      </Eq>
     </And> 
  </Where>
</Query>

This is just some XML. It’s saying it is a query where the column name “SomeColumnName” has the value “data1” AND the column aDifferentColumn has the value “data2”

You can use all kinds of different tags for standard operators like “equal, not equal, less than, greater than, as well as AND’s and OR’s. For more details on the CAML query schema, you can go here.

So how would we use this in PowerShell? It starts with Here Strings. Assuming we are going to use the query above, you would put it into a herestring and store it in a variable called $query as shown below. Also note that I am casting the variable to [XML]

[xml]$query = @"
<Query>
  <Where>
    <And>
      <Eq>
        <FieldRef Name="SomeColumnName"></FieldRef>
        <Value Type="Text">data1</Value>
      </Eq>
      <Eq>
        <FieldRef Name="aDifferentColumn"></FieldRef>
        <Value Type="Text">data2</Value>
      </Eq>
     </And> 
  </Where>
</Query>
"@

Now that we have this query variable, we can pass it as a parameter in the constructor for the GetListItems. Remember in the last article we passed a list name and six $null’s. One of those options is a query.

Assuming we have a webservice object for a Sharepoint list (which you can see in the previous article using the wonderful “Get-WebService” function) you can use the following code to return only the rows that meet the criteria of the query.

$result = $webService.GetListItems($listname,$null, $query, $null, $null, $null, $null)

There are some other options that we can play with as well to make  a lot of processing and filtering happen server side so that we only get what we want on the client side as well as minimize the amount of data that has to come back over the wire.