Now, it’s time to evaluate PowerShell. As of late, PowerShell has become my preferred method for scripting. PowerShell is built on top of the .NET framework. It uses cmd-lets (commandlets) to perform the majority of it’s command line actions. It’s also fully integrated with WMI and the .NET Framework. It works off of Objects, which we discussed previously with VBScript. It shares a lot of similarities to the BASH Shell, but the object based pipe is one of the more powerful features. Objects in the pipe remain objects through to output. This is an important element to remember if you are already familiar to the BASH shell and find yourself working in a Windows environment now (such as myself).
There’s a lot of good reading over what PowerShell is, so I’ll go ahead and start with our code and break it down as we’ve done in the past. We’ll be introducing CASE statements this time around, as well as arguments. I hope you enjoy it.
Function Get-PowerShell {
The purpose of this script is to start, stop, or restart services on a single machine or list of machines read from a file.
#Author Daniel Belcher #Set-RemoteService start/stop/restart servicename filename.txt Param ( [parameter(Mandatory=$TRUE, HelpMessage="You need a valid state entry. Start, Stop, or Restart")] [string]$STATE, [parameter(Mandatory=$TRUE, HelpMessage="Please enter a valid service name. example: spooler")] [string]$SERVICE, [parameter(Mandatory=$TRUE)][AllowEmptyString()][string]$TARGET ) Function SRS{ PARAM ($SERVICE,$STATE,$MACHINE) $SOBJ = New-Object PSObject $PING = gwmi -Query "select * from Win32_PingStatus where Address='$MACHINE'" if ($PING.StatusCode -ne 0) {$SOBJ | Add-Member -MemberType NoteProperty -Name MachineName -Value $MACHINE $SOBJ | Add-Member -MemberType NoteProperty -Name Status -Value "Unreachable"} else{ TRY{$ACTION = gwmi win32_service -filter "name like '$SERVICE'" ` -computername "$MACHINE" -ErrorAction continue switch ($STATE) {stop{$ACTION.invokemethod("stopservice",$NULL) | out-null} start{$ACTION.invokemethod("startservice",$NULL) | out-null} restart{$ACTION.invokemethod("stopservice",$NULL) | out-null start-sleep 2 $ACTION.invokemethod("startservice",$NULL) | out-null}} $ACTION = gwmi win32_service -filter "name like '$SERVICE'" -computername "$MACHINE" $SOBJ | Add-Member -MemberType NoteProperty -Name MachineName -Value $MACHINE $SOBJ | Add-Member -MemberType NoteProperty -Name Name -Value $ACTION.Name $SOBJ | Add-Member -MemberType NoteProperty -Name Status -Value $ACTION.State} CATCH [System.UnauthorizedAccessException] {$SOBJ | Add-Member -MemberType NoteProperty -Name MachineName -Value $MACHINE $SOBJ | Add-Member -MemberType NoteProperty -Name Status -Value "Access Denied"} }$SOBJ } Write-Host -foregroundcolor Yellow `nAttempting to $STATE $SERVICE if($TARGET -eq "") {$TARGET = gc env:computername} $FILE = test-path $TARGET -ErrorAction SilentlyContinue if($FILE -eq $TRUE) {$READ = gc $TARGET ForEach ($MACHINE in $READ){ SRS $SERVICE $STATE $MACHINE }} else { SRS $SERVICE $STATE $TARGET }
Copy and paste the above code and save it as a .ps1 which is the standard file extension for PowerShell scripts. Now, lets set our execution settings from the PowerShell command line:
Set-ExecutionPolicy remotesigned
You can now execute the script by right-clicking and run with PowerShell, or by launching it from within the shell by navigating to it’s directory and typing it’s name.
Now lets look at our interest areas:
- Sequence
- Command usage
- Output usage
- Conditional logic
Sequence:
Sequence inside PowerShell is as expected. It’s all run inline, and requires everything called to be defined before it’s call. Looking at the above code you will see that we call Set-RemoteService two times, and we’ve defined it at the beginning of the script. In VBScript, or in Command, calling a sub, function, or a GOTO statement will find the appropriate section and run that code. So being aware of how your scripts process instructions is very important to their functionality.
Now for something new, arguments. Arguments, more or less, are user defined variables. They are dynamic, and as such, need to be controlled to some degree. With PowerShell you are capable of defining the variable type, and various restrictions. This gives you incredible control and removes the potential for unforeseen errors by controlling the flow of input.
Param ( [parameter(Mandatory=$TRUE, HelpMessage="You need a valid state entry. Start, Stop, or Restart")] [string]$STATE, [parameter(Mandatory=$TRUE, HelpMessage="Please enter a valid service name. example: spooler")] [string]$SERVICE, [parameter(Mandatory=$TRUE)][AllowEmptyString()] [string]$TARGET )
As you can see, with the PARAM statement we are opening 3 variables for input. They are $STATE, $SERVICE, $TARGET. We are also insuring whatever is entered is a string value. So that leaves us with 2 issues. The user needs to enter something, and it needs to be useable. So here we have provided a mandatory attribute that will prompt the user as well as a help message that the user can access for guidance on what to enter. Now with our $TARGET variable, we want to leave it open for files, localhost, and remote machines so it will need to be a bit dynamic in nature. There are ways to handle this in powershell with objects, but I will demonstrate conditional ways to handle it as well later on.
Command usage:
As we discussed at the beginning, PowerShell uses cmd-lets for the majority of it’s work. It also allows you to call WMI and .NET methods and functions. So similarly to to VBScript we can instantiate them for additional functionality. The possibilities are plain astounding, and well beyond scope for this blog post.
Output usage:
Same as with our previous entries, all the work done is aimed at getting an appropriate output. As such, capturing, reading, and modifying the output of all our commands is important. How PowerShell does this, is similar to both VBScript and BASH. You can pipe for output as input for other commands as well allowing for some very elegant one liner solutions.
Since PowerShell keeps everything in the pipe an object, your variables become immensely powerful since they are more than just stored returns from commands, but instead stored objects with invoke able methods, or property lookup, etc. Lets evaluate how this works quickly:
$ACTION = gwmi win32_service -filter "name like '$SERVICE'" -computername "$MACHINE"
Here we are using a get-wmiobject Cmd-Let alias to grab a specific service, on a specific machine. Now the variable service we are grabbing will have certain native METHODS and PROPERTIES we can leverage to either invoke or pull data. What we need to understand is that the $ACTION variable is now an Object, not a Value.
$ACTION.invokemethod("stopservice",$NULL)
StartService and StopService are two methods belonging to Win32_Service objects. So what we are doing here is saying essentially:
$SERVICE object, Invoke Method StopService
So everything you see at the above link is now callable, or retrievable through this variable. Pretty cool huh? One thing of note, if you invoke a method that changes the state of an object, it’s properties will not be refreshed until it is called again. You will notice in the code I re-declare $ACTION after the service method invocation.
That’s because the state is changed so the property Status has changed. Hence I couldn’t just recycle $ACTION with the $ACTION.State when building my Status property. The object needs to be refreshed.
I use two methods for error handling in this script, one is try and catch. The other is a in line command erroraction preference of silently continue:
TRY{$ACTION = gwmi win32_service -filter "name like '$SERVICE'".... ...$SOBJ | Add-Member -MemberType NoteProperty -Name Status -Value $ACTION.State} CATCH [System.UnauthorizedAccessException] {$SOBJ | Add-Member -MemberType NoteProperty -Name MachineName -Value $MACHINE $SOBJ | Add-Member -MemberType NoteProperty -Name Status -Value "Access Denied"}
and
$FILE = test-path $TARGET -ErrorAction SilentlyContinue
The TRY and CATCH are fairly straight forward. TRY (DO) the statement and CATCH if there is an error of [type] then do {statement}.
The –erroraction has 4 possibilities:
- Continue
- Inquire
- SilentlyContinue
- Stop
They are fairly straight forward, but all work to manage errors or exceptions in one way or another.
Conditional logic:
We’ve discussed if statements before, and as promised, I offer you a practical case statement:
switch ($STATE){ stop{$ACTION.invokemethod("stopservice",$NULL) | out-null} start{$ACTION.invokemethod("startservice",$NULL) | out-null} restart{$ACTION.invokemethod("stopservice",$NULL) | out-null start-sleep 2 $ACTION.invokemethod("startservice",$NULL) | out-null} }
The syntax says switch, but the methodology is the same. All that is happening is we are saying if $STATE = “Stop” or “Start” or “Restart” do this. Either 3 separate if statements, or 1 case statement. Fairly efficient manner for handling complex conditions that hinge around one variable.
So what does this PowerShell script do?
It begins by declaring 3 variables that are to be string values. All 3 as Mandatory, while one is allowed to be an empty string.
Next it builds a function to be reused through the remainder of the script “SRS”. In this function we create a powershell object for storing property data. A ping test is done using a WQL to determine if the machine is online, or if the name is even valid. (the reason we do this, is that it’s increasingly faster to ping for response than to wait for a timeout to occur on a RPC request).
If the machine successfully pings it moves on to declare the $ACTION variable object for use in the Set-RemoteService function. We then check the $STATE string against our SWITCH (CASE) statement and perform the appropriate method.
Reset the $ACTION variable and begin creating and storing note properties to our $SOBJ for reporting. If either the ping fails, or an unauthorized access error occurs we generate a coresponding note property to the sobj for reporting, and then the function writes the objects properties to the console, then we close the function.
Now the script writes to host a message confirming the attempted status service, ee evaluate the 3rd variable $TARGET and set it’s value to the local machines name if the value is an empty string.
if($TARGET -eq "") {$TARGET = gc env:computername }
Check to see if that $TARGET is a file, if it is we read it and for each item in the file we pass it to our SRS function. If it wasn’t a file, it does the same thing without a for each statement.
}
So you want a little more?
I’m going to quickly show you how you can integrate this script, into a profile function. It’s exceptionally easy, and I recommend it for scripts you’ve built that you find invaluable for day to day use.
Here goes the modified code:
Function Set-RemoteService{ Param ( [parameter(Mandatory=$TRUE, HelpMessage="You need a valid state entry. Start, Stop, or Restart")] [string]$STATE, [parameter(Mandatory=$TRUE, HelpMessage="Please enter a valid service name. example: spooler")] [string]$SERVICE, [parameter(Mandatory=$TRUE)][AllowEmptyString()][string]$TARGET ) Function SRS{ PARAM ($SERVICE,$STATE,$MACHINE) $SOBJ = New-Object PSObject $PING = gwmi -Query "select * from Win32_PingStatus where Address='$MACHINE'" if ($PING.StatusCode -ne 0) {$SOBJ | Add-Member -MemberType NoteProperty -Name MachineName -Value $MACHINE $SOBJ | Add-Member -MemberType NoteProperty -Name Status -Value "Unreachable"} else{ TRY{$ACTION = gwmi win32_service -filter "name like '$SERVICE'" ` -computername "$MACHINE" -ErrorAction continue switch ($STATE) {stop{$ACTION.invokemethod("stopservice",$NULL) | out-null} start{$ACTION.invokemethod("startservice",$NULL) | out-null} restart{$ACTION.invokemethod("stopservice",$NULL) | out-null start-sleep 2 $ACTION.invokemethod("startservice",$NULL) | out-null}} $ACTION = gwmi win32_service -filter "name like '$SERVICE'" -computername "$MACHINE" $SOBJ | Add-Member -MemberType NoteProperty -Name MachineName -Value $MACHINE $SOBJ | Add-Member -MemberType NoteProperty -Name Name -Value $ACTION.Name $SOBJ | Add-Member -MemberType NoteProperty -Name Status -Value $ACTION.State} CATCH [System.UnauthorizedAccessException] {$SOBJ | Add-Member -MemberType NoteProperty -Name MachineName -Value $MACHINE $SOBJ | Add-Member -MemberType NoteProperty -Name Status -Value "Access Denied"} }$SOBJ } Write-Host -foregroundcolor Yellow `nAttempting to $STATE $SERVICE if($TARGET -eq "") {$TARGET = gc env:computername} $FILE = test-path $TARGET -ErrorAction SilentlyContinue if($FILE -eq $TRUE) {$READ = gc $TARGET ForEach ($MACHINE in $READ){ SRS $SERVICE $STATE $MACHINE }} else { SRS $SERVICE $STATE $TARGET } }
We’ve enclosed the entire thing into a Function statement now, and redefined the internal function. The reason for this was, well, I prefer to stick with cmd-let naming patterns. I’d rather call set-remoteservice at the command line.
Now, we need to build our profile, from inside PowerShell, at the command line type:
notepad.exe $PROFILE
Now paste the above code and save it. If you are inside ISE press F5 to run the script and update your profile, or if you are in the standard shell type:
powershell
This will start a new shell instance inside your previous shell with the updated profile.
Be mindful, PowerShell and PowerShell ISE have two separate profiles. So if you perform this in ISE the standard shell won’t receive the update and vice versa.
That does it for my stick to the script series. I hope you’ve learned a lot regarding scripts through this and it’s shed some light onto what scripting is and de-mystified it for you.
I will continue to post scripts for various things over time, as well as neat tricks as I learn them so I wouldn’t say this is truly the end.
Editors:
If you intend to do a lot of scripting, I recommend you grab yourself a steady editor. Preferably one with configurable highlighters. Below are a list of a few free and premium editors I recommend. I’ll start with the freebies:
- Context – A nice, windows based, free editor (my usual windows editor). Configurable highlighters, comparison tool, and configurable execution keys. All the basic needs of a script writer. Minus integrated libraries. I believe development has all but stopped since 09, so this might not be the best editor to stick to.
- Emacs – Extremely powerful development environment built for OSS, but with Windows versions. This is more than a simple editor, it’s a Swiss army knife. It will require a bit of a learning curve to begin using it but is worth it in the end.
- VIM – My preferred editor in Linux, switchable modes, highlighters, line numbers, split views, compares, command line style editing. Like emacs, it’s extremely powerful, and has a bit of a learning curve. I prefer VIM, but Emacs is far more extensible, just fyi.
- NotePad++ – A really nice OSS editor for Windows. I should probably start using this over Context, but I’ve already spent so much time with Context configuring it, I’ve become extremely comfortable with it. I’d recommend this editor to any novice windows scripter.
- PowerGUI – Both a free, and paid edition. I recommend this editor for PowerShell whole heartedly because of it’s syntax and object recognition tools. If you are going to do a lot of work with PowerShell, I recommend using this editor for it.
- PSEdit – Included with the PowerShell ISE. It’s the standard PowerShell editor.
Premium:
If you are interested in learning more:
- W3Schools – Great educational material for beginners with multiple languages and types
- DevGuru – Good resource for syntax, and reference
- Hey, Scripting Guy! – VBScript and PowerShell, great scripting blog
- Advanced Bash-Scripting – Wonderful web based tutorial for beginners and advanced users alike
- TechNet Script Center – A repository, nothing like learning from examples
- Rex Swain’s Perl 5 Guide – Great online resource for syntax in Perl ( popular for CGI use)
Stick to the Script Parts 1 – 4:
- Stick to the Script…
- Stick to the script #!/BIN/BASH…
- Stick to the script CreateObject(“Wscript.Shell”)…
- Stick to the Script PS C:> PowerShell …