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.
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>.
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:
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.
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
:
And we see that in our session the Get-ScheduledTask
command is found in the PowerShellPack module with Get-Command Get-ScheduledTask
.
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:
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:
- Alias
- Function
- Cmdlet
- 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.
Recapitulation
Get-ScheduledTask
auto-imports the PowerShellPack module and runs the function from that moduleGet-ScheduledTask | Stop-ScheduledTask
loads the ScheduledTasks module and runs both functions from thereGet-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.
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.
But, let's tap \<Up Arrow> twice again, and \<Enter>.
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:
That's a bit misleading, because when we remove the ScheduledTasks module from the session and then Get-Command
again, it has magically returned.
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.