Auto-Logging Command Output

In the previous tip we introduced the PreCommandLookupAction supported by PowerShell 3 and better. Today we have a special implementation for you.

When you run below code, PowerShell will accept any command that starts with “*” and log the command output in a text file. The text file opens after the command is done.

So you can now run *dir instead of dir to log the results, or *Get-Process instead of Get-Process.

$ExecutionContext.SessionState.InvokeCommand.PreCommandLookupAction = {
    # is called whenever a command is ready to execute
    param($command, $eventArgs)

    # check commands that start with "*" and were not
    # executed internally by PowerShell
    if ($command.StartsWith('*') -and $eventArgs.CommandOrigin -eq 'Runspace')
    {
        # save command output here
        $debugPath = "$env:tempdebugOutput.txt"
        # clear text file if it exists
        $exists = Test-Path $debugPath
        if ($exists) { Remove-Item -Path $debugPath }
        # remove leading "*" from a command name
        $command = $command.Substring(1)
        # tell PowerShell what to do instead of
        # running the original command
        $eventArgs.CommandScriptBlock = {
          # run the original command without "*", and
          # submit original arguments if there have been any
          $( 
          if ($args.Count -eq 0)
          { & $command }
          else
          { & $command $args }
          ) | 
          # log output to file
          Tee-Object -FilePath $debugPath | 
          # open the file once all output has been processed 
          ForEach-Object -Process { $_ } -End {
           if (Test-Path $debugPath) { notepad $debugPath } 
           }
        }.GetNewClosure()
    }
}

Twitter This Tip! ReTweet this Tip!

Replacing Commands

PowerShell comes with a couple of “secret” (better: under-documented) settings. One is PreCommandLookupAction, and it gives you great power: this action is executed whenever PowerShell is ready to execute a command.

Your event handler can now adjust, change, or manipulate both the original command and the arguments submitted to it.

Today we’ll be using this simply to secretly exchange a command with another:

$ExecutionContext.SessionState.InvokeCommand.PreCommandLookupAction = {
    # is called whenever a command is ready to execute
    param($command, $eventArgs)

    # not executed internally by PowerShell
    if ($command -eq 'Get-Service' -and $eventArgs.CommandOrigin -eq 'Runspace')
    {
        # tell PowerShell what to do instead of
        # running the original command
        $eventArgs.CommandScriptBlock = {
          # run the original command without "*", and
          # submit original arguments if there have been any
          $command = 'dir'

          $( 
          if ($args.Count -eq 0)
          { & $command }
          else
          { & $command $args }
          ) 
        }.GetNewClosure()
    }
}

This handler looks for the command “Get-Service”, and replaces it with “dir” instead. So when you run “Get-Service”, you get a folder listing instead. Of course, this makes no sense, and for legitimate reasons you could have used an alias. It simply shows the power of this approach. In the next days, we’ll show some more useful examples.

Twitter This Tip! ReTweet this Tip!

Easy Parsing of Setting Files (Part 3)

In the previous tip you have discovered how ConvertFrom-StringData can turn plain text key-value pairs into a hash table. What’s missing is the other way: turn a hasht able into plain text. With that in place, you’d have a really simple framework to save settings and information to files.

Let’s first create a hash table with some data:

$test = @{
    Name = 'Tobias'
    ID = 12
    Conf = 'PowerShell Conference EU'
}

$test

The result looks like this:

 
Name                           Value                                                                                        
----                           -----                                                                                        
Conf                           PowerShell Conference EU                                                                     
Name                           Tobias                                                                                       
ID                             12
 

Now here is the function ConvertFrom-Hashtable that takes a hash table and converts it to plain text:

filter ConvertFrom-Hashtable
{
    $_.GetEnumerator() |
      ForEach-Object {
        # get hash table key and value
        $value = $_.Value
        $name = $_.Name
        
        # escape "" in strings
        if ($value -is [string]) { $value = $value.Replace('','\') }
        
        # compose key-value pair as plain text
        '{0}={1}' -f $Name, $value
      }
}

Let’s see how the hash table converts:

 

PS> $test | ConvertFrom-Hashtable
Conf=PowerShell Conference EU
Name=Tobias
ID=12

PS>  
 

You can use ConvertFrom-StringData to go the other way:

 
PS> $test | ConvertFrom-Hashtable | ConvertFrom-StringData

Name                           Value                                                                                        
----                           -----                                                                                        
Conf                           PowerShell Conference EU                                                                     
Name                           Tobias                                                                                       
ID                             12                                                                                           



PS>  
 

So essentially, you can now take your hash table, save it as plain text, and reuse it later:

$test = @{
    Name = 'Tobias'
    ID = 12
    Conf = 'PowerShell Conference EU'
}

$path = "$env:tempsettings.txt"

# save hash table as file
$test | ConvertFrom-Hashtable | Set-Content -Path $path -Encoding UTF8
notepad $path 

# read hash table from file
Get-Content -Path $path -Encoding UTF8 |  
  ConvertFrom-StringData |
  Out-GridView

Note that this approach works well for simple string and numeric data. It will not work for complex data types since the conversion does not serialize objects.

Twitter This Tip! ReTweet this Tip!

Easy Parsing of Setting Files (Part 2)

In the previous tip we used ConvertFrom-StringData to parse plain text key-value pairs into hash tables.

Here is an example where such conversion fails:

$settings = @'
Machine=Server12
Path=c:test
'@

$settings | ConvertFrom-StringData

When you look at the result, you’ll quickly see why:

 
Name                           Value                                                                                        
----                           -----                                                                                        
Machine                        Server12                                                                                     
Path                           c:	est
 

Apparently, ConvertFrom-StringData treats “” as an escape character, and in above example adds a tab (“t”) and eats the literal “t”.

To work around this, always escape “” with “\”. Here is the corrected code:

$settings = @'
Machine=Server12
Path=c:\test
'@

$settings | ConvertFrom-StringData

The result now looks just fine:

 
Name                           Value                                                                                        
----                           -----                                                                                        
Machine                        Server12                                                                                     
Path                           c:test
 

Twitter This Tip! ReTweet this Tip!

Easy Parsing of Setting Files (Part 1)

Let’s assume you want to save settings to a file in the most simplest form. Your settings may look like this:

$settings = '
Name=Weltner
FirstName=Tobias
ID=12
Country=Germany
Conf=psconf.eu
'

You could save these settings to file using Set-Content, and read them back using Get-Content.

Yet how would you parse the information so that you can access individual items? There is a cmdlet called ConvertFrom-StringData that turns your key-value pairs into a hash table:

$settings = @'
Name=Weltner
FirstName=Tobias
ID=12
Country=Germany
Conf=psconf.eu
'@

$hash = $settings | ConvertFrom-StringData

$hash.Name
$hash.Country

Twitter This Tip! ReTweet this Tip!

Adding Live Clock to PowerShell Title Bar (Part 2)

In the previous tip we presented code that would update the PowerShell title bar in a background thread, displaying a live clock.

Wouldn’t it be nice to also show the current path location? The challenge: how would the background thread know the current path of the foreground PowerShell?

There is a PowerShell variable called $ExecutionContext which provides all kinds of useful information on the state of a context, including the current path. By passing the $ExecutionContext from the foreground process to your background thread, the thread can display the current path of the foreground process.

Try it:

$code = 
{
    # submit the host process RawUI interface and the execution context
    param($RawUi, $ExecContext)

    do
    {
        # find the current location in the host process
        $location = $ExecContext.SessionState.Path.CurrentLocation
        # compose the time and date display
        $time = Get-Date -Format 'HH:mm:ss dddd MMMM d'
        # compose the title bar text
        $title = "$location     $time" 
        # output the information to the title bar of the host process
        $RawUI.WindowTitle = $title
        # wait a half second
        Start-Sleep -Milliseconds 500
    } while ($true)
}
$ps = [PowerShell]::Create()
$null = $ps.AddScript($code).AddArgument($host.UI.RawUI).AddArgument($ExecutionContext)
$handle = $ps.BeginInvoke()

When you run this code, the PowerShell title bar shows the current path and the live clock. When you switch the current path, i.e. by running “cd c:windows”, the title bar immediately updates.

There are plenty of use cases here that can be tackled with the code above:

  • You could show notification messages when lunch time approaches
  • You could end your PowerShell session after a given time
  • You could display RSS feed items in your title bar

Twitter This Tip! ReTweet this Tip!