May 1st, 2011

Best Practice for Using Aliases in PowerShell Scripts

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.”

Image of Dr. Scripto

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

Author

0 comments

Discussion are closed.