Printing text on the same line as previous input

382 Views Asked by At

This is essentially what I want to do:

Write-Host "Enter username: " -NoNewLine
$username = Read-Host -NoNewLine
if ($username -eq "") {
    Write-Host "None"
}

If the user were to enter nothing, then this would be my desired output: Enter username: None

However I have been unable to find a way to read user input without it generating a new line, and -NoNewLine does not work with Read-Host.

2

There are 2 best solutions below

0
NiMux On BEST ANSWER

You should be able to do this by first recording the position of the terminal's cursor using the PSHostRawUserInterface.CursorPosition property, which can be found at $host.UI.RawUI.CursorPosition

After prompting for the username and determining that it's blank, reset the cursor back to its original position and then output the desired text.

Example code:

# Output the prompt without the new line
Write-Host "`rEnter username: " -NoNewLine

# Record the current position of the cursor 
$originalPosition = $host.UI.RawUI.CursorPosition

# Prompt for the username
$username = Read-Host

# Check the response with the IsNullOrWhiteSpace function to also account for empty strings
if ([string]::IsNullOrWhiteSpace($username)) {
    # Set the position of the cursor back to where it was before prompting the user for a response
    [Console]::SetCursorPosition($originalPosition.X,$originalPosition.Y)

    # Output the desired text
    Write-Host "None"
}
0
mklement0 On

To add to NiMux's excellent solution:

  • It works robustly on Windows, in regular console windows (conhost.exe).

  • In Windows Terminal and on Unix-like platforms, it breaks situationally, which can be remedied in this simple case, however (see code below).

    • The reason is that, as of .NET 6, in these environments the cursor row isn't reported as an absolute position in the entire scrollback buffer, but relative to the current "viewport" (the currently visible rows).

      • ANSI / VT escape sequences are used to control the terminal (console) in these environments, and, at least currently, there is no widely implemented, standardized support across terminal applications for moving the scroll window, per this comment on GitHub.

      • The comment is taken from GitHub issue #52374, which discusses a future, more capable cross-platform terminal .NET API to supersede the Windows-focused [Console] in general, with GitHub issue #64487 keeping track of work for .NET 7 and maintaining a backlog of issues.

    • Thus, if after saving the cursor position, output causes the window to scroll (due to additional output pushing older rows out of view), restoring the position will return to a different row (with respect to its content).

      • You can only compensate for this problem if you how how many rows were scrolled out of view.

In this case, given that Read-Host only accepts single-line input, we know that:

  • only ever one row can scroll out of view
  • namely when the current row is at the very bottom of the window.

Thus, it is possible to write the following cross-terminal, cross-platform solution, which detects the relevant environments and adjusts the restore position if the current row is found to be the bottom-most one.

# Write the prompt string without a newline.
Write-Host 'Enter username: ' -NoNewLine

# Save the current cursor position.
$x, $y = [Console]::CursorLeft, [Console]::CursorTop

# Prompt, then trim incidental whitespace from the response.
$username = (Read-Host).Trim()

if (-not $username) { # No user input given.

    # Compensate for the scrolling problem in Windows Terminal and on Unix.
    if ($env:WT_SESSION -or $env:OS -ne 'Windows_NT') {
      if ([Console]::CursorTop -eq [Console]::WindowHeight - 1) {
        --$y
      }
    }

    # Restore the saved cursor position.
    [Console]::SetCursorPosition($x, $y)

    # Write the text signaling that no user input was provided, followed by a newline.
    Write-Host "None"
}