Tag Archives: WindowTitle

PowerShell (Tab) Titles

I’m at a newer stage of life, and no, this has nothing to do with the current, worldwide pandemic or staying at home on a daily basis now. It’s hardly that serious. It has everything to do with my computer and the plethora of PowerShell tabs I have open. More so these days, it’s actually the elevated number of Windows Terminal instances running on my single, physical laptop. Sure, I could keep everything in a single instance of Windows Terminal, but I’m a fan of using multiple desktops. For me for example, it’s “here’s my general desktop, here’s my Bitbucket repository desktop, here’s my VS Code and Git desktop, and here’s my Docker desktop.” That’s four instances of Windows Terminal, although my VS Code and Git desktop would only have a Git Bash tab in Windows Terminal. Anyway, I needed a quicker way to know which desktop I’m currently on, and since I’m often at a PowerShell prompt, I thought I should help myself out.

This took me back to my earlier days learning PowerShell. See, those things really matter. I took advantage of my knowledge of the host program’s title bar. I was already doing that, but I needed to add more to it. Anyway, here’s what you’d see by default after I opened Windows PowerShell (5.1) and PowerShell (7.0.0), and then switched back to PowerShell.

As you can see, I use the tabs to ensure I know which version of PowerShell I’m using, although it’s mostly clear anyway due to the icons, host background color, etc. So yes, I have my version as clear as can be, but that’s not really the point today. I want to know which desktop I’m on with some help from PowerShell.

In order to know which desktop I’m currently using, I need to alter the WindowTitle. To be exact, I need to alter the $Host.UI.RawUI.WindowTitle. The value assigned to this property of this variable is displayed in the console host’s title, or in Windows Terminal, right on the tab. In this image, the WindowTitle simply says “PS 7.” I’ve been good with that, but now I want more.

The below function is the one I quickly authored for this purpose. It will allow me to alter the title, so that what I want is added to the already existing value, such as “PS 7” and “WPS 5.1.” Additionally, I added parameter sets and a second parameter, in order that I can reset the title back to its original value, and not just add something new.

Function Update-WindowTitle {
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory, ParameterSetName = 'Add')]
		[string]$AdditionalTitle,

		[Parameter(ParameterSetName = 'Reset')]
		[switch]$Reset
	) # End Param.

	If (-Not(Get-Variable -Name OriginalTitle -Scope Global -ErrorAction SilentlyContinue)) {
		New-Variable -Name OriginalTitle -Value $Host.UI.RawUI.WindowTitle -Option Constant -Scope Global
	} # End If.

	If ($AdditionalTitle) {
		$Host.UI.RawUI.WindowTitle = "$OriginalTitle | $AdditionalTitle"
	} ElseIf ($Reset) {
		$Host.UI.RawUI.WindowTitle = $OriginalTitle
	} # End If-Else.
} # End Function: Update-WindowTitle.

Here’s a series of images that walk through some edits of the WindowTitle. I updated the title to show Docker, then Pandoc, and finally, reset its value. With multiple instances of Windows Terminal, on multiple virtual desktops, I can now quickly remind myself of the desktop I’m currently using. Although the tab title values are slightly different, I’ve included a gif below as well, in order to show movement between the desktops.

There’s more I could add to this function to ensure it works under odd conditions, but for now and for me, it’ll work just fine. Now to add it to my $PROFILE, so it’s always available.

Run Background Commands after Every Command (Part II)

The first part of this post began on PowerShell.org. Start there, and it’ll get you to Part I: http://powershell.org/wp/2015/10/12/run-background-commands-after-every-command.

Recently I shared a small function I wrote to replace my prompt function. In addition to creating the prompt (PS: C\>) each time one command ended, it would run some background commands. In my attempt to take advantage of this function, I added code that would modify the WindowTitle, the text at the top of the console host or ISE, to indicate if there were any background jobs. Today, I’ve improved the function.

The additions I made include all of the following:
– Uses the singular word “job” when there is only one background job.
– Uses the plural word “jobs” when there is more than one background job.
– Adds an asterisk (*) when any job is actively running.
– Adds a plus sign (+) when any job has more data.

Here’s the full, updated function, and some sequential images beneath that. I’ve dumped the nested If statement for a switch statement, improving the function’s readability. Before you get into this, there’s something to consider. The indicators are only as accurate as the last time the prompt function was invoked. They won’t update without the user pressing the Enter key. That said, it’s quite convenient that you can determine if a background job is still running by pressing Enter. You don’t even have to type anything.

Function Prompt {
    If (-Not($OriginalTitle)) {
        $Global:OriginalTitle = $Host.UI.RawUI.WindowTitle
    }

    If (Get-Job) {
        $Job = Get-Job
        Switch ($Job) {
            {$Job.State -eq 'Running'} {$State = '*'}
            {$Job.HasMoreData -eq $true} {$MoreData = '+'}
            {$Job.Count -eq 1} {$Host.UI.RawUI.WindowTitle = "[$($State)Job$($MoreData)] $OriginalTitle"; break}
            {$Job.Count -gt 1} {$Host.UI.RawUI.WindowTitle = "[$($State)Jobs$($MoreData)] $OriginalTitle"}
        }
    } Else {
        $Host.UI.RawUI.WindowTitle = $OriginalTitle
    }

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

Run-background-commands-after-every-command-PartII-01

Run-background-commands-after-every-command-PartII-02

Run-background-commands-after-every-command-PartII-03

Run-background-commands-after-every-command-PartII-04

Update: After looking at this a few more times, I decided to move the asterisk indicator (running jobs) to the back of the word Job, or Jobs, next the plus sign indicator (has more data). I thought it looked better. The code changes I made, are seen below, and a comparison image is down there, too. Cheers!

Function Prompt {
...
            {$Job.Count -eq 1} {$Host.UI.RawUI.WindowTitle = "[Job$($State)$($MoreData)] $OriginalTitle"; break}
            {$Job.Count -gt 1} {$Host.UI.RawUI.WindowTitle = "[Jobs$($State)$($MoreData)] $OriginalTitle"}
...
}

Run-background-commands-after-every-command-PartII-05

Run Background Commands after Every Command

For a complete introduction to this post, please read the first two paragraphs at PowerShell.org: http://powershell.org/wp/2015/10/12/run-background-commands-after-every-command.

There’s a part II, now. Be sure to read that when you’re done here: http://tommymaynard.com/quick-learn-run-background-commands-after-every-command-part-ii-2015.

You know the prompt, it often looks like this: PS C:\> or this: PS>, or even this [DC05]: PS C:\Users\tommymaynard\Documents>, when you’re interactively remoting to another computer. That built-in function determines the appearance of your prompt. I’ve seen several different modifications of the prompt. You can add the the date and time to the prompt, or like the example in the about_Prompt help file, you can add ‘Hello World’ every time the prompt is displayed. Doable, yes, but helpful, probably not.

This post isn’t about changing the prompt’s appearance, but instead about doing something else inside the prompt function. What the prompt looks like is defined by a function called, prompt — you guessed it. Take a look at your current prompt, as in the example below.

PS> (Get-ChildItem -Path Function:\prompt).ScriptBlock
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
# .Link
# http://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll-help.xml

This is the standard prompt function beginning in PowerShell 3.0 up though PowerShell 5.0, the current version of PowerShell, as of this writing.

This function is invoked each time a command is run and the prompt is displayed again. What if we added additional code inside this function? It would also be run every time a new prompt was displayed. My initial idea was to add the current directory to the WindowTitle. If you don’t know what the WindowTitle is, it’s the text at the top of the console host that says “Administrator: Windows PowerShell,” or “Windows PowerShell,” when in a non-elevated host.

I tried the current directory and quickly realized it took more time to look up at the WindowTitle, from the current prompt, than to leave the default prompt alone and get this information from there. The next idea was to add the date and time to the WindowTitle. This way I would know the time the last prompt was displayed. This is practically the time the last command ended. It seemed useful… for about a minute. I finally decided on putting an indicator in the WindowTitle so that I would know if there were any background jobs or not. I seem to open and close new consoles all day long, and knowing if I was about to dump a console with an active job, whether it was still running or not, or whether it still had data, seemed useful.

Let’s walk though what I did to get this to happen. Before we do that, let’s compare the two images below. The first one shows the standard WindowTitle when there’s no job, and the [Job] indicator when there is a job. We’re going to add a few lines of PowerShell to make this happen.

run-background-commands-after-every-command01run-background-commands-after-every-command02

The first thing I did was to define a function in my profile script ($PROFILE). This function would overwrite the default prompt function, as PowerShell reads in the profile script, after it has already created the default prompt.

Function prompt {

}

Next, I enter the default, prompt text. Notice I’m not changing how the prompt is displayed.

Function Prompt {
    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

Following this, I added the first piece of conditional logic. We test for a variable called $OriginalTitle. This variable will not exist the first time the prompt function is run, as the variable is created inside this If statement. It effectively ends up holding whatever was in WindowTitle when the console host was first opened. You’ll soon see how we reuse this value.

Before we go on, I should mention that I’ve made this a globally-scoped variable. This is because the variable needs to retain its value outside the function, and it needs to exist each time the function ends. Because of the way scope works, when we enter the function the second time and it can’t find $OriginalText, the function will go up a level and check for the existence of the variable in the parent scope, which in this case is the global scope.

Function Prompt {
    If (-Not($OriginalTitle)) {
        $Global:OriginalTitle = $Host.UI.RawUI.WindowTitle
    }

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

In the next part of the function, I added a check to see if the Get-Job cmdlet returns anything. If it does, there are current background jobs, and if it doesn’t, then there are no current background jobs. We’ll start with what happens when there aren’t any jobs, first. In the Else portion of this If-Else statement, we set the current WindowTitle to whatever is stored in the $OriginalTitle variable. This ensures that the WindowTitle looks just like it did when we initially started the console host and there were no background jobs.

Function Prompt {
    If (-Not($OriginalTitle)) {
        $Global:OriginalTitle = $Host.UI.RawUI.WindowTitle
    }

    If (Get-Job) {
        # Coming
    } Else {
        $Host.UI.RawUI.WindowTitle = $OriginalTitle
    }

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

So what happens when there are jobs? In this final portion of the function, we have an embedded If statement. If the current WindowTitle doesn’t already include the string [Job], then we add it. If it’s already there, then we leave it alone, and write the prompt.

Function Prompt {
    If (-Not($OriginalTitle)) {
        $Global:OriginalTitle = $Host.UI.RawUI.WindowTitle
    }

    If (Get-Job) {
        If ($Host.UI.RawUI.WindowTitle -NotLike '[Job]*') {
            $Host.UI.RawUI.WindowTitle = "[Job] $OriginalTitle"
        }
    } Else {
        $Host.UI.RawUI.WindowTitle = $OriginalTitle
    }

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

Thanks to the prompt function, we have another way to do something the moment the console is opened, and now, every time a new command is entered and a new prompt is displayed in the host. This little project has already got me wondering about some other things I may want to try with this function. I might expand what I’ve done so far, and provide indicators when there’s more than one job ([Job] vs. [Jobs]), if the jobs are still running, and perhaps if they have more data (whether it hasn’t been received yet, or was kept when it was received).

Keep in mind that this function runs after every command, successful or otherwise. Do your best not to overload the actions in the function. The default prompt comes in under a millisecond. My prompt, with the code above added, comes in at an average of 1 millisecond. If you end up doing too much in the function, you might end up waiting longer than you’re used to, making your additions to the function questionable.

Have fun, and thanks to everyone for reading this post.