// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"


// Exit macros
#define ConExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
#define ConExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
#define ConExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
#define ConExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
#define ConExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
#define ConExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__)
#define ConExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_CONUTIL, p, x, e, s, __VA_ARGS__)
#define ConExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_CONUTIL, p, x, s, __VA_ARGS__)
#define ConExitOnNullDebugTrace(p, x, e, s, ...)  ExitOnNullDebugTraceSource(DUTIL_SOURCE_CONUTIL, p, x, e, s, __VA_ARGS__)
#define ConExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_CONUTIL, p, x, s, __VA_ARGS__)
#define ConExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_CONUTIL, e, x, s, __VA_ARGS__)
#define ConExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_CONUTIL, g, x, s, __VA_ARGS__)


LPCSTR NEWLINE = "\r\n";

static HANDLE vhStdIn = INVALID_HANDLE_VALUE;
static HANDLE vhStdOut = INVALID_HANDLE_VALUE;
static BOOL vfStdInInteractive = FALSE;
static BOOL vfStdOutInteractive = FALSE;
static BOOL vfConsoleOut = FALSE;
static CONSOLE_SCREEN_BUFFER_INFO vcsbiInfo;


static HRESULT DAPI ReadInteractiveStdIn(
    __deref_out_z LPWSTR* ppwzBuffer
    );
static HRESULT ReadRedirectedStdIn(
    __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer,
    __out DWORD* pcchSize,
    BOOL fReadLine,
    DWORD dwMaxRead
    );


extern "C" HRESULT DAPI ConsoleInitialize()
{
    Assert(INVALID_HANDLE_VALUE == vhStdOut);
    HRESULT hr = S_OK;
    DWORD dwStdInMode = 0;
    DWORD dwStdOutMode = 0;

    vhStdIn = ::GetStdHandle(STD_INPUT_HANDLE);
    if (INVALID_HANDLE_VALUE == vhStdIn)
    {
        ConExitOnLastError(hr, "failed to open stdin");
    }

    vfStdInInteractive = ::GetFileType(vhStdIn) == FILE_TYPE_CHAR && ::GetConsoleMode(vhStdIn, &dwStdInMode);

    vhStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
    if (INVALID_HANDLE_VALUE == vhStdOut)
    {
        ConExitOnLastError(hr, "failed to open stdout");
    }

    vfStdOutInteractive = ::GetFileType(vhStdOut) == FILE_TYPE_CHAR && ::GetConsoleMode(vhStdOut, &dwStdOutMode);

    if (::GetConsoleScreenBufferInfo(vhStdOut, &vcsbiInfo))
    {
        vfConsoleOut = TRUE;
    }

    if (!::SetConsoleCP(CP_UTF8) || !::SetConsoleOutputCP(CP_UTF8))
    {
        ConExitWithLastError(hr, "failed to set console codepage to UTF-8");
    }

LExit:
    if (FAILED(hr))
    {
        if (INVALID_HANDLE_VALUE != vhStdOut)
        {
            ::CloseHandle(vhStdOut);
        }

        if (INVALID_HANDLE_VALUE != vhStdIn && vhStdOut != vhStdIn)
        {
            ::CloseHandle(vhStdIn);
        }

        vhStdOut = INVALID_HANDLE_VALUE;
        vhStdIn = INVALID_HANDLE_VALUE;
    }

    return hr;
}


extern "C" void DAPI ConsoleUninitialize()
{
    BOOL fOutEqualsIn = vhStdOut == vhStdIn;

    memset(&vcsbiInfo, 0, sizeof(vcsbiInfo));

    if (INVALID_HANDLE_VALUE != vhStdOut)
    {
        ::CloseHandle(vhStdOut);
    }

    if (INVALID_HANDLE_VALUE != vhStdIn && !fOutEqualsIn)
    {
        ::CloseHandle(vhStdIn);
    }

    vhStdOut = INVALID_HANDLE_VALUE;
    vhStdIn = INVALID_HANDLE_VALUE;
}


extern "C" void DAPI ConsoleGreen()
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
    if (vfConsoleOut)
    {
        ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
    }
}


extern "C" void DAPI ConsoleRed()
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
    if (vfConsoleOut)
    {
        ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_RED | FOREGROUND_INTENSITY);
    }
}


extern "C" void DAPI ConsoleYellow()
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
    if (vfConsoleOut)
    {
        ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
    }
}


extern "C" void DAPI ConsoleNormal()
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
    if (vfConsoleOut)
    {
        ::SetConsoleTextAttribute(vhStdOut, vcsbiInfo.wAttributes);
    }
}

extern "C" HRESULT DAPI ConsoleWrite(
    CONSOLE_COLOR cc,
    __in_z __format_string LPCSTR szFormat,
    ...
    )
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
    HRESULT hr = S_OK;
    LPSTR pszOutput = NULL;
    DWORD cchOutput = 0;
    DWORD cbWrote = 0;
    DWORD cbTotal = 0;

    // set the color
    switch (cc)
    {
    case CONSOLE_COLOR_NORMAL: break;   // do nothing
    case CONSOLE_COLOR_RED: ConsoleRed(); break;
    case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break;
    case CONSOLE_COLOR_GREEN: ConsoleGreen(); break;
    }

    va_list args;
    va_start(args, szFormat);
    hr = StrAnsiAllocFormattedArgs(&pszOutput, szFormat, args);
    va_end(args);
    ConExitOnFailure(hr, "failed to format message: \"%s\"", szFormat);

    cchOutput = lstrlenA(pszOutput);
    while (cbTotal < (sizeof(*pszOutput) * cchOutput))
    {
        if (!::WriteFile(vhStdOut, reinterpret_cast<BYTE*>(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL))
        {
            ConExitOnLastError(hr, "failed to write output to console with format: %s", szFormat);
        }

        cbTotal += cbWrote;
    }

    // reset the color to normal
    if (CONSOLE_COLOR_NORMAL != cc)
    {
        ConsoleNormal();
    }

LExit:
    ReleaseStr(pszOutput);
    return hr;
}


extern "C" HRESULT DAPI ConsoleWriteW(
    __in CONSOLE_COLOR cc,
    __in_z LPCWSTR wzData
    )
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
    HRESULT hr = S_OK;
    LPSTR pszOutput = NULL;
    DWORD cchOutput = 0;
    DWORD cbWrote = 0;
    DWORD cbTotal = 0;

    // set the color
    switch (cc)
    {
    case CONSOLE_COLOR_NORMAL: break;   // do nothing
    case CONSOLE_COLOR_RED: ConsoleRed(); break;
    case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break;
    case CONSOLE_COLOR_GREEN: ConsoleGreen(); break;
    }

    hr = StrAnsiAllocString(&pszOutput, wzData, 0, CP_UTF8);
    ExitOnFailure(hr, "failed to convert console output to utf-8: %ls", wzData);

    cchOutput = lstrlenA(pszOutput);
    while (cbTotal < (sizeof(*pszOutput) * cchOutput))
    {
        if (!::WriteFile(vhStdOut, reinterpret_cast<BYTE*>(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL))
        {
            ConExitOnLastError(hr, "failed to write output to console with format: %ls", wzData);
        }

        cbTotal += cbWrote;
    }

    // reset the color to normal
    if (CONSOLE_COLOR_NORMAL != cc)
    {
        ConsoleNormal();
    }

LExit:
    ReleaseStr(pszOutput);
    return hr;
}


extern "C" HRESULT DAPI ConsoleWriteLine(
    CONSOLE_COLOR cc,
    __in_z __format_string LPCSTR szFormat,
    ...
    )
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
    HRESULT hr = S_OK;
    LPSTR pszOutput = NULL;
    DWORD cchOutput = 0;
    DWORD cbWrote = 0;
    DWORD cbTotal = 0;

    // set the color
    switch (cc)
    {
    case CONSOLE_COLOR_NORMAL: break;   // do nothing
    case CONSOLE_COLOR_RED: ConsoleRed(); break;
    case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break;
    case CONSOLE_COLOR_GREEN: ConsoleGreen(); break;
    }

    va_list args;
    va_start(args, szFormat);
    hr = StrAnsiAllocFormattedArgs(&pszOutput, szFormat, args);
    va_end(args);
    ConExitOnFailure(hr, "failed to format message: \"%s\"", szFormat);

    //
    // write the string
    //
    cchOutput = lstrlenA(pszOutput);
    while (cbTotal < (sizeof(*pszOutput) * cchOutput))
    {
        if (!::WriteFile(vhStdOut, reinterpret_cast<BYTE*>(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL))
            ConExitOnLastError(hr, "failed to write output to console: %s", pszOutput);

        cbTotal += cbWrote;
    }

    //
    // write the newline
    //
    if (!::WriteFile(vhStdOut, reinterpret_cast<const BYTE*>(NEWLINE), 2, &cbWrote, NULL))
    {
        ConExitOnLastError(hr, "failed to write newline to console");
    }

    // reset the color to normal
    if (CONSOLE_COLOR_NORMAL != cc)
    {
        ConsoleNormal();
    }

LExit:
    ReleaseStr(pszOutput);
    return hr;
}


HRESULT ConsoleWriteError(
    HRESULT hrError,
    CONSOLE_COLOR cc,
    __in_z __format_string LPCSTR szFormat,
    ...
    )
{
    HRESULT hr  = S_OK;
    LPSTR pszMessage = NULL;

    va_list args;
    va_start(args, szFormat);
    hr = StrAnsiAllocFormattedArgs(&pszMessage, szFormat, args);
    va_end(args);
    ConExitOnFailure(hr, "failed to format error message: \"%s\"", szFormat);

    if (FAILED(hrError))
    {
        hr = ConsoleWriteLine(cc, "Error 0x%x: %s", hrError, pszMessage);
    }
    else
    {
        hr = ConsoleWriteLine(cc, "Error: %s", pszMessage);
    }

LExit:
    ReleaseStr(pszMessage);
    return hr;
}


extern "C" HRESULT DAPI ConsoleReadW(
    __deref_out_z LPWSTR* ppwzBuffer
    )
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
    Assert(ppwzBuffer);

    HRESULT hr = S_OK;
    DWORD cchSize = 0;

    if (vfStdInInteractive)
    {
        hr = ReadInteractiveStdIn(ppwzBuffer);
        ExitOnFailure(hr, "failed to read from interactive console");
    }
    else
    {
        hr = ReadRedirectedStdIn(ppwzBuffer, &cchSize, TRUE, 0);
        ExitOnFailure(hr, "failed to read from redirected console");
    }

LExit:
    return hr;
}


extern "C" HRESULT DAPI ConsoleReadNonBlockingW(
    __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer,
    __out DWORD* pcchSize,
    BOOL fReadLine
    )
{
    Assert(INVALID_HANDLE_VALUE != vhStdIn && pcchSize);
    HRESULT hr = S_OK;

    LPSTR psz = NULL;

    ConExitOnNull(ppwzBuffer, hr, E_INVALIDARG, "Failed to read from console because buffer was not provided");

    DWORD dwRead;
    DWORD dwNumInput;

    DWORD cchTotal = 0;
    DWORD cch = 8;

    DWORD dwIndex = 0;
    DWORD er;

    INPUT_RECORD ir;
    WCHAR chIn;

    *ppwzBuffer = NULL;
    *pcchSize = 0;

    // If we really have a handle to stdin, and not the end of a pipe
    if (!PeekNamedPipe(vhStdIn, NULL, 0, NULL, &dwRead, NULL))
    {
        er = ::GetLastError();
        if (ERROR_INVALID_HANDLE != er)
        {
            ExitFunction1(hr = HRESULT_FROM_WIN32(er));
        }

        if (!GetNumberOfConsoleInputEvents(vhStdIn, &dwRead))
        {
            ConExitOnLastError(hr, "failed to peek at console input");
        }

        if (0 == dwRead)
        {
            ExitFunction1(hr = S_FALSE);
        }

        for (/* dwRead from num of input events */; dwRead > 0; dwRead--)
        {
            if (!ReadConsoleInputW(vhStdIn, &ir, 1, &dwNumInput))
            {
                ConExitOnLastError(hr, "Failed to read input from console");
            }

            // If what we have is a KEY_EVENT, and that event signifies keyUp, we're interested
            if (KEY_EVENT == ir.EventType && FALSE == ir.Event.KeyEvent.bKeyDown)
            {
                chIn = ir.Event.KeyEvent.uChar.UnicodeChar;

                if (0 == cchTotal)
                {
                    cchTotal = cch;
                    cch *= 2;
                    StrAlloc(ppwzBuffer, cch);
                }

                (*ppwzBuffer)[dwIndex] = chIn;

                if (fReadLine && (L'\r' == (*ppwzBuffer)[dwIndex - 1] && L'\n' == (*ppwzBuffer)[dwIndex]))
                {
                    *ppwzBuffer[dwIndex - 1] = L'\0';
                    dwIndex -= 1;
                    break;
                }

                ++dwIndex;
                cchTotal--;
            }
        }

        *pcchSize = dwIndex;
    }
    else
    {
        // otherwise, the peek worked, and we have the end of a pipe
        if (0 == dwRead)
        {
            ExitFunction1(hr = S_FALSE);
        }

        hr = ReadRedirectedStdIn(ppwzBuffer, pcchSize, fReadLine, dwRead);
    }

LExit:
    ReleaseStr(psz);

    return hr;
}


extern "C" HRESULT DAPI ConsoleReadStringA(
    __deref_inout_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPSTR* ppszCharBuffer,
    CONST DWORD cchCharBuffer,
    __out DWORD* pcchNumCharReturn
    )
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
    HRESULT hr = S_OK;
    if (ppszCharBuffer && (pcchNumCharReturn || cchCharBuffer < 2))
    {
        DWORD iRead = 1;
        DWORD iReadCharTotal = 0;
        if (ppszCharBuffer && *ppszCharBuffer == NULL)
        {
            do
            {
                hr = StrAnsiAlloc(ppszCharBuffer, cchCharBuffer * iRead);
                ConExitOnFailure(hr, "failed to allocate memory for ConsoleReadStringA");

                // ReadConsoleW will not return until <Return>, the last two chars are 13 and 10.
                if (!::ReadConsoleA(vhStdIn, *ppszCharBuffer + iReadCharTotal, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn == 0)
                {
                    ConExitOnLastError(hr, "failed to read string from console");
                }

                iReadCharTotal += *pcchNumCharReturn;
                iRead += 1;
            } while((*ppszCharBuffer)[iReadCharTotal - 1] != 10 || (*ppszCharBuffer)[iReadCharTotal - 2] != 13);

            *pcchNumCharReturn = iReadCharTotal;
        }
        else
        {
            if (!::ReadConsoleA(vhStdIn, *ppszCharBuffer, cchCharBuffer, pcchNumCharReturn, NULL) ||
                *pcchNumCharReturn > cchCharBuffer || *pcchNumCharReturn == 0)
            {
                ConExitOnLastError(hr, "failed to read string from console");
            }

            if ((*ppszCharBuffer)[*pcchNumCharReturn - 1] != 10 ||
                (*ppszCharBuffer)[*pcchNumCharReturn - 2] != 13)
            {
                // need read more
                hr = ERROR_MORE_DATA;
            }
        }
    }
    else
    {
        hr = E_INVALIDARG;
    }

LExit:
    return hr;
}


extern "C" HRESULT DAPI ConsoleReadStringW(
    __deref_inout_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPWSTR* ppwzCharBuffer,
    CONST DWORD cchCharBuffer,
    __out DWORD* pcchNumCharReturn
    )
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
    HRESULT hr = S_OK;

    if (ppwzCharBuffer && (pcchNumCharReturn || cchCharBuffer < 2))
    {
        DWORD iRead = 1;
        DWORD iReadCharTotal = 0;
        if (*ppwzCharBuffer == NULL)
        {
            do
            {
                hr = StrAlloc(ppwzCharBuffer, cchCharBuffer * iRead);
                ConExitOnFailure(hr, "failed to allocate memory for ConsoleReadStringW");

                // ReadConsoleW will not return until <Return>, the last two chars are 13 and 10.
                if (!::ReadConsoleW(vhStdIn, *ppwzCharBuffer + iReadCharTotal, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn == 0)
                {
                    ConExitOnLastError(hr, "failed to read string from console");
                }

                iReadCharTotal += *pcchNumCharReturn;
                iRead += 1;
            } while((*ppwzCharBuffer)[iReadCharTotal - 1] != 10 || (*ppwzCharBuffer)[iReadCharTotal - 2] != 13);

            *pcchNumCharReturn = iReadCharTotal;
        }
        else
        {
            if (!::ReadConsoleW(vhStdIn, *ppwzCharBuffer, cchCharBuffer, pcchNumCharReturn, NULL) ||
                *pcchNumCharReturn > cchCharBuffer || *pcchNumCharReturn == 0)
            {
                ConExitOnLastError(hr, "failed to read string from console");
            }

            if ((*ppwzCharBuffer)[*pcchNumCharReturn - 1] != 10 ||
                (*ppwzCharBuffer)[*pcchNumCharReturn - 2] != 13)
            {
                // need read more
                hr = ERROR_MORE_DATA;
            }
        }
    }
    else
    {
        hr = E_INVALIDARG;
    }

LExit:
    return hr;
}


extern "C" HRESULT DAPI ConsoleSetReadHidden(void)
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
    HRESULT hr = S_OK;

    ::FlushConsoleInputBuffer(vhStdIn);
    if (!::SetConsoleMode(vhStdIn, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT))
    {
        ConExitOnLastError(hr, "failed to set console input mode to be hidden");
    }

LExit:
    return hr;
}


extern "C" HRESULT DAPI ConsoleSetReadNormal(void)
{
    AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
    HRESULT hr = S_OK;

    if (!::SetConsoleMode(vhStdIn, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT))
    {
        ConExitOnLastError(hr, "failed to set console input mode to be normal");
    }

LExit:
    return hr;
}


static HRESULT DAPI ReadInteractiveStdIn(
    __deref_out_z LPWSTR* ppwzBuffer
    )
{
    HRESULT hr = S_OK;
    LPWSTR pwz = NULL;
    DWORD cch = 8;
    DWORD cchRead = 0;
    DWORD cchTotalRead = 0;

    hr = StrAlloc(&pwz, cch);
    ConExitOnFailure(hr, "failed to allocate memory to read from console");

    // loop until we read the \r\n from the console
    for (;;)
    {
        // read one character at a time, since that seems to be the only way to make this work
        if (!::ReadConsoleW(vhStdIn, pwz + cchTotalRead, 1, &cchRead, NULL))
        {
            ConExitOnLastError(hr, "failed to read string from console");
        }

        cchTotalRead += cchRead;
        if (0 == cchRead)  // nothing more was read
        {
            pwz[cchTotalRead] = L'\0';  // null terminate and bail
            break;
        }
        else if (0 < cchTotalRead && L'\n' == pwz[cchTotalRead - 1])
        {
            if (1 < cchTotalRead && L'\r' == pwz[cchTotalRead - 2])
            {
                pwz[cchTotalRead - 2] = L'\0';  // chop off the \r\n
            }
            else
            {
                pwz[cchTotalRead - 1] = L'\0';  // chop off the \n
            }

            break;
        }

        if (cchTotalRead == cch)
        {
            cch *= 2;   // double everytime we run out of space
            hr = StrAlloc(&pwz, cch);
            ConExitOnFailure(hr, "failed to allocate memory to read from console");
        }
    }

    hr = StrAllocString(ppwzBuffer, pwz, 0);
    ConExitOnFailure(hr, "failed to copy stdin buffer to return buffer");

LExit:
    ReleaseStr(pwz);
    return hr;
}


static HRESULT ReadRedirectedStdIn(
    __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer,
    __out DWORD* pcchSize,
    BOOL fReadLine,
    DWORD dwMaxRead
    )
{
    HRESULT hr = S_OK;
    LPSTR psz = NULL;
    DWORD cch = 8;
    DWORD cchRead = 0;
    DWORD cchTotalRead = 0;

    hr = StrAnsiAlloc(&psz, cch);
    ConExitOnFailure(hr, "failed to allocate memory to read from console");

    while (dwMaxRead == 0 || cchTotalRead < dwMaxRead)
    {
        // read one character at a time, since that seems to be the only way to make this work
        if (!::ReadFile(vhStdIn, psz + cchTotalRead, 1, &cchRead, NULL))
        {
            ConExitOnLastError(hr, "failed to read string from console");
        }

        cchTotalRead += cchRead;
        if (0 == cchRead)  // nothing more was read
        {
            psz[cchTotalRead] = '\0';  // null terminate and bail
            break;
        }
        else if (fReadLine && 0 < cchTotalRead && '\n' == psz[cchTotalRead - 1])
        {
            if (1 < cchTotalRead && '\r' == psz[cchTotalRead - 2])
            {
                psz[cchTotalRead - 2] = '\0';  // chop off the \r\n
            }
            else
            {
                psz[cchTotalRead - 1] = '\0';  // chop off the \n
            }

            break;
        }

        if (cchTotalRead == cch)
        {
            cch *= 2;   // double everytime we run out of space
            hr = StrAnsiAlloc(&psz, cch);
            ConExitOnFailure(hr, "failed to allocate memory to read from console");
        }
    }

    *pcchSize = cchTotalRead;

    hr = StrAllocStringAnsi(ppwzBuffer, psz, 0, CP_UTF8);
    ConExitOnFailure(hr, "failed to convert console data");

LExit:
    return hr;
}