Tag Archives: CursorTop

Forum Problem to Posted Solution and Article Post

I was reading the Stack Overflow (PowerShell) forum when I happened on a question that I decided I would take on. It reminded me of an old post I had written. In that post, Countdown Options, I wrote a countdown that overwrote itself as it counted down. Here’s s GIF from that post.

In the question on Stack Overflow, someone wanted to get away from the way Do-Until works. Here’s their, modified-by-me question:

I have this simple bit of code in a PS script where I want to get a simple yes or no answer…The problem is that when user inputs something other than y or n it reprompts on new line…How can I avoid the new line and make the code as simple and easy to understand as possible?

I didn’t bother taking my time to explain that this is how it works and that they should expect more from their users. Instead, I set out to use what I learned from my countdown article. Before we get to that solution, let’s look at how this works before the changes I implemented.

Do {
    $Answer = Read-Host -Prompt 'Found missing roles. Install them now? (y/n)'
}
Until ($Answer -eq 'y' -or $Answer -eq 'n')

According to the OP, the problem with the above option and below results is that it’s re-prompting on the next line if the answer doesn’t match “y” or “n.” That needed to be avoided, if possible.

Found missing roles. Install them now? (y/n): 1
Found missing roles. Install them now? (y/n): 2
Found missing roles. Install them now? (y/n): 3
Found missing roles. Install them now? (y/n): 4
Found missing roles. Install them now? (y/n): 
Found missing roles. Install them now? (y/n): 
Found missing roles. Install them now? (y/n): a
Found missing roles. Install them now? (y/n): b
Found missing roles. Install them now? (y/n): c
Found missing roles. Install them now? (y/n): n

In my example, we overwrite the previously entered value. Take a look at the slight modifications I’ve made to the code. Then, take a look at the GIF made for this article.

$Cursor = [System.Console]::CursorTop
Do {
    [System.Console]::CursorTop = $Cursor
    Clear-Host
    $Answer = Read-Host -Prompt 'Found missing roles. Install them now? (y/n)'
}
Until ($Answer -eq 'y' -or $Answer -eq 'n')

And with that, my work is done here, and over there on Stack Overflow. Well, until the next question, where I can hopefully make a positive impact.

Countdown Options

In a somewhat recent post here — that I can’t remember off the top of my head — I put something together much like the below example. It completed a countdown from 10 to 1 seconds (or did it count up?), paying attention to whether it should use the word “second” or “seconds.” I’m not going to chase down that post, but do take a look at the example.

10..1 | ForEach-Object {
    If ($_ -eq 1) {
        "$_ second"
    } Else {
        "$_ seconds"
    }
    Start-Sleep -Seconds 1
}

Here’s what that looks like once it’s done executing. Do notice that each string includes seconds up until we get down to one second. It works as expected!

10 seconds
9 seconds
8 seconds
7 seconds
6 seconds
5 seconds
4 seconds
3 seconds
2 seconds
1 second

Before we move on, if you find this in my code, you’ll likely see it use substantially less amount of lines. Like one. While I pride on myself on readable code, something this simplistic and of little consequence need not be on full display at times — it’s up to your discretion.

10..1 | ForEach-Object {If ($_ -eq 1) {"$_ second"} Else {"$_ seconds"}; Start-Sleep -Seconds 1}

I got to thinking (due a project — which is typical), what if we didn’t move downward in the console as we did the count down? Is it possible to overwrite the last countdown indication? What I mean is, is it possible to overwrite 10 seconds, with 9 seconds, and so on? if you’re reading this, then yes, it’s possible.

10..1 | Foreach-Object {
    If ($_ -eq 10) {
        $Cursor = [System.Console]::CursorTop
    }
    [System.Console]::CursorTop = $Cursor
    '{0:d2}' -f $_
    Start-Sleep -Seconds 1
}

The following gif indicates how the above code executes. You can see that the countdown occurs right over the top of itself. There’s no moving downward in the console, and for me, that’s preferred. Do notice that we’re using the the -f formatting operator. I bring this up, because it’s helping me deal with a bit of a problem. If we didn’t use it, and used single digits for the values 9 through 1, we’d have the left over zero from 10 to the right of each single digit. That would make 9, 90, it would make 8, 80, etc. There’s probably a better way to handle this obnoxiousness, but this is the option I went with this evening. I’ll keep this in mind, and perhaps update this post if I determine a better way to avoid double digit, single digits.

In this example, we’ve combined the previous two. We’re including the string values of “seconds,” and “second” in our countdown. I’ve also included a gif for this example further below.

10..1 | Foreach-Object {
    If ($_ -eq 10) {
        $Cursor = [System.Console]::CursorTop
    }
    [System.Console]::CursorTop = $Cursor
    If ($_ -eq 1) {
        "$('{0:d2}' -f $_) second"
    } Else {
        "$('{0:d2}' -f $_) seconds"
    }
    Start-Sleep -Seconds 1
}

Take a look at this gif, and then we’ll discuss it. Ugh.

Did you see it? The problem I mentioned previously is back. It needs to be dealt with now, not some time in the future, which was what was preferred. When it goes to 1 second remaining, our final “s,” in the plural form of seconds, sticks around and gives the appearance the code is failing in a manner different than one might expect at first.

I wanted to get this post published, so let me show you what I did. I’ll do my best to get back to this post later, but for now, here’s the “fix.”

        10..1 | Foreach-Object {
            If ($_ -eq 10) {
            $Cursor = [System.Console]::CursorTop
            }
            [System.Console]::CursorTop = $Cursor
            "Seconds remaining: $('{0:d2}' -f $_)"
            Start-Sleep -Seconds 1
        }

Yeah, you saw that correctly. I’ve moved the numeric values to the end, or right-most, portion of our string. This means that we’re not going to leave the final “s” in seconds. Even so, we do have to continue to use double digits for single digits (the whole leading-zero thing). I can live with that for now. Have a look. Now to add this to my project, so the user can have something to do while these seconds pass before a restart.

Clear-Host, Without Clearing the Host

After you read this, read part 2 (and download the TMModule)

I use the Clear-Host cmdlet alias, cls, throughout the day to clear out whatever typing I have inside my Windows PowerShell console. It does its job well, but recently I’ve wanted it to work differently. I wanted it to appear that the console host has been cleared, but still allow me to scroll back up to see what was on the screen before it was cleared. I started playing around with the console class, [Console]. While not necessary, this can also be written using the namespace, System, such as [System.Console]. I like the idea of being as complete as possible and so you’ll see me use the namespace even though it’s not necessary.

Before I could write something reusable, such as a function, I had to figure out if what I wanted to accomplish, was even possible. I knew I was working with [System.Console] and so I piped that to Get-Member, but it returned the methods and properties of System.RuntimeType, seen below.

PS C:\> [System.Console] | Get-Member

    TypeName: System.RuntimeType

I struggled for a moment until I remembered an article I had read on using static classes. I found that page again, http://technet.microsoft.com/en-us/library/dd347632.aspx, and was quickly reminded that using the -Static parameter of the Get-Member cmdlet would get me the correct results.

PS C:\> [System.Console] | Get-Member -Static

    TypeName: System.Console

Running the command above produces the TypeName as shown, but it also produces all the methods and properties. I started looking over the properties and a couple about the cursor quickly caught my eye, especially the CursorTop property. After the Get-Member command from above, and based on the results of returning the CursorTop property, my cursor was positioned at line 59 inside my console, as can been seen in the example below on line 2. I cleared the screen, and beginning on line 4 below, I reran the command three more times. Each time, it gave me the location where the cursor was last positioned.

PS C:\> [System.Console]::CursorTop
59
PS C:\> cls
PS C:\> [System.Console]::CursorTop
1
PS C:\> [System.Console]::CursorTop
3
PS C:\> [System.Console]::CursorTop
5
PS C:\>

I decided I would assign the value, 0, to the CursorTop property and suddenly I was writing over the text on the top line. Take a close look at line 1 below.

PS C:\> blahblahConsole]::CursorTop
59
PS C:\> cls
PS C:\> [System.Console]::CursorTop
1
PS C:\> [System.Console]::CursorTop
3
PS C:\> [System.Console]::CursorTop
5
PS C:\> [System.Console]::CursorTop = 0

I could move my cursor, great, but this wasn’t exactly what I wanted. What I wanted was to push that scroll bar down so that anything that was already on the screen was pushed off the top of my console, and all that was left was my PowerShell prompt. I still believed there was a way to do this and so I spent a little more time looking over the properties. I found four that began with Window – WindowHeight, WindowLeft, WindowTop, and WindowWidth – and began to experiment with them. I didn’t suspect I’d be doing anything with the height and width but I thought I would check out their values anyway – 50 and 120, respectively.

PS C:\> [System.Console]::WindowHeight
50
PS C:\> [System.Console]::WindowWidth
120
PS C:\>

WindowLeft didn’t seem to be that important, because no matter how much was typed before I entered [System.Console]::WindowLeft, the property value was still set to 0. Then I entered in [System.Console]::WindowTop and it was also 0 every time. Then it dawned on me, what if I changed its value like I did with CursorTop. I tried it and my scroll bar started jumping all over. I’m getting close!

PS C:\> [System.Console]::WindowTop = 10
PS C:\> [System.Console]::WindowTop = 200
PS C:\> [System.Console]::WindowTop = 2000
PS C:\> [System.Console]::WindowTop = 0

We know the CursorTop value changes, so what would happen if we set the value of WindowTop to CursorTop? I tried it, and it worked!

PS C:\> [System.Console]::WindowTop = [System.Console]::CursorTop
PS C:\>

I thought I was done when I took another moment and scanned over the methods. I found one called SetWindowPosition. Instead of simply assigning a new value to the property WindowTop, I decided I would use the method to do the work for me. I eventually ran both of these options through the Measure-Command cmdlet and determined that there was no gain in speed by using one option over the other.

So, once I knew what to do, I opened my profile ($PROFILE) and created an empty function. For whatever reason, I called it clx thinking that this would be a good option for me. Turns out that while clx has little meaning, I was able to quickly remember it and start using it right away. Now, every time I want it to appear that I’ve cleared my host, but didn’t really, I type clx and press Enter.

Function clx {
    [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop)
}

I added one additional feature to this function as is seen in the example below. This option allowed me to run the clx function and leave the last n number of rows on the screen. Try it out by ensuring your have some output in your console and then entering clx 2. This will “clear” the console screen but still allow you to view the last two rows without scrolling back up. Try it and it may make more sense.

Function clx($SaveRows) {
    If ($SaveRows) {
        [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop-($SaveRows+1))
    } Else {
        [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop)
   }
}

Here’s a video of the function in action. The first thing we do is return 5 processes and then 5 services. Then we use cls and notice that we cannot scroll back up to see what was cleared. This is the typical behavior. When we add the processes and services back, and then use the clx function, we can see that we have the option to scroll back up and see what was on the screen, before we “cleared” it.