Hey, Scripting Guy! How Can I Write and Run a Windows PowerShell Script?

ScriptingGuy1

Hey, Scripting Guy! Question

Hey, Scripting Guy! I appreciate the command-line stuff you are showing, but to be honest, I am not interested in returning to the DOS days to administer my network. I am not a UNIX administrator-I am a Windows guy. If I wanted to do UNIX kinds of things, I would download the latest *nix distro, obtain a silly haircut, grow facial hair, and be a UNIX guy. I want to learn Windows PowerShell scripting. How about throwing me a bone?

– MT

SpacerHey, Scripting Guy! Answer

Hi MT,

Ed here. Several years ago, the Scripting Wife and I were on holiday in Germany. Even though I am usually a very detail-oriented person, for this week of holiday, we decided to buy a weeklong rail pass and backpack around Germany. The good thing about the weeklong rail pass is that you do not need reservations. You can in fact hop on and off trains at will. This required us to plan no itinerary. Because everything in Germany is very cool, it really did not matter where we ended up. In effect, we made no choice in our destination. One day, we ended up in Fussen, Germany, which is in Bavaria. As we were walking around, we happened to look up and see this castle:

Image of the Neuschwanstein Castle

 

This time, we had to choose. We could walk to the Neuschwanstein Castle, or we could take a horse-and-buggy ride. As with hiking up long, winding, tree-lined mountain paths, the decision to manage your Windows Server from the command line by using Windows PowerShell is a choice. Whether it is easier to do that than to use the graphical user interface (GUI) tools depends in part on your definition of easier. Is it easier to hike up the side of a mountain, or to stand in line for 45 minutes while you wait for a free horse and carriage? If Windows PowerShell is already running, I can type gps (the alias for Get-Process) quicker than I can right-click the Taskbar, and select Task Manager from the shortcuts menu. The nice thing is we have the choice.

This week we are looking at scripting Windows PowerShell. Windows PowerShell is installed by default on Windows Server 2008 R2 and Windows 7. It is an optional installation on Windows Server 2008, and a download for Windows Vista, Windows XP, and Windows Server 2003. The Windows PowerShell Scripting Hub is a good place to start using Windows PowerShell. An excellent book for learning Windows PowerShell is the Microsoft Press book, Microsoft Windows PowerShell Step by Step. This book has many hands-on labs and uses real-world examples to show the use of Windows PowerShell.

Everything we looked at in last week’s series of “Hey, Scripting Guy!” articles applies to writing scripts. In its most basic form, a Windows PowerShell script is a collection of Windows PowerShell commands. Let’s take, as an example, this command from last Wednesday’s “Hey, Scripting Guy!” article (that is the article that had the picture of the Shark I took while I was diving in Little Cayman; that’s my favorite shark picture.)

Get-Process notepad | Stop-Process

We can put that command in a Windows PowerShell script and run it directly as it is written. The StopNotepad.ps1 script is shown here:

Image of the StopNotepad.ps1 script in Notepad

 

To create a Windows PowerShell script, we only have to copy the command to a text file and save the file with a .ps1 extension. If you double-click the file, it will open in Notepad by default. To run the script, you can open the Windows PowerShell console, and drag the file to the console if you are running Windows Server 2003 or Windows XP. Beginning with Windows Vista, dragging to the command line was disabled because of potential security implications. However, Windows Vista introduced a very helpful command that you can use instead of dragging and dropping to a console, and that is the Copy as Path command. Hold down the SHIFT key, right-click the .ps1 file, and click Copy as Path in the shortcuts menu, as shown here:

Image of the Copy as Path command, which was introduced in Windows Vista to simplify working with long paths inside the Windows PowerShell console

 

Windows 7 has fixed dragging and dropping to the command line, and it also keeps the Copy as Path action option, giving you the best of both worlds. Now you are ready to run your first script, and you copy the path of the script and right-click inside the Windows PowerShell console to paste the path of your script there. You just displayed a string that represents the path of the script as seen here:

PS C:\> “C:\BestPracticesBook\StopNotepad.ps1”
C:\BestPracticesBook\StopNotepad.ps1

In Windows PowerShell when you want to display a string in the console, you put it in quotation marks. You do not have to use Wscript.Echo or similar commands such as you used in VBScript. It is easier and simpler, but it is something that takes getting used to. If you remove the quotation marks and press ENTER, and you receive an error message. “What now?” you may ask. The error message, seen in the following image, is related to the script execution policy that disallows the running of scripts:

Image of the error message generated when attempting to run a Windows PowerShell script

 

By default, support for running Windows PowerShell scripts is disallowed. Script support can be controlled by using Group Policy, but if it is not and you have Administrator rights on your computer, you can use the Set-ExecutionPolicy cmdlet to turn on script support. There are four levels that can be enabled by using the Set-ExecutionPolicy cmdlet:

Restricted: Does not load configuration files or run scripts. This is the default.

AllSigned: Requires that all scripts and configuration files be signed by a trusted publisher, including scripts that you write on the local computer.

RemoteSigned: Requires that all scripts and configuration files downloaded from the Internet be signed by a trusted publisher.

Unrestricted: Loads all configuration files and runs all scripts. If you run an unsigned script that was downloaded from the Internet, you are prompted for permission before it runs.

In Windows PowerShell 2.0 two additional levels are added.

Bypass: Nothing is blocked and there are no warnings or prompts.

Undefined: Removes the currently assigned execution policy from the current scope. This parameter will not remove an execution policy that is set in a Group Policy scope.

With so many choices available to you for a script execution policy, you may be wondering which one is appropriate for you? The Windows PowerShell team recommends the RemoteSigned setting, stating that it is “appropriate for most circumstances.” Remember that, even though descriptions of the various policy settings use the term “Internet,” this may not always refer to the World Wide Web or even to locations outside your own firewall. This is because Windows PowerShell obtains its script origin information by using the Internet Explorer Zone Settings. This basically means anything that comes from a computer other than your own is in the Internet Zone. You can change the Internet Explorer Zone Settings by using Internet Explorer, the registry, or Group Policy.

In Windows PowerShell 1.0, when you use the Set-ExecutionPolicy cmdlet to change the script execution policy, the change occurs silently and without incident. In Windows PowerShell 2.0, confirmation of the command is required:

Image of the the Set-ExecutionPolicy cmdlet requiring confirmation in Windows PowerShell 2.0

 

If you do not want to see the confirmation message when you change the script execution policy on Windows PowerShell 2.0, you use the -force parameter and the behavior will be the same as it was in Windows PowerShell 1.0. Unfortunately, Windows PowerShell 1.0 does not have a -force parameter for the Set-ExecutionPolicy cmdlet and therefore attempts to cause this parameter to fail. A batch file command that will change the script execution policy on Windows PowerShell 2.0 is seen in  ChangeScriptExecutionPolicyPs2.bat.

REM ChangeExecutionPolicyPs2.bat
REM Ed Wilson, 4/27/2009
REM Sets the script execution policy to remotesigned. Other values:
REM AllSigned, Restricted, Unrestricted, ByPass
cls
Powershell -noexit -command “& {Set-ExecutionPolicy remotesigned -Force}”

To perform the same command on Windows PowerShell 1.0, you would remove the -Force parameter. The rest of the batch file can remain the same as seen in ChangeExecutionPolicyPs1.bat.

REM ChangeExecutionPolicyPs1.bat
REM Ed Wilson, 4/27/2009
REM Sets the script execution policy to remotesigned. Other values:
REM AllSigned, Restricted, Unrestricted
cls
Powershell -noexit -command “& {Set-ExecutionPolicy remotesigned}”

Now that you have everything set up to enable script execution, we can run our StopNotepad.ps1 script. This is seen here.

Get-Process Notepad | Stop-Process

If an instance of the Notepad process is running, everything is groovy. However, if no instance of Notepad is running, the error seen here is generated:

Get-Process : Cannot find a process with the name ‘Notepad’. Verify the process
 name and call the cmdlet again.
At C:\Documents and Settings\ed\Local Settings\Temp\tmp1DB.tmp.ps1:14 char:12
+ Get-Process  <<<< Notepad | Stop-Process

The first part of the error message gives a description of the problem. In this example, it could not find a process with the name of Notepad. The second part of the error message shows the position in the code where the error occurred. This is known as the position message. The first line of the position message states the error occurred (in our script) on line 14. The second portion has a series of arrows that point to the command that failed. In our example, the Get-Process cmdlet command is the one that failed. This is seen here:

At C:\Documents and Settings\ed\Local Settings\Temp\tmp1DB.tmp.ps1:14 char:12
+ Get-Process  <<<< Notepad | Stop-Process

The easiest way to eliminate this error message is to use the -erroraction parameter and specify the SilentlyContinue value. This is basically the same as using the On Error Resume Next command from VBScript. The really useful feature of the -erroractionparameter is that it can be specified on a cmdlet by cmdlet basis. In addition, there are four values that can be used:

SilentlyContinue

Continue (the default value)

Inquire

Stop

In the StopNotepadSilentlyContinue.ps1 script, we add the -erroraction parameter to the Get-Process cmdlet to skip past any error that may arise if the Notepad process does not exist. To make the script easier to read, we break the code at the pipeline character. The pipeline character is not the line continuation character. The back tick (`) character is used when a line of code is too long and must be broken into two physical lines of code. Remember that the two physical lines form a single logical line of code. An example of how to use line continuation is seen here:

Write-Host -foregroundcolor green “This is a demo ” `
          “of the line continuation character”

The StopNotepadSilentlyContinue.ps1 script is shown here:

Get-Process -name Notepad -erroraction silentlycontinue | 
Stop-Process

Because we are writing a script, we can take advantage of some features of a script. One of the first things we can do is use a variable to hold the name of the process to be stopped. This has the advantage of enabling us to easily change the script to allow for stopping of processes other than Notepad. All variables begin with the dollar sign. The line that holds the name of the process in a variable is seen here:

$process= “notepad”

Another improvement to the script is one that provides information about the process that is stopped. The Stop-Process cmdlet returns no information when it is used. But by using the -passthru parameter, the process object is passed along the pipeline. We use this parameter, and pipeline the process object to the ForEach-Object cmdlet. We use the $_ automatic variable to refer to the current object on the pipeline and select the name and the process ID of the process that is stopped. The concatenation operator in Windows PowerShell is the plus (+) sign and we use it to display the values of the selected properties in addition to the strings completing our sentence. This line of code is seen here:

ForEach-Object { $_.name + ‘ with process ID: ‘ +  $_.ID + ‘ was stopped.’}

The complete StopNotepadSilentlyContinuePassThru.ps1 script is seen here:

$process = “notepad”
Get-Process -name $Process -erroraction silentlycontinue | 
Stop-Process -passthru | 
ForEach-Object { $_.name + ‘ with process ID: ‘ +  $_.ID + ‘ was stopped.’}

When we run the script with two instances of Notepad running, the following output is seen:

notepad with process ID: 2088 was stopped.
notepad with process ID: 2568 was stopped.

An additional advantage of the StopNotepadSilentlyContinuePassThru.ps1 script is that you can use it to stop different processes. We can assign multiple process names (an array) to the $process variable, and when we run the script, each process will be stopped. In this example, we assign the Notepad process and the Calc process to the $process variable. This is seen here.

$process= “notepad”, “calc”

When we run the script, both processes are stopped as shown here:

calc with process ID: 3428 was stopped.
notepad with process ID: 488 was stopped.

MT, we could continue changing our script. We could put the code in a function, write command-line Help, and change the script so that it accepts command-line input. Or we could have the script read a list of processes from a text file. As soon as we move from the command line to the script, such activities suddenly become possible. We will continue our Introduction to Windows PowerShell Scripting Week tomorrow. If you want to be among the first to know what we are writing in the future, follow us on Twitter. Until then take care.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

0 comments

Discussion is closed.

Feedback usabilla icon