I’ve been working on an advanced function that I can’t wait to share (and no, it’s not the one in this post). I really think it’s something that the Windows PowerShell community has been missing. Okay fine, maybe it’s just something I’ve been missing.
I noticed in development that my Write-Output messages to the user were crossing the pipeline, when the custom object (created by the function), was passed to Select-Object (in certain manners). I have a function below that does the same thing as the one in development.
Here’s how this thing works: The function requires the user to provide the value Write-Output (or, wo), or Write-Host (or, wh) for the -Option parameter. This will determine how the message to the user is written inside the Begin block. The only other thing that’s happening in this function, is that a custom object is being created in the Process block, based on some properties of Win32_BIOS.
Function Test-TMWriteOutputVsHost { [CmdletBinding()] Param ( [Parameter(Mandatory=$true, HelpMessage="Enter Write-Output or Write-Host")] [ValidateSet('Write-Output','wo','Write-Host','wh')] [string]$Option ) Begin { If ($Option -eq 'Write-Output' -or $Option -eq 'wo') { Write-Output 'Inside the Begin block (using Write-Output).' } ElseIf ($Option -eq 'Write-Host' -or $Option -eq 'wh') { Write-Host 'Inside the Begin block (using Write-Host).' } } # End Begin Process { $CollectionVariable = Get-WmiObject -Class Win32_BIOS $Object = @() $Object += [pscustomobject]@{ Manufacturer = $CollectionVariable.Manufacturer; Name = $CollectionVariable.Name; Version = $CollectionVariable.Version } } # End Process End { Write-Output $Object } # End, End } # End Function
As we can see below, everything works great with both Write-Output or Write-Host, when we don’t pipe the function to the Select-Object cmdlet.
PS C:\> Test-TMWriteOutputVsHost -Option Write-Output Inside the Begin block (using Write-Output). Manufacturer Name Version ------------ ---- ------- Dell Inc. BIOS Date: 08/27/13 11:12:44 Ver: A1... DELL - 1072009 PS C:\> Test-TMWriteOutputVsHost -Option Write-Host Inside the Begin block (using Write-Host). Manufacturer Name Version ------------ ---- ------- Dell Inc. BIOS Date: 08/27/13 11:12:44 Ver: A1... DELL - 1072009
Now, let’s pipe our object to some variations of the Select-Object cmdlet and watch some things blow up (when using Write-Output).
PS C:\> # Use the horizontal scrollbar to see the results... PS C:\> Test-TMWriteOutputVsHost -Option Write-Output | select * Length ------ 44 PS C:\> Test-TMWriteOutputVsHost -Option Write-Output | select Name,Ver* Name Ver* ---- ---- BIOS Date: 08/27/13 11:12:44 Ver: A13.00 PS C:\> Test-TMWriteOutputVsHost -Option Write-Host | select * Inside the Begin block (using Write-Host). Manufacturer Name Version ------------ ---- ------- Dell Inc. BIOS Date: 08/27/13 11:12:44 Ver: A1... DELL - 1072009 PS C:\> Test-TMWriteOutputVsHost -Option Write-Host | select Name,Ver* Inside the Begin block (using Write-Host). Name Version ---- ------- BIOS Date: 08/27/13 11:12:44 Ver: A13.00 DELL - 1072009
The problem with Write-Output, is that the object it’s producing is crossing our pipeline and causing unpredictable behavior—something we can’t include in a function we want to distribute. Here’s the proof: When we pipe our function to Get-Member, we reveal the objects that show up on the other side, and we don’t want the string object coming over with us.
PS C:\> Test-TMWriteOutputVsHost -Option Write-Output | Get-Member | Select-Object TypeName -Unique TypeName -------- System.String System.Management.Automation.PSCustomObject PS C:\> Test-TMWriteOutputVsHost -Option Write-Host | Get-Member | Select-Object TypeName -Unique Inside the Begin block (using Write-Host). TypeName -------- System.Management.Automation.PSCustomObject
I don’t profess to know it all, so if there’s a way to get around this using Write-Output, then I’d love to hear about it. While I haven’t tried it, I suspect I may be able to create an embedded function—that might be the trick I need. Perhaps I’ll play with that option at another time. Thanks for reading!
Oh, and before someone mentions it, I explained how I feel about Write-Verbose in the comments on a post by Adam Bertram: http://www.adamtheautomator.com/use-write-host-lot.
Is the purpose of using Write-Host or Write-Output here to show a progress message to the user while the script is being ran? If so, why not use a progress bar http://blogs.technet.com/b/heyscriptingguy/archive/2011/01/29/add-a-progress-bar-to-your-powershell-script.aspx
I’ve had that thought, for the part that informs the user that the function is running. There’s still a part that dumps some limited hardware information (based on a switch parameter) that wouldn’t make sense in Write-Progress. Good thinking, though.
It is by design.
“Write-Output” write to the pipeline.
“Write-Host” write to the host.
PipelineHost
If a function return 2 different kinds of object (example : string and pscustomobject) to the pipeline, you have to provide with your own, a receiver command for you “anormal” pipe.
This resolve the problem in pipe :
Test-TMWriteOutputVsHost -Option Write-Output | Where { $_.GetType().Name -eq "PSCustomobject" }
Another solution is to put the 2 differents objets more inside :
Begin {
If ($Option -eq 'Write-Output' -or $Option -eq 'wo') {
Write-Output ( [pscustomobject]@{
Value = 'Inside the Begin block (using Write-Output).'
} )
} # End Begin
Process {
$CollectionVariable = Get-WmiObject -Class Win32_BIOS
$Object = [pscustomobject]@{
Value = @{
Manufacturer = $CollectionVariable.Manufacturer;
Name = $CollectionVariable.Name;
Version = $CollectionVariable.Version
}
}
} # End Process
Write-Output is like a special return that can be invoke a any moment. But you have a contract to only return same type. Or you have a very very special case. Look at get-member on a folder. Get-ChildItem produce type File and Directory.
I use Write-Verbose to see what is doing
I use Write-Debug for deep deep error
I use Write-Host only with script and Write-Verbose with Module.
I hope it will help you 🙂
I changed my Measure-TMCommand function yesterday, in a couple ways:
One, it now allows you to pause, in seconds, between measurements. Two — the better of the two changes — I dumped my notification text. This was the text that indicated the function was actually doing something during long running measurements. It was using Write-Host, but now, it’s gone. I still have the feature that let’s you get some system information about the computer you’re using (RAM, CPU, etc.), but now it uses Write-Verbose, as it always should have. Just wanted to share! Here’s the download link: https://gallery.technet.microsoft.com/Measure-Command-with-52158178