Windows PowerShell, Microsoft's new object-oriented console shell, has three out-of-box behaviors that might initially confuse veterans of other console shells, including Windows-based cmd.exe and command.com and UNIX-based consoles. In its default configuration, PowerShell
- doesn’t search the working directory for executables
- associates scripts with your default text editor and not PowerShell, so they’re edited instead of invoked
- doesn’t execute scripts within PowerShell at all
These characteristics are all part of a more secure command-shell design. PowerShell developer Lee Holmes has blogged about them in detail (you can check out this blog at http://www.leeholmes.com/blog/demonstrationofmonadssecurityfeatures.aspx), demonstrating how these characteristics produce a strong defense against malicious shell scripts and showing how to handle script signing on the cheap without implementing full-blown certificate services. Holmes’s comments are also now included in the script-signing documentation in PowerShell; you can see them by typing the following at a PowerShell prompt:
PS> man about_signing
Although they deliver tighter command-shell security, these behaviors can be frustrating if you’re used to other consoles. Let’s look at how these three PowerShell security features can affect your real-world shell usage and how to resolve problems they might cause you.
Searching for Executables
When you type the name of an external executable at a command prompt in cmd.exe or command.com, the shell searches for the executable in your current directory before looking through the directories listed in the %PATH% variable. Although convenient at times, this behavior can be a security hole on multi-user systems.
Tools frequently traverse directories to simplify file manipulation. The automatic working-directory search means that a malicious file in the current directory can spoof any executable that your script doesn’t invoke by using a complete path. Because partially trusted or untrusted users might have ownership rights to many directories (sometimes the most sensitive directories), your script or even interactive commands might be run while in a folder containing untrusted content.
To eliminate this avenue of attack, PowerShell doesn’t search the working directory for external executables. Although the security benefits of not searching the current directory are clear, this change can be awkward for users who access tools by changing their working directory. For example, I frequently use Windows support tools such as acldiag, which lets me work with access control lists (ACLs). On my system, the path to this file is C:\Program Files\Support Tools\acldiag.exe. Although support tools add their folders to the system path when installed, I usually police my system path to remove all folders except the core ones Windows provides. So in the cmd.exe shell, I can simply change my directory by using the command
cd "C:\Program Files\Support Tools"
and acldiag (as well as other tools in that folder) just work. If I try this change from PowerShell, I get an error, such as the following:
'acldiag' is not recognized as a Cmdlet, function, operable program, or script file.
At line:1 char:7
How can you run the acldiag command from PowerShell in this scenario? The simplest option if you’re already changing to that directory is to invoke the executable by using a relative path. As with Windows and UNIX-based command shells, in PowerShell, you can prefix acldiag with the dot backslash (.\) characters to specify that you want to use a command found in the current directory:
Note that using ./acldiag also works; PowerShell recognizes both the backslash (\) and the forward slash (/) as a directory delimiters.
Alternatively, you can quickly enter the full path to the command without changing directories by using tabs for automatic path expansion. Tabbing for path completion works in PowerShell just as it does in cmd.exe in Windows XP and later. However, you’ll notice one minor difference when expanding the fully qualified path to an executable in PowerShell. If you’re working in the root of the C drive, cmd.exe expands the path to acldiag as “C:\Program Files\Support Tools\acldiag.exe”. However, PowerShell's expansion depends on whether you explicitly include a quote mark when you begin typing the path. If you start with C:\Pr, for example, PowerShell expands the path as & "C:\Program Files\Support Tools\acldiag.exe"; if you start with "C:\Pro, however, it expands the path as "C:\Program Files\Support Tools\acldiag.exe" (no ampersand—&—character).
What's the difference? In PowerShell, unlike in cmd.exe, characters surrounded by quotes are just text, not a command. In PowerShell, if you type the phrase "C:\Program Files\Support Tools\acldiag.exe" (with quotes) and press Enter, PowerShell echoes C:\Program Files\Support Tools\acldiag.exe. However, the & character is PowerShell's invocation operator; it tells PowerShell to treat as a command any text that follows.
Installing with Scripts Disabled
Many users are surprised to find that PowerShell doesn’t execute PowerShell scripts when you install it. For example, if the script file Demo-Script.ps1 is in the PowerShell search path and you try to execute it within PowerShell by entering Demo-Script, you’ll get an error message that reads something like this:
The term 'Demo-Script' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
If you specify the complete path to the script—such as C:\bin\Demo-Script.ps1—you’ll instead get the following error message:
File C:\bin\Demo-Script.ps1 cannot be loaded because the execution of scripts is disabled on this system. Please see "get-help about_signing" for more details.
PowerShell developers chose to disable script execution as part of Microsoft’s secure-by-default initiative. In my experience, administrative users will want to execute scripts, so you’ll likely need to change this default configuration when you install PowerShell.
The script-execution restriction comes from a simple registry setting that PowerShell monitors, so you can modify the setting by using policies, your favorite registry-editing tool, or even an interactive PowerShell session. The change will take effect immediately, even during PowerShell sessions.
The registry setting governing PowerShell script execution is in the registry path HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell in a string value named ExecutionPolicy. The default value is Restricted; other options include AllSigned (scripts run only if signed by a trusted source) and RemoteSigned (locally created scripts run, but remotely created ones run only if signed). If you’re interested in using certificates but don’t have a certificate server available, read the PowerShell about_signing Help by entering the following at a PowerShell prompt:
PS> get-help about_signing
If you have a sizable network, using mixed settings might be effective. For example, an administrator might want to allow RemoteSigned script execution on a certain machine for testing, then sign completed scripts before deploying them for the network at large, which would use the AllSigned mode.
As I noted earlier, you can also modify the script-execution setting from within PowerShell. Although you can make the change by accessing the registry value directly, PowerShell comes with the built-in commands Get-ExecutionPolicy and Set-ExecutionPolicy for checking and modifying the script-execution policy. To view the current policy, run Get-ExecutionPolicy:
You can then change the PowerShell script execution policy by using the Set-ExecutionPolicy cmdlet; note that the policy name isn’t case sensitive:
Again, this change takes effect immediately; you don’t have to exit the PowerShell shell and restart it, which is convenient if you’re using PowerShell remotely.
For security, if the registry entry for ExecutionPolicy is missing or is set to something invalid (such as the misspelled RemoteSinged), PowerShell behaves as though it were restricted. Also, if you reinstall PowerShell, it automatically resets this value to Restricted at install time.
Treating PowerShell Scripts as Text Files
Even with PowerShell script execution enabled, you can’t run a PowerShell script from Windows directly. If you click a PowerShell script in the Windows Explorer shell or type its path in a console shell such as cmd.exe that uses Windows file associations, Notepad—not PowerShell—opens the script. Working around this behavior on a case-by-case basis is a good idea, and I’ve built a tool to allow automatic creation of shortcuts to execute PowerShell scripts. But before talking about the tool, let’s look at why PowerShell doesn’t tell Windows how to invoke PowerShell scripts.
Windows treats the PowerShell script as an unknown file containing arbitrary data. To run the script, you would need to invoke it from another executable that knows the path to PowerShell. This restriction means a user can’t accidentally invoke a PowerShell script attached to a Web page or an email message; the user would be able only to view the script as a text file or possibly download it. Likewise, a user surfing the local file system can’t accidentally invoke local administrative scripts that might have significant impact on a system. Finally, this behavior prevents scripts from exploiting the cmd.exe and explorer.exe "search working directory first" hole I explained earlier.
Although you can create shortcuts to execute PowerShell scripts, there are several barriers to having them work as anticipated. First, PowerShell scripts might be designed for specific PowerShell hosts. PowerShell runs within a hosting application, with the default host being powershell.exe. However, you can also create a custom host by implementing the PSHost class. If a PowerShell script uses features of a particular host application, you need to make sure the shortcut specifies the correct host application.
In addition, the syntax that works in shortcut arguments, the Run box, the cmd.exe shell, and the Task Scheduler interface won't work when passed to PowerShell without extra processing. Finally, PowerShell scripts are designed to work best as part of a process involving several scripts, functions, or PowerShell commands. Their value is reduced when you try to run them as clickable tools, unless you have specifically designed them for that role.
For the times you need click-launch capability for specific scripts, I’ve written a convenient Windows Scripting Host (WSH) script called New-PowerShellScriptLink.vbs, which Listing 1 shows, to automatically create launcher shortcuts to PowerShell scripts. You can leave scripts in locations secured by NTFS file permissions, and WSH can automatically handle problems such as correctly escaping the script path and finding the path to the powershell.exe host. Although it might seem ironic that I implemented this functionality as a WSH script instead of a PowerShell script, using WSH was the most logical choice because it works as a Windows drop target: You can drag a PowerShell script onto New-PowerShellScriptLink.vbs to create the shortcut without typing.
To get started, first move or save the PowerShell scripts where you want to keep them long term, because the shortcut will point to that location. If you later move the script, the shortcut will no longer work. Next, make sure you have appropriate security on the physical script file: Only users who absolutely need to should be able to edit the script because anyone with editing privileges can insert any code they want.
Now, you can drag and drop one or more PowerShell scripts onto New-PowerShellScriptLink.vbs to create shortcuts for each script in the same folder as the original script. You can move the shortcuts to wherever you want them. You can also run New-PowerShellScriptLink.vbs from the command line, with each script supplied as an argument. If you're running New-PowerShellScriptLink.vbs from a command line and the target script has spaces in its path, make sure you quote the path so that it isn’t interpreted as multiple script names.
If the target script is C:\Documents and Settings\All Users\Documents\PowerShell\Test-Firewall.ps1, the new shortcut will be in the folder C:\Documents and Settings\All Users\Documents\PowerShell\ and will be named Test-Firewall.ps1.lnk. In the Explorer shell, the .lnk file suffix is never displayed, so you’ll see two separate files with both showing Test-Firewall.ps1 as their display name. However, you should easily be able to distinguish the shortcut even in Explorer. The shortcut will have whatever shortcut icon overlay your system uses and will have the powershell.exe executable icon because it’s technically a shortcut to powershell.exe.
Working with Tighter Security
Although certain features in PowerShell’s design might seem strange at first, they provide a more secure command shell. And with these tips and workarounds, you can implement the command-shell functionality you need without sacrificing safety or efficiency.