PowerShell is the best unkept secret in the Windows developer world. Although it is highly lauded by its users, it has not gained a lot of traction with the Windows developer crowd. It is all the rage with the Windows admin crowd, however. Powershell is tightly integrated with the operating system, .NET framework, and most Microsoft server products (SQL Server, IIS, TFS, etc).
In this article, I discuss why PowerShell should be used more by developers and I show you some useful stuff you can do with PowerShell. Finally, I'll go over my PowerShell profile and show how I use PowerShell every day to make my life easier.
PowerShell: The Conspiracy Theory
Historically, Windows had the worst shell ever: command.com, cmd.exe coupled with the worst terminal Window ever. That had two important outcomes:
- A strong aversion to working from the command-line on Microsoft operating systems
- Excellent GUI tools to perform tasks
PowerShell 1.0 became available in 2006 for Windows XP. However, it became ubiquitous (that is, installed by default) only in Windows 7 and Windows Server 2008 R2. During the five year delay, the trend toward mobile apps, Web applications, and the cloud emerged and the attention of the developer crowd shifted away from PowerShell.
But this neglect is unwarranted. Let me quickly show you why before diving into the meat of the article. Here are some cool one-liners.
Generate the integer between 5 and 20 (inclusive):
5..20
Pick a random number between 1 and 100:
1..100 | Get-Random
Play a video:
(New-object –COM WMPlayer.OCX).openPlayer("Your video")
Calculate total size of current directory in MB:
"{0:N2}" -f ((Get-ChildItem . -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB)
Progress bar while computing total size of directory (OK, not a one liner, but very cool)
$files = Get-ChildItem . -Recurse
$total = 0
For ($i = 1; $i -le $files.Count-1; $i++)
{
Write-Progress -Activity "Calculating total size..." -status $files[$i].Name -percentComplete ($i / $files.Count * 100)
$total += $files[$i].Length
Start-Sleep -Milliseconds 50
}
Write-Host "Total size: $($total / 1MB)"
#Write-Host "Total size: {0:N2}MB" -f ($total / 1MB)
Introductory PowerShell
PowerShell is object-based. UNIX (Linux, Mac OS X) shells are text-based. The idea is that using the shell pipeline on the UNIX variants, you can glue together any number of small tools that eat and spit text lines to create arbitrarily complex work-flows. This works and gives you infinite flexibility. But, at a price. The price is that you often have to perform some elaborate text processing between commands in the pipeline to adapt the output of one command to the input expected by the next one. This can lead to cumbersome and brittle pipelines that are both wonderfully concise, and yet completely unintelligible for the uninitiated.
PowerShell is different: Commands eat and spit out .NET objects. This approach is better suited to the Windows environment where many of the system facilities are exposed through APIs and object models (Registry, COM, .NET, ADSI, WMI, Certificate store). This aspect makes it easy to user PowerShell for GUI front ends. For example, Figure 1 shows a form with a check box, date picker and OK button that was easy to assemble using free tools such as Sapien's PrimalForms GUI generator. Dr. Dobb's has previously discussed the ins and outs of writing GUIs in Powershell by hand.
Figure 1: A GUI applet written in Powershell.
The "How" of PowerShell
PowerShell was designed for Windows system administrators and power users. It was optimized for interactive use on the command-line first, and for scripting second. If you have some experience with UNIX shells, PowerShell will sometimes feel very familiar and sometimes quite alien. The reason is that the UNIX-based operating systems and Windows evolved along very different trajectories. UNIX shells were optimized to manage a text-based ecosystem in which everything is or is made to appear as a text file. PowerShell was designed to manage the API-based Windows ecosystem.
The PowerShell team did a good job evaluating the needs of their target audience. They also looked at how sysadmins operate in the UNIX environment, then compared this environment to the Windows environment, distilled all this input, and came up with an architecture and design for a consistent, flexible, and extensible command execution engine and shell.
Before we get started, let's do a quick refresher on Powershell: $variable describes a variable name. Most commands rely on Cmdlets, which are named objects such as Set-ExecutionPolicy. Many of these commands have multiple built-in aliases. For example, the Get-ChildItem command has three official aliases: dir, ls, and gci. The dir alias is there to ease the transition for old Dos/Windows hands. The ls alias is to placate the UNIX crowd, and gci is there because if you have no previous experience with other shells, then it's the "PowerShell-y" way to define aliases (first letter of every word in the command). This is a very thoughtful, considerate, and humane approach that, when added to the fact that PowerShell is case insensitive, makes reading other people's code (or even your own code from a few months ago) challenging.
Finally, a word about operators: The comparison operators in PowerShell use mnemonics like -lt (less than), -gt (greater than), and -eq (equal to), instead of the familiar symbols = (or ==), !=, <, and >. The reason is a tip of the hat to ancient tradition. In particular, the long-time shell output redirection symbol is '>'. PowerShell designers wanted to keep this tradition, so they created the set of comparison operators -lt, -eq, -neq, -le, -ge, which takes a little getting used to.
Starting Up
Presuming you have PowerShell installed, you need to set the security policy to allow the running of scripts. You do this by typing Set-ExecutionPolicy RemoteSigned. This will allow you to launch PowerShell scripts locally. PowerShell, being an administrator's tool first and foremost, is configured by default to not allow running scripts as a security measure. This is a sensible default on remote machines, but you will definitely want to run scripts on your machine.
Next, set your profile. The PowerShell profile is where you can collect all the stuff you use commonly, so it's available in every session. There could be several profile files with the more specific files overriding more general ones. To create a PowerShell profile that applies to all users type:
new-item -path $env:windir\System32\WindowsPowerShell\v1.0\PowerShell_profile.ps1 -itemtype file -force
The path to the current profile is always in the $profile global variable. You can verify it exists by typing: Test-Path $profile.
Once you have a profile, you can edit the file and add your stuff. Remember to execute the profile after each change, so your changes will be available in the current . $profile session.
Don't try to learn PowerShell in a methodological manner. Play with it. Even playing may feel strange at first. The best way to go is to look for cool stuff other people have done and just stick it in your profile and use it.
Once you feel comfortable in the PowerShell environment, start expanding your horizons. Check your profile and look at all the stuff you stole from other people. Read a little about the commands they use and their parameters. Try to understand the tricks and idioms they use. PowerShell has an excellent help system. Explore it. Get-Help (or just the the alias help) is your friend. If it's too much work, you can also just type a Cmdlet name followed by -? to get help on it. For example, Get-Service -?.
If you want more extensive help with explanations of each parameter and lots of examples, add the -detailed switch:
help Get-Service -detailed
Beyond Cmdlet help, PowerShell also provides meta help about different topics. To find out which topics are available, type:
help about_
and PowerShell will display all the topics. Then you can just pick one, such as 'History' and type: help about_history.
A Developer's PowerShell Profile
I'm not a system administrator and I have a strong aversion to long pipelines of commands with convoluted parameters. If you ask me how to find a service that was installed at-most three days ago and its name is longer than 10 characters, I'll have to look it up and do some trial and error until I get it right. I use PowerShell just like I use Bash on Mac OS X and Linux. I define a bunch of functions and aliases in my profile and I rely on them for my command-line needs. I rarely type commands with parameters. This saves me some typing, but more importantly, it saves me from the mental burden of keeping most of the complexity in my head.
Navigation
I almost always work in deeply nested folder hierarchies. I hate typing long path names on the command-line, and even tab completion can take too long if you have many sibling sub-directories all with the same prefix. I also regularly visit a relatively small set of locations. So my standard practice, if I want to run a command that accepts a path, is to navigate to this location and run "<cmd> .". To be able to navigate quickly to various locations, I define little functions that consist of a series of 'cd' statements:
function cdr { cd 'c:\r' }
function cdp { cdr; cd ProjectCool }
function cdb { cdr; cd Branches }
function cdt { cdr; cd Testing }
Locations and programs
There are several common locations I use multiple times in the profile or full paths to various programs:
$program_files = 'C:\Program Files (x86)\' $editor = $program_files + 'Notepad++\notepad++.exe' $rabbitmq_sbin = $program_files + "RabbitMQ Server\rabbitmq_server-2.8.2\sbin\" $rabbitmq_ctl = $rabbitmq_sbin + 'rabbitmqctl.bat' $rabbitmq_server = $rabbitmq_sbin + 'rabbitmq-server.bat' $rabbitmq_service = $rabbitmq_sbin + 'rabbitmq-service.bat' $dot_net_framework = 'C:\Windows\Microsoft.NET\Framework\v4.0.30319\' $install_util = $dot_net_framework + "installutil.exe" $root_dir = 'c:\root'


