On Day 1, we looked at PowerShell as a replacement for the cmd.exe console. Today, we’ll be shifting angles (and expectations) and examine PowerShell as a scripting language.
The by far easiest way of re-using old script code is by leaving it as-is. If the batch file or VBScript script you got does what you expect it to do – fine. Simply launch it from inside your PS console just like any other external program or document.
But: remember from Day 1 that in order to launch an external script (bat or vbs or whatever), either one of the following must be true:
· Specify the full path name
· Store the script in a folder that is listed in your %path% environment variable. This way, you can type in just the script name and omit the path name.
· Precede the script file name with "./" to launch it in your current directory. You must therefore first set your current directory to the directory the script file is stored in (use cd)
When you launch scripts that way, they do their job ok but they don’t interact with PowerShell. Since we live in a vibrant world, chances are that you will have to change your scripts eventually or feel the faint desire to automate new things.
Again, you may be perfectly fine by not touching your existing scripts or even create new scripts stubbornly using your old and accustomed technique. That’s because you can fairly easy integrate results from your existing scripts with PowerShell.
Feeding VBScript Results Into PowerShell
Let’s start with a real simple VBScript. It looks like this:
set fs = CreateObject("Scripting.FileSystemObject")
for each file in fs.GetFolder("C:\").Files
WScript.Echo file.name
next
All it does is dumping the file names in drive C:\. To illustrate how the results from this script integrate with PowerShell, save the script in a file called c:\dump.vbs. Now, try this:
A note to all WSH geeks: if you made CScript the default host for your scripts (using the command wscript //H:CScript), you can omit CScript.exe. If you however use the default host WScript.exe and launch your script without preceding CScript.exe, you will have a lot of clicking to do. Each and every returned file name will pop up in a separate window – no fun.
If you did it right, the file names will be dumped right into the PowerShell console. Cool, but why?
Because the script was executed by cscript.exe which is the console-based scripting host (yes, that’s why it has a “c” in its name). And because the script has chosen to output stuff using WScript.Echo. That’s the only way for the script to communicate with the PowerShell console.
PowerShell can pick it up from there. You can simply pipe the script results into PowerShell commandlets to further refine them:
CScript C:\dump.vbs | select-string "vbs"
This would filter out all files that do not contain “vbs” in their names. Of course you could have processed the result in any other imaginable way as well. And: The same applies for output generated by a batch file and echoed to the console, or from any other external command that produces console output for that matter:
Ipconfig | select-string "IP"
Now while it is quite comfortable to re-use old script code as-is, the question arises whether this old script code may be obsolete altogether. This immediately rises the (silent) question whether or not your existing knowledge is also obsolete, along with your job perspective.
Let’s first look at the technical side. The VBScript above would have been easily replaced by this code:
Dir c:\*.vbs | foreach-object {$_.name}
The foreach-object commandlet in this line works very similar to where-object from Day 1. It’s sole purpose here is to enumerate the resulting files and display only their name properties. Remember the $_ thing? That represents the object the loop is currently inspecting so it's as if in VBScript you had written: for each $_ in Pipeline.
Now, the true reason why this VBScript could be condensed to this embarrasingly simple line of course is because it was an embarrassingly stupid example in the first place.
Translating VBScript Into PowerShell Lingo
So let’s take a look at a “real” VBScript that utilizes some fancy object model.
set ie = CreateObject("InternetExplorer.Application")
ie.visible = true
ie.navigate "www.powershelllive.com"
When you launch it from within PowerShell, it opens a new instance of Internet Explorer and navigates to a web page. Now, how would you translate this in PowerShell, Dude?
The good news is that VBScript (as is true for any WSH script language) really can’t do much. Its amazing automation capabilities arise from two commands: CreateObject and GetObject. CreateObject can load an external command library like a dll with automation interface.
So most of the time, advanced VBScripts hang out in some external libraries and use their intelligence to take credit for spectacular things.
That’s good news because this way you don’t really need to translate anything. You can re-use your complete existing knowledge and experience from such objects and their object models. All you need in PowerShell is the CreateObject command to break into the object of desire. And that's the career perspective: your hard work, experience and knowledge as script writer isn't wasted at all with PowerShell.
Here’s how you do it:
$ie = new-object -comobject "InternetExplorer.Application"
$ie.visible = $true
$ie.navigate("www.powershelllive.com")
Cool, eh? It almost looks like VBScript. No need to dump your experience and start over again. Here are the most important scoops:
· Variables in PowerShell always start with a “$”. There is no need for the “Set” statement as you would need in VBScript to alarm the interpreter that you are about to store object data. Simply use a “$” followed by your variable name of choice to store stuff – anything.
· Just as PowerShell replaced the internal cmd.exe console commands with its own commandlets, the same is true for the CreateObject command. Its new name in PowerShell land is new-object. So this is a commandlet, not a traditional COM method. That’s why you use parameters like -comObject and arguments like "InternetExplorer.Application", and that’s also why you don’t place them into parenthesis. If you feel a bit klumsy now, please go back to Day 1 and review the anatomy of a well-bred commandlet.
· The Boolean value true is a predefined variable in PowerShell. So it starts with a “$” (like all variables) and its name is true (obviously). Likewise, there is a predefined variable named $false, and if you are wondering what else variables there may be living in your PowerShell, use Dir Variable:
· Now that’s important: All methods require to be followed by parenthesis, and the arguments for a method must be enclosed in such parenthesis. That’s why navigate needs the parenthesis. If you omit the parenthesis, you get an error.
And you may have noticed another thing: PowerShell works differently than VBScript in the way it accepts and processes input. While in VBScript you needed an external editor to first compose your script and then had to feed it into the WSH, keeping your fingers crossed that everything was spelled right, in PowerShell each line is executed as you enter it. You can go by the flow. You can experiment. And explore. And you can (still) get all kinds of errors but the complexity is limited to the line you are currently executing.
Ok, but that’s not a true script then! Oh right. So far these were individual lines of code executed in the scope of the running PowerShell console.
Writing PowerShell Scripts (And Handling Security Settings)
To compose a script, you do it as you would with a batch file. You take what you typed and save it as a script file instead. While batch files use “bat” as extension and VBScript uses “vbs”, PowerShell also reserved its own extension which is called “ps1”.
So open an editor of your choice and hack in those three lines, then save it all as c:\ie.ps1.
$ie = new-object -comobject "InternetExplorer.Application"
$ie.visible = $true
$ie.navigate("www.powershelllive.com")
If you try to open your PS1 file from within the windows explorer, you will experience a slight deja-vú. Your editor throws back the code at you, the cursor eagerly blinking awaiting any changes and improvements.
With ps1 files, there is no accidental click-and-run anymore. To run a PowerShell script you really need to know what you are doing.
To be more precise, you would have to launch powershell and specify the script path as an argument to it:
If you do this from inside powershell (and not from inside a classic cmd.exe console), you can omit Powershell and type:
However, most likely you will receive an error complaining about some "execution policy". This is yet another security feature because by default, scripts are turned off. So when in some distant future you launch a script that wrecks your computer or contains malicious code, Microsoft can lean back and point to the one who enabled scripts.
The good news though is that there are a lot of different execution modes which give you a much better control over what gets executed and what will be disabled.
So to be able to run your script, you should change your execution policy to RemoteSigned:
Set-executionpolicy RemoteSigned
Now, finally, you can successfully launch your script. That is, if and only if your script is stored locally on your computer. Scripts that origin from a remote place can only be executed if they are correctly signed with a digital certificate – something we’ll cover in a later session.
Here are the other executionpolicy options:
· Unrestricted: All scripts can be launched. That’s how VBScript works by default.
· RemoteSigned: Only scripts from a remote location need to be signed, local scripts always run
· AllSigned: All scripts need to be signed.
· Restricted: No scripts can run. That’s the default
· Default: Same as Restricted
Ok, the script runs now. To really do something cool and flexible with it, you want your scripts to accept arguments. In VBScript, you would have needed to access the WScript.Arguments collection. In PowerShell, you use the internal variable $args.
Let's see how we would have to reconstruct the script to launch any website we pass as argument:
$ie = new-object -comobject "InternetExplorer.Application"
$ie.visible = $true
$ie.navigate($args[0])
The change really happens only in the last line where we ask IE to navigate somewhere. Instead of a fixed URL, now the arguments come into play. With these changes, you could supply the website URL when you launch your script:
Or, you could add fixed text to the url so you can omit all the “www” and “.com” stuff:
$ie.navigate("http://www." + $args[0] + ".com")
Now, all you needed to do is launch your script like this:
Note that $args really is an array and contains any number of arguments. All arrays start with index “0” so in the previous example, we only recognized the first argument supplied. If you added more than one argument, all others would be friendly ignored. Likewise, if you do not supply any arguments at all, the script would work and treat the argument as empty value. However your browser would probably toss an error message at you for pointing him so unpolitely into nowhere.
The fun thing about PowerShell is that it really works like a kid's construction set, and if you are wondering how you could possibly process all given arguments, you know the missing part already: use foreach-object! This commandlet really works like for each…next in VBScript. To launch any number of webpages, make these changes:
$args | foreach-object {
$ie = new-object -comobject "InternetExplorer.Application"
$ie.visible = $true
$ie.navigate("http://www." + $_ + ".com")
}
So all you do is take your arguments in $args and put them into the pipeline. Next, have foreach-object suck out each given argument and process it individually inside the curly braces. Note at the bottom that the argument foreach-object processes inside of its loop is again represented by $_.
Now you can launch your script like this and launch a whole armada of webpages with a single line:
C:\ie.ps1 google microsoft powershell powershelllive
You get four IE windows, each navigating to the site you specified.
Writing Your Own Functions
Things work as expected. That's good. However, one important ingredient is still missing: functions. They are used to re-use code and give structure to larger scripts.
Let’s create a function much similar to the script you developed. To start a function, give it a name:
Once you press ENTER, you see a “>>” prompt. PowerShell has recognized that your input was not complete and allows you to enter additional code. You now start the function body with a curly brace:
Next, you enter the code for your function:
$args | foreach-object {
$ie = new-object -comobject "InternetExplorer.Application"
$ie.visible = $true
$ie.navigate("http://www." + $_ + ".com")
}
Finally, you close your function:
A last ENTER brings you back to your prompt. You have now successfully defined your new function called “web”. To open web pages, you can now use your new command “web “ like this:
Web powershelllive microsoft hotmail
Of course, you can use the same basic architecture inside of your scripts as well.
Hmmm... Interesting approach. This function does not use formal parameters, though. You can supply as many parameters as you wish, thanks to $args. And: the function does not return anything, so it really is more like a procedure. To give you the complete picture, here's a function definition with formal parameters and a return value:
function Add($number1, $number2)
{
$result = $number1 + $number2
return $result
}
Try it:
Important: Since functions really are no methods, PowerShell expects the parameters you pass to that function to follow without parenthesis and separated by spaces. More traditional script programmers would probably intuitively write Add(1,2).
While the first and correct example returns 3 as a result, the latter syntax would not raise an error but return 1 and 2. Why?
Because a comma really creates a multiple value like an array. So you really provided only one parameter to the function which is an array of two numbers. The second parameter is empty. Since you did not use any variable types, Add does not care what you provide and uses the existing variable type. So adding an empty value to the array of two numbers is still an array of two numbers which Add happily returns.
So to avoid an error like this, you could have typed your parameters. In other words: you could have defined which variable types are acceptable:
function Add([int] $number1, [int] $number2)
{
$result = $number1 + $number2
return $result
}
With this definition, when you call Add 1,2 or Add(1,2), you would receive an error because your parameters would not qualify as integers.
Wew, that took us way into theory, and to be honest, there's a lot more stuff to fiddle around. For example, you don't even need return. Return is there only to calm down traditional programmers. Your function can in reality return anything, not just one thing but even multiple values:
function Add([int] $number1, [int] $number2)
{
$number1 + $number2
}
Anything your function outputs is treated as result. Amazing. What about optional parameters and initializing them? Ok, since you asked:
function Add([int] $number1, [int] $number2=10)
{
$number1 + $number2
}
When you call Add 1 2, you receive 3. When you call Add 5, you receive 15. Why? Since you omitted the second parameter, it used its predefined value of 10.
Summary
No panic – if you are currently using older scripting techniques, you can still continue to use them – and even have their results interact with PowerShell. All you need to do is output results to the console where PowerShell can pick them up and take it from there.
Or, you dive into object models using new-object -comobject. This way, you can continue to use the same objects and object models you are used to.
PowerShell supports variables (they always start with a “$”), provides arguments (inside the $args collection) and allows for structured code using functions. You have also learned more about execution policies and what is necessary for PowerShell to allow execution of a script.
In the next session, we’ll look at some even more interesting techniques.
While it is perfectly ok and sometimes even smart to re-use old code and familiar object models, PowerShell contains a lot of new ways how to do common stuff more effectively. So we’ll look into a number of often used VBScript techniques and see how PowerShell can do it better. Again, that’s not a must. It’s simply more fun that way, and you’ll see why.
We also look into the script control which does exactly the opposite. It allows you to re-use plain old VBScript/JScript/PerlScript code inside of your PowerShell scripts – so you can even mix old and new code in one script.
And we find out how to translate the VBScript GetObject command into PowerShell lingo and look at some of the specialized WMI and ADSI techniques built into PowerShell.