# encoding: ascii
# api: powershell
# title: 
# description: Replacement of Write-Host function to work around an issue where use of Write-Host can cause an eventual problem with launching EXEs from within the same Powershell session. See https://connect.microsoft.com/PowerShell/feedback/details/496326/stability-problem-any-application-run-fails-with-lastexitcode-1073741502 for more info.
# version: 0.1
# type: function
# license: CC0
# function: Write-Host
# x-poshcode-id: 3314
# x-archived: 2016-05-19T03:23:07
#
#
function Write-Host
{
    <#
    .SYNOPSIS
        Replacement of Write-Host function to work around an issue where use of Write-Host can
        cause an eventual problem with launching EXEs from within the same Powershell session.

    .DESCRIPTION
        This Write-Host replacement can act as a temporary workaround to an issue with some 
        PowerShell versions currently released. The high-level description of the problem 
        is that excessive use of Write-Host eventually leads to failures to execute EXEs
        from the same PowerShell session. 

        For platforms where the problem exists, the following has been seen to reproduce the issue:
        for( $i = 0; $i -lt 1000; $i += 1 ) { Write-Host line }; whoami.exe; "0x{0:X}" -f $LASTEXITCODE

        On repro, the $LASTEXITCODE will be 0xC0000142. 

        This issue is described by the following Connect article:
        https://connect.microsoft.com/PowerShell/feedback/details/496326/stability-problem-any-application-run-fails-with-lastexitcode-1073741502

        Some folks workaround the issue by simply using pipeline output to display status messages. 
        Use the pipeline in this manner may be ideal for situations where it is not used for other
        purposes.
        
        If you have scripts that use the pipeline for other purposes, and rely on Write-Host 
        for non-pipeline status output, where your scripts hit this issue, perhaps this workaround
        may be of use. 

    .PARAMETER Object
        Object to be outputed to stdout.

    .PARAMETER NoNewLine
        Specify this flag if you don't what the output to end with NewLine character

    .PARAMETER ForegroundColor
        Specifies the text color. There is no default.
        Use Get-Colors command to output all available colors in color.

        Possible color names taken from '[ConsoleColor] | gm -Static' are:
            Blue    | DarkBlue
            Cyan    | DarkCyan
            Gray    | DarkGray
            Green   | DarkGreen
            Magenta | DarkMagenta
            Red     | DarkRed
            Yellow  | DarkYellow
            White   | Black

    .PARAMETER BackgroundColor
        Specifies the background color. There is no default.
        Use Get-Colors command to output all available colors in color.

        Possible color names taken from '[ConsoleColor] | gm -Static' are:
            Blue    | DarkBlue
            Cyan    | DarkCyan
            Gray    | DarkGray
            Green   | DarkGreen
            Magenta | DarkMagenta
            Red     | DarkRed
            Yellow  | DarkYellow
            White   | Black

    .EXAMPLE
        Write-Console "=)", "test" -ForegroundColor Green -NoNewLine

        Prints all items in passed string array to stdout with green color used in console.

    .LINK
        Write-Colorized
        Write-Host
        Get-Colors
    #>

    param
    (
        [Parameter(Mandatory = $true, Position = 0, ValueFromRemainingArguments = $true, ValueFromPipeline = $true)]
        [object] $Object,
        [object] $Separator = " ",
        [ConsoleColor] $ForegroundColor,
        [ConsoleColor] $BackgroundColor,
        [switch] $NoNewLine
    )

    begin
    {
        function printObject($o)
        {
            function WriteObject([string]$s)
            {
                if ($s.Length -gt 0)
                {
                    if ($consoleHost) {
                        [Console]::Write($s);
                    } else {
                        $host.UI.Write($ForegroundColor, $BackgroundColor, $s)
                    }
                }
            }

            # If null, exit.
            if ($o -eq $null) {
                return;
            }

            # If type is directly a string, write it directly.
            if ($o -is [string]) {
                WriteObject $o
            } else {

                # Object is not a string. See if it is enumerable.
                if ($o -is [System.Collections.IEnumerable]) {
                    # Access the enumerator and print each item with any defined separation.
                    $objectEnumerator = $o.GetEnumerator();
                    $printSeparator = $false;
                    foreach ($element in $objectEnumerator) {

                        # If separator defined, print it if this is not first item.
                        if ($Separator -ne $null -and $printSeparator) {
                            WriteObject $Separator
                        } 

                        # Recursive.
                        printObject $element

                        # Print separator after first item. 
                        $printSeparator = $true;
                    }
                } else {
                
                    # Object was not a string, and it did not have a
                    # GetEnumerator() method, so simply use the object's
                    # ToString() method to access the printable content.
                    WriteObject $o.ToString()
                }
            }
        }

        $consoleHost = $host.Name -eq "ConsoleHost"
        if (!$consoleHost)
        {
            if (!$ForegroundColor)
            {
                $ForegroundColor = $host.UI.RawUI.ForegroundColor
            }
            if (!$BackgroundColor)
            {
                $BackgroundColor = $host.ui.RawUI.BackgroundColor
            }
        }
    }

    process
    {
        if ($consoleHost)
        {
            if( $ForegroundColor )
            {
                $previousForegroundColor = [Console]::ForegroundColor
                [Console]::ForegroundColor = $ForegroundColor
            }

            if( $BackgroundColor )
            {
                $previousBackgroundColor = [Console]::BackgroundColor
                [Console]::BackgroundColor = $BackgroundColor
            }
        }

        try
        {
            printObject $Object $consoleHost

            if( $NoNewLine -eq $false)
            {
                [Console]::WriteLine('') # Parameterless WriteLine seems to not print a newline from ISE whereas empty string works on both cmd line/ISE.
            }

        }
        finally
        {
            if ($consoleHost)
            {
                if( $ForegroundColor )
                {
                    [Console]::ForegroundColor = $previousForegroundColor
                }

                if( $BackgroundColor )
                {
                    [Console]::BackgroundColor = $previousBackgroundColor
                }
            }
        }
    }
}

# Simple test...
function Test-WriteConsole {

    Write-Host  'new console write';
    Write-Host  @(1,2,3,4) -Separator '-sep-'
    Write-Host  @(1,2,3,4) -Separator '-sep-' -ForegroundColor Yellow
    Write-Host  @(1,2,3,4) -Separator '-sep-' -ForegroundColor Black -BackgroundColor White
    Write-Host  ([int32]123) -Separator '-sep-' -ForegroundColor Cyan -BackgroundColor DarkGreen
    Write-Host  '123' -Separator '-sep-' -ForegroundColor Green -BackgroundColor Black
    Write-Host  @([int32]123,[int32]123,[int32]123,[int32]123,[int32]123) -Separator '-sep-' -ForegroundColor Cyan -BackgroundColor DarkGreen
    Write-Host 'This is one write. ' -NoNewLine;
    Write-Host 'This is another write on the same line. ' -NoNewLine -ForegroundColor Black -BackgroundColor White
    Write-Host @('abc', [uint32]123, 'def', ' array on same line. ') -Separator ',' -NoNewLine -ForegroundColor Cyan -BackgroundColor DarkMagenta
    Write-Host 'This is the last sentence on this line.'
    Write-Host 'This should be on a new line.';
    1..5 | Write-Host
    Write-Host 1 2 3 4 5
} 

Test-WriteConsole
