Learn When to Write PowerShell Scripts and When Not To

Learn When to Write PowerShell Scripts and When Not To

  • Comments 2
  • Likes

  

Summary: Microsoft Scripting Guy Ed Wilson discusses when to write a Windows PowerShell script and when not to write a script.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! I am wondering why everyone is talking about Windows PowerShell. To me it seems to simply be another scripting language, and I do not know why I should worry about it. I already know VBScript, and my friends use Perl. So what is the big deal?

-- BW

 

Hey, Scripting Guy! AnswerHello BW, Microsoft Scripting Guy Ed Wilson here. If Windows PowerShell were simply a new scripting language I would not be as excited as I am about the product. The truth of the matter is that Windows PowerShell is both an interactive command shell in addition to a scripting language.

Portions of today’s Hey, Scripting Guy! Blog post are adapted from material in my book Windows PowerShell 2.0 Best Practices, published by Microsoft Press in December 2009.

Not everything in Windows PowerShell 2.0 has to be scripted. This was true in Windows PowerShell 1.0 and it is very true in Windows PowerShell 2.0. People coming from a VBScript background or a Perl background frequently feel they must write a script. But a tremendous amount of work can be done from the command line, without the need for writing a script.

One of the more powerful things that can be done with Windows PowerShell is the ability to use language statements from the command line. The for statement lets you control looping operations that would have required creating a script in other languages. To facilitate work from the command line, Windows PowerShell will let you create incomplete commands on one line, and to continue them to the next line. When you are finished, you press the enter key a second time. The command seen here sends a ping command to each IP address in the range of 192.168.2.1 through 192.168.2.10.

PS C:\> for($i = 1 ; $i -le 10 ; $i++)
>> { Test-Connection -Destination 192.168.2.$i -Count 1 -ErrorAction Silentlycontinue |
>> Format-Table –property Address, statusCode, ResponseTime -AutoSize }
>>

Address     statusCode ResponseTime
-------     ---------- ------------
192.168.2.1          0            1
Address     statusCode ResponseTime
-------     ---------- ------------
192.168.2.3          0            2
Address     statusCode ResponseTime
-------     ---------- ------------
192.168.2.5          0            0
Address      statusCode ResponseTime
-------      ---------- ------------
192.168.2.10          0           10

 

The command above could have been much shorter by taking advantage of several economies provided by the Windows PowerShell syntax such as using aliases, partial parameters and positional arguments.

When talking about Windows PowerShell scripts, the first thing to realize is that in its most basic form a Windows PowerShell script is a collection of PowerShell commands stored in a file with a specific extension. If you do not want to do that, you can have a text file with a collection of commands, and store them as a text file as seen in the following figure.

 

By using Windows PowerShell, you can easily read the commands.txt text file, and execute the commands. To do this, you use the Get-Content cmdlet to retrieve the commands in the text file. The default parameter for the Get-Content cmdlet is the path parameter, and when you are working from the command line, you do not have to supply it if you do not want to do so. The path can be a local path or even a Universal Naming Convention (UNC) path as long as you have rights to read the text file. The best way to use this technique is to pipeline the results to the Invoke-Expression cmdlet. Each command that streams across the pipeline from the Get-Content cmdlet will be executed in turn as it arrives to the Invoke-Expression cmdlet. This is seen here:

Get-Content -Path C:\fso\Commands.txt | Invoke-Expression

 

The results are seen in the following figure.

 

When you are using the Windows PowerShell remoting features against an untrusted domain, it is rather easy to become confused when you use cmdlets such as Get-Content. The path parameter that is used refers to a path that is local to the target computer, not a path that is local to the launching computer. In the example that follows the path c:\fso\commands.txt points to a text file that is named commands.txt that must reside in the fso folder on the C:\ drive of a computer named Sydney in the Woodbridgebank.com domain. If the commands.txt file is not found in that location, the error seen here will be emitted.

PS C:\> invoke-command -ComputerName sydney.woodbridgebank.com -Credential admin
istrator@woodbridgebank.com -ScriptBlock {get-content -Path C:\fso\Commands.txt
| Invoke-Expression}
Invoke-Command : Cannot find path 'C:\fso\Commands.txt' because it does not exi
st.
At line:1 char:15
+ invoke-command <<<<  -ComputerName sydney.woodbridgebank.com -Credential admi
nistrator@woodbridgebank.com -ScriptBlock {get-content -Path C:\fso\Commands.tx
t | Invoke-Expression}
    + CategoryInfo          : ObjectNotFound: (C:\fso\Commands.txt:String) [Ge
   t-Content], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetCo
   ntentCommand

 

You might think that you could use an UNC path and point to the commands.txt file on the launching computer. Because the remote domain is un-trusted, there is no security context that enables the remote command to access the file system of the local computer. When the command that is expressed in the ScriptBlock parameter is evaluated, it is evaluated in the context of the target computer which in this case is the Sydney.WoodBridgeBank.Com computer. The local computer which is the launching point for the command is Vista.NWTraders.com and as there is no trust relationship between the two domains, there are no credentials that can be supplied to enable the command to run. The results of trying to run the command are seen here.

PS C:\> invoke-command -ComputerName sydney.woodbridgebank.com -Credential admin
istrator@woodbridgebank.com -ScriptBlock {get-content -Path '\\vista\fso\Command
s.txt' | Invoke-Expression}
Invoke-Command : Cannot find path '\\vista\fso\Commands.txt' because it does no
t exist.
At line:1 char:15
+ invoke-command <<<<  -ComputerName sydney.woodbridgebank.com -Credential admi
nistrator@woodbridgebank.com -ScriptBlock {get-content -Path '\\vista\fso\Comma
nds.txt' | Invoke-Expression}
    + CategoryInfo          : ObjectNotFound: (\\vista\fso\Commands.txt:String
   ) [Get-Content], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetCo
   ntentCommand

 

The part that can seem to be confusing is that the Get-Content command works just fine when it is run alone. In working on a computer named Vista that has a folder named fso that contains a text file that is named Commands.txt the command completes successfully when it is ensconced with single quotes. This is seen here.

PS C:\> Get-Content -Path '\\vista\fso\Commands.txt'
Get-Service -Name bits -ComputerName vista
Get-Process -Name explorer -ComputerName berlin
Get-EventLog -LogName application -Newest 1 -ComputerName berlin,vista
Invoke-Command -ComputerName Berlin { Get-Date }
Get-Date

 

But this is to be expected as the logged on user has rights to the folder, and can therefore use the Get-Content cmdlet to read a UNC path to the command.txt file.

You could map a drive on the remote domain, and copy the file from your local computer to the appropriate folder on the remote server. You would, of course, be required to open additional ports in the Windows Firewall which, depending on your network configuration, may or may not be an acceptable solution. If you decide to use this route, you could use Windows PowerShell to perform the configuration changes as shown here.

PS C:\> Invoke-Command -ComputerName Sydney.WoodBridgeBank.Com -Credential Admin
istrator@WoodbridgeBank.com -ScriptBlock { netsh advfirewall firewall set rule g
roup="File and Printer Sharing" new enable=Yes }

Updated 28 rule(s).
Ok.

 

After you have enabled the firewall exception, you can then map a drive using the GUI or the Net Use command from within Windows PowerShell, or any of the other programmatic methods. After you have mapped the drive, you could then copy the commands.txt file to the remote server by using the Copy-Item cmdlet as seen here.

When you use Copy-Item to copy an item to a mapped drive, you have to consider the structure of the mapped drive. It is quite common to map a drive to a share on a remote computer. The remote share is almost invariably a share of a folder, and not a complete drive. Because your remote drive is a map point of a single folder, it will change the destination. The z: drive in the command which follows is a share of the fso folder on the remote server. The destination parameter is to the root of the mapped drive, and not to z:\fso.

Copy-Item -Path C:\fso\Commands.txt -Destination z:

 

You could now use the commands.txt directly in the Windows PowerShell command as seen here.

PS C:\> invoke-command -ComputerName Sydney.WoodbridgeBank.com -Credential admin
istrator@WoodBridgeBank.com -ScriptBlock { Get-Content -Path C:\fso\Commands.txt
 | Invoke-Expression }

 

One solution to the dilemma of mapping drives is to use Remote Desktop. Remote Desktop will enable you to access local resources if you decide to make them available in your session. By selecting Remote Desktop / Options / Local Resources, you can choose to enable printer connections, clip board access, and local drives to be available within the Remote Desktop Protocol (RDP) session. You can access Remote Desktop by going to Start / All Programs / Accessories and selecting Remote Desktop Connection. There is a problem with using remote desktop. It must first be enabled. If it has not been previously enabled, you will be greeted with the friendly access denied message seen in the following figure.

 

To enable Remote Desktop access, on Windows 2008 and Windows 2008 R2, select Configure Remote Desktop from Server Manager. If you are running Windows Vista or Windows 7 you select Remote Settings from Control Panel / System. In any case, the System Properties dialog appears, and you have to select the Remote tab which is shown in the following figure. The Remote Desktop options are seen in the bottom half of the screen and present three choices. By default Remote Desktop connections are not allowed to the computer. The safest choice is to enable connections only from computers that are running Remote Desktop with Network Level Authentication. You are also able to specify which users will be allowed to make the connection. By default, members of the Domain Administrators group will be permitted to make connections.

 

After you have enabled Remote Desktop an exception is automatically created to allow for RDP traffic through the Windows Firewall. It is still a good idea to double-check to make sure the exception was permitted. 

BW, that is all there is to working interactively with Windows PowerShell. To Script or Not to Script week will continue tomorrow when I will talk about how to work with command history.

I invite you to follow me on Twitter or Facebook. If you have any questions, send email to me at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi Ed,

            can you clearly summarise When to Write PowerShell Scripts and When Not To please ?

  • @russ You should write a PowerShell script when you anticipate the need to run the command on more than one occassion. You should write a script if the command is very long and complicated (for me this is a command that takes up more than two lines in the PowerShell console). There are a few exceptions. If a command is a simple PowerShell cmdlet (stop-computer) then I do not put that in a script. In addition, most WMI commands are short enough for me not to mess with. For example I have yet to write a script to retrieve the bios (gwmi win32_bios). If on the other hand, I had a need for a sorted list of unique processes, and I wanted a particular style of output I might put that in a script. Keep in mind, that at its most basic, a script is just a collection of commands you might otherwise type in the console.