26

I have a PowerShell script that I am debugging and would like to redirect all Write-Host statements to a file. Is there an easy way to do that?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ChiliYago
  • 11,341
  • 23
  • 79
  • 126
  • 1
    I just found Start-Transcript which outputs everything from your window to a file but does not seem to work yet when I add it to ps1 file. – ChiliYago Apr 07 '11 at 23:34

13 Answers13

29

Until PowerShell 4.0, Write-Host sends the objects to the host. It does not return any objects.

Beginning with PowerShell 5.0 and newer, Write-Host is a wrapper for Write-Information, which allows to output to the information stream and redirect it with 6>> file_name.

http://technet.microsoft.com/en-us/library/hh849877.aspx

However, if you have a lot of Write-Host statements, replace them all with Write-Log, which lets you decide whether output to console, file or event log, or all three.

Check also:

Community
  • 1
  • 1
Emiliano Poggi
  • 24,390
  • 8
  • 55
  • 67
  • 2
    I don't think this is an answer. In Powershell 3.0, if I try `write-host "test" > c:\temp\out.txt` I don't get anything in the file. the output is written to the console – ekkis Oct 02 '13 at 02:00
  • 1
    @ekkis redirection operator is unuseful when applied to cmdlets without output, obviously – Emiliano Poggi Dec 16 '13 at 13:20
  • 2
    yes but the answer states that if I don't want to replace the write-host statements that I should "try to append redirection" - which is what I tried in my answer, which fails – ekkis Dec 17 '13 at 18:41
  • 1
    I see, I wrote "try" actually. Thank you for your feedback, I corrected the answer with better indications hopefully. – Emiliano Poggi Dec 18 '13 at 13:59
  • 1
    [Write-Log in Internet Archive](https://web.archive.org/web/20170123204619/https://poshcode.org/2575) – eel ghEEz Aug 12 '22 at 17:05
16

You can create a proxy function for Write-Host which sends objects to the standard output stream instead of merely printing them. I wrote the below cmdlet for just this purpose. It will create a proxy on the fly which lasts only for the duration of the current pipeline.

A full writeup is on my blog here, but I've included the code below. Use the -Quiet switch to suppress the console write.

Usage:

PS> .\SomeScriptWithWriteHost.ps1 | Select-WriteHost | out-file .\data.log  # Pipeline usage
PS> Select-WriteHost { .\SomeScriptWithWriteHost.ps1 } | out-file .\data.log  # Scriptblock usage (safer)

function Select-WriteHost
{
   [CmdletBinding(DefaultParameterSetName = 'FromPipeline')]
   param(
     [Parameter(ValueFromPipeline = $true, ParameterSetName = 'FromPipeline')]
     [object] $InputObject,

     [Parameter(Mandatory = $true, ParameterSetName = 'FromScriptblock', Position = 0)]
     [ScriptBlock] $ScriptBlock,

     [switch] $Quiet
   )

   begin
   {
     function Cleanup
     {
       # Clear out our proxy version of write-host
       remove-item function:\write-host -ea 0
     }

     function ReplaceWriteHost([switch] $Quiet, [string] $Scope)
     {
         # Create a proxy for write-host
         $metaData = New-Object System.Management.Automation.CommandMetaData (Get-Command 'Microsoft.PowerShell.Utility\Write-Host')
         $proxy = [System.Management.Automation.ProxyCommand]::create($metaData)

         # Change its behavior
         $content = if($quiet)
                    {
                       # In quiet mode, whack the entire function body,
                       # simply pass input directly to the pipeline
                       $proxy -replace '(?s)\bbegin\b.+', '$Object'
                    }
                    else
                    {
                       # In noisy mode, pass input to the pipeline, but allow
                       # real Write-Host to process as well
                       $proxy -replace '(\$steppablePipeline\.Process)', '$Object; $1'
                    }

         # Load our version into the specified scope
         Invoke-Expression "function ${scope}:Write-Host { $content }"
     }

     Cleanup

     # If we are running at the end of a pipeline, we need
     #    to immediately inject our version into global
     #    scope, so that everybody else in the pipeline
     #    uses it. This works great, but it is dangerous
     #    if we don't clean up properly.
     if($pscmdlet.ParameterSetName -eq 'FromPipeline')
     {
        ReplaceWriteHost -Quiet:$quiet -Scope 'global'
     }
   }

   process
   {
      # If a scriptblock was passed to us, then we can declare
      #   our version as local scope and let the runtime take
      #   it out of scope for us. It is much safer, but it
      #   won't work in the pipeline scenario.
      #
      #   The scriptblock will inherit our version automatically
      #   as it's in a child scope.
      if($pscmdlet.ParameterSetName -eq 'FromScriptBlock')
      {
        . ReplaceWriteHost -Quiet:$quiet -Scope 'local'
        & $scriptblock
      }
      else
      {
         # In a pipeline scenario, just pass input along
         $InputObject
      }
   }

   end
   {
      Cleanup
   }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
latkin
  • 16,402
  • 1
  • 47
  • 62
11

You can run your script in a secondary PowerShell shell and capture the output like this:

powershell -File 'Your-Script.ps1' > output.log

That worked for me.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
alienisty
  • 129
  • 1
  • 3
  • 1
    Works great! Best solution I've found that doesn't require any changes to code or proxy functions. KISS – John Homer Oct 04 '17 at 19:19
  • 1
    This worked for `Write-Output` but did not work for `Write-Host`. [Emiliano Poggi](https://stackoverflow.com/a/5752877/)'s answer seems more complete. – Trisped Feb 09 '23 at 22:18
8

Using redirection will cause Write-Host to hang. This is because Write-Host deals with various formatting issues that are specific to the current terminal being used. If you just want your script to have flexibility to output as normal (default to shell, with capability for >, 2>, etc.), use Write-Output.

Otherwise, if you really want to capture the peculiarities of the current terminal, Start-Transcript is a good place to start. Otherwise you'll have to hand-test or write some complicated test suites.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
labyrinth
  • 13,397
  • 7
  • 35
  • 44
  • 1
    I would nominate this answer as the correct answer since it basically states you cannot redirect write-host and instead suggests what to use in its place – ekkis Oct 02 '13 at 02:02
  • I think this should be the accepted answer. I'm not sure why it isn't?? ;) – AJ. Sep 16 '21 at 15:07
4

Try adding a asterisk * before the angle bracket > to redirect all streams:

powershell -File Your-Script.ps1 *> output.log

When stream redirection is requested, if no specific stream is indicated then by default only the Success Stream(1>) is redirected. Write-Host is an alias for Write-Information which writes to the Information Stream (6>). To redirect all streams use *>.

Powershell-7.1 supports redirection of multiple output streams:

  • Success Stream (#1): PowerShell 2.0 Write-Output
  • Error Stream (#2): PowerShell 2.0 Write-Error
  • Warning Stream (#3): PowerShell 3.0 Write-Warning
  • Verbose Stream (#4): PowerShell 3.0 Write-Verbose
  • Debug Stream (#5): PowerShell 3.0 Write-Debug
  • Information Stream (#6): PowerShell 5.0 Write-Information
  • All Streams (*): PowerShell 3.0
3

This worked for me in my first PowerShell script that I wrote few days back:

function logMsg($msg)
{
    Write-Output $msg
    Write-Host   $msg
}

Usage in a script:

logMsg("My error message")
logMsg("My info message")

PowerShell script execution call:

ps> .\myFirstScript.ps1 >> testOutputFile.txt

It's not exactly answer to this question, but it might help someone trying to achieve both logging to the console and output to some log file, doing what I reached here :)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
TechnicalSmile
  • 1,387
  • 5
  • 16
  • 30
  • 5
    Be careful with this approach. Anything you write with write-output becomes part of the return value of the function, which can be surprising, especially if there is an explicit return statement later in the function. – skiggety Apr 22 '16 at 15:44
2

If you have just a few Write-Host statements, you can use the "6>>" redirector operator to a file:

Write-Host "Your message." 6>> file_path_or_file_name

This is the "Example 5: Suppress output from Write-Host" provided by Microsoft, modified accordingly to about_Operators.

2

Define a function called Write-Host. Have it write to a file. You may have some trouble if some invocations use a weird set of arguments. Also, this will only work for invocations that are not Snapin qualified.

1

I just added Start-Transcript at the top of the script and Stop-Transcript at the bottom.

The output file was intended to be named <folder where script resides>-<datestamp>.rtf, but for some reason the trace file was being put where I did not expect it — the desktop!

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ChiliYago
  • 11,341
  • 23
  • 79
  • 126
0

I have found the best way to handle this is to have a logging function that will detect if there is a host UI and act accordingly. When the script is executed in interactive mode it will show the details in the host UI, but when it is run via WinRM or in a non-interactive mode it will fall back on the Write-Output so that you can capture it using the > or *> redirection operators

function Log-Info ($msg, $color = "Blue") {
    if($host.UI.RawUI.ForegroundColor -ne $null) {
        Write-Host "`n[$([datetime]::Now.ToLongTimeString())] $msg" -ForegroundColor $color -BackgroundColor "Gray"
    } else {
        Write-Output "`r`n[$([datetime]::Now.ToLongTimeString())] $msg"
    }
}

In cases where you want to capture the full output with the Write-Host coloring, you can use the Get-ConsoleAsHtml.ps1 script to export the host's scrolling buffer to an HTML or RTF file.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Greg Bray
  • 14,929
  • 12
  • 80
  • 104
  • 2
    Write-Output can be dangerous if someone uses the function Log-Info inside a pipeline or a function. You'll have then suddenly the console output in the pipeline or as function return value. – Mehrdad Mirreza Nov 08 '16 at 16:40
0

Use Write-Output instead of Write-Host, and redirect it to a file like this:

Deploy.ps1 > mylog.log or Write-Output "Hello World!" > mylog.log
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

You should not use Write-Host if you wish to have the messages in a file. It is for writing to the host only.

Instead you should use a logging module, or Set/Add-Content.

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
-1

Try using Write-Output instead of Write-Host.

The output goes down the pipeline, but if this is the end of the pipe, it goes to the console.

> Write-Output "test"
test
> Write-Output "test" > foo.txt
> Get-Content foo.txt
test
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Walter Mitty
  • 18,205
  • 2
  • 28
  • 58