Search code examples
powershelloutput-formattingwrite-host

Output file doesn't match Write-Host


When I Write-Host from my .ps1 file I see:

Parentfolder >> ChildFolder

When I output to a file, I see:

ParentFolder
>>
ChildFolder

I am using a simple write-host ($childgroup.name), ">>", ($object.samaccountname)

When I try to output the same information using Return, Out-File, Export to CSV, etc... I get 3 lines for what Write-Host prints as a single line.

I just want the output file to be in the same format as the Write-Host output.

as requested:

function getchildgroups($groupname) {

    # Get initial group details and members
    $childgroup = get-adgroup $groupname -properties member

    # Only continue if this group has members
    if (($childgroup.member).count -gt 0) {

        # Loop through each member of the group
        foreach ($memberobject in $childgroup.member) {

            try {
                $object = get-adobject $memberobject -properties *;

                # If the member of the group is another group
                if ($object.objectclass -eq "group")  {

                    # Print it to the screen

                    write-host ($childgroup.name),">>", ($object.samaccountname) 
                   #$cgname = $childgroup.name
                    #$objname =$object.samaccountname
                    #Return (($cgname, ">>", $objname)) >> 
c:\Temp\NestedGroups.txt

                    # Recursive lookup the members of the sub-group (if 
not self-nested)
                    if ($memberobject -ne $object.distinguishedname) {

                        getchildgroups($object.distinguishedname);
                    }
                }
            } catch {}
        }
    }
}

# Run the function with your group name
$Groups = Get-Content C:\temp\ListOfFolders.txt
Foreach ($Group in $Groups){
getchildgroups("$group") 
}

Solution

  • Caveat:

    • Write-Host is meant for to-display output, not for outputting data - it bypasses PowerShell's success output stream (PowerShell's stdout equivalent), so that output from Write-Host cannot (directly[1]) be captured in a variable, nor redirected to file - see the bottom half of this answer for more information.

    • Use Write-Output or - preferably - PowerShell's implicit output behavior to output data, suitable for further programmatic processing.

    In addition to this fundamental difference, Write-Host and Write-Output also differ in how they handle arguments:

    # What Write-Host prints to the display is a *single string* that is 
    # the space-separated list of the (stringification of) its arguments.
    PS> Write-Host file1, '>>', file2
    file1 >> file2  # printed to *display* only
    
    # Write-Output outputs each argument - whatever its data type - *separately*
    # to the success output stream.
    # In the case of *string* arguments, each string renders *on its own line*.
    PS> Write-Output file1, '>>', file2
    file1
    >>
    file2
    

    Using implicit output, the equivalent of the above Write-Output command is:

    # Send an array of 3 strings to the success stream.
    PS> 'file1', '>>', 'file2'
    file1
    >>
    file2
    

    If you redirect the Write-Output command or its implicit equivalent to a file (with > / Out-File or Set-Content[2]), you'll get the same 3-line representation.

    Additionally, Write-Host performs simple .ToString() stringification on complex objects, which often results in unhelpful output; by contrast, Write-Output / implicit output uses PowerShell's rich formatting system:

    # Write-Host: Unhelpful representation; entries are enumerated
    #             and .ToString() is called on each.
    PS> Write-Host @{ foo = 1; bar = 2 }
    System.Collections.DictionaryEntry System.Collections.DictionaryEntry
    
    # Write-Output / implicit output: rich formatting
    PS> @{ foo = 1 }
    
    Name                           Value
    ----                           -----
    foo                            1
    bar                            2
    

    Note: If you use the Out-Host cmdlet and pipe a command to it (e.g.
    @{ foo = 1; bar = 2 } | Out-Host) you do get the usual, rich output formatting that you get via Write-Output / by default - while still printing to the display only.


    If you do want to output a single line as data, use a quoted string; to reference variables and embed subexpressions in a string, use an expandable string (string interpolation), "..." (see about_Quoting_Rules), or use string concatenation (+)

    $arg1 = 'file1'; $arg2 = 'file2'
    
    # Expandable string
    PS> "$arg1 >> $arg2"
    file1 >> file2
    
    # String concatenation
    PS> $arg1 + ' >> ' + $arg2
    file1 >> file2
    

    [1] In PowerShell v5 or higher, you can capture/redirect Write-Host output, via the information output stream, number 6; e.g.: $writeHostOutput = & { Write-Host hi } 6>&1. However, note that the information output stream is primarily designed to work with the PSv5+ Write-Information cmdlet and the common -InformationAction parameter.

    [2] With strings only, > / Out-File behave the same as Set-Content, except that in Windows PowerShell a different default character applies (not in PowerShell Core). For more information about the differences, see this answer.