Pipelines, Consoles and Hosts

I continue to come across a particular topic in discussion forums that causes many PowerShell beginners a lot of headaches and more than a little frustration. I know I’ve written about this before and I’m sure I’ll cover it again, but when writing anything in PowerShell that you see in the PowerShell console, you have two choices. You can write directly to the host or to the pipeline.

The host is the application that is hosting or running the underlying PowerShell plumbing. For many people, this will be the PowerShell.exe which results in the familiar large blue console you’ve come to know and love (I hope). Without going too far, suffice it to say that other applications can also host or run the PowerShell bits, each with perhaps a slightly different implementation than what most of us think of as the traditional (ie blue) console.

The pipeline is that special piece of PowerShell plumbing that objects travel. Cmdlets, functions and scriptblocks can work with objects in this pipeline. When you execute a cmdlet, by design it writes objects to the pipeline. If you have a function you’ve created, it too can write functions to the pipeline. Generally all you have to do is run what ever PowerShell commands you wish in your function and objects will come out. Or if you need something custom, you can use the New-Object cmdlet and create your own. The cmdlet for explicitly writing to the pipeline is Write-Output, which has an alias of Write.

However, you can also use a cmdlet called Write-Host. The result is displayed in the console and it’s impossible to tell whether it is an object or not.

They look the same. This is what confuses people. But if you look at the results more closely you’ll see a difference. Piping the first command generates an exception because nothing was written to the pipeline.

However the second example will produce an object. I’ll let you try that out on your own.

How can you fix this? Whenever I use Write-Host, which is great for communicating a message to the person running your code, I use the -Foregroundcolor parameter and change the font color. This makes it stand out and understand that anything colorized is not going to the pipeline. Let’s look at this one more way.

I have a demo function that can accept pipelined input.

Function Set-Foo {
    Param(
    [Parameter(Position=0,Mandatory=$False,
    ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
    [Alias("name","user")]
    [string]$username="You",
    [Parameter(Position=1,Mandatory=$False,
    ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
    [ValidateScript({$_ -gt 0})]
    [int32]$radius    
    )

    Process {
        $pi=[Math]::PI
        $area=$pi * ($radius*$radius)
       
        New-Object -TypeName PSObject -Property @{
            Radius=$radius
            Area=$area
            Calculated=Get-Date
            User=$username
        }
       
    } #process

} #end function

I also have another function that is intended to generate the inputs for Set-Foo. But I’m not getting the results I expect.

Function Get-Foo {

echo "Working...please wait"
echo "Jeff"
$i=Get-Random -Minimum 1 -Maximum 10
echo $i

} #end function

For people coming from the CMD shell or VBScript they see the echo keyword and think this is just what they need. Echo is an alias for Write-Output. In Get-Foo, I want to also display a message to the user about what the function is doing. But look what happens when I pipe Get-Foo to Set-Foo.

My status message is being accepted as input which is not what I want. Let me tweak Get-Foo.

Function Get-Foo {

write-host "Working...please wait" -ForegroundColor Green
echo "Jeff"
$i=Get-Random -Minimum 1 -Maximum 10
echo $i

} #end function

Now I’ll re-run the command.

Better. At least now my status message isn’t getting commingled with the pipelined output and messing up Set-Foo’s input, which was my primary goal. But while I’m at it, I might as well fix Get-Foo so that it writes something to the pipeline that Set-Foo can actually use.

Function Get-Foo {

    write-host "Working...please wait" -ForegroundColor Green
    $name="Jeff"
    $i=Get-Random -Minimum 1 -Maximum 10
    New-Object -TypeName PSObject -Property @{
        Name=$name
        Radius=$i
    }

} #end function

I used the New-Object cmdlet to create a custom object with the property names Set-Foo is expecting. Now let’s see what happens.

That’s much more along the lines of what I was expecting.

If you follow these guidelines your PowerShell scripting experience will be much more productive and pleasant.

  • Use Write-Host with a foreground color to display status messages. Or use Write-Progress.
  • Use Write-Output to write objects to the pipeline.
  • Always try to leverage the pipeline by accepting input objects and writing objects.
  • Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

    This entry was posted in PowerShell, Scripting and tagged , , . Bookmark the permalink.