31

I have a powershell script that gives some status output via write-output. I am intentionally not using write-host because the output may be captured and written to a logfile like this:

./myscript.ps1 | out-file log.txt

But if the output is not redirected it would be nice to have colored output on the console, because the script is producing a lot of different status messages. I know that colored output is possible with write-host but the status messages should be pipeable.

Any ideas how to solve this?

binford
  • 1,655
  • 1
  • 18
  • 36
  • MS suggests using `write-host` http://technet.microsoft.com/en-us/library/ff406264.aspx, but I understand your worries – Dennis G Jan 10 '11 at 14:47
  • 2
    This sucks because `Write-Host` has a `-ForegroundColor` argument, but `Write-Output` does not. – Kellen Stuart Sep 01 '18 at 20:22
  • 1
    @DennisG The issue is when you are display a `Psobject` using `Write-Host`, it will look like this `@{test=test; test1=test1; test2=test2}` which is horrible to look at – Kellen Stuart Sep 01 '18 at 20:23

6 Answers6

22

I have tried this extra function and it basically works fine:

function Write-ColorOutput($ForegroundColor)
{
    # save the current color
    $fc = $host.UI.RawUI.ForegroundColor

    # set the new color
    $host.UI.RawUI.ForegroundColor = $ForegroundColor

    # output
    if ($args) {
        Write-Output $args
    }
    else {
        $input | Write-Output
    }

    # restore the original color
    $host.UI.RawUI.ForegroundColor = $fc
}

# test
Write-ColorOutput red (ls)
Write-ColorOutput green (ls)
ls | Write-ColorOutput yellow

The result of this particular test is a little bit funny though: we really get lines in red, green and yellow but the table header is in red, i.e. the color of the the first call of the function.

Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117
  • 4
    That won't work so good in ISE or other non-console-based hosts. :-) You might try `$host.UI.RawUI.ForegroundColor` instead. – Keith Hill Jan 10 '11 at 16:58
  • 1
    @Keith, you are definitely right (even though the author asks about console). I have updated the code. – Roman Kuzmin Jan 10 '11 at 17:10
  • 3
    [Console]::ForegroundColor is actually better, because $host.UI.RawUI.ForegroundColor does **not** do what you are expecting in powershell_ise. – VoidStar Nov 10 '14 at 04:03
20

This way:

function Green
{
    process { Write-Host $_ -ForegroundColor Green }
}

function Red
{
    process { Write-Host $_ -ForegroundColor Red }
}

Write-Output "this is a test" | Green
Write-Output "this is a test" | Red

enter image description here

Francesco Mantovani
  • 10,216
  • 13
  • 73
  • 113
  • 7
    This simply redirects the output to Write-Host, so the effect is the same as if you used Write-Host in the first place, and crucially doesn't resolve the issue of the output being redirected to a file. Using this method, if you redirect the output of your script to a file, any text returned using this method is displayed on screen, not outputted to the file. – Keith Langmead Jan 09 '20 at 09:43
8

Separate the results on the pipeline from the status messages in the console.

E.g., use a function like this in your script:

function write-status( $status ){
   $status | write-host -fore green -back red;  #send a status msg to the console
   $status | write-output; #send a status object down the pipe
}

I would also recommend you use one of the following cmdlets over write-host for outputting status messages from your scripts:

  • write-debug
  • write-error
  • write-verbose
  • write-warning

The appearance of these status messages will vary depending on the cmdlet used. In addition, the user can disable specific levels of status using the $(warning|error|verbose|debug)preference variables, or capture specific status messages using the -(warning|error|verbose|debug)variable common cmdlet parameters.

beefarino
  • 1,121
  • 7
  • 8
  • 4
    Not very nice because all message are shown twice when the output is not piped to a file. – binford Jan 13 '11 at 13:03
  • 1
    I agree, any function that uses this will be unsuitable for interactive use. The only decent way to capture anything you've write-hosted is Start-Transcript. – VoidStar Nov 12 '14 at 09:36
6

I had the same problem, so I share my solution which I think works quite well:

Write-ColorOutput "Hello" Green Black -NoNewLine
Write-ColorOutput " World" Red

This is the Cmdlet to use it

function Write-ColorOutput
{
    [CmdletBinding()]
    Param(
         [Parameter(Mandatory=$False,Position=1,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)][Object] $Object,
         [Parameter(Mandatory=$False,Position=2,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)][ConsoleColor] $ForegroundColor,
         [Parameter(Mandatory=$False,Position=3,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)][ConsoleColor] $BackgroundColor,
         [Switch]$NoNewline
    )    

    # Save previous colors
    $previousForegroundColor = $host.UI.RawUI.ForegroundColor
    $previousBackgroundColor = $host.UI.RawUI.BackgroundColor

    # Set BackgroundColor if available
    if($BackgroundColor -ne $null)
    { 
       $host.UI.RawUI.BackgroundColor = $BackgroundColor
    }

    # Set $ForegroundColor if available
    if($ForegroundColor -ne $null)
    {
        $host.UI.RawUI.ForegroundColor = $ForegroundColor
    }

    # Always write (if we want just a NewLine)
    if($Object -eq $null)
    {
        $Object = ""
    }

    if($NoNewline)
    {
        [Console]::Write($Object)
    }
    else
    {
        Write-Output $Object
    }

    # Restore previous colors
    $host.UI.RawUI.ForegroundColor = $previousForegroundColor
    $host.UI.RawUI.BackgroundColor = $previousBackgroundColor
}
Kelon
  • 821
  • 10
  • 10
1

I know this post is ancient, but this could come handy to someone out there.

I wanted to change colors and the accepted answer was not the best solution. In my eyes, the following code is better solution as it takes advantage of the native PowerShell functionality:

EDIT:

# Print User message using String Array $message
function PrintMessageToUser {
    param(
        [Parameter( `
            Mandatory=$True, `
            Valuefrompipeline = $true)]
        [String]$message
    )
    begin {
        $window_private_data = (Get-Host).PrivateData;
        # saving the original colors
        $saved_background_color = $window_private_data.VerboseBackgroundColor
        $saved_foreground_color = $window_private_data.VerboseForegroundColor
        # setting the new colors
        $window_private_data.VerboseBackgroundColor = 'Black';
        $window_private_data.VerboseForegroundColor = 'Red';
    }
    process {
        foreach ($Message in $Message) {
            # Write-Host Considered Harmful - see http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/
            # first way how to correctly write it
            #Write-host $message;
            Write-Verbose -Message $message -Verbose;
            # second correct way how to write it
            #$VerbosePreference = "Continue"
            #Write-Verbose $Message;
        }
    }
    end {
      $window_private_data.VerboseBackgroundColor = $saved_background_color;
      $window_private_data.VerboseForegroundColor = $saved_foreground_color;
    }

} # end PrintMessageToUser
tukan
  • 17,050
  • 1
  • 20
  • 48
1

I have the same problem, I need to log the output by screen with colors in interactive way and send that output to a file in autometed way.

My solution is to use a parameter to indicate the output type ('Screen' or 'File'), then the function can decide how to render de output.

function Write-Color([String[]]$Text, [ConsoleColor[]]$Color, [ConsoleColor]$BackgroundColor = ([console]::BackgroundColor), $OutputType='Screen') {
    switch ($OutputType) {
        'Screen' { 
            for ($i = 0; $i -lt $Text.Length; $i++) {
                Write-Host $Text[$i] -Foreground $Color[$i] -NoNewLine -BackgroundColor $BackgroundColor
            }
            Write-Host
            break
        }
        'File' {
            # Assuming $OFS built-in Variable is an space
            write-output "$Text"
            break
        }
        Default {
            throw '$OutputType must be "Screen" or "File".'
        }
    }
}

$CodeBlock = {
    param ($OutputType)
    Write-Color -T "=== STARTING ===" -C Cyan -B Gray -O $OutputType
    Write-Color -T 'Date: ', (Get-Date).ToString('yyyy-MM-dd hh:mm:ss') -C Green, Yellow -O $OutputType
    Write-Color -T 'Processing..' -C Cyan -O $OutputType
    Write-Color -T 'Date: ', (Get-Date).AddSeconds(3).ToString('yyyy-MM-dd hh:mm:ss') -C Green, Yellow -O $OutputType
    Write-Color -T "=== ENDING ===" -C Cyan -B Gray -O $OutputType
}

$Dir = 'D:\Tmp'  # Set your output directory

#### Screen Test ####

& $CodeBlock -OutputType 'Screen'
& $CodeBlock -OutputType 'File'

### File Test ####

# This file have unwanted newlines, notice the IO redirection with "*>"
& $CodeBlock -OutputType 'Screen' *> "$Dir\Screen.log"

& $CodeBlock -OutputType 'File' > "$Dir\File.log"
FcoJavier99
  • 321
  • 2
  • 8