Does this site look plain?

This site uses advanced css techniques

A PowerShell cmdlet can take its instruction from the command line or from the pipeline input (or from session state, to be fair), and the ease by which parameters can be defined on the command line makes it really easy to just use them for everything.

Indeed, those of us coming from longtime UNIX/Linux background are so used to the getopt() library (including the substantially improved GNU enhancements) that this becomes a mental default.

But this is missing out on pipeline input, and this was the basis of many bad design decisions early in my cmdlet-building career that hampered me for years.

What's my Noun?

It took years before I figured out that Pipeline Input Is Your Friend, and eventually came to realize:


A general guideline is that the item mentioned (or even referenced) by the cmdlet's Noun is a good candidate for pipeline input.

This applies whether it's the actual object itself, or a reference to that object (say, an ID).

My early mistake was a cmdlet that backed up application-specific data files, where each data file was represented by an integer: A support ticket could request files 47, 63, and 2738.

Dealing with this on the command line was a disaster, because there's just no good way (as far as I could find) to pass a list of items that might have only one item.

[Cmdlet(VerbsData.Backup, "SpecialFile")]
public class Backup_SpecialFile : Cmdlet
{
  [Parameter(Mandatory = true, ValueFromRemainingArguments = true)]
  public int[] Files { get; set; }

  protected override void ProcessRecord()
  {
      foreach (var file in Files)
      {
          // do stuff with "file"
      }
  }
}

The only way to use a command-line only mechanism, is, unsurprisingly, on the command line:

PS> Backup-SpecialFile 47,63,2738

This is fine for occasional command-line use—putting aside the difference between a single integer and an array—but it gets more messy when building wrapper PowerShell scripts to make it all work as part of a larger system.

I messed around with parameter sets, with an incestuous interaction between BeginProcessing() and ProcessRecord(), and it was frustrating and awful.

General rule of thumb: if your ProcessRecord() is explicitly looping over some input variable, you're probably doing it wrong.

The right answer, of course, is to take the file identifiers from the input pipeline, because it allows for per-item processing one at a time, exactly how PowerShell cmdlets were designed to operate.

[Cmdlet(VerbsData.Backup, "SpecialFile")]
public class Backup_SpecialFile : Cmdlet
{
  [Parameter(Mandatory = true, ValueFromPipeline = true)]
  public int File { get; set; }

  protected override void ProcessRecord()
  {
    // process one "File", being called repeatedly as needed
  }
}

This is superior in every way even if the mechanism is a bit odd when run by hand (compared with a traditional command-line mentality).

PS> 47,63,2738 | Backup-SpecialFile

Now this is a real cmdlet: it accepts pipeline input, which means these input file numbers are subject to Where-Object filtering, the file numbers can be generated by some other process in a Foreach-Object loop, and it just generally works and plays well with others.

Which Parameter for ValueFromPipeline ?

The command line can support an enormous variety of options, but we generally have only one kind of object on the input: how do you decide whether this or that parameter should be the one associated with pipeline input?

As noted at the top of the paper, the item referenced by the cmdlet's noun is an excellent candidate for being The One: if you want to Verb one thing, you may want to Verb more than one thing.

Consider: where will this item's reference come from? If it might come from some other process, it's a good candidate for being The One for ValueFromPipeline support.

This can (but need not) apply even when the cmdlet inherently can only deal with one of a thing. A cmdlet such as Set-Location can only change to one directory at a time, so even though it's commonly used with a command-line parameter (Set-Location C:\TEMP), it will take pipeline input if provided.

Supporting dual-mode parameters (pipeline / command line) is more than this paper is up for, and even for plausible cases such as Set-Location it's fine to only support the more obvious command line parameter case.

We'll cover dual-mode parameter (pipeline / command line) support in a later CmdLetter, but the main point here is that you're far more likely to regret not using pipeline support than you are to have used it.


First published: 2019/07/25