The day History stopped repeating itself

In this article I talk about how command history can fool us. So you think you're repeating that command with Up Arrow? Module precedence and Command precedence can cross our plans.

Command History

In the console we can avoid retyping a command we issued before thanks to command history. This means we can execute the same command over and over again by, either using the \<Up Arrow> or typing the first characters and hit \<F8> to cycle through the statements we typed or pasted recently.

Case

When I looked at scheduled tasks at a computer, I got surprised the output of a command was very different the second time I executed.
The computer involved ran Windows 10 OS, and had a lot of PowerShell modules available.

Reconstruction

This computer was replaced, but still on the network just to make sure we could reach files on its local drives in case we missed something in the migration process. We usually keep it for a week or two so we can get necessary stuff on a new computer. Scheduled tasks were copied to the replacement too, and needed to be disabled on the old computer.

I started with Get-ScheduledTask to see what was running or ready to run.

Get-ScheduledTask

We can't disable running tasks, so we stop those first, like this: Get-ScheduledTask | Stop-ScheduledTask

And to check again, I tapped \<Up Arrow> twice, so I saw Get-ScheduledTask again, and hit \<Enter>.

Get-ScheduledTask

Huh????
You see the output is completely different than before?

The Mystery revealed

Well, there happen to be two modules containing the Get-ScheduledTask command:

PowerShellPack

The PowerShellPack was created by James Brundage, and I had it on some computers since 2009. Usually I copy complete Module directories from one computer to another, so this one travelled with me for 8 years.

The ScheduledTasks module was created by Microsoft, and is in a default PowerShell installation.

When starting a console, neither of those modules is loaded. Since I'm on PS V5, when I call a command, the module is autoloaded ( that's with PS V3 and higher, BTW ).

Module precedence

When autoloading, PowerShell loops over the directories in the Module Path variable, in the order they are listed, and as soon as the requested module or command name is encountered, that one is loaded.

Get-Module

As you can see, D:\Documents\WindowsPowerShell\Modules is the first directory in my Module Path, and, as shown above, the PowerShellPack module is in there.
Microsoft's SheduledTasks module is in C:\Windows\System32\WindowsPowerShell\v1.0\Modules, which is third in the list.

So, after the first Get-ScheduledTask, we can see the PowerShellPack module is loaded with Get-Module:

Get-Module

And we see that in our session the Get-ScheduledTask command is found in the PowerShellPack module with Get-Command Get-ScheduledTask.

Get-Command

But, in the next step, I piped to Stop-ScheduledTask. That one is not in the PowerShellPack module, so PowerShell went through the Module Path again, and loaded the ScheduledTasks module. An other Get-Module shows that we have both modules imported now:

Get-Module

That module contains the needed command, and also another Get-ScheduledTask.
As a consequence, I now have two Get-ScheduledTask commands imported in my session!

Command precedence

When two commands with the same name are imported into a session, there are some rules to decide which one will be executed:
About Command Precedence

First, the command type is decisive. The order is:

  1. Alias
  2. Function
  3. Cmdlet
  4. Native Windows command

Both commands are of the type function, so that's a tie. The next criterium dictates that the most recently added command is executed. In this case, that's the one from the ScheduledTasks module.

Get-Command

Recapitulation

  1. Get-ScheduledTask auto-imports the PowerShellPack module and runs the function from that module
  2. Get-ScheduledTask | Stop-ScheduledTask loads the ScheduledTasks module and runs both functions from there
  3. Get-ScheduledTask runs from the ScheduledTasks module because that's added last

It wasn't lost, it just got mislaid

Strangely, when looking for a command, we get different information depending on the time we issue a Get-Command. Executing this is enough to import a module if it isn't yet loaded.

PowerShellPack

Again, we find the Get-ScheduledTask function in the PowerShellPack module, that's no surprise.

And the ScheduledTasks module has it too, as we already know.

ScheduledTasks

But, let's tap \<Up Arrow> twice again, and \<Enter>.

PowerShellPack

The PowerShellPack module doesn't have a Get-ScheduledTask anymore?

We don't see the function now because, starting with PS V3, only the commands that will be run are shown.
The documentation on MSDN says we can force Get-Command to show all available versions anyway using the -All switch. But on my system that didn't work:

allswitch

That's a bit misleading, because when we remove the ScheduledTasks module from the session and then Get-Command again, it has magically returned.

Get-Command

Conclusion

Simply put, we need to be aware that command history contains the previously executed statements and resubmits them. It does not hold the history of the actually executed commands.

Remediation

If we want to have more control over which command has to be executed, we can move our modules to directories further or earlier in the module path. This has its effects, but is cumbersome to maintain and works in only one direction. If next time I prefer another order between commands with the same name, I have to reorder again.

We can type PowerShellPack\Get-ScheduledTask to make sure the command of our choice is executed.

About Command Precedence
This article contains one line that says it all:

If you specify the path to a command, Windows PowerShell runs the command at the location specified by the path.

We can also explicitly import a module and assign a prefix.

Previous Post Next Post