Category Archives: Uncategorized

Generic Collections and Arrays revisited

I noticed a pingback from Simon Wahlid’s blog on Boe’s post on the V4 Foreach and Where array methods.

Simon uses this example

$list = New-Object -TypeName System.Collections.ArrayList
1,2 | Foreach {
    $object = [PSCustomObject]@{
        ID = "$_"
        Array = @("first","second")
    }
    [void]$list.Add($object)   
}

and notes that this doesn’t work:

$entry2 = $list.Where({$_.ID -eq 2})
$entry2.Array += "third"
$list

because of the return type being a collection, but does work with the cmdlet which returns a single object from his array:

$entry1 = $list | Where-Object {$_.ID -eq 1}
$entry1.Array += "third"
$list

and offers a couple of workarounds to fix his problem:

$entry2[0].Array += "fourth"
$list

or

$entry2.Item(0).Array += "fifth"

The difference in return types is more result of how these methods are implemented than the methods themselves, and the difference is the pipeline. Remember that pipelines “unroll” objects and collections into a stream of elements, and that Powershell will package up any expression that returns multiple objects into an array when you assign it to a variable, or a scalar (single object) if just one object is returned. The where-object example uses the pipeline, and we see that in the returned result. Using indirection (left-hand assignment) of the where() method return doesn’t use the pipeline, and we get the collection.

So if we use the pipeline to set our variable rather than indirection we can get the same return type we would get using the where-object comdlet

$entry2 = $list.Where({$_.ID -eq 2})
$entry2.gettype()


IsPublic IsSerial Name                                     BaseType                                  
-------- -------- ----                                     --------                                  
True     True     Collection`1                             System.Object  

$list.Where({$_.ID -eq 2}) | Set-Variable entry2
$entry2.GetType()


IsPublic IsSerial Name                                     BaseType                                  
-------- -------- ----                                     --------                                  
True     False    PSCustomObject                           System.Object 

So we can make that work by simply changing the way we do the variable assigment:

$list = New-Object -TypeName System.Collections.ArrayList
1,2 | Foreach {
    $object = [PSCustomObject]@{
        ID = "$_"
        Array = @("first","second")
    }
    [void]$list.Add($object)   
}

$list.Where({$_.ID -eq 2}) | Set-Variable entry2
$entry2.Array += "third"
$list



ID                                                 Array                                             
--                                                 -----                                             
1                                                  {first, second}                                   
2                                                  {first, second, third} 

More on generic collections in Powershell V4, and a gotcha.

In a previous post about generic collections I talked about using the invoke() method of a script block to get a collection.

Boe Prox covered the new Foreach and Where methods of arrays in V4 in this this post .

Turns out they have something in common – they both return the same type of generic collection.

Starting with a generic collection using {}.invoke(), and an array:


$col = {1..10}.invoke()
$col.gettype() | ft -AutoSize


IsPublic IsSerial Name         BaseType     
-------- -------- ----         --------     
True     True     Collection`1 System.Object

$arr = 1..10
$arr.gettype() | Format-Table -AutoSize


IsPublic IsSerial Name     BaseType    
-------- -------- ----     --------    
True     True     Object[] System.Array

Running that array through it’s where() method and checking the type returned:

$x = $arr.where({$_})
$x.gettype()| Format-Table -AutoSize

IsPublic IsSerial Name         BaseType     
-------- -------- ----         --------     
True     True     Collection`1 System.Object

Where the Where-Object cmdlet would return an array:


$y = $arr | where {$_}
$y.gettype() | Format-Table -AutoSize


IsPublic IsSerial Name     BaseType    
-------- -------- ----     --------    
True     True     Object[] System.Array

Another notable difference is that the where() method will always return that collection even if nothing passed and it’s empty:


$x = $arr.where({})
$x.gettype()| Format-Table -AutoSize

IsPublic IsSerial Name         BaseType     
-------- -------- ----         --------     
True     True     Collection`1 System.Object

Where the cmdlet will return $null:

$y = $arr | where {}
$y.gettype()| Format-Table -AutoSize

You cannot call a method on a null-valued expression.
At line:3 char:1
+ $y.gettype()| Format-Table -AutoSize
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

Which can sometimes lead to unexpected results:

Starting with an array of values, and a threshold value to test for:

$arr = -10..10
$Warn = 15

Then test the array for values over the threshold:

$warnings = $arr | where { $_ -gt $Warn } 
  if ($warnings -eq $null)
    { 'All OK' }
   else { 'Over limit' }
All OK

But if we change that to using the where() method:

$warnings = $arr.where({ $_ -gt $Warn }) 
  if ($warnings -eq $null)
    { 'All OK' }
   else { 'Over limit' }
Over limit

Splatting Parameters Pt 2 – Remote Possibilities

In my last post I talked about using splatted parameters to shorten long command lines and help organize code.

Splatting parameters is usually discussed as a way to pass parameters to cmdlets, but it can also be used to pass parameters to scripts, functions, filters and even an anonymous script block.

Here’s a simple script block that just spits out whatever arguments it’s given:

{$args}

Let’s invoke that, and splat one of the hash tables from the vacation notice script:

Param (
$ImportFile     =  './user_vacation_list.csv',
$EmailFrom      =  'HR@mycompany.com',
$EmailSubject   =  "'Unused vacation reminder'",
$SMTPServer     =  'mail.mycompany.com'
)
 
$AllEmailParams =
        @{
          From       = $EmailFrom
          Subject    = $EmailSubject
          SMTPServer = $SMTPServer
          }

&{$args} @AllEmailParams

-Subject:
'Unused vacation reminder'
-From:
HR@mycompany.com
-SMTPServer:
mail.mycompany.com

The splat operation took the hash table keys and added a hyphen to the front and a colon to the end to make them conform with Powershell parameter syntax, and then spit out each key and it’s value.

That will get implicitly cast as [string] when it’s gets used as a parameter set. Let’s do that explicitly to see what it will look like:

"$(&{$args} @AllEmailParams)"

-Subject: 'Unused vacation reminder' -From: HR@mycompany.com -SMTPServer: mail.mycompany.com

And that’s how splatting works.

Now, back on the vacation notification front, it turns out my script has stopped working because the SMTP server I was using is no longer available. I do have an Exchange server, but by default Exchange receive connectors won’t do anonymous mail relay without special configuration of the access controls. But here a little secret about those receive connectors – the access controls only apply to network connections. An Exchange transport server will relay mail for a script running locally using ‘localhost’ as it’s relay host, even if it’s receive connectors aren’t configured for relay.

So I can use that Exchange server for my mail relay if I change my -SMTPServer to ‘localhost’, and run my script there.
I have remoting enabled on that server, so I should be able to do that using Invoke-Command to run the Send-MailMessage command there. But now all those parameters that are using local variables as arguments are a problem. I can’t put the hash tables in he script block because the variables in them are on my computer, and Invoke-Command won’t let me splat them to it’s Scriptblock. It will only take an argument list, and you can’t used named parameters there. I’ll have hope all those parameters are positional, figure out what the position is for each one, and construct and argumentlist in that order. If I can’t do that, I’ll have to rewrite it to use explicit $args elements, or re-write it by explicitly scoping all the variables with the $Using: prefix. But there is another way.

Sometimes it’s easier to get a script block using local variables by creating it from an expandable string. Using what we know about splatting, we can take our existing hash tables of arguments, and modify the existing script to run that Send-MailMessage command on the Exchange server like this:

Param (
$ImportFile     =  './user_vacation_list.csv',
$EmailFrom      =  'HR@mycompany.com',
$EmailSubject   =  'Unused vacation reminder',
$SMTPServer     =  'localhost' #Changed to 'localhost'
)
 
$AllEmailParams =
        @{
          From       = $EmailFrom
          Subject    = $EmailSubject
          SMTPServer = $SMTPServer
          }
 
Foreach ($User in import-csv $ImportFile)
 {
   $ADUser = Get-ADUser $User.Name -Propertes Mail,Manager
   $Manager = Get-ADUser $User.Manager -Properties Mail
 
   $ThisEmailParams = 
        @{
          To   = $ADUser.Mail
          Cc   = $Manager.Mail
          Body = "You have $($User.VacationDays) unused vacation days."
         }
 
#These lines added in place of the Send-MailMessage command

  $Scriptblock = [Scriptblock]::Create(
   "Send-MailMessage $(&{$args} @AllEmailParams) @ThisEmailParams)")
 Invoke-Command -ScriptBlock $Scriptblock -ComputerName ExchangeServer
 }

Create the script block using the Send-MaillMessage command and the splat strings, and it’s ready to go. All the local variables are already expanded to literal values in the script block, and it’s ready to be invoked on the remote system.
Since I’ve only made one parameter change and added a couple of extra lines to make this work with the existing code, it would be relatively easy to add a another parameter set to include the Exchange server and have it work either way.

Splatting Parameters

This is a quick post on using hash tables to splat parameters to help organize and tidy up your powershell scripts.

I’m going to assume that everyone is familiar with the concept and simply present a quick sample script. It’s intentionally very basic (no parameter validation or error checking). The premise is that you have a csv file from HR with a list of user names and the number of vacation days they have remaining. You need to send them an email notification and CC their manager.

First, this script without any splatting:


Param (
$ImportFile     =  './user_vacation_list.csv',
$EmailFrom      =  'HR@mycompany.com',
$EmailSubject   =  'Unused vacation reminder',
$SMTPServer     =  'mail.mycompany.com'
)

Foreach ($User in inport-csv $ImportFile)
 {
   $ADUser = Get-ADUser $User.Name -Propertes Mail,Manager
   $Manager = Get-ADUser $User.Manager -Properties Mail

   $EmailTo = $ADUser.Mail
   $EmailCC = $Manager.Mail
   $EmailBody = "You have $($User.VacationDays) unused vacation days."

Send-MailMessage -From $EmailFrom -To $EmailTo -Cc $EmailCC -Subject $EmailSubject -SmtpServer $SMTPServer -Body $EmailBody

}

Now I’m going to do the same thing, but using hash tables to pass my parameters to send-mailmessage:

Param (
$ImportFile     =  './user_vacation_list.csv',
$EmailFrom      =  'HR@mycompany.com',
$EmailSubject   =  'Unused vacation reminder',
$SMTPServer     =  'mail.mycompany.com'
)

$AllEmailParams =
        @{
          From       = $EmailFrom
          Subject    = $EmailSubject
          SMTPServer = $SMTPServer
          }

Foreach ($User in inport-csv $ImportFile)
 {
   $ADUser = Get-ADUser $User.Name -Propertes Mail,Manager
   $Manager = Get-ADUser $User.Manager -Properties Mail

   $ThisEmailParams = 
        @{
          To   = $ADUser.Mail
          Cc   = $Manager.Mail
          Body = "You have $($User.VacationDays) unused vacation days."
         }

 Send-MailMessage @AllEmailParams @ThisEmailParams
 }

The “fixed” parameters that are being passed as script parameters can be in one hash table, close to the param block where it’s easy to associate the script parameters being passed with the send-mail message parameters they will map to.

Another hash table inside the loop is used to hold and set the parameters that will unique to each email, and then both are splatted to the command when it’s time to send the email. I think this produces much cleaner and more intuitive code.

Arrays and generic collections in Powershell

As I noted in my last post, Powershell likes to make arrays.

But it’s been noted that using arrays can be horribly inefficient when it comes to adding or removing items from them, and collections work much better for those operations. The method you normally see to get a collection instead of an array is to instantiate a particular collection type, and then iterate through some objects using the .add() method of the collection to populate it.

In a previous post about script blocks I noted that the Invoke() method of a script block makes the script block return a collection.

$col = {get-process}.invoke()
$col.gettype()


IsPublic IsSerial Name                                     BaseType                                  
-------- -------- ----                                     --------                                  
True     True     Collection`1                             System.Object                             

If you research that collection type, it’s basically just a generic collection of PS objects. Since it’s a generic collection, it can contain any type of objects so the methods it offers is limited to basic methods for adding, removing, and inserting elements.


Get-Member -InputObject $col -MemberType Method | ft



   TypeName: System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, 
System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]

Name          MemberType Definition                                                                  
----          ---------- ----------                                                                  
Add           Method     void Add(psobject item), void ICollection[psobject].Add(psobject item), i...
Clear         Method     void Clear(), void ICollection[psobject].Clear(), void IList.Clear()        
Contains      Method     bool Contains(psobject item), bool ICollection[psobject].Contains(psobjec...
CopyTo        Method     void CopyTo(psobject[] array, int index), void ICollection[psobject].Copy...
Equals        Method     bool Equals(System.Object obj)                                              
GetEnumerator Method     System.Collections.Generic.IEnumerator[psobject] GetEnumerator(), System....
GetHashCode   Method     int GetHashCode()                                                           
GetType       Method     type GetType()                                                              
IndexOf       Method     int IndexOf(psobject item), int IList[psobject].IndexOf(psobject item), i...
Insert        Method     void Insert(int index, psobject item), void IList[psobject].Insert(int in...
Remove        Method     bool Remove(psobject item), bool ICollection[psobject].Remove(psobject it...
RemoveAt      Method     void RemoveAt(int index), void IList[psobject].RemoveAt(int index), void ...
ToString      Method     string ToString()  

Most of the time this is all your really need.

You’ll note that there is no .ToArray() method, but remembering Powershell’s propensity for turning things into arrays it’s relatively trivial to get it to do that for you:

$col | set-variable col
$col.GetType()


IsPublic IsSerial Name                                     BaseType                                  
-------- -------- ----                                     --------                                  
True     True     Object[]                                 System.Array                              

So you can go back and forth between an array and a generic collection quite easily:


$array = get-process

$collection = {$array}.invoke()
$collection | set-variable array

$array.gettype()
$collection.GetType()


IsPublic IsSerial Name                                     BaseType                                  
-------- -------- ----                                     --------                                  
True     True     Object[]                                 System.Array                              
True     True     Collection`1                             System.Object                             

Another take on using the += operator

In this post

http://powershell.org/wp/2013/09/16/powershell-performance-the-operator-and-when-to-avoid-it/

Dave Wyatt talks about the performance issues involving using the += operator to do array population and repetitive string concatenation.  I can’t argue with the points on string concatenation, or the proposed solution of using a StringBuilder, but there is a much easier solution to getting that array.

When he invokes the StringBuilder method, Dave is rightfully careful to redirect the output to $null so he doesn’t pollute the pipeline, and includes a comment in the script to that effect, so there’s a perfectly good pipeline there that isn’t being used at all.

When you use left-hand assignment to send the output of an expression to a variable (what’s referred to as “indirection” in the specification documents), Powershell will automatically create an array for you if there are multiple objects being returned from the expression.  Rather than create a generic collection or list and adding individual elements to that and then converting it to an array, you can just output to the pipeline and then use indirection to send that to your array variable and let Powershell do what it does.

So, rather than doing this:


$stringBuilder = New-Object System.Text.StringBuilder
$list = New-Object System.Collections.Generic.List[System.String]

for ($i = 0; $i -lt 10000; $i++)
    {

        $stringBuilder.Append("Line $i`r`n")
        $list.Add("Array Element $i")
    }

    $outputString = $stringBuilder.ToString()
    $array = $list.ToArray()

You can just do this:

$stringBuilder = New-Object System.Text.StringBuilder

 $array =  
    for ($i = 0; $i -lt 10000; $i++)
      {
        $null = $stringBuilder.Append("Line $i`r`n")
        "Array Element $i"
      }

 $outputString = $stringBuilder.ToString()

outputting your array elements to the pipeline, and using indirection to send them back to your array variable which will automatically be created as an array.

Here’s the performance test:



Write-Host "Using += operators:"

$outputString = ""
$array = @()

Measure-Command {
    for ($i = 0; $i -lt 10000; $i++)
    {
        $outputString += "Line $i`r`n"
        $array += "Array Element $i"
    }
} | select totalmilliseconds | fl 

Write-Host "Using StringBuilder and List:  (array)"

$stringBuilder = New-Object System.Text.StringBuilder
$list = New-Object System.Collections.Generic.List[System.String]

Measure-Command {
    for ($i = 0; $i -lt 10000; $i++)
    {

        $stringBuilder.Append("Line $i`r`n")
        $list.Add("Array Element $i")
    }

    $outputString = $stringBuilder.ToString()
    $array = $list.ToArray()
}| select totalmilliseconds | fl



Write-host "Using StringBuilder and indirection (array)"
Measure-Command {
 
 $stringBuilder = New-Object System.Text.StringBuilder

 $array =  
    for ($i = 0; $i -lt 10000; $i++)
      {
        $null = $stringBuilder.Append("Line $i`r`n")
        "Array Element $i"
      }

 $outputString = $stringBuilder.ToString()

}| select totalmilliseconds | fl

And the result:

Using += operators:

TotalMilliseconds : 8212.8541

Using StringBuilder and List: (array)

TotalMilliseconds : 124.5424

Using StringBuilder and indirection (array)

TotalMilliseconds : 91.4757

The indirection code is much simpler, and the performance is even better than using the intermediate generic collection.

Handling lots of parameters

The Scripting Games practice event for this season evokes scripts that have a lot of switch parameters.

I saw lots of stacks of IF statements among the entries dealing with those switches.

When I see stacks of IF statements I have to wonder if might not be better to have a Switch instead.

After tinkering at it a while, with the intent of producing something that was relatively easy to maintain (after you put it into production, somebody is going to want stuff added to it, trust me on this) I have this:


function Get-Inventory
{
[cmdletbinding()]

param (
[String[]]$ComputerName,
[Switch]$Hardware,
[Switch]$LastPatch,
[Switch]$Roles,
[Switch]$Components,
[Switch]$AllReports
)

End {

foreach ($ComputerName in $ComputerNames)
{
$PropHash = [ordered]@{}

Switch -Regex ($PSBoundParameters.GetEnumerator().
Where({$_.Value -eq $true}).Key)
{
'Hardware|AllReports' 
{ 'Manufacturer','Model','CPU','RAM','Disks' | Get-ReportProp }

'LastPatch|AllReports' 
{ 'LastPatch','LastReboot' | Get-ReportProp }

'Roles|AllReports' 
{ 'ServerRoles'| Get-ReportProp }

'Components|AllReports'
{ 'Components' | Get-ReportProp }

} #End Switch

[PSCustomObject]$PropHash

} #End ForEach

} #End End Block


Begin{

[Array]$ComputerNames = $ComputerName

function Get-Manufacturer {}
function Get-Model {}
function Get-CPU {}
function Get-Disks {}
function Get-LastPatch {}
function Get-LastReboot {}
function Get-ServerRoles {}
function Get-Components {}

filter Get-ReportProp { $PropHash[$_] = Invoke-Expression "Get-$_ $ComputerName" }

} #End Begin block

Process { $ComputerNames += $_ } #Process Block

}#End Get-Inventory Function

2013 Scripting Games event 5 notes

I haven’t blogged for awhile, but I find myself with a little extra time. My test bed became a victim of the weather, so I can’t properly test an entry in the last event. I’ve decided to invest that time in writing this blog entry instead.

This is related to my Event 5 entry, but applicable to any situation where you’re going to be reading and extracting data from possibly large text files, and challenge some “conventional wisdom” with regard to the most efficient way read data using Get-Content.

The “conventional wisdom” is that Get-Content -ReadCount 0 is the fastest, most efficient way to read data using Get-Content. One of the celebrity judges made this observation about one of the entries:

“It also uses Get-Content with the parameter -ReadCount 0, which causes the cmdlet to return a string array in one chunk. This increases performance by approximately 6000%, and scanning large log files will take seconds, not minutes.”

You might be able to get that kind of performance increase with the right kind of file (lots and lots of very small records), but real world testing on IIS logs produced different results.

This is my test setup. I’m using a single log file that’s about 250MB, and contains about 500K records.


&{
$path = 'C:\TestLogs\u_ex130422.log'
'Readcount 1'
gps -id $pid
$t = (Measure-Command {gc $path -r 1}).TotalMilliseconds
gps -id $pid
"Elapsed time $t ms `n"}

&{
$path = 'C:\TestLogs\u_ex130422.log'
'Readcount 0'
gps -id $pid
$t = (Measure-Command {gc $path -r 0}).TotalMilliseconds
gps -id $pid
"Elapsed time $t ms `n"}

&{
$path = 'C:\TestLogs\u_ex130422.log'
'Readcount 1200'
gps -id $pid
$t = (Measure-Command {gc $path -r 1200 }).TotalMilliseconds
gps -id $pid
"Elapsed time $t ms `n"}

Each of those script blocks was then run in a separate, fresh PS instance. Here are the results:

Readcount 1

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
379 33 98760 104372 633 1.70 8096 powershell
377 33 94784 102180 632 12.11 8096 powershell
Elapsed time 10509.9201 ms

Readcount 0

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
398 34 99540 104728 634 1.65 6516 powershell
373 35 672492 667200 1166 5.60 6516 powershell
Elapsed time 3950.5904 ms

Readcount 1200

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
392 31 97012 104164 628 1.67 7972 powershell
352 31 107696 115308 628 4.46 7972 powershell
Elapsed time 2775.5476 ms

Using ReadCount 0 is certainly faster than ReadCount 1, but not nearly 6000%. It also has a dramatic affect on the memory footprint of the process.

The last option -ReadCount 1200 shows what happens when you tune ReadCount to match the type of file being processed.

You avoid the massive memory footprint required to read large files using -ReadCount 0, and reduce the number of returns you have to iterate through from Get-Content by a factor of whatever that ReadCount setting is.

The end result is that with the right ReadCount setting, you can actually read the file data faster than using ReadCount 0, and maintain virtually the same (minimal) memory footprint of using ReadCount 1.

My entry for Advanced Event 5 uses Get-Content with this ReadCount setting, and is able to extract the IP addresses from the log files at an average rate of a little over 25,000 records per second on my laptop. It’s #1074 if you’d like to try it out. I appreciate any comments you’d care to leave, but to save you typing I’ve already gotten a proper dressing down about naming that filter :).

Splatting parameters in the Powershell ISE, revisited

 Revisiting the idea of using splatting for commands that produce long lines of paramaters and arguments I decided to try leveraging the built in Command Add-On to simplify the command generation.  This add-on provides a convenient means of creating commands using a simple “fill-in-boxes”  format for arguments, and checkboxes for switches. 

 Capture1

but the output it produces is a single command line with all the parameters and arguments inline.   The following script is my attempt to simplify the process of converting that to a more script-friendly format using parameters and arguments splatted from a hash table.

function SplatFromBuffer{

$cmd = [windows.forms.clipboard]::GetText()

$errors = @()
$tokens = [system.management.automation.psparser]::Tokenize($cmd, [ref]$errors)

try {get-command $tokens[0].content -ea Stop}
 
   catch {
           throw 'Parse failed'
         }

$Pad = $tokens |
  Where {$_.Type -eq 'CommandParameter'} |
   select -ExpandProperty Content |
    sort length |
     select -Last 1 |
      select -ExpandProperty length

$ParamHash = '${0}_Params' -f ($tokens[0]).content.replace('-','')

$Indent = ' ' * ($ParamHash.Length + 7)

$Scriptblock = @"
&{
   $ParamHash = @{

"@

$i = 1
$line = $null

while ($i -lt $tokens.count) {

  if (
       $tokens[$i].Type -eq 'CommandParameter'
     )
     
       {

         if ($line) {$Scriptblock += ($line + "`n")}

         $line = $indent + $($tokens[$i].content).trimstart('-') + ' ' * ($pad - $tokens[$i].content.length) + '  =  '

         if (
             ($tokens[$i+1] -eq $null) -or
             ($tokens[$i+1].Type -eq 'CommandParameter')
            )
             {
               $line += '$True'
             }

       $i++
       
       }

   
    if (
         $tokens[$i].Type -ne 'CommandParameter'
        )
         {
           Switch ($tokens[$i].Type) {

             'Variable'          {$line += '$' + $tokens[$i].Content}

             'Operator'          {$line += $tokens[$i].content}

             $null               {}

             Default             {$line += '"' + $tokens[$i].Content + '"'}

           }

       $i++
      }
  }
 
              
 $Scriptblock += ($line + "`n")
 $Scriptblock += ($Indent + '}' + "`n`n")
 $Scriptblock += '    {0} @{1}' -f $tokens[0].content,$ParamHash.TrimStart('$')
 $Scriptblock += ("`n" + '  }')
 
 $ise_current_file = $psise.CurrentFile
 $ise_current_file.Editor.InsertText($ScriptBlock)
 }

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Splat From Buffer",{SplatFromBuffer},"Ctrl+Shift+S")

 

Adding that code to your ISE profile enables an Add-On (splatfrombuffer) and assigns it to the hot-key combination Ctrl+Shift+S.  With this, you can generate your command line from the Command Addd-On, or by using Show-Command. When you’ve finished specifying your switches and arguments, us the Copy command at the bottom of the pane to copy the generated command line to the clipboard.  From the Add Ons menu, or using the assigned hotkeys invoke the splatfrombuffer add on, and it will read that command line from the clipboard and generate and paste a scriptblock into the script pane to exeucte that command using a parameter hash.

Capture2

The command is created as a standalone script block to prevent any conflict with existing variables in the script, but you can easily remove that if you don’t need it. 

Comments and suggestions welcome…

Using a re-entrant script block to get user input

$AskForColor = {
  $EnteredColor = Read-Host "Enter a color, or Q to quit" 
   switch ($EnteredColor) {
   'Red' {'You chose red'}
   'Blue' {'You chose blue'}
   'Yellow' {'You chose yellow'}
   'Q' {Return}
  default { 'I do not have that color.  Try again.'
            .$AskForColor}
           }
}
.$AskForColor