PowerShell Tips Part 1 - PowerShell Profile

Entry #1837, Sat, August 01, 2020, 14:52 CDT (Coding, Hacking, & CS stuff)
(posted when I was 42 years old.)

I've been using PowerShell a lot of late and have been learning and customizing things I'd like to remember, so... part 1 of maybe multiple parts is going to focus on the PowerShell profile file, a script that is run when you start a PowerShell session, similar to a .bashrc or .cshrc. The location of this file is stored in the $profile environment variable and probably looks something like

C:\Users\username\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

It probably also doesn't exist unless you've created it yourself. The WindowsPowerShell directory may also not exist. So you'll need to create those yourself. The next bit of difficulty is that out-of-the-box, PowerShell probably won't like that file unless it's signed. I'll touch on that some more at the end. But first some more fun stuff.

Per-instance command history

Visual Studio Code has a nice built-in terminal feature, and by default that terminal will be a PowerShell terminal in Windows. It's not uncommon that I have several projects I'm working on simultaneously, and while it's great that PowerShell will save your command history across sessions, it annoys me to find commands from other sessions in my history when I'm working in the context of a specific project. Google didn't find me a quick answer, so I did it the old fashioned way and figured it out myself. Add the following to your PowerShell profile script:

$psLocalHistoryFile = ".pslocalhistory.txt"

function Use-LocalHistory {
	if (test-path $psLocalHistoryFile) {
		Set-PSReadlineOption -HistorySavePath (Resolve-Path $psLocalHistoryFile).Path
	}
}

function Start-LocalHistory {
	Add-Content "" -Path $psLocalHistoryFile
	Use-LocalHistory
}

Use-LocalHistory

This does two things:

  1. On Startup, PowerShell will check for the existence of a .pslocalhistory.txt file and if found will use that local file instead of the global file for its command history.
  2. Defines a Start-LocalHistory function you can use to create the file if it doesn't exist and start using it.

This has worked great so far for my use case in the Visual Studio Code integrated terminal. Just don't forget to add .pslocalhistory.txt to your git/svn ignore list. Also, this does not work as expected for the explorer context menu "Open PowerShell window here" option, because apparently that starts PowerShell inside C:\Windows\System32 and then changes directory to your directory only after running your profile. Annoying, but that's not my target use case so I haven't searched for a workaround.

Custom Prompt

My Linux shell prompt, for at least 20 years now has looked as follows, and I am pretty fond of the way it looks:

[14:17:44] prijks@esgeroth:pts/0 [/var/www/] (0)
prompt-fu =>

I really like having the time and current directory in there, and the username and hostname are often helpful too, especially when you want to be sure you're on the right server before running a command. Since all that information will often fill most of a line, I added a new line before the actual prompt so there'd be an almost full line for typing the command. And then I added "prompt-fu" because it looked weird if the line was too empty. So here's what that looks like in my .cshrc file:

set prompt="[%P] %B%n@%M%b:%l [%~] (%?)\nprompt-fu =%# "

PowerShell handles the prompt a little differently — instead of setting a special variable, you define a function called Prompt. PowerShell runs this function each time it needs to display your prompt and displays the return value of that function. Here's what my PowerShell prompt looks like, which is close to but not quite the same as the above:

function Prompt {
	$LastCommandResult = $?
	"[$(Get-Date -Format "HH:mm:ss")] $($env:UserName)@$($env:ComputerName) [$($executionContext.SessionState.Path.CurrentLocation)] ($LastCommandResult)`r`nprompt-fu =$('>' * ($nestedPromptLevel + 1))"
}

And here's what it looks like:

[14:04:15] prijks@esgeroth [C:\Users\prijks] (False)
prompt-fu =>

The main differences are

  1. It doesn't display the tty (%l in the tcsh prompt variable above) since that's not really a thing in Windows
  2. The username@hostname aren't bold since that's apparently non-trivial to do
  3. The $? will show True/False as to whether the previous command executed successfully instead of the exit code of the previous command, which is what %? in tcsh does. $? has to be captured in a variable before formatting the string, since if inlined in the string it will always show true. I assume that's because it's capturing the result of Get-Date or something like that.
  4. It will show multiple >s depending on the PowerShell nesting level. This isn't something I've really ever come across before, but it's what the default PowerShell prompt does, so I figured I'd emulate that behavior. That's the '>' * ($nestedPromptLevel + 1).

And for reference, you can see the current definition of your prompt function with this command:

(get-command Prompt).ScriptBlock

The default is

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

Convincing PowerShell to run your profile

Depending on your execution policy, PowerShell may refuse to run your profile script and instead show you something like this on startup:

. : File C:\Users\username\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 cannot be loaded because running
scripts is disabled on this system. For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170.

There are a couple ways around this:

  1. Change the Execution Policy using Set-ExecutionPolicy. I still haven't made up my mind on how I feel about PowerShell execution policies, so I won't make a specific recommendation here other than to know what you're doing before you change this setting.
  2. Sign your profile. To do this, you'll need a code signing certificate. You can use a self-signed certificate or get a properly signed one. Again, I'm not going to recommend a specific approach or document how to get that certificate (it's easy enough to find instructions). Once you have that certificate, you can sign your profile using the command
    Set-AuthenticodeSignature -FilePath $profile -Certificate $cert
    
    Where $cert is obtained for example (if your code signing certificate is in your user's personal certificate store) with the command
    $cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert
    

Nobody has rated this entry.
[ previous entry ]

A brief history of the Log

The log was started towards the end of 1998, as an attempt to keep track of my wacky adventures. It was slow going at first, with entries being relatively short and rare. At this time the log was simply a set of html files, updated by hand. Hardly ideal.

In October of 1999, something was done about the situation, and a mysql database was created to hold all the log entries. There was a php front end which even included a (very limited) search interface to the log. And there was a perl script for adding new log entries. And life became much better. Log entries increased in quality and length and frequency. But life was not entirely perfect. The search feature needed improving. The keywords system I had decided to use for searches proved to be something I disliked. And I wanted to add more features, such as user comments, an improved mailing list, entry topics and titles, etc...

So, when, in August of 2000, the newest version of mysql decided to no longer accept a column called "when", I found myself in need of updating my log scripts simply to keep it functional. And thus this, the second version of my log scripties, was born. Since then, enhancements come whenever I get bored or really want them. Thus comments, ratings, graphs, random entries, and much more found their way into the code.

Fast forward to 2009 and the realization that the code base is now 10 years old and showing its age. For the first time in the history of the log, I set up a test server and began work on refactoring the code. The code was streamlined, some security improvements made, yet another new layout designed, and various other fixes, features and tweaks added. There haven't been version numbers so far, but these changes would definitely warrant a new major version number.

What does the future hold? Stay tuned to find out!

How to use this page

The navigation bar on the left side of this screen should be a good place to get started. Viewing entries individually is pretty straightforward.

A few notes on searching: The default is to search the entry text, using boolean and to join the search terms, and to display the results in short list format. However, these options can be changed on the search page.

And a few notes on general oddities: When I first started manually entering log entries, I only kept track of the date, not the time. Since the log switched to a database backend, the time has been recorded as well, but entries from before that time will show up with only a date, not a time. Similarly, I only started associating titles with entries when I wrote version 2 of the log. So entries from before then have very boring titles of the format "Log Entry ###".

I also added a "graph" feature which is kind of independent of the main log, which I use to generate graphs of datapoints I keep track of over time. This feature is currently broken.

Mailing List

Due to popular demand (i.e. Arun asked for it), I implemented a mailing list feature for log entries. However, times have changed. Since my mailing list functionality didn't support HTML and I wanted to start including pictures and other rich content in log entries, I disabled the mailing list and added a RSS feed instead. It can be found at www.esgeroth.org/log/feed.php

Other Online Journals

Quite a few of my friends now maintain online journals. Mine was first, tho! haha! Here's a list, which I by no means guarantee to be complete:

And I'd like to dedicate the space below to people formerly listed above, but who no longer maintain journals I know about.

May this serve as pressure on them to return to the online journalling world, and if they do so, I hope they'll let me know.