Quick Learn – Write-Host, Does it Have a Place?

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.

4 thoughts on “Quick Learn – Write-Host, Does it Have a Place?

    1. tommymaynard Post author

      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.

      Reply
  1. FlavienMc

    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 🙂

    Reply
  2. tommymaynard Post author

    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

    Reply

Leave a Reply

Your email address will not be published.