1

This PowerShell function does exactly what I want when writing to the console using Write-Host, which I know does some default formatting. However, I cannot find any way to get the exact same output saved to a text file.

Here is my function:

function Get-FileHashInfo
{

Param($mydir)

    foreach ($item in Get-ChildItem -recurse -file $mydir -force) 
    {
        $mypath = [string]$item.directory + "\" + [string]$item.name
        $myhash = Get-FileHash -Path $mypath
        Write-Host $myhash.hash $item.name $item.directory
    }
}

Here is my console output:

F887266A20CAA2E44E63F1B4E26218FFD4D3541C7A7100ED9FBF3A45F03066A7 a.txt C:\data\tmp
6D21DED17244347CE2C02A96D5F81CED1CAB82343E26D7E3C57A00F0971B58C2 b.txt C:\data\tmp
2732A420088BBACD56DEB0E19D4EF5A28FA20F11B403ED3134EFDC0CC1EA0EC6 c.txt C:\data\tmp\subdir

I just need these lines to be written to a text file exactly as displayed on the console. However, no matter what I try, I end up with a lot of additional data in the output file, a messed up format, or nothing in the text file at all.

Krispy
  • 11
  • 1
  • In short: [`Write-Host` is typically the wrong tool to use](http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/), unless the intent is to write _to the display only_, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, redirect it to a file. To output a value, use it _by itself_; e.g., `$value` instead of `Write-Host $value` (or use `Write-Output $value`, though that is rarely needed). See also: the bottom section of https://stackoverflow.com/a/50416448/45375 – mklement0 Mar 22 '20 at 05:38

1 Answers1

1

As @mklement0 pointed out in the comments, Write-Host is not the right tool for this, as its only used for displaying purposes.

Here is a simple alternative that doesn't change too much of your original code:

function Get-FileHashInfo
{
    Param($mydir)

    & {
        foreach ($item in Get-ChildItem -recurse -file $mydir -force) 
        {
            $mypath = [string]$item.directory + "\" + [string]$item.name
            $myhash = Get-FileHash -Path $mypath
            "$($myhash.hash) $($item.name) $($item.directory)"
        }
    } | Out-File -FilePath ".\output.txt"
}

Which wraps your code in a script block and redirects the output to Out-File. This Cmdlet is the PowerShell way of doing the standard output redirection > operator. It also saves for-display representations of its input objects, given that it uses the same formatting as in the console. You could also have a look at Out-File: Using PowerShell to Redirect Output to File for a nice explanation on what Out-File does as well.

You also don't need to use Write-Output here, since supplying the string by itself is already good enough for passing it down the pipeline to Out-File. You could test this out by using Write-Output "$($myhash.hash) $($item.name) $($item.directory)" in the above snippet, and notice that it does the same thing.

Additionally, to call the script block you need to use the Call operator &, which goes into more detail in About Operators. If you don't use this, you will end up writing the foreach loop code block to the file.


Update

As @mklement0 helpfully suggested in the comments, Removing Out-File from the function and instead passing it down the pipeline to Get-FileHashInfo is a better idea. I wrote the earlier example just for quick demo purposes, but the snippet below is a better way of doing it. It also doesn't need to use a script block anymore, but demonstrating how to use it in conjunction with pipelines is good for learning purposes anyways :).

function Get-FileHashInfo
{
    Param($mydir)

    foreach ($item in Get-ChildItem -recurse -file $mydir -force) 
    {
        $mypath = [string]$item.directory + "\" + [string]$item.name
        $myhash = Get-FileHash -Path $mypath
        "$($myhash.hash) $($item.name) $($item.directory)"
    }    
}

Get-FileHashInfo -mydir "C:\test" | Out-File -FilePath ".\output.txt"
RoadRunner
  • 25,803
  • 6
  • 42
  • 75