PowerShell 4.0 Now Available

You can get PowerShell 4.0 for down level operating systems now via the WMF 4.0 download.  NOTE: Be sure you have .NET 4.5 installed *before* you install WMF 4.0.  For various reasons, the WMF 4.0 installer doesn’t alert you to this pre-requisite which can cause PowerShell to indicate it is still version 3.0 after installation i.e. you didn’t get a good install.

Posted in PowerShell, PowerShell 4.0 | 5 Comments

PowerShell Tidbit: Capturing a ScreenShot with PowerShell

This is a crude approach but works for capturing the main window of an application who’s process object you can find –typically via Get-Process.  If you run this script from PowerShell.exe or PowerShell_ISE.exe you will capture a screen shot of the host application’s main window which will get saved to your $home folder as screenshot.bmp.


$src = @'
using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
public static class NativeMethods
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
}
'
@


Add-Type -TypeDefinition $src
Add-Type -AssemblyName System.Drawing

# Get a process object from which we will get the main window bounds
$iseProc = Get-Process -id $pid

$bmpScreenCap = $g = $null
try {
$rect = new-object PInvoke.RECT
if ([PInvoke.NativeMethods]::GetWindowRect($iseProc.MainWindowHandle, [ref]$rect))
{
$width = $rect.Right - $rect.Left + 1
$height = $rect.Bottom - $rect.Top + 1
$bmpScreenCap = new-object System.Drawing.Bitmap $width,$height
$g = [System.Drawing.Graphics]::FromImage($bmpScreenCap)
$g.CopyFromScreen($rect.Left, $rect.Top, 0, 0, $bmpScreenCap.Size,
[System.Drawing.CopyPixelOperation]::SourceCopy)
$bmpScreenCap.Save("$home\screenshot.bmp")
}
}
finally {
if ($bmpScreenCap) { $bmpScreenCap.Dispose() }
if ($g) { $g.Dispose() }
}

Have fun.

Posted in PowerShell, PowerShell 3.0, PowerShell 4.0 | 2 Comments

PSReadLine: A Better Line Editing Experience for the PowerShell Console

When Windows PowerShell 3.0 shipped, the team created an extensibility mechanism to allow a third party to “take over” the line editing experience.  That hook is a function called PSConsoleHostReadline that PowerShell will call – if it exists – to read a line of text from the user.  The simplicity of this interface belies the potential complexity of what it needs to do.  Users expect to be able to do more than just type a command, mistake free and press enter.  No, they want editing features like cursor left/right, backspace, delete, kill to end of line, multiline editing, etc. 

Fortunately for the community, one of the PowerShell team members – Jason Shirk – has implemented a very nice replacement line editor that uses this hook. It is called PSReadLine and you can get it here on GitHub.  There are no releases on GitHub at this point in time but you can just download the PSReadline.zip file from the master branch, be sure to unblock the ZIP file and then extract the module your Modules folder. Alternatively, if you have PsGet you can install PSReadLine using its Install-Module command e.g:

C:\PS> Install-Module PSReadLine

After installation, import the PSReadLine module to have it take over your command line editing experience. 

C:\PS> Import-Module PSReadLine

Note: the PSReadLine module only works for the PowerShell.exe console. It does not work for the PowerShell ISE console pane. 

You have now imported PSReadLine, so what does it get you?  If you use the default Windows (cmd) mode you get the standard CMD line editing  features:

  • Home/End navigation
  • Up/DownArrow history navigation
  • Right/Left Arrow single char navigation
  • Ctrl+Right/LeftArrow word navigation
  • Ctrl+Home to delete from cursor to beginning of line
  • Ctrl+End to delete from cursor to end of line
  • Esc to revert line
  • Delete to delete a char
  • Backspace to delete char before the cursor

You also get tab & shift+tab completion that you expect from the PowerShell console.  So what else does it buy you? Well syntax color for one thing:

image

And how about indicating when you have a syntax error:

image

Notice the red “>” in my prompt?  That is telling me I have a syntax error in this command.  If you look at the end, I’m missing a closing “}”.  Type  in that closing “}” and the red goes away.

How about being able to paste into the console using Ctrl+V like you can in ISE. PSReadLine has got that covered.  Ever deleted something accidentally in the line and wanted to get it back?  Try the handy Ctrl+Z/Ctrl+Y shortcuts for Undo and Redo.  Yes!!  This line editor has a full blown undo/redo stack. 

Ever had a command go over a single line?  You know multiline commands are a pain in the PowerShell console because:

  1. You can’t go to a previous line to edit it. You have to completely start over. Pressing the UpArrow just cycles through history on the current line which means your current line edits get blown away.
  2. You can’t use Up/DownArrow history recall to recall the *whole* multiline command. You can only recall one line at a time which makes multiline commands a major PITA to recall & edit in the console.

Check this out with PSReadLine.  It doesn’t “look” much different than standard PowerShell e.g.:

image

But you can use the Right/LeftArrow keys to move the cursor between different lines and then edit those lines.  You still don’t want to use the Up/DownArrow keys for line navigation as these keys are still bound to history recall.  However, when you do need to recall this multiline command using the UpArrow, what you get is exactly what you see above.  All three lines included in that one history entry *AND* you can press LeftArrow to move up to the previous lines to edit them.

Last up is my favorite feature called PossibleCompletions.  If I type “Get-Process –<Ctrl+Space>” this is what PSReadLine gives me:

image

Seriously!  Is that cool or what!?  PSReadLine shows you all the parameters in this case.  If you had typed part of the parameter name, it would have shown all parameters that would have matched what you typed.  By the way, this also works for parameter argument values e.g.:

image

It also works for static and instance type members e.g.:

image

There are more capabilities in PSReadLine but most of those come with Emacs mode.  However you can create your own keybindings to do whatever command PSReadLine offers.  If you want to use the Emacs mode, execute this command:

image 

If you like this new command line editing experience as much as I do you will want this in your profile script.  This is my section of profile script for PSReadLine:

if ($host.Name -eq 'ConsoleHost') {
Import-Module PSReadline

Set-PSReadlineKeyHandler -Key UpArrow -Function HistorySearchBackward
Set-PSReadlineKeyHandler –Key DownArrow -Function HistorySearchForward
}

Note that I’ve defined a few more keybindings that allow me to use the UpArrow and DownArrow keys to not only navigate backward and forward in history on an empty line but navigate based on a match as well.   That is, when I type the beginning of some command line, the UpArrow and DownArrow keys search history backward or forward respectively for command lines that match what’s been typed.

If you spend a lot of time at the PowerShell console you owe it yourself to give this module a try.  And if you run into issues, remember these are still somewhat early bits, the project developer has been responsive to fixing bugs.  You can post/view issues here on GitHub.  For more info on PSReadLine, check out the about_PSReadline topic after you have installed the module.

Update 12-29-2013:

Jason has delivered some new PSReadLine presents for the holidays.  Version 1.0.0.6 supports some notable new features.  My favorite of which is that you can select text using just the keyboard.  As if you were in Notepad or VS, press Shift+Right/LeftArrow to select one character at a time or use Ctrl+Shift+Right/LeftArrow to select whole words at a time. 

Once you have the desired text selected you can press Ctrl+x to cut or Ctrl+Shift+c to copy.  Before careful with the copy keyboard shortcut because if you forgot to press Shift, the resulting Ctrl+c will abort the current command line and you can’t get it back – not even with Ctrl+z (undo).  I wonder if perhaps the default copy shortcut should be something like Shift+Insert – old school – to avoid any potential mishaps with Ctrl+c?

You might find the following custom keyhandler handy for selecting the whole command line with Ctrl+A:

Set-PSReadlineKeyHandler -Chord Ctrl+A `
-BriefDescription SelectEntireCommandLine `
-Description "Selects the entire command line" `
-ScriptBlock {
param($key, $arg)

[PSConsoleUtilities.PSConsoleReadLine]::BeginningOfLine($key, $arg)

$line = $null
$cursor = $null
[PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)

while ($cursor -lt $line.Length) {
[PSConsoleUtilities.PSConsoleReadLine]::SelectForwardChar($key, $arg)
[PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
}
}

Another feature that has been added is CharacterSearch and CharacterSearchBackward. By default these are mapped to F3 and Shift+F3.  Say you are at the beginning of the command line with multiple pipeline stages.  Press F3, then press Shift+\ to specify the “|” character.  PSReadLine will advance the cursor to the next “|” character.  This is cool.  But the next two custom keyhandlers pretty nifty:

Set-PSReadlineKeyHandler -Chord Ctrl+\ `
-BriefDescription SearchForwardPipeChar `
-Description "Searches forward for the next pipeline character" `
-ScriptBlock {
param($key, $arg)
[PSConsoleUtilities.PSConsoleReadLine]::CharacterSearch($key, '|')
}
Set-PSReadlineKeyHandler -Chord Ctrl+Shift+\ `
-BriefDescription SearchBackwardPipeChar `
-Description "Searches backward for the next pipeline character" `
-ScriptBlock {
param($key, $arg)
[PSConsoleUtilities.PSConsoleReadLine]::CharacterSearchBackward($key, '|')
}

These two keyhandlers will allow you to navigate around your command line by jumping forward and backward between pipeline stages.  They use CharacterSearch and CharacterSearchBackward to move around the command line by searching for “|” characters.  So Ctrl+\ moves forward to the next “|” character and Ctrl+Shift+\ moves backwards to the previous “|” character.

And finally, some help for quickly finding out what keyhandlers are mapped to which keyboard shortcuts, just press Ctrl+Alt+? which on a US keyboard is really Ctrl+Alt+Shift+/ and you get this information:

Key                  Function                 Description
---                  --------                 -----------
Enter                AcceptLine               Accept the input or move to the...
Shift+Enter          AddLine                  Move the cursor to the next lin...
Escape               RevertLine               Equivalent to undo all edits (c...
LeftArrow            BackwardChar             Move the cursor back one charac...
RightArrow           ForwardChar              Move the cursor forward one cha...
Ctrl+LeftArrow       BackwardWord             Move the cursor to the beginnin...
Ctrl+RightArrow      NextWord                 Move the cursor forward to the ...
Shift+LeftArrow      SelectBackwardChar       Adjust the current selection to...
Shift+RightArrow     SelectForwardChar        Adjust the current selection to...
Ctrl+Shift+LeftArrow SelectBackwardWord       Adjust the current selection to...
Ctrl+Shift+RightArrow SelectNextWord          Adjust the current selection to...
UpArrow              PreviousHistory          Replace the input with the prev...
DownArrow            NextHistory              Replace the input with the next...
Home                 BeginningOfLine          Move the cursor to the beginnin...
End                  EndOfLine                Move the cursor to the end of t...
Delete               DeleteChar               Delete the character under the ...
Backspace            BackwardDeleteChar       Delete the charcter before the ...
Ctrl+Spacebar        PossibleCompletions      Display the possible completion...
Tab                  TabCompleteNext          Complete the input using the ne...
Shift+Tab            TabCompletePrevious      Complete the input using the pr...
Ctrl+v               Paste                    Paste text from the system clip...
Ctrl+c               CancelLine               Abort editing the current line ...
Ctrl+C               Copy                     Copy selected region to the sys...
Ctrl+l               ClearScreen              Clear the screen and redraw the...
Ctrl+x               Cut                      Delete selected region placing ...
Ctrl+y               Redo                     Redo an undo
Ctrl+z               Undo                     Undo a previous edit
Ctrl+Backspace       BackwardKillWord         Move the text from the start of...
Ctrl+Delete          KillWord                 Move the text from the cursor t...
Ctrl+End             ForwardDeleteLine        Delete text from the cursor to ...
Ctrl+Home            BackwardDeleteLine       Delete text from the cursor to ...
Ctrl+]               GotoBrace                Go to matching brace
Ctrl+Alt+?           ShowKeyBindings          Show all key bindings
Alt+?                WhatIsKey                Show the key binding for the ne...
F3                   CharacterSearch          Read a character and move the c...
Shift+F3             CharacterSearchBackward  Read a character and move the c...

 

And to find out what a specify keyboard shortcut is mapped to, pres Alt+? (or on a US keyboard Alt+Shift+/) and then the keyboard shortcut.  For example, if I press Alt+Shift+/ and then Ctrl+Shift+LeftArrow I get this information:

Ctrl+Shift+LeftArrow: SelectBackwardWord - Adjust the current selection to inclu
de the previous word

Major kudos to Jason and this awesome module.  It is funny how quickly you just get used to it and then when you’re on someone else’s PC without this module – well – you convince them pretty quickly that they need PSReadLine.  Smile 

In closing, I’ll leave you with two more custom keyhandlers I have in my profile. The first creates a pair of single or double quotes and puts the cursor in the middle:

Set-PSReadlineKeyHandler -Chord "Ctrl+'","Ctrl+Shift+'" `
-BriefDescription SmartInsertQuote `
-Description "Insert paired quotes if not already on a quote" `
-ScriptBlock {
param($key, $arg)

$line = $null
$cursor = $null
[PSConsoleUtilities.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor)

$keyChar = $key.KeyChar
if ($key.Key -eq 'Oem7') {
if ($key.Modifiers -eq 'Control') {
$keyChar = "`'"
}
elseif ($key.Modifiers -eq 'Shift','Control') {
$keyChar = '"'
}
}

if ($line[$cursor] -eq $keyChar) {
# Just move the cursor
[PSConsoleUtilities.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
}
else {
# Insert matching quotes, move cursor to be in between the quotes
[PSConsoleUtilities.PSConsoleReadLine]::Insert("$keyChar" * 2)
[PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
[PSConsoleUtilities.PSConsoleReadLine]::SetCursorPosition($cursor - 1)
}
}

The second handler was inspired by mjolinor’s Clip-ToArray ISE add-on.  It is a more basic version of his function which I call – PasteAsHereString.  It is indeed handy and could be easily modified to do all that the original Clip-ToArray function does.

Set-PSReadlineKeyHandler -Chord Ctrl+Shift+v `
   -BriefDescription PasteAsHereString `
   -Description "Pastes the clipboard text as a here string" `
   -ScriptBlock {
param($key, $arg)

Add-Type -AssemblyName System.Windows.Forms
$str = [windows.forms.clipboard]::GetText() -replace '(?m)[`n\s*]+$',''
[PSConsoleUtilities.PSConsoleReadline]::Insert("@'`n${str}`n'@")
}

Posted in PowerShell, PowerShell 3.0, PowerShell 4.0 | 4 Comments

PowerShell Community Extensions 3.1.0 Released

We released a minor update to PSCX that adds support for Windows PowerShell 4.0 in addition to its support for PowerShell 3.0.  You can grab the updated bits for your shiny new Windows 8.1 or WMF 4.0 install here.

Posted in PowerShell, PowerShell 3.0, PowerShell 4.0 | 3 Comments

Calling WinRT Async Methods from Windows PowerShell

Windows 8 introduced a new API for modern applications called the Windows Runtime API or WinRT API for short.  One of the hallmarks of this API is that it eschews synchronous methods for any method that could take longer than 50 milliseconds to complete.  The reason is that many developers still do most of their logic on the UI thread.  Microsoft wants to make sure that end users have a good experience using modern apps.  These apps should not appear to hang – even momentarily.  The experience should be, as Microsoft has said on many occasions, “fast and fluid”.  By ensuring that long running calls can’t be called synchronously, the chances improve that end users will have a better experience with modern apps.

In order to make this work well for developers, C# and VB have been modified to handle these async methods in a “simple to program” way via new async and await keywords.  Unfortunately, for us PowerShell scripters, we haven’t gotten such keywords added to PowerShell.  And considering PowerShell’s main focus, Windows administration, we may never see language features to make it easier to call these async WinRT APIs. 

Fortunately, where there is a will there is a way.  We can create a C# wrapper class to make it possible for PowerShell script to call these async methods in a synchronous manner.  If you think about it, PowerShell doesn’t usually present GUI that needs to remain responsive.  In many cases, our scripts run unattended where 50 millisecond or longer calls is really no big deal. 

Here is an example of a WinRT API you might like to use from PowerShell:

C:\PS> [Windows.Storage.StorageFile,Windows.Storage,ContentType=WindowsRuntime] > $null
C:\PS> $path = "$home\Pictures\foo.png" C:\PS> $asyncOp = [Windows.Storage.StorageFile]::GetFileFromPathAsync($path)
C:\PS> $asyncOp System.__ComObject

 

First up, loading WinRT types in PowerShell takes a bit of a funky syntax as shown in the first line above.  You first specify the WinRT type – Windows.Storage.StorageFile that you want to load, next I believe you specify the name of the winmd file – Windows.Storage (located in $env:WinDir\System32\WinMetadata).  The third component – ContentType – specifies that the winmetadata refers to a WindowsRuntime type.  Documentation on this format is somewhere between sparse and non-existent as far as I can tell.

Once we have the WinRT type loaded, we try to call one of its static type members – “GetFileFromPathAsync()”.  This method returns an object that implements IAsyncOperation<StorageFile>.  To PowerShell it looks like a System.__ComObject which means you don’t get any type info on the object and as far as I can tell, it isn’t usable.  The interface should implement a Completed property and a GetResults() method, neither of which are recognized as a member of the PowerShell object $asyncOp. 

Whenever we run into these sort of problems with PowerShell, the solution is typically to create a “PowerShell friendly” C# wrapper around the functionality.  In this case, I have created a C# wrapper around objects that implement IAsyncOperation.  The code is pretty simple.  You can view it here on GitHub.

With this compiled into a .NET assembly called PoshWinRT.dll, I can then use the API like so:

C:\PS> [Windows.Storage.StorageFile,Windows.Storage,ContentType=WindowsRuntime] > $null
C:\PS> $path = "$home\Pictures\foo.png"
C:\PS> $asyncOp = [Windows.Storage.StorageFile]::GetFileFromPathAsync($path)
C:\PS> Add-Type -Path ~\PoshWinRT.dll
C:\PS> $typeName = 'PoshWinRT.AsyncOperationWrapper[Windows.Storage.StorageFile]'
C:\PS> $wrapper = new-object $typeName -Arg $asyncOp
C:\PS> $file = $wrapper.AwaitResult()
C:\PS> $file


ContentType      : image/png
FileType         : .png
Attributes       : Archive
DateCreated      : 8/9/2013 10:34:22 AM -06:00
Name             : foo.png
Path             : C:\Users\Keith\Pictures\foo.png
DisplayName      : foo
DisplayType      : PNG File
FolderRelativeId : 6AE4A0202B76C225\foo.png
Properties       : Windows.Storage.FileProperties.StorageItemContentProperties
Provider         : Windows.Storage.StorageProvider
IsAvailable      : True

 

Note that we have to Add-Type our helper DLL PoshWinRT.dll.  Then after we get back the $asyncOp object from the GetFileFromPathAsync() call, we create an instance of the PoshWinRT.AsyncOperationWrapper class.  This class is a generic class so we need to pass in the type parameter – Windows.Storage.StorageFile in this case.  This, by the way, means that you can use this wrapper class with other WinRT async APIs that return types other than StorageFile.  Just pass the appropriate type in as the type parameter when constructing the AsyncOperationWrapper class.  The last thing we do when creating the wrapper is to pass in the object that we got back from the async API call.  The one that is of type IAsyncOperation<T>.  The wrapper will wrap that object.

Once we have the wrapper class created, we simply call its AwaitResult() method.  That will return whatever type you specified when creating the wrapper class.  In this case, it will return a Windows.Storage.StorageFile object as you can see when we dump the result in $file to screen.

There are a number of challenges using WinRT from PowerShell.  Fortunately we can overcome async APIs that return IAsyncOperation with a C# wrapper class like the one introduced in this post.  One of the next challenges to overcome is how to subscribe to WinRT events which is not supported by PowerShell V3 out-of-the-box.

Posted in PowerShell, PowerShell 3.0, WinRT | 4 Comments

Using PowerShell to Modify DCOM Launch & Activation Settings

A few weeks ago I had the need to customize DCOM launch & activation permissions for a COM component.  I came up with this hack, er script, that I thought I would share “as-is”.  If anybody wants to take this and run with it – go for it.

function New-DComAccessControlEntry {
param(
[Parameter(Mandatory=$true, Position=0)]
[string]
$Domain,

[Parameter(Mandatory=$true, Position=1)]
[string]
$Name,

[string]
$ComputerName = ".",

[switch]
$Group
)

#Create the Trusteee Object
$Trustee = ([WMIClass] "\\$ComputerName\root\cimv2:Win32_Trustee").CreateInstance()
#Search for the user or group, depending on the -Group switch
if (!$group) {
$account = [WMI] "\\$ComputerName\root\cimv2:Win32_Account.Name='$Name',Domain='$Domain'" }
else {
$account = [WMI] "\\$ComputerName\root\cimv2:Win32_Group.Name='$Name',Domain='$Domain'"
}

#Get the SID for the found account.
$accountSID = [WMI] "\\$ComputerName\root\cimv2:Win32_SID.SID='$($account.sid)'"

#Setup Trusteee object
$Trustee.Domain = $Domain
$Trustee.Name = $Name
$Trustee.SID = $accountSID.BinaryRepresentation

#Create ACE (Access Control List) object.
$ACE = ([WMIClass] "\\$ComputerName\root\cimv2:Win32_ACE").CreateInstance()

# COM Access Mask
# Execute = 1,
# Execute_Local = 2,
# Execute_Remote = 4,
# Activate_Local = 8,
# Activate_Remote = 16

#Setup the rest of the ACE.
$ACE.AccessMask = 11 # Execute | Execute_Local | Activate_Local
$ACE.AceFlags = 0
$ACE.AceType = 0 # Access allowed
$ACE.Trustee = $Trustee
$ACE
}

$Name = 'IUsr'
$ComComponentName = 'foo'

# Configure the DComConfg settings for the component so it can be activated & launched locally
$dcom = Get-WMIObject Win32_DCOMApplicationSetting `
            -Filter "Description='$ComComponentName'" -EnableAllPrivileges
$sd = $dcom.GetLaunchSecurityDescriptor().Descriptor
$nsAce = $sd.Dacl | Where {$_.Trustee.Name -eq $Name}
if ($nsAce) {
$nsAce.AccessMask = 11
}
else {
$newAce = New-DComAccessControlEntry $env:COMPUTERNAME -Name $Name
$sd.Dacl += $newAce
}

$dcom.SetLaunchSecurityDescriptor($sd)

Have fun!

Posted in PowerShell | 9 Comments

PowerShell V4 – PipelineVariable Common Parameter

The big new feature in Windows PowerShell 4.0 is Desired State Configuration however there are a number of other minor features one of which is a new common parameter called PipelineVariable.  This parameter allows you to store the current pipeline object into the variable specified by the name you provide.  This capability comes in handy when, in the process of executing commands in the pipeline, information is lost due to transformations on the objects flowing down the pipeline.  For example, take the following case:

PS> Get-ChildItem *.ps1 | Select-String function | Get-Member


   TypeName: Microsoft.PowerShell.Commands.MatchInfo

Name         MemberType Definition
----         ---------- ----------
Equals       Method     bool Equals(System.Object obj)
GetHashCode  Method     int GetHashCode()
GetType      Method     type GetType()
RelativePath Method     string RelativePath(string directory)
ToString     Method     string ToString(), string ToString(string directory)
Context      Property   Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename     Property   string Filename {get;}
IgnoreCase   Property   bool IgnoreCase {get;set;}
Line         Property   string Line {get;set;}
LineNumber   Property   int LineNumber {get;set;}
Matches      Property   System.Text.RegularExpressions.Match[] Matches {get;set;}
Path         Property   string Path {get;set;}
Pattern      Property   string Pattern {get;set;}

Note in this output that the FileInfo objects output by Get-ChildItem got replaced with MatchInfo objects in the pipeline.  Most of time this is perfectly fine except when you’d like to access some information that was available on a pipeline object in a preceding stage of the pipeline. 

An example of this kind of scenario comes from a question on StackOverflow, the OP wanted to know how to print out the filename and just the immediate parent directory’s name e.g. output “Scripts\foo.ps1” instead of “C:\Users\Keith\Scripts\foo.ps1”.  This can be accomplished with Split-Path and Join-Path manipulation of the MatchInfo’s Path property e.g.:

Get-ChildItem *.ps1 | Select-String function | 
    Foreach {"$(Split-Path (Split-Path $_ -Parent) -Leaf)\$($_.Filename)"}

 

This works just fine but being a clever PowerShell user you happen to know that the FileInfo objects output by Get-ChildItem have a convenient property called “Directory.BaseName” that would be perfect in this scenario.  Unfortunately you quickly realize that after Select-String you no longer have access to that FileInfo object.  Select-String outputs a new type of object called Microsoft.PowerShell.Commands.MatchInfo. You could make the FileInfo object available via another variable ($fi) using the Foreach command as shown below:

Get-ChildItem *.ps1 | Foreach {$fi = $_; $_} |
    Select-String function | 
    Foreach {"$($fi.Directory.BaseName)\$($_.Filename)"}

 

The new PipelineVariable allows you to eliminate the extra Foreach command.  The example below effectively does the same thing as the previous example:

Get-ChildItem *.ps1 -PipelineVariable fi | Select-String function | 
    Foreach {"$($fi.Directory.BaseName)\$($_.Filename)"}

 

There are a few things to note about the PipelineVariable parameter.  First, it has a conveniently short alias “pv” just like the other common parameters tend to have e.g. “ea” for ErrorAction, “ov” for OutputVariable, “wv” for WarningVariable.  Second, you do not specify a “variable” as the value for a PipelineVariable parameter. Instead you specify the name of a variable.  In other words, do not do this: “-PipelineVariable $fi” unless you really want to programmatically provide the name of another variable in which to store the pipeline object.  Most of the time you will not specify the “$” but just the name of a variable e.g. “-PipelineVariable fi”.  Third, using the PipelineVariable parameter works great as long as all the commands in the pipeline between the storage and the use of the PipelineVariable are streaming commands.  If you use Sort-Object or Group-Object in between, then the object your pipeline variable refers to will not be correct because the way these commands work.  Commands like Sort-Object and Group-Object aggregate all pipeline input, process it and then start a new flow of objects down the pipeline. 

Here’s a simple example that demonstrates the problem. We have the following filenames and sizes:

PS> ls *.txt
…
Mode           LastWriteTime       Length Name
----           -------------       ------ ----
-a---     7/19/2013 12:16 AM     20000006 Large.txt
-a---     7/19/2013 12:16 AM       200006 Medium.txt
-a---     7/19/2013 12:16 AM         2006 Small.txt
-a---     7/19/2013 12:16 AM           26 VerySmall.txt

 

Let’s try to sort the files based on length while using the PipelineVariable parameter.  Yes I know, completely contrived but it demonstrates the point.

PS> Get-ChildItem *.txt -pv fi | Sort-Object Length |
>>     Foreach {"$($_.Name) size is $($fi.Length)"}
>>
VerySmall.txt size is 26
Small.txt size is 26
Medium.txt size is 26
Large.txt size is 26

 

This is obviously not correct.  What happens is that the Foreach scriptblock doesn’t execute until Get-ChildItem has output *ALL* its objects.  That’s because the Sort-Object command buffers up all its input before it sends any output down the pipeline.  So by the time the Foreach begins to execute, the pipeline variable $fi will always contain the last object output by Get-ChildItem.  If you think about it this makes sense.  Sort-Object can’t send any “sorted” output down the pipeline until it sees all its input.  The issue happens with any non-streaming command which also includes Group-Object and Measure-Object.

Finally, one last example where this feature comes in handy.  This has to do with using Select –ExpandProperty which is a great way to walk down collections of collections.  The issue I’ve run into doing this is sometimes I want to preserve a property from the collection’s parent object but can’t because there is an identically named property on the items in the collection.  Here’s an example:

PS> Get-Command -Type Cmdlet | Select Name -ExpandProperty ParameterSets
Select : Property cannot be processed because property "Name" already exists.

 

In other words, I can’t include the Name from the CmdletInfo object because it would conflict with the ParameterSet.Name property. This means you have to use the Foreach trick I showed earlier:

Get-Command -Type Cmdlet | Foreach {$cmd=$_;$_} | Select -ExpandProperty ParameterSets

 

But if you then go on to expand the Parameters collection in each ParameterSet you run into the same problem again since each Parameter object also has a Name property.  Rather than injecting another Foreach command into the pipeline, this can be simplified using the PipelineVariable parameter in PowerShell 4.0 like so:

Get-Command -Type Cmdlet -pv cmd | 
    Select -Exp ParameterSets -pv pset |
    Select -Exp Parameters |
    Format-Table @{n='Cmdlet';e={$cmd.Name}}, @{n='ParameterSet';e={$pset.Name}}, Name

 

BTW you may be tempted to use a feature of Select-Object where you can add synthetic properties to objects in the collection as PowerShell expands the collection like so:

Get-Command -Type Cmdlet | Select @{n='Cmdlet';e={$_.Name}} -ExpandProperty ParameterSets

 

This works great the first time you execute it.  But upon subsequent executions, you get an error indicating that the property Cmdlet already exists.  I believe this happens because in PowerShell 3.0 this approach modifies the actual objects via the Dynamic Language Runtime. This is fine except when those objects are cached i.e. not regenerated every time they are asked for.  When that happens, you get an error because you are trying to add the same property again to the same objects you previously added the property to. 

That wraps up this discussion on the new PipelineVariable parameter in PowerShell 4.0.  I expect this parameter will save you some typing in scenarios like the ones described above.  Just watch out for aggregating commands like Group-Object and Sort-Object.

Posted in PowerShell, PowerShell 4.0 | 8 Comments