Summary: Microsoft Scripting Guy, Ed Wilson, discusses the use of aliases in Windows PowerShell scripts. He covers the pros and cons in this blog.
Weekend Scripter
Microsoft Scripting Guy, Ed Wilson, is here. “You can have my aliases when you pry my cold dead fingers from my keyboard,” was not one of the emails I received during the 2011 Scripting Games, but it easily could have been. It seems that in the world of Windows PowerShell, two things really seem to cause a lot of heated discussions—one is the use of aliases and the other is what goes in a profile. These two topics can overlap, but for now, I will focus on aliases.
In general, I believe it is a best practice not to use aliases in a script. I also believe it is OK—in fact, I encourage the use of aliases at the Windows PowerShell prompt when one is working in an interactive fashion. Of course, part of the decision point around this depends on the definition of “best.” What do I mean by best?
Here is a scenario to describe one view of best. A couple of weeks ago, the southeastern portion of the United States was hit by massive thunderstorms. Charlotte, North Carolina, unfortunately, was hit with this storm. In the image below, Dr. Scripto assists with cleanup in the aftermath of “The storm that tried to eat the 2011 Scripting Games.”
As luck would have it, this was during the middle of the 2011 Scripting Games. If you follow ScriptingGuys on Twitter, you know that the Scripting Wife and I were without electricity for more than 25 hours (as were over 100,000 other people, so it was not some kind of conspiracy to keep the Scripting Wife from getting her scripts entered on time).
It is 2:00 in the morning and the Scripting Wife wakes me up.
“Can you make that UPS shut up,” she asks.
“Mmmmmrrrrr umph,” I reply in somewhat incoherent speech.
“Come on, wake up. That beeping is driving me crazy,” she complains.
“Huh? Oh, all right,” I say.
The house is completely dark. I grab a flashlight (a “torch” for my friends in the UK), and I tell the Scripting Wife to come with me into the office. I shake the mouse on my desktop to cause it wake up. I open the Windows PowerShell ISE, and I begin to write a script to shut down all 12 computers that are on my home network.
“Hmmm, I say to myself. I think I will query AD with the AdsiSearcher and do a nice LDAP query to retrieve computer objects.”
NOT!
I open the Windows PowerShell console as an Administrator and type the command that is shown here.
Stop-Computer $mycomputers -force
That is it. One reason is that I have a variable in my Windows PowerShell profile that performs the following command.
$mycomputers = Get-Content –path c:\fso\mycomputers.txt
Although it is true that I have two pretty big UPSs in my office to support my twelve computers, networking equipment, and monitor, I am not certain of exactly how long they will keep everything up. In my mind, two o’clock in the morning, during a massive thunderstorm is not the time to be conducting load tests on my power supplies. It is true that I might very well need to run that command again—in fact, with our electric power supplier, it is a virtual certainty I will need the command again, but code reuse was not my top priority at that time. I did not use any aliases in my command; but then, there is no default alias for the Stop-Computer cmdlet. There is nothing to keep one from creating an alias for it. However, I am considering really simplifying things and adding a new function to my profile. It is shown here.
Stop-MyComputers Function
Function Stop-MyComputers
{
Stop-Computer -ComputerName $mycomputers -Force
} #end function Stop-MyComputers
Set-Alias -Name sac -Value Stop-MyComputers -Description “Shutdown all computers”
When do I use aliases?
The previous function and alias combine to form one of my favorite things to do…I love to create a function with a descriptive name, and then create an alias for that function to facilitate using it from the Windows PowerShell console. Now, if a huge storm brews up in the sky, I can shut down my entire network with three key strokes—that is pretty quick. Of course, the function assumes that I will have a $mycomputers variable, but that is defined inside my profile in the same location that the Stop-MyComputers function will reside, so using the variable is a safe bet.
The only trick is to remember to update the mycomputers.txt file when I add a new computer to the network. Another approach is to query AD in your profile and use the results of your query to populate the $mycomputers variable. I might make that change one of these days. If I do, I will probably create three variables: $mycomputers, $myServers, and $myvirtualmachines. I might also create $allcomputers, which would be created by adding the three previous variables.
OK, so I got off on a bit of a tangent. Blame it on Boz Scaggs—I am listening to Boz Scaggs on my (soon to be extinct) Zune HD (I am heartbroken), and I guess I let my train of thought shuffle off to Toledo. Oh well.
I was talking about the use of aliases. Largely I believe that it is a best practice to use aliases when working interactively at the Windows PowerShell console. I feel that when working interactively at the Windows PowerShell console, the goal is to accomplish something specific. This might not always be the case, and at times, I am simply experimenting, playing, or otherwise trying out various commands. In these scenarios, I do not feel too invested in the particular command, and I wish to try out many commands in a short amount of time. Aliases help and support these goals.
When to not use aliases
When might I not want to type a series of aliases at the Windows PowerShell console? I can think of at least three scenarios. The first scenario involves typing a critical command or two. If I am getting ready to type a critical command, I want to be sure I am getting the syntax correct. Therefore, I will use the full cmdlet name, and I will use all of the named parameters. I will not rely on positional parameters.
Another scenario in which I do not use aliases at the Windows PowerShell console involves what I call command prototyping. In this scenario, I am trying to figure out the syntax of a series of commands that I will use in a Windows PowerShell script. I have the Windows PowerShell ISE opened as well as the Windows PowerShell console. I will run my Windows PowerShell commands in the console until they are performing exactly the way I intend, and then I copy the command into my script in the Windows PowerShell ISE. Because I am copying directly from the Windows PowerShell console into my script, I use tab expansion to expand the complete cmdlet name and to avoid using aliases. For me this is less work than replacing the aliases in my script code after the fact.
The third scenario is when I am teaching or making a presentation to customers. One of my pet peeves is Windows PowerShell presentations that are heavy with aliases…especially when the target audience is beginners. I believe it is extremely unfair to expect a novice user to come up to speed immediately with the intricacies of Windows PowerShell code and to learn 138 aliases for 236 cmdlets all at the same time. (I used the following commands to retrieve this information. The alias for Get-Alias is gal, and the alias for Get-Command is gcm.)
PS C:\> gal | measure
Count : 138
Average :
Sum :
Maximum :
Minimum :
Property :
PS C:\> gcm -command cmdlet | measure
Count : 236
Average :
Sum :
Maximum :
Minimum :
Property :
Why worry about aliases in the first place?
What is the big deal about using aliases anyway? If they make the code easier to type, what is the harm in using them in scripts? There are two things at work when it comes to a script. The first is that no alias is guaranteed to exist—even aliases that are created by Windows PowerShell. There are two classes (or types) of aliases in Windows PowerShell. The first alias type is those that are marked Read-only. The following command displays the Read-only aliases. (The alias for Get-Alias is gal, the ? is an alias for the Where-Object, and ft is an alias for the Format-Table cmdlet. The –a uses partial parameter completion for the autosize switch.)
PS C:\> gal | ? { $_.options -match ‘readonly’ } | ft name, options -a
Name Options
—- ——-
% ReadOnly, AllScope
? ReadOnly, AllScope
ac ReadOnly, AllScope
asnp ReadOnly, AllScope
clc ReadOnly, AllScope
clhy ReadOnly, AllScope
cli ReadOnly, AllScope
clp ReadOnly, AllScope
clv ReadOnly, AllScope
compare ReadOnly, AllScope
cpi ReadOnly, AllScope
cpp ReadOnly, AllScope
cvpa ReadOnly, AllScope
dbp ReadOnly, AllScope
diff ReadOnly, AllScope
ebp ReadOnly, AllScope
epal ReadOnly, AllScope
epcsv ReadOnly, AllScope
fc ReadOnly, AllScope
fl ReadOnly, AllScope
foreach ReadOnly, AllScope
ft ReadOnly, AllScope
fw ReadOnly, AllScope
gal ReadOnly, AllScope
gbp ReadOnly, AllScope
gc ReadOnly, AllScope
gci ReadOnly, AllScope
gcm ReadOnly, AllScope
gcs ReadOnly, AllScope
gdr ReadOnly, AllScope
ghy ReadOnly, AllScope
gi ReadOnly, AllScope
gl ReadOnly, AllScope
gm ReadOnly, AllScope
gmo ReadOnly, AllScope
gp ReadOnly, AllScope
gps ReadOnly, AllScope
group ReadOnly, AllScope
gsnp ReadOnly, AllScope
gsv ReadOnly, AllScope
gu ReadOnly, AllScope
gv ReadOnly, AllScope
gwmi ReadOnly, AllScope
iex ReadOnly, AllScope
ihy ReadOnly, AllScope
ii ReadOnly, AllScope
ipal ReadOnly, AllScope
ipcsv ReadOnly, AllScope
ipmo ReadOnly, AllScope
ise ReadOnly, AllScope
iwmi ReadOnly, AllScope
measure ReadOnly, AllScope
mi ReadOnly, AllScope
mp ReadOnly, AllScope
nal ReadOnly, AllScope
ndr ReadOnly, AllScope
ni ReadOnly, AllScope
nmo ReadOnly, AllScope
nv ReadOnly, AllScope
ogv ReadOnly, AllScope
oh ReadOnly, AllScope
rbp ReadOnly, AllScope
rdr ReadOnly, AllScope
ri ReadOnly, AllScope
rmo ReadOnly, AllScope
rni ReadOnly, AllScope
rnp ReadOnly, AllScope
rp ReadOnly, AllScope
rsnp ReadOnly, AllScope
rv ReadOnly, AllScope
rvpa ReadOnly, AllScope
rwmi ReadOnly, AllScope
sal ReadOnly, AllScope
saps ReadOnly, AllScope
sasv ReadOnly, AllScope
sbp ReadOnly, AllScope
sc ReadOnly, AllScope
select ReadOnly, AllScope
si ReadOnly, AllScope
sl ReadOnly, AllScope
sleep ReadOnly, AllScope
sort ReadOnly, AllScope
sp ReadOnly, AllScope
spps ReadOnly, AllScope
spsv ReadOnly, AllScope
start ReadOnly, AllScope
sv ReadOnly, AllScope
swmi ReadOnly, AllScope
tee ReadOnly, AllScope
where ReadOnly, AllScope
write ReadOnly, AllScope
Now, compare the results of this list with the output for the following listing of aliases that are not marked Read-only.
PS C:\> gal | ? { $_.options -notmatch ‘readonly’ } | ft name, options -a
Name Options
—- ——-
cat AllScope
cd AllScope
chdir AllScope
clear AllScope
cls AllScope
copy AllScope
cp AllScope
del AllScope
dir AllScope
echo AllScope
epsn AllScope
erase AllScope
etsn AllScope
exsn AllScope
gjb AllScope
gsn AllScope
h AllScope
history AllScope
i None
icm AllScope
ipsn AllScope
kill AllScope
lp AllScope
ls AllScope
man AllScope
md AllScope
mount AllScope
move AllScope
mv AllScope
nsn AllScope
popd AllScope
ps AllScope
pushd AllScope
pwd AllScope
r AllScope
rcjb AllScope
rd AllScope
ren AllScope
rjb AllScope
rm AllScope
rmdir AllScope
rsn AllScope
sajb AllScope
set AllScope
spjb AllScope
type AllScope
wjb AllScope
The r alias is mine; it is short for Invoke-History. But that is beyond the point. If you look at the results of the previous list, you see things like md, rd, rmdir, and ls, cat, and man. I call these aliases compatibility aliases because they make Windows PowerShell compatible with UNIX or Windows shell environments. Therefore, a Windows administrator probably knows how to use dir to produce a list of files from the file system. It is easier to use the dir command, especially early on, to tell that Windows admin you must learn the gci or Get-ChildItem.
The first set of aliases (the ones marked Read-only) are not compatible with either Windows or UNIX. The commands only have meaning within Windows PowerShell itself. Many times, they are the first initials of stressed syllables. This is seen in gci for Get-ChildItem or ebp for Enable-BreakPoint, gps for Get-ProceSs, or even gsv for Get-SerVice. Nevertheless, this is not always the case. For example, gmo for Get-Module does not seem to follow the pattern.
Therefore, if you do not like gmo for Get-Module, you can remove it. But how does one remove an alias? The logical first step is to use the Get-Command cmdlet to see if there is a cmdlet named Remove-Alias. As shown in the following output, such a command does not exist.
PS C:\> Get-Command -Noun alias
CommandType Name Definition
———– —- ———-
Cmdlet Export-Alias Export-Alias [-Path] <String> [[-Name]…
Cmdlet Get-Alias Get-Alias [[-Name] <String[]>] [-Exclu…
Cmdlet Import-Alias Import-Alias [-Path] <String> [-Scope …
Cmdlet New-Alias New-Alias [-Name] <String> [-Value] <S…
Cmdlet Set-Alias Set-Alias [-Name] <String> [-Value] <S…
Remember the *-item cmdlets? The reason they are all named something about an item is that an item can be anything. It all depends on the provider. In Windows PowerShell, there are many different providers, each of which makes use of the *-item cmdlets. As shown here, on my laptop, there are eight different providers. A quick review of the list will show to even the most casual observer that there is a provider named Alias, and that the Alias provider exposes a drive named Alias.
PS C:\> Get-PSProvider
Name Capabilities Drives
—- ———— ——
WSMan Credentials {WSMan}
Alias ShouldProcess {Alias}
Environment ShouldProcess {Env}
FileSystem Filter, ShouldProcess {C, Bin, D, E}
Function ShouldProcess {Function}
Registry ShouldProcess, Transactions {HKLM, HKCU, HKCR}
Variable ShouldProcess {Variable}
Certificate ShouldProcess {cert}
A review of the *-item cmdlets shows that there is a Remove-Item cmdlet. This is shown here.
PS C:\> Get-Command -Noun item
CommandType Name Definition
———– —- ———-
Cmdlet Clear-Item Clear-Item [-Path] <String[]> [-Force]…
Cmdlet Copy-Item Copy-Item [-Path] <String[]> [[-Destin…
Cmdlet Get-Item Get-Item [-Path] <String[]> [-Filter <…
Cmdlet Invoke-Item Invoke-Item [-Path] <String[]> [-Filte…
Cmdlet Move-Item Move-Item [-Path] <String[]> [[-Destin…
Cmdlet New-Item New-Item [-Path] <String[]> [-ItemType…
Cmdlet Remove-Item Remove-Item [-Path] <String[]> [-Filte…
Cmdlet Rename-Item Rename-Item [-Path] <String> [-NewName…
Cmdlet Set-Item Set-Item [-Path] <String[]> [[-Value] …
OK, so it should be a simple matter to use the alias drive and remove an alias. Let’s try it.
PS C:\> Get-Alias gmo
CommandType Name Definition
———– —- ———-
Alias gmo Get-Module
PS C:\> Remove-Item alias:gmo
Remove-Item : Alias was not removed because alias gmo is constant or read-only.
At line:1 char:12
+ Remove-Item <<<< alias:gmo
+ CategoryInfo : WriteError: (gmo:String) [Remove-Item], SessionStateUnauthorizedAcce
ssException
+ FullyQualifiedErrorId : AliasNotRemovable,Microsoft.PowerShell.Commands.RemoveItemCommand
Well, that did not work. It says the alias might be constant or Read-only. It does not specify a remedy (interestingly enough). But I know from working with the variable drive that I can remove a Read-only variable by using the –Force switch. Let’s try it here.
PS C:\> Remove-Item alias:gmo –Force
The command did not generate an error; however, I am not convinced that it actually worked because nothing was returned. The way to check it is to use the Get-Alias cmdlet to retrieve it. I do this, and an error is generated that states the alias gmo does not exist. This is shown here.
PS C:\> Get-Alias gmo
Get-Alias : This command cannot find a matching alias because alias with name ‘gmo’ do not exist.
At line:1 char:10
+ Get-Alias <<<< gmo
+ CategoryInfo : ObjectNotFound: (gmo:String) [Get-Alias], ItemNotFoundException
+ FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand
What if I later suffer from “deletion regret?” Am I stuck? Do I have to reinstall Windows 7 just to get that silly alias back?
The answer, of course, is, “No, I do not have to reinstall Windows 7 to get the gmo alias back.” All I have to do is to close Windows PowerShell, and then open it. When I do that, I use the Get-Alias cmdlet again to see if it is there. As shown here, it is.
PS C:\> Get-Alias gmo
CommandType Name Definition
———– —- ———-
Alias gmo Get-Module
Now, if the alias returns every time I start Windows PowerShell, then what is the harm? Maybe I really hate the gmo alias, and I never want to look at it again. All I need to do is to add my previous command to my Windows PowerShell profile, and the alias will never again appear, unless I choose to use it for something.
The real danger of using an alias in a script
This then, is the danger of using aliases in a script. I will give a quick story, and end this. I was teaching a Windows PowerShell class in Copenhagen, Denmark a few years ago, and one of my students spent all of his free time (and long into the night) working on his profile. He spent hours and hours making his Windows PowerShell environment look like a command environment he had on a different system. He created various variables, customized his prompt, changed the background color, modified the Windows PowerShell window title and everything else he could think of to do. One of the things he did was remove the alias ls (a compatibility alias for Get-ChildItem). After he did that, he redefined the alias to Set-Location.
The interesting thing is that both commands accept a string as the default argument…and that string is a path. This means that a script that uses ls to get a directory listing would instead change the working location of the script to that directory. Depending on what the following commands were, the results could be disastrous, and no error would be generated because the signatures are the same.
For my student, this decision made sense—after all, aliases are a convenience factor, and because he never intended to use ls for Get-ChildItem, there is no harm. The big problem, of course, would be if he ran a script from someone else who used that alias for Get-ChildItem.
You might not always control where and how a script will be run. You cannot be guaranteed that an alias will exist in an environment. And if that alias does exist, you cannot be guaranteed that it means the same thing everywhere. Of, course, you can always check to see if an alias exists, and if it means what you expect it to mean. An example of this is shown here.
if ((Get-Alias ls).definition -match ‘Get-Childitem’) { ls }
But using code such as this really defeats the purpose of using an alias. In the end, it is a best practice to avoid using aliases in enterprise production-level scripts. The same is true when it comes to using positional arguments or partial parameter names.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
0 comments