Wrapping History to a File

posh-wrapper3

Previously, (was that really five months ago?) we looked at how to wrap a number of strings up into a function, and then just yesterday I finally wrote about how to take Windows PowerShell history and wrap it into a function for later use. Here’s where we left off:

function global:Wrap-History { 
    param( $count=32, $fun=”Wrapped” ) 
    Get-History -Count $count `
    | ForEach-Object { $_.CommandLine } `
    | Wrap-Function $fun 
}

 

If we want to use this Wrap-History function to create a function from our recent history and save that function in a file, we could simply invoke the function and redirect the output to a file. Consider the following examples:

Wrap-History >myfun1.ps1 
Wrap-History -count 60 -fun Second >myfun2.ps1 
Wrap-History 60 Third >myfun3.ps1

 

The first example just uses Wrap-History without parameters – this assumes that we want to wrap up the 32 most recent commands into a function called “Wrapped” and save that function in a script file called myfun1.ps1.

The second example explicitly specifies a count of 60 history items (the most recent ones). Also, it names the resultant function “Second” and saves it in a file called myfun2.ps1.

Finally, the third example also uses an explicit count and function name, yet doesn’t use the parameter names -count and -name but instead depends on positional parameters. Note that Wrap-History treats the first parameter as the count and the second one as the function name according to the “param” block definition. This example saves the resultant function in the file myfun3.ps1.

How could we make the Wrap-History function accept a file name and perform the file redirection for us as well? Consider the following modified version of Wrap-History.

function global:Wrap-History { 
  param( $count=32, $fun=”Wrapped”, $file=$null ) 
  if( $file -eq $null ){ $out = “Out-Default” }   
  else{ $out = { $input | Out-File $file } } 
  Get-History -Count $count `
  | ForEach-Object { $_.CommandLine } `
  | Wrap-Function $fun `
  | &$out 
}

 

Note that I included a similar definition of Wrap-History in the course materials for Microsoft’s “Automating Windows Server 2008 Administration with Windows PowerShell” course 6434, yet with an error. Sorry about that. The above version includes the correction of including “$input | “ before the Out-File invocation in the else clause. If you’re teaching or attending that course, feel free to adjust the script file there.

Let’s take a look at how this version differs from the one at the top of this post.

The first notable difference is that a third parameter can be used to specify the file name. If used positionally, this would follow the count and resultant function name. By name, it could appear in any order.

In the middle of the expanded version is the “if” block which checks if the file name has not been supplied in which case this version of Wrap-History emulates the simpler form, and uses the Out-Default cmdlet to emit the resultant function at the end. If however a file name was supplied to Wrap-Function, then the “else” clause takes the resultant function and outputs it to a file. Yes, “outputs” is a verb just like “saves,” “writes,” “emits,” and all their friends, right? Note that both of these clauses don’t do the actual output, but merely define a variable $out which will be invoked later. The scenario which does not save to a file just defines $out as the string value “Out-Default” which is the name of a cmdlet. The more exciting scenario is when we’ve given Wrap-Function a file name to use. In this case, the $out variable is assign a code block rather than a string (thus the curly braces) which includes the pipeline $input | Out-File $file. Note that the values of these variables are not evaluated until the code block is actually invoked.

And now for the fun part. The Get-History pipeline at the end of the script starts off the same as in the simpler version of Wrap-History. Then we’ve added another stage to the pipeline at the end – the expression &$out. That’s the magic that invokes the variable $out. In the case where we aren’t redirecting to a file, recall from the discussion above that in that scenario, $out = “Out-Default” which means that &$out will use that string value as code to execute and just send the pipeline output (the resultant function) to the Out-Default cmdlet. In situations where we’ve given a file name to save to, note that $out will have been assigned the value { $input | Out-File $file } by this point. Therefore, &$out will send the output of the pipeline thus far (the resultant function) to this code block.

The bug I had in a previously published version of this code block is that if the code block had been just { Out-File $file } then the objects coming down the pipeline to it would not actually get written to the file, and the Out-File cmdlet would just create an empty file (actually 2 characters, carriage return and line feed, but that’s another story). The fix we included here is to use the $input variable in another level of pipeline within the code block. This takes the resultant function coming down the “top-level” pipeline (Get-History | ForEach-Object | Wrap-Function) and coerces it into Out-File in the right way.

Do you remember those three examples of using the simpler Wrap-Function with redirection? Those could be rewritten as follows using the new version.

Wrap-History -file myfun1.ps1 
Wrap-History -count 60 -fun Second `
             -file myfun2.ps1 
Wrap-History 60 Third myfun3.ps1

 

The ability to save recent commands to a script or function can be immensely powerful for ad hoc script development. Often times we might not think of saving what we’ve just done until after we’ve done it. Also, the interactive nature of the shell lends itself well to prototyping a line at a time, which leads to the desire/need to later save what worked well and discard what didn’t.

Naturally, when saving a history of recent commands to a file, there may be some commands which you really do want in the resultant function and some you don’t. For now, we’d suggest that you just edit the output script file from Wrap-History using your favorite text editor and prune and adjust as necessary. Of course, if there’s interest, perhaps we’ll revisit this topic in the future. Let me know what you think.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.