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.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
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.
[cc lang="powershell"]
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
[/cc]
I also have another function that is intended to generate the inputs for Set-Foo. But I'm not getting the results I expect.
[cc lang="Powershell"]
Function Get-Foo {
echo "Working...please wait"
echo "Jeff"
$i=Get-Random -Minimum 1 -Maximum 10
echo $i
} #end function
[/cc]
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.
[cc lang="PowerShell"]
Function Get-Foo {
write-host "Working...please wait" -ForegroundColor Green
echo "Jeff"
$i=Get-Random -Minimum 1 -Maximum 10
echo $i
} #end function
[/cc]
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.
[cc lang=Powershell]
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
[/cc]
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.
1 thought on “Pipelines, Consoles and Hosts”
Comments are closed.