Stop visible countdown from constantly scrolling its content to bottom of PowerShell window

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
DOSadnie
Posts: 143
Joined: 21 Jul 2022 15:12
Location: Coding Kindergarten

Stop visible countdown from constantly scrolling its content to bottom of PowerShell window

#1 Post by DOSadnie » 21 Aug 2023 04:02

This OK working test script

Code: Select all

Write-Host "Line of text at the very top"
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host "Line of text in the middle"
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host
Write-Host "Last line of text"

Write-Host "Executing in:"
Write-Host



# Countdown:

$topRow = [System.Console]::CursorTop

10..0 | ForEach-Object {
    [System.Console]::SetCursorPosition(0, $topRow)

    if ($_ -eq 10) {
        Write-Host $_ -NoNewline
    } else {
        Write-Host " $_" -NoNewline
    }

    Start-Sleep -Seconds 1
    Write-Host " `b" -NoNewline
}



# Here will be the main script

Write-Host
Write-Host "Script has been executed"



# Wait for specific user input (ESC, ENTER, or SPACE) before closing the window:

Write-Host "To close this window press either:
Enter
Space
Esc"
Write-Host

do {
    $key = $Host.UI.RawUI.ReadKey('NoEcho, IncludeKeyDown').Character
} while ($key -notin @(' ', [char]13, [char]27))
stops at a countdown and after it ends proceeds with execution of its remaining part

The problem with it is that every appearing digit makes the PS window scrolls all the way down - thus I can only see for a second what is shown above this active counter. I can constantly scroll up manually but it will always goes back to bottom to show a new digit. I could of course replace this with a silent countdown like

Code: Select all

$CountdownSeconds = 10
Write-Host "Executing in:"
Write-Host $CountdownSeconds
Write-Host "seconds"
Start-Sleep -Seconds $CountdownSeconds
but then I would not be informed anymore about how much time has left


So is there maybe some way to eat a cake and have it - i.e. to see a countdown at the bottom of window but also be able to scroll up and not have the view constantly being scrolled / thrown down?

DOSadnie
Posts: 143
Joined: 21 Jul 2022 15:12
Location: Coding Kindergarten

Re: Stop visible countdown from constantly scrolling its content to bottom of PowerShell window

#2 Post by DOSadnie » 10 Sep 2023 01:28

DOSadnie wrote:
21 Aug 2023 04:02
[...]
So is there maybe some way to eat a cake and have it - i.e. to see a countdown at the bottom of window but also be able to scroll up and not have the view constantly being scrolled / thrown down?
Any ideas, anyone?

aGerman
Expert
Posts: 4654
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Stop visible countdown from constantly scrolling its content to bottom of PowerShell window

#3 Post by aGerman » 12 Sep 2023 09:35

PowerShell is a little off-topic here. However, I had a look at it and found that this behavior is unrelated to PowerShell and happens in Batch as well. It's by design, at least as long as you're running the shell in conhost. Windows Terminal does not override mouse scrolling.
If you really want to work around this effect you need to use the low-level console API. If you look at the man page of these functions, you'll see that Microsoft doesn't want us to use them. The reason is that they are dedicated to conhost and using them along with any other terminal app may cause unexpected results.
It's very easy to import native API functions in PowerShell though. It only requires a type definition in C# code. This may cause a small delay when your script starts because the C# code is compiled to a temporaray DLL first.

Steffen

*.ps1

Code: Select all

# Win32 API wrapper class
Add-Type Console -NS Win32 -MemberDefinition @'
  [DllImport("kernel32.dll")]
  private static extern IntPtr GetStdHandle(int id);
  [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
  private static extern int WriteConsoleOutputCharacter(IntPtr hConOut, string str, int strLen, uint coord, out int written);
  
  //# This function simply writes through to the console screen buffer at the
  //# specified coordinates. It does not update the current position of the
  //# console cursor.
  //# NOTE: The low-level console API is invoked here. Do not use this function
  //#       in a terminal application other than the classic console (conhost).
  //# parameters:
  //#   x     zero-based column index of the writing position
  //#   y     zero-based line index of the writing position
  //#   text  output to be written
  //# returns:
  //#   True if the function succeeds, False otherwise
  public static bool WriteAtPosition(uint x, uint y, string text) {
    int written;
    return 0 != WriteConsoleOutputCharacter(GetStdHandle(-11), text, text.Length, (y << 16) | x, out written);
  }
'@

# Just 200 lines of output to scroll the screen buffer.
0 .. 199 | %{ $_ }

# Additional blank line to where the custom function writes (incl. new line
# to where the console cursor jumps).
''

# Always write in the same place, which will result in overwriting the previous
# text. The cursor position is not affected. The screen is not automatically
# scrolled to where the output is updated.
0 .. 50 | %{
  if (-not [Win32.Console]::WriteAtPosition(0, 200, "~~~ $_ ~~~")) { 'ERROR' }
  Sleep 1
}

DOSadnie
Posts: 143
Joined: 21 Jul 2022 15:12
Location: Coding Kindergarten

Re: Stop visible countdown from constantly scrolling its content to bottom of PowerShell window

#4 Post by DOSadnie » 18 Sep 2023 07:30

Thank you; this works as a standalone test PS1 file

But how do I incorporate this into that test script of mine from my initial post?

DOSadnie
Posts: 143
Joined: 21 Jul 2022 15:12
Location: Coding Kindergarten

Re: Stop visible countdown from constantly scrolling its content to bottom of PowerShell window

#5 Post by DOSadnie » 26 Sep 2023 04:36

I meant: how do I limit scrolling down to just where the counting takes place? Because your script makes it possible to scroll down through excess of empty lines

I reworked both test into this script

Code: Select all

# Win32 API wrapper class
Add-Type Console -NS Win32 -MemberDefinition @'

[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(int id);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern int WriteConsoleOutputCharacter(IntPtr hConOut, string str, int strLen, uint coord, out int written);

//# The below function:
//# ● writes through to the console screen buffer at the specified coordinates
//# ● it does not update the current position of the console cursor
//# ● invokes a low-level console API
//# ● mist not be used in a terminal application other than the classic Windows Console [conhost]
//# ● if it succeeds it return True 
//# ● if it fails it returns False
//# ● parameters for it are:
//#   x ▶ zero-based column index of the writing position
//#   y ▶ zero-based line index of the writing position
//#   text ▶ output to be written

public static bool WriteAtPosition(uint x, uint y, string text)
{
int written;
return 0 != WriteConsoleOutputCharacter(GetStdHandle(-11), text, text.Length, (y << 16) | x, out written);
}

'@

Write-Host "Line of text - at the very top"
Write-Host
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host
Write-Host "Line of text - in the middle"
Write-Host
Write-Host
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host "Line of text"
Write-Host
Write-Host "Line of text - the last one"

# The countdown - duration and lines below it
$Countdown_Values = 3

# Function to set the buffer height
function SetBufferHeight($height)
{
    [System.Console]::BufferHeight = $height
}

# Function to display the countdown without scrolling
function DisplayCountdown($count)
{
    $topRow = [System.Console]::CursorTop
    $leftColumn = 0

    # Set the buffer height to prevent scrolling below the countdown
    SetBufferHeight($topRow + $count + 1)

    for ($i = $count; $i -ge 0; $i--)
    {
        if ($i -eq $count)
        {
            [Win32.Console]::WriteAtPosition($leftColumn, $topRow, "$i") | Out-Null
        }
        else
        {
            [Win32.Console]::WriteAtPosition($leftColumn, $topRow, " $i") | Out-Null
        }

        Start-Sleep -Seconds 1
    }

    # Restore the original buffer height
    SetBufferHeight($Countdown_Values + [System.Console]::WindowHeight + 1)
}

# Display the countdown
Write-Host "Executing in:"
DisplayCountdown $Countdown_Values
Write-Host "Script has been executed"

# Wait for specific user input (ESC, ENTER, or SPACE) before closing the window:
Write-Host "To close this window press either:"
Write-Host "Enter"
Write-Host "Space"
Write-Host "Esc"
Write-Host

do
{
    $key = $Host.UI.RawUI.ReadKey('NoEcho, IncludeKeyDown').Character
}
while ($key -notin @(' ', [char]13, [char]27))
which does not work correctly - because its issues are:

A] the "Line of text - at the very top" is missing

B] the number that governs the amount of empty lines below the countdown is also the number od seconds for that counter

C] in the end it produces
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text

Line of text - in the middle


Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text
Line of text

Line of text - the last one
Executing in:
Exception setting "BufferHeight": "The console buffer size must not be less than the current size and position of the
console window, nor greater than or equal to Int16.MaxValue.
Parameter name: height
Actual value was 54."
At C:\q\! SCRIPTS\ZZZ_TEST.ps1:106 char:5
+ [System.Console]::BufferHeight = $height
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting

Script has been executed
To close this window press either:
Enter
Space
Esc
i.e. it shows an error

DOSadnie
Posts: 143
Joined: 21 Jul 2022 15:12
Location: Coding Kindergarten

Re: Stop visible countdown from constantly scrolling its content to bottom of PowerShell window

#6 Post by DOSadnie » 13 Oct 2023 08:12

The Windows Console [conhost.exe] it is supposedly not possible to see digits of a countdown at the bottom of window while also retaining ability to scroll it up an not have the view being thrown back down after passing of another second - but the new Windows Terminal [windowsterminal.exe] however will not snap to the bottom on output if user scrolls up, as it supposedly only snaps to the bottom on output if user is already at the bottom of window when it occurs

Windows Terminal by default replaces the old Windows Terminal since Windows 11 22H2; while installing it on older systems has some prerequisites. Thus it will be easier for me to just wait out this issue


However if anyone can fix the above so-close-to-my-goal script, then please do

Post Reply