Posts Tagged ‘Development’

Someone asked on the PowerShell Newsgroup about writing Advanced Functions, and specifically:

looking for a … guide to putting together an advanced function that is visible and usable every time I start Powershell. By visible I mean that when I do a ‘get-command’ I want my [advanced function]s to be listed alongside all the regular cmdlets. What makes that possible? ... what do I need to do to make that happen? Whats the difference between an [advanced function] and a module?

There are lots of articles on the Microsoft PowerShell team blog about both topics, but it seems there’s not really been any sort of step-by-step written, so I posted this to the newsgroup, and since the person who asked the original question said he found it useful, I figured I’d share it here…

Read the rest of this entry »

Awhile back I wrote a series of posts about WPF From PowerShell From PowerShell” which were about how you could load XAML in previous PowerShell 2 CTPs to create WPF user interfaces … a few people have mentioned loading XAML in PowerBoots, and a couple of people have posted other samples showing XAML even since I published the most recent release, so I figure it’s time to point out that you really can load that XAML into Boots, and get all the threading and other support.

Just for fun, I’m going to rehash an earlier post about updating windows to show how you can go about this using PowerBoots, and hopefully show that it’s a little easier (and a lot more async). Compare and contrast the code in this article with that one, just for fun.

This works with any version of PowerShell

Unlike the original article, most this code (except where explicitly mentioned) works on either PowerShell v2 with PowerBoots, or PowerShell 1.0 with PoshWPF, a snapin that is part of the PowerBoots module but is also released separately… Also, unlike those previous posts, this does not require you to be running PowerShell.exe with the -STA switch, since the New-BootsWindow cmdlet takes care of threading for us.

Read the rest of this entry »

WPF uses a concept called “Attached Properties” to handle certain things, like when you put controls into a DockPanel. Basically, anything you put inside a DockPanel has a property “Dock” which you can set … but because the property is actually defined by the DockPanel, it doesn’t show up in PowerBoots, so you can’t just say -Dock "Bottom" when you create the child items … at least, not yet.

I’m still thinking about the best way for me to expose this in PowerBoots, because the problem is that attached properties can get attached several ways, and not just on direct children of an element. So I wanted to show you a way that you can do it for now so you can at least use the DockPanel effectively:

Boots {
   DockPanel -LastChildFill $true {
      DockPanel -LastChildFill $true {
         Button "OK" -Padding "2,0,2,0" |
            ForEach-Object { $_.SetValue(
               [System.Windows.Controls.DockPanel]::DockProperty,
               [System.Windows.Controls.Dock]::Right)
               $_
            }
         TextBox -HorizontalAlignment Stretch
      } | ForEach-Object { $_.SetValue(
               [System.Windows.Controls.DockPanel]::DockProperty,
               [System.Windows.Controls.Dock]::Bottom)
               $_
            }
      TextBox -AcceptsReturn $true -minWidth 300 -minHeight 200
   }
}

A better way …

Here’s what I’m thinking about for the next release of PowerBoots (you can take this and use it now, if you like it, but I’m really looking for, uhm … better ideas). Basically, I have written a function: Set-AttachedProperty, which takes an attached property and a value, and passes through the element on the pipeline. You may want to use this in conjunction with the module I published awhile back for creating type accelerators, because it lets you run a line like this:

Add-Accelerator DockPanel System.Windows.Controls.DockPanel

Which will let you substitute [DockPanel] for [System.Windows.Controls.DockPanel] … and of course, you can use it for all the types you want to use attached properties from, and it’s a real lifesaver if you need to use a bunch of them. Of course, in this particular example, we really only need to use a single attached property, so it’s enough to define that property ahead of time:

$DockProperty = [System.Windows.Controls.DockPanel]::DockProperty

Then you can use the Set-AttachedProperty function through an alias sap, and rewrite that huge block above like this:

Boots {
   DockPanel -LastChildFill $true {
      DockPanel -LastChildFill $true {
         Button "OK" -Padding "2,0,2,0" | sap $DockProperty Right
         TextBox -HorizontalAlignment Stretch
      } | sap $DockProperty Bottom
      TextBox -AcceptsReturn $true -minWidth 300 -minHeight 200
   }
}

Pretty slick, right? And it will even print out the list of values in the error message if you invoke it with an invalid value against an enum property like dock: sap $DockProperty "" … The problem I have with it is that you have to predefine your $DockProperty variable, and you can’t just define it against the root class. So I’m trying to find a way to tweak the dynamic property generation to make it so that the pipe into | sap $DockProperty Bottom can just be a parameter to the original element: -Dock Bottom … if I can’t find that, in the worst case scenario, I’ll just add an -AttachedProperties parameter with a hashtable of $DockProperty,“Bottom” or something … what do you think?

In any case, here’s the sap function, for now, and remember to use it as part of the pipeline:


function Set-AttachedProperty {
[CmdletBinding()]
PARAM(
   [Parameter(Position=0,Mandatory=$true)
   [System.Windows.DependencyProperty]
   $Property
,
   [Parameter(Mandatory=$true,ValueFromPipeline=$true)
   $Element
)
DYNAMICPARAM {
   $paramDictionary = new-object System.Management.Automation.RuntimeDefinedParameterDictionary
   $Param1 = new-object System.Management.Automation.RuntimeDefinedParameter
   $Param1.Name = "Value"
   # $Param1.Attributes.Add( (New-ParameterAttribute -Position 1) )
   $Param1.Attributes.Add( (New-Object System.Management.Automation.ParameterAttribute -Property @{ Position = 1 }) )
   $Param1.ParameterType = $Property.PropertyType
           
   $paramDictionary.Add("Value", $Param1)
   
   return $paramDictionary
}
PROCESS {
   $Element.SetValue($Property, $Param1.Value)
   $Element
}
}

New-Alias sap Set-AttachedProperty
 

Announcing the release of PowerBoots 0.1

This release of PowerBoots is the most exciting release software I’ve cranked out in awhile. It finally has almost all of the features that I have thought of so far (we’re still missing proper support for attached properties).

  • You can create pretty much any WPF element, including ones I haven’t thought of adding yet (use Add-BootsFunction to add a new assembly or a single type).
  • You can create graphical user interfaces from PowerShell 1.0 all the way to the latest PowerShell 2.0 CTP.
  • You can create your WPF GUIs -Async in their own threads, or inline in PoshConsole, or as synchronous dialogs that return values when they close.
  • You can capture screenshots of your UIs (eg: generate .jpg charts from Visifire). You can even do so without displaying them!
  • You can take advantage of XAML Data Templates to generate graphical representations of any objects in the PowerShell pipeline.
  • You can add to, or modify your running GUIs from the command line.

You can read the “and much much more” between the lines right?

Read the rest of this entry »

[new] You should check out the new release of PowerBoots, and the walkthrough PowerBoots Tutorial that I wrote up … this post is getting out of date. :)

If you haven’t seen the Ruby Shoes graphical framework, you should check it out. In fact, go read the tutorial and come back, because the rest of this will make a lot more sense then.

It’s a very slick toolkit, right?

From my previous post introducing PowerBoots

Well, here it is: PowerBoots 0.0.1

I hacked some things together in binary form, and Jeffrey Snover did some pure magic in a script, and after playing with various different mixes of it, I’m going to release the pure-script, CTP3-only, PowerBoots module now, and I’ll follow up with the backwards-compatible Out-WPF that gives you external threading later on.

I think you’ll find this is pretty slick already

Unpack the “PowerBoots” folder into one of your Module folders, and then just run imo PowerBoots and you’re off to the races. Imo is not short for immolate, nor for “in my opinion” ... it’s the alias for the newly renamed Import-Module cmdlet.

Once you’ve got that set up, check out the Demo-PowerBoots.ps1 script. Don’t just run it — open it in an editor and check it out, then run a few of the individual sections, and play with them to get a feel for how this works.

I’ll have more very soon, but for now, let me tantalize you with this little demo:


#Requires -version 2.0
#And requires -STA
Import-Module PowerBoots # http://huddledmasses.org/downloads/PowerBoots.7z
Import-Module HttpRest   # Version 1.0.1 http://www.poshcode.org/787

$url = "http://www.gutenberg.org/catalog/world/readfile"
function Update-Page($pg,$bk=53){
   @($textBox)[0].Text = Get-WebPageContent $url -with @{fk_files=$bk; pageno=$pg} //pre
   @($pageLabel)[0].Content = "Page: $pg"
}

#################
## The actual GUI code:
$window = Window -Title "Shakespeare's Richard III" -SizeToContent "WidthAndHeight" -Content (
            StackPanel -Children $(
              GridPanel -Children $(
                Image -Source 'http://huddledmasses.org/wordpress/wp-content/uploads/2009/01/eagle-crop.jpg' -height 250
                Label -Content "Richard III" -FontSize 52 -HorizontalAlignment Right -VerticalAlignment Bottom
              )
              StackPanel -Orientation Horizontal -Children $(
                Button -Content "Previous Page" -On_Click {Update-Page (--$page) $book}
                Label  -Content "Page $pg" -OutVariable PageLabel
                Button -Content "Next Page"     -On_Click {Update-Page (++$page) $book}
              )
              TextBox -OutVariable TextBox
            )
          )

$page=20; $book=53
Update-Page $page $book
$window.ShowDialog()

Awhile back I wrote a Get-Web script, which I later converted to a compiled Get-Web cmdlet and module (pssnapin) ... adding support for POST forms. At the time, it seemed like GET and POST with some string variables pretty much covered what I’d ever want to do with web pages, however I found a few places where I needed to POST a file attachment, and then a couple of full-on REST APIs, which use not just GET and POST, but PUT and DELETE as well.

While I was playing with scripting the REST API for DekiWiki … I experimented with the MindTouch Dream SDK … and as a result, I’ve refactored the part of that module that actually does the HTTP work out into a little PowerShell 1.0 compatible script library. Of course, it depends on several dll’s (mindtouch.core.dll, mindtouch.dream.dll, and SgmlReaderDll.dll and log4net.dll) from the Dream SDK, which you can download from SourceForge.

Example Uses


## Retrieve Google search results:
function Get-Google {
  Invoke-Http GET http://www.google.com/search @{q=$args} |
    Receive-Http Xml "//h3[@class='r']/a" | Select href, InnerText
}

## Download a file from a web page
function Get-WebFile($url,$cred) {
  Invoke-Http GET $url -auth $cred | Receive-Http File  
}

## Upload a PowerShell script to PasteBin
function Send-Paste {
PARAM($PastebinURI="http://posh.jaykul.com/p/",[IO.FileInfo]$file)
PROCESS {
  if($_){[IO.FileInfo]$file=$_}

  if($file.Exists) {
    $ofs="`n"
    $result = Invoke-Http POST $PastebinURI @{
      format="posh"           # PowerShell
      expiry="d"              # (d)ay or (m)onth or (f)orever
      poster=$([Security.Principal.WindowsIdentity]::GetCurrent().Name.Split("\")[-1])
      code2="$((gc $file) -replace "http://","http``://")" # To get past the spam filter.
      paste="Send"
    } -Type FORM_URLENCODED -Wait
    $xml = $result.AsDocument().ToXml()
    write-output $xml.SelectSingleNode("//*[@class='highlight']/*").href
  } else { throw "File Not Found" }
}}
#########################################################

## Example, to download my PasswordRequired.dll:
$cred = new-object System.Management.Automation.PSCredential `
                   "test", $(ConvertTo-SecureString "password")
Get-WebFile http://huddledmasses.org/downloads/PasswordRequired.dll $cred

## Example, to upload a script called TestScript.ps1 to the Pastebin we use on IRC:
ls TestScript.ps1 | Send-Paste "http://posh.jaykul.com/p/"

 

There are basically two main functions: Invoke-Http and Receive-Http. There are also a few extra functions which are mostly just for use by these two functions, and then Set-HttpDefaultUrl and Set-HttpCredential which you can use to simplify your scripts. I intend to rewrite these as cmdlets in C# and contribute them to the PowerShell Community Extensions along with some of my other networking cmdlets (Get-DNS), so if you have any suggestions, I’d love to hear them!

Invoke-Http

Invokes HTTP verbs (like GET, POST, PUT, and DELETE) against a URL with optional parameters (eg: name = value), input, etc … and returns the output as a MindTouch.Dream.Result or (optionally) as a MindTouch.Dream.DreamMessage …

Syntax

Invoke-Http [-Verb] <String> [[-Path] <String>] [[-With] <Hashtable>] [[-Content] <Object>] [[-Type] <String>] [[-Authenticate] <String>] [-WaitForResponse]

Parameters

Verb

The HTTP verb you want to invoke. Defaults to GET, but can be anything your HTTP server supports.

Path

The URL you want to invoke against. Can be a partial URL, if you first call Set-HttpDefaultURL first. You can also specify this as an array or a hashtable. This is particularly useful if you’re specifying a partial URL, and your URL is built based on logic in your code. If you pass an array, the values in the array are joined with forward-slashes. If you pass a hashtable, the keys and values are appended joined with forward-slashes, but the values are double encoded (by UrlEncode), ie: $key/$(UrlEncode(UrlEncode($value))

With

The parameters to be passed with the verb call, as a hashtable. (these are particularly used for POST, etc). These are passed in the query as named parameters, with the values encoded.

Content

Anything you want to pass as content. In actuality, we really only handle three things: XML Documents, file paths (the file will be used as content), and everything else is passed as simple text. If you need other data types, the “DreamMessage” that the scripts depend on can handle all sorts of data types, so you’ll just need to add it around line 50 of the script.

Type

This is the type of content. We can automatically deduce this for XML or File objects, but if you want to force a type, you should use this parameter. For instance, to fake a web form submission, you need to specify the type as FORM or FORM_URLENCODED but you can also specify the type as any of: ATOM, BINARY, BMP, CSS, DREAM_EXCEPTION, FORM_URLENCODED, GIF, HTML, HTTP, JPEG, JS, JSON, MULTIPART_MIXED, NOTHING (mimetype -/-), ANY (mimetype */*), PDF, PHP, PNG, RDF, RELAXNG, SVG, ANY_TEXT (mimetype text/*), TEXT (mimetype text/plain, with the ISO-8859-1 encoding), TEXT_UTF8 (mimetype text/plain, with UTF8 encoding), TEXT_XML, TIFF, VCAL, VERSIT, XHTML, XML, XRDS.

Authenticate

You can pass either a boolean (in which case the HTTPRest scripts will use a default $global:HttpRestCredential which it will prompt for if you haven’t set it), or an actual PSCredential or NetworkCredential object. In any case, this causes the invoke to contain network credentials. Note that sometimes you may need to pre-authenticate before making calls to a web-service.

WaitForResponse

A switch parameter that causes the query to wait until the response completes downloading, and return a DreamResponse instead of returning an incomplete Result ... this is useful when you know the response is a simple XML file and you need to parse it to determine whether your call has succeeded. If you need to actually get the data out, or if you just need to know the method was invoked successfully, you shouldn’t pass this parameter.

Receive-Http

Receives the response or result from a Dream.Result or DreamMessage. as XML, XDoc, text, raw bytes, or even to file. This function is really only useful to receive the output of Invoke-Http when you need the response content.

Syntax

Receive-Http [[-Output] <string:[Xml|File|Text|Bytes]>] [[-Path] <string>] [[-InputObject] <MindTouch.Dream.Result<MindTouch.Dream.DreamMessage>>]

Receive-Http [[-Output] <string:[Xml|File|Text|Bytes]>] [[-Path] <string>] [[-InputObject] <MindTouch.Dream.DreamMessage>]

Parameters

Output

The TYPE of output you want:

  • Xml will return an XmlDocument — or XmlNode(s) if you pass a Path.
  • File will write to file — currently, you must pass a path, I didn’t make it guess it automatically.
  • Text will output text — in the case where the actual output is XML, this returns the InnerText.
  • Bytes returns an array of bytes…
  • XDoc returns a MindTouch.Dream.XDoc (not the .Net native class), which has a boatload of extra web-related methods and features you might find useful such as accepting XPath operators as indexers.

Path

A string containing the path for file output or XPath selector for XML, XDoc, and Text output

InputObject

The input must be either a Result<DreamMessage> or a DreamMessage itself. Values passed on the pipeline are accepted into this parameter.

Set-HttpDefaultUrl

This takes the passed URI and sets it as the base (default) URL for the Invoke-Http function (if you set this, any path passed to Invoke-Http is treated as relative to this path).

Set-HttpCredential

This takes a PSCredential or NetworkCredential and stores it as a PSCredential for use by the Invoke-Http function. Particularly useful when you want to script a bunch of authenticated methods without being re-prompted. Note that if you don’t pass a credential, you’ll be prompted for one the usual way, so calling just Set-HttpCredential is sufficient when working on the command-line.

Miscellaneous

There are other functions in here, a few should be considered “internal” functions (when this is a module, they would not be exported: Get-DreamMessage, Get-DreamPlug, Get-HttpCredential) and a few of which came out of my utilities functions (like Encode-Twice, and Join-Url, Get-FileName, Get-UtcTime, and Get-CredentialBetter)...

Code

Hashtables are IEnumerable, but they don’t behave that way in PowerShell … this seems to cause all sorts of odd behavior and such, so I thought I’d write up all the examples I can think of in one place. That means this post is going to be a little bit rambling, so please bear with me.

PowerShell enumerates IEnumerable


add-type @"
<code lang="
csharp">
using System.Collections;
public class enumer : IEnumerable {
  public IEnumerator GetEnumerator() {
    for(int i = 0; i < 10; i++) {
     yield return i;
   }
  }
}
"
@

$e -is [Collections.IEnumerable]      # is true
$e = new-object enumer
$e                                    # will output 0..9 to the console
$e.GetEnumerator()                    # will output the same
$e | measure-object                   # will show a count of 10.

$table = @{test="This is a test";exam="this is an exam";defense="defend your thesis"}

$table -is [Collections.IEnumerable]  # is true too!

$table                                # will output three DictionaryEntry items
$table.GetEnumerator()                # will output the same thing
$table.Count                          # will output 3
$table | measure-object               # will output 1! WHAT!?
 

It turns out that in the case of Hashtables, PowerShell does NOT enumerate them into the pipeline. Instead, it passes the entire Hashtable object. Of course, nobody realizes this … because the ouput cmdlets unwrap them (what?!).

But there are bugs caused by special treatment

The first bug is in Add-Member, which doesn’t work on Hashtables until you’ve already used the Hashtable.


$table = @{test="This is a test";exam="this is an exam";defense="defend your thesis"}
Add-Member -in $table NoteProperty Quiz "Surprise, hope you're ready!"
$table.Quiz  # It's not there! There is NO OUTPUT
Add-Member -in $table NoteProperty Quiz "Surprise, hope you're ready!"
$table.Quiz  # This time it works ...
 

NOT ONLY does Add-Member not work the first time, it’s not just a matter of calling it twice: you just have to try to access something in the hashtable before you can use Add-Member on it:


$table = @{test="This is a test";exam="this is an exam";defense="defend your thesis"}
Add-Member -in $table NoteProperty Puzzle "How on earth does this work?"
Add-Member -in $table NoteProperty Quiz   "Surprise, hope you're ready!"
$table.Quiz  # It's not there! There is NO OUTPUT
Add-Member -in $table NoteProperty Quiz   "Surprise, hope you're ready!"
Add-Member -in $table NoteProperty Puzzle "How on earth does this work?"
$table | gm -type NoteProperty | %{ $_.Name }  # Output: Puzzle, Quiz
 

Here’s another buggy manifestation, in the Formatting cmdlets. This time, the Format-* cmdlets unroll the hashtable … to make it look like it’s being enumerated the way it should be.


$table = @{test="This is a test";exam="this is an exam";defense="defend your thesis"}
## Prime it, and then Add-Member won't work
$table.PrimeTheHashtableSoWeCanAddMember
Add-Member -in $table NoteProperty Quiz   "Surprise, hope you're ready!"

## Note how Measure-Object and Get-Member operate on the HASHTABLE
## There's only a single item, of course...
$table | measure-object | %{$_.Count}  
## And we have 7 Properties, plus the Quiz NoteProperty
$table | get-member -type Properties | %{ $_.Name }

## But Format-List shows the properties of the ITEMS
## So we think we can list those properties like:
$table | format-list *
 

Clearly, the Format-* cmdlets have magic code that unwraps hashtables. Which just leads to even more confusion: $table looks the same (in the console output) as $table.GetEnumerator() ... but it doesn’t behave the same way, EXCEPT to the format cmdlets.

PowerShell 2.0 uses Hashtables more

In PowerShell 2.0, the PowerShell team is adding another special feature based on hashtables (which appears at first to be based on IEnumerables):

Jeffrey Snover gave an example of splatting in his PDC presentation. Splatting is where a collection is unwrapped so that you can take an array of values and pass one to each parameter of a cmdlet or function. But in Jeffrey Snover’s demo, he splatted a hashtable. Basically, the hashtable keys are matched up to parameter names as though they had been specified by name. That made me wonder why splatting can’t work with custom objects, but after investigating a bit, I’m actually frustrated with the inconsistency of how hashtables are treated in Posh.

  • In the splatting scenario they are unrolled as a collection of named parameters …
  • If you pipe them, they’re treated as a single object instead of being unrolled …
  • Even though you can access hashtable items using dotted property syntax, you can’t use them to set ValueFromPipelineByPropertyName values, because they aren’t really properties.

The new splatting feature seems to only work with simple arrays and hashtables … adding yet another scenario where the hashtable is being treated specially (even though it doesn’t need to be: if they just splatted IEnumerable, we could work with List, and any IEnumerable of DictionaryEntry objects, or KeyValuePair could be matched by name … that would make hashtables work, but it would also let you use the more powerful generic collections, etc.

You know what would be cool? If I could splat any object (like a custom PSObject that I have added members to), and have it’s property names matched to parameter names as though all the parameters had ValueFromPipelineByPropertyName set.

You know what would be really cool? If I could specify that I want pipeline objects splatted, forcing ALL parameters to be treated as ValueFromPipelineByPropertyName, without needing to use: ForEach-Object { Test-Splatting @_ } … maybe a syntax like: Get-HashTablesToSplat | Test-Splatting @@ …

I’ll write up more information later, but a couple people have asked for this in #PowerShell on irc.freenode.net, and I had it already written, so here you go … my ConvertFrom-Html cmdlet (in a Huddled.HtmlSnapin). It converts HTML to valid xml using the SGML Parser which was available on GotDotNet years ago. It only works with files (doesn’t do URL downloads yet). Use it like this:


$url = "http://huddledmasses.org/"
$file = Join-Path $pwd "HuddledMasses.html"

$client = new-object System.Net.WebClient
$client.DownloadFile( $url, $file ) #NOTE: You need to use a full path here, not relative

$xml = ConvertFrom-Html $file

# Or even
(ConvertFrom-Html $file).Save($file)

The source code to my plugin may be considered public domain, and is included in the Huddled HTML SnapIn Zip.

However, the SgmlReader library is a Microsoft Sample which is licensed under the old MS Samples license which doesn’t allow reuse with viral open source software. I’ve seen some work being done on an HtmlAgilityPack on CodePlex (using a Creative Commons ASA license) but I have not really looked at it except to see that it has a several active issues related to entity encoding and dropping malformed tags which I haven’t encountered in SgmlReader …

The SGMLReader library has been re-released on MSDN Code Gallery under an Ms-PL license, and all is well with the world. :D

Well, the first alpha CTP release of PowerShell 2.0 is out, and there’s a lot of new stuff in it … but I won’t repeat the list from the PowerShell blog, because I’m sure you’ve seen it five or six times already. Instead, lets just skip straight to talking about one of the features we’ve been hearing about the longest: in PowerShell 2, you can create Cmdlets in script … bringing nearly full parity between whats possible in a C# cmdlet and what’s possible in script.

There are a few caveats still (Parameter Sets aren’t working yet, and neither is help, really), and a few surprises … there’s a few downsides to PowerShell script vs C# ... but in this particular context one thing that stands out is that in C# the BeginProcessing, ProcessRecord, and EndProcessing blocks are actually methods which can call each other, and as demonstrated in my tutorial for writing cmdlets that work in the pipeline, they can be recursive — without getting duplicate variables.

A sample Script Cmdlet

In the interests of being the first to publish an interesting script cmdlet ;) and to continue my recent trend of talking about writing for the PowerShell pipeline, I’ve merged the logic of my script function and my pipeline cmdlet into a single sample script cmdlet for PowerShell 2.0 and it works great!

A few observations from the process, in no particular order:

  • If you recursively call your cmdlet from within itself, you have to test for parameters using the new $CommandLineParameters.ContainsKey because parameter variables keep their values through recursion if you don’t explicitly pass a value.
  • $CommandLineParameters.ContainsKey works differently in the Begin block where it will return $false for arguments which will get their values from the pipeline, than in the Process block where it will treat values which were passed as CommandLineParameters the same as those which were passed via the pipeline.
  • If you want to see how your function behaves in a pipeline, you should make sure to test it at different points in the pipeline: at the front, in the middle, and at the end.
  • Cmdlets are functions: they show up in the Function provider.
  • Cmdlets are functions: they have to be dot-sourced before you can call them.
  • Cmdlets are not functions: they are a single command Cmdlet which takes a name (which must have a – in it) and a couple of other parameters followed by a function script block.
  • When you recurse by executing &($MyInvocation.InvocationName), that second invocation has an InvocationName of “&” ... so you can’t go any further (this might be a good thing, if you want to stop recursion at one level no matter what. If you want to go further, you need to put your commands into a string, and use Invoke-Expression.

#requires -version 2.0
###################################################################################################
## A Template for Script Cmdlets which can _also_ be executed in the pipeline ....
##   by Joel Bennett, in hopes it will help...
## Version History
## v1.0 - First public release (after over 9 different versions in my various other functions)
## v1.2 - Show the use of Write-Output, and change "return" in the BEGIN to "Write-Output" to avoid
##      the pooling of the output from the process block when it's invoked as a function.
## v1.3 - Switched back to "break" instead of "return" so that if you pass via the pipeline AND via
##      the inputObject, only the inputObject gets process (this is how cmdlets behave).
##      - Cleaned up the comments, and removed the confusing alternate method and $args handling
##
## v2.0 - First Version as a Script Cmdlet.
##      This is much easier with support for [ValueFromPipeline] and [ValueFromPipelineByName]
##
###################################################################################################
Cmdlet Test-PipelineV2 -ConfirmImpact low -snapin Huddled.Tests
{
   Param (
      [Position(0)] [ConsoleColor] $Color,
      [Position(1)] [Mandatory] [ValueFromPipeline] [String[]] $InputObject
   )
   BEGIN {
      if ($CommandLineParameters.ContainsKey("InputObject")) {
         # Don't do anything here, because we're about to get re-invoked...
         $FromArgs = $true
      } else {
         # Normal "run-once" BEGIN processing
         $FromArgs = $false
         Write-Verbose "Begin $Color"
      }
   }
   PROCESS {
      # We no longer have to test for $_ or even to see if the [ValueFromPipeline] param is set
      # It *HAS* to be set, because it's a [Mandatory] parameter :)
      if ($FromArgs) {
         # Don't do anything here except re-invoke ourselves.
         Write-Output $InputObject | &($MyInvocation.InvocationName) $Color
      } else {
         # Normal Pipeline-friendly per-item processing
         Write-Host "Process: $InputObject" -Fore $Color
         ## You should make a practice of explicitly calling Write-Output on things
         ## That's how you emit them into the pipeline instead of just printing them
         Write-Output $InputObject
      }
   }
   END {
      if ($FromArgs) {  
         # Don't do anything here ... it just confuses things
      } else {
         # Normal "run-once" END processing
         Write-Verbose "End $Color"
      }
   }
}
 

A test case


## Test Script:
##
## "a","b","c" | Test-PipelineV2 "Cyan" -verbose
## @("a","b","c") | Test-PipelineV2 "Cyan" -verbose
## Test-PipelineV2 "Cyan" @("a","b","c") -verbose
##
## "a","b","c" | Test-PipelineV2 "Cyan" -verbose | Test-PipelineV2 "Green" -verbose
## @("a","b","c") | Test-PipelineV2 "Cyan" -verbose | Test-PipelineV2 "Green" -verbose
## Test-PipelineV2 "Cyan" @("a","b","c") -verbose  | Test-PipelineV2 "Green" -verbose
###################################################################################################
## Expected Output (sorry, no color here...)

VERBOSE: Begin Cyan
Process: a
a
Process: b
b
Process: c
c
VERBOSE: End Cyan
VERBOSE: Begin Cyan
Process: a
a
Process: b
b
Process: c
c
VERBOSE: End Cyan
VERBOSE: Begin Cyan
Process: a
a
Process: b
b
Process: c
c
VERBOSE: End Cyan
VERBOSE: Begin Cyan
VERBOSE: Begin Green
Process: a
Process: a
a
Process: b
Process: b
b
Process: c
Process: c
c
VERBOSE: End Cyan
VERBOSE: End Green
VERBOSE: Begin Cyan
VERBOSE: Begin Green
Process: a
Process: a
a
Process: b
Process: b
b
Process: c
Process: c
c
VERBOSE: End Cyan
VERBOSE: End Green
VERBOSE: Begin Green
VERBOSE: Begin Cyan
Process: a
Process: a
a
Process: b
Process: b
b
Process: c
Process: c
c
VERBOSE: End Cyan
VERBOSE: End Green

In a continuation of what is, sadly, becoming a series on how the PowerShell Pipeline works … Karl Prosser brought to my attention that certain powershell commands which have an -InputObject parameter don’t actually work when you pass something into it … so I thought I should create a cmdlet to show you how to correctly handle the InputObject parameter with the ValueFromPipeline set so you can pass the input in either way.

To demonstrate the problem, try this:


$a = @("A","B","A","C")
$a | Select -First 3 -Unique
Select -First 3 -Unique -InputObject $a

This should expose two weirdnesses about how the Select-Object cmdlet works:

  1. The -First parameter affects the input before the -Unique parameter does.
  1. When you pass the input in via -InputObject, the whole array is treated as a single object, and the command basically doesn’t do anything.

The big problem with this behavior is that there’s essentially no hint that you’ve done something wrong — there’s actually no way to make Select-Object work properly except by passing the objects in via the pipeline. The bigger problem is that it would have been simple for the Microsoft team to catch this and alert you, but they didn’t — so you probably won’t even notice there’s a problem until you run it on a trivial data set like my example. The even bigger problem is that it doesn’t just affect Select-Object (try it with Where-Object, just for instance). Read the rest of this entry »

Search My Content