Complete the following tutorial and answer the questions on pages 12.
Before we begin this homework, letβs change the execution policy to allow scripts written on the local machine to be run. Open a PowerShell window as Administrator (the easiest way to do this and be in your preferred directory is to open File Explorer, browse to the directory of your choice, then click on the File tab, selecting Open Windows PowerShell β Open Windows PowerShell as administrator). Once the prompt is available, type
Get-help *execution*
to see what commands affect execution policy. For help on the command you find type get-help and the command name (e.g., get-help set-executionpolicy). As discussed in the slides and in many online sources, there are seven possible execution policies. You should choose RemoteSigned. Set your execution policy using the set-executionpolicy cmdlet.
You should recall that you always have to provide a path and filename in order to execute a script. You should also know the difference between running a script in the Integrated Scripting Environment (ISE) and the console. In the ISE, scripts run in the global scope. In the normal shell console, scripts get their own scope. For more information on the scope, click here.
Try the examples. If you type (or copy and paste) the script examples into the Windows PowerShell ISE or in PowerGUI (available on Blackboard).
Windows PowerShell Script Files
Just like Batch files, a Windows PowerShell script file is nothing more than a plain-text file. Rather than .BAT, it has a .PS1 filename extension. The β1β doesnβt refer to the version of Windows PowerShell, but rather the version of the language engine. Windows PowerShell version 1 and 2 both use language engine version 1.
A Windows PowerShell script isnβt exactly like a command-line batch file, and running a script isnβt precisely the same as running the same commands yourself in the same sequence. For example, open a console window and run the following, pressing Enter after each line (remember not to type the line numbers):
Get-Service Get-Process
Now type those exact same lines into a script file, or the ISE script editing pane, and run the script. Youβll get different-looking results. Each time you hit Enter in Windows PowerShell, you start a new pipeline. Whatever commands you typed are run in that single pipeline. At the end of the pipeline, Windows PowerShell converts its contents into a text display. When you run the two commands in the normal console, youβve done so in two distinct pipelines.
Windows PowerShell was able to construct a unique display for each set of output. When entered into a script, however, both commands ran in the same pipeline. The Windows PowerShell formatting system isnβt sophisticated enough to construct the same unique output for two different sets of results. Try running this in the console:
Get-Service;Get-Process
Those results should look the same as they did when you ran the script containing those two commands. In this case, both commands ran in a single pipeline. Thatβs what happened when you ran the script.
The practical upshot of all this is that a script should produce only one kind of output. Itβs a bad idea, due in large part to the limitations of the formatting system. There are other considerations, as well. You donβt want a script dumping several different kinds of things into the pipeline at the same time.
Focus on that as a rule for everything weβll cover. A script should generate one, and only one, type of output. The only exception would be if itβs a script being used as a repository for multiple functions. In that case, each function should generate one, and only one, type of output.
Variables
PowerShell is object-oriented, and all the data items used within it are objects. You can think of variables as boxes. You can put one or more things, even dissimilar things, into a box. Each box has a name, and in Windows PowerShell those names can include almost anything. βVarβ can be a variable name, as can β{my variable}β. In the second example, the curly braces enclose a variable name that contains spaces, which is pretty ugly. As a rule, try to stick with variable names that include letters, numbers and underscores.
Using a variableβs name references the entire βbox.β However, because the box is an object, there is a lot of other information you may not want. If you want to reference the contents of the box, add a dollar sign: $var. Youβll often see Windows PowerShell variables preceded with the dollar sign because the whole point of using one is to get at the contents. Itβs important to remember, however, that the dollar sign isnβt part of the variable name. Itβs just a cue to tell Windows PowerShell that you want the contents, rather than the box itself. For example:
$var = 'hello' $number = 1 $numbers = 1,2,3,4,5,6,7,8,9
Those examples show you how to place items into a variable using the assignment operator (=). That last example creates an array, because Windows PowerShell interprets all comma-separated lists as an array,
or collection, of items. The first example assigns a string object, with the characters in the string contained within quotation marks.
Thereβs one aspect of Windows PowerShell that can be confusing, Windows PowerShell doesnβt βunderstandβ any meaning you may associate with a variable name. A variable like $computername doesnβt βtellβ the shell that the variable will contain a computer name.
Similarly, $numbers doesnβt βtellβ the shell that a variable will contain more than one number. The shell doesnβt care if you use a plural variable name. The statement
$numbers = 1
is equally valid to the shell, as is $numbers = ‘fred.’
2
When a variable does contain multiple values, however, you can use a special syntax to access just a single one of them. You would use $numbers[0] as the first item, $numbers[1] as the second, $numbers[-1] as the last, $numbers[-2] as the second-last, and so on.
Quotation Marks
As a best practice, use single quotes to delimit a variable unless you have a specific reason to do
otherwise. There are three specific instances where you would want to use double quotes.
The first is when you need to insert a variableβs contents into a string. Within double quotes only, Windows PowerShell will look for the $, and will assume that everything after the $, up to the first character thatβs illegal in a variable name, is a variable name. The contents of that variable will replace the variable name and the $:
$name = 'Don' $prompt = "My name is $name"
The $prompt will now contain βMy name is Donβ because $name will be replaced with the variable contents. This is a great trick for joining strings together without having to concatenate them.
Use double quotes in Windows PowerShell when you need to look for PowerShellβs escape character, the backtick or grave accent, and act accordingly. Here are a couple of examples:
$debug = "`$computer contains $computer" $head = "Column`tColumn`tColumn"
In the first example, the first $ is being βescaped.β That removes its special meaning as a variable accessor. If $computer contained ‘SERVER,’ then $debug would contain “$computer contains SERVER.”
In the second example, `t represents a horizontal tab character, so Windows PowerShell will place a tab between each Column. You can read about other special escape characters here, or in the shellβs about_escape_characters help topic.
Finally, use double quotes when a string needs to contain single quotes:
$filter1 = "name='BITS'" $computer = 'BITS' $filter2 = "name='$computer'"
In this example, the literal string is name=’BITS’. The double quotes contain the whole thing. Both $filter1 and $filter2 end up containing exactly the same thing, but $filter2 gets there by using the variable-replacement trick of double quotes. Note that only the outermost set of quotes actually matters. The single quotes inside the outer double quotes are considered part of the string by Windows PowerShell.
3
Object Members and Variables
Everything in Windows PowerShell is an object. Even a simple string such as βnameβ is an object, of the type System.String. You can pipe (just as in Batch files and command line, the β|β character is the pipe operator). any object to Get-Member to see its type name (that is, the kind of object it is) as well as its members, which includes its properties and methods:
$var = 'Hello' $var | Get-Member
Use a period after a variable name to tell the shell, βI donβt want to access the entire object within this variable. I want to access just one of its properties or methods.β After the period, provide the property or method name.
Method names are always followed by a set of parentheses. Some methods accept input arguments, and those go within the parentheses in a comma-separated list. Other methods require no arguments, and so the parentheses are empty, but don’t forget the parentheses:
$svc = Get-Service $svc[0].name $name = $svc[1].name $name.length $name.ToUpper()
Notice the second line starts by accessing the first item in the $svc variable. The period means, βI donβt want that entire object. I just want a property or method.β This accesses just the name property. Line five illustrates how to access a method, by providing its name after a period, followed by parentheses.
A period is normally an illegal character within a variable name, because the period means we want to access a property or method. That means line two in the following example wonβt work the way you might expect:
$service = 'bits' $name = "Service is $service.ToUpper()" $upper = $name.ToUpper() $name = "Service is $upper"
On line two, $name will contain “Service is BITS.ToUpper()” whereas on line four $name will contain βService is BITS.β
Parentheses
Aside from their use with object methods, parentheses also act as an order-of-execution marker for Windows PowerShell, just like in algebra. In other words, parentheses tell the shell to βexecute this first.β The entire parenthetical expression is replaced by whatever that expression produces. Hereβs a mind- bending couple of examples:
$name = (Get-Service)[0].name Get-Service -computerName (Get-Content names.txt)
4
In the first line, $name will contain the name of the first service on the system. Reading this takes a bit of effort. Start with the parenthetical expression. Thatβs what Windows PowerShell will start with as well. The βGet-Serviceβ resolves to a collection, or array, of services. The [0] accesses the first item in an array, so that will be the first service. Because itβs followed by a period, we know weβre accessing a property or method of that service, rather than the entire service object. Finally, we pull out just the name of the service.
On line two, the parenthetical expression is reading the contents of a text file. Assuming the file contains one computer name per line, βGet-Contentββ will return an array of computer names. Those are fed to the ββcomputerNameβ parameter of βGet-Service.β In this case, the shell can feed any parenthetical expression that returns an array of strings to the ββcomputerNameβ parameter, because the parameter is designed to accept arrays of strings.
Scope
Scope is a programming concept that acts as a container or compartment system. Things like variables, aliases, PSDrives and other Windows PowerShell elements are all stored in a scope. The shell maintains a hierarchy of scopes, and has a set of rules that determine how scopes can interact and share information with each other.
The shell itself is a single scope, called the global scope. When you run a script, it constructs a new scope and the script runs within that. Anything created by the script, such as a new variable, is stored within the scriptβs scope. It isnβt accessible by the top-level shell.
When the script finishes running, its scope is discarded, and anything created within that scope disappears. For example, create a script called hw3drive.ps1 that contains the following and then run that script from the console window:
New-PSDrive -PSProvider FileSystem -Root C:\ -Name Sys Dir SYS:
After running the script, manually run βDir SYS:β and you should see an error. Thatβs because the SYS: drive was created in the script. Once the script was done, everything it created was discarded. The SYS: drive no longer exists. Some things in a shell are not scoped. Items such as modules are handled globally at all times. A script can load a module and the module will remain loaded after the script is done.
If a scope tries to access something that wasnβt created within that scope, then Windows PowerShell looks to the next-higher scope (the βparentβ scope). Thatβs why the Dir alias worked in that script you just entered. Although Dir didnβt exist in the scriptβs scope, it did exist in the next-higher scope: the global scope. A scope is free to create an item that has the same name as an item from a higher-level scope, though. Hereβs another script to try, save it as hw3dir.ps1:
New-Alias Fold Dir Fold Set-Alias Fold Get-Host Fold
5
The first time Fold runs it is an alias for the globally defined Dir command. We can then redefine it to the Get-Host command. Now try setting Dir as an alias, enter the following in hw3dira.ps1.
Dir Set-Alias Dir Get-Alias Dir
Later versions of PowerShell prevent reserved words and some of the default aliases from accidently being erased or replaced.
Scope can be especially confusing when it comes to variables. As a rule, a given scope should never access out-of-scope items, especially variables. Thereβs a syntax for doing so, such as using $global:var to forcibly access the global scopeβs $var variable, but thatβs a bad practice except under very specific circumstances.
Windows PowerShell Scripting Language
Windows PowerShell contains a very simplified scripting language of less than two dozen keywords. Thatβs a stark contrast to a full programming language such as VBScript, which contains almost 300 keywords.
Simplified though it may be, the Windows PowerShell language is more than sufficient to do the job, especially since in integrates with .NET, WMI, and other Microsoft products. You can always get more help on the major constructs of the language by reading the appropriate βaboutβ topic within the shell. For example, help about_switch contains information on the Switch construct, while help about_if contains information on the If construct. Run help about* for a list of all βaboutβ topics.
The If Construct
This is the Windows PowerShell main decision-making construct. In its full form, it looks like this: If ($this -eq $that) {
# commands
} elseif ($those -ne $them) {
# commands
} elseif ($we -gt $they) {
# commands
} else {
# commands
}
The βIfβ keyword is a mandatory part of this construct. A parenthetical expression follows that must evaluate to either True or False. Windows PowerShell will always interpret zero as False, and any nonzero value as True. If youβve ever programmed in C this is very familiar.
Windows PowerShell also recognizes the built-in variables $True and $False as representing those Boolean values. If the expression in parentheses works out to True, then the commands in the following
set of curly brackets will execute. If the expression is False, then the commands wonβt execute. Thatβs really all you need for a valid If construct.
6
You can go a bit further by providing one or more ElseIf sections. These work the same way as the If construct. They get their own parenthetical expression. If itβs True, the commands within the following curly brackets will execute. If not, they wonβt.
You can wrap up with an Else block, which will execute if none of the preceding blocks execute. Only the block associated with the first True expression will execute. For example, if $this did not equal $that, and $those did not equal $them, then the commands on line four would executeβand nothing else. Windows PowerShell wonβt even evaluate the second elseif expression on line five.
The # character is a comment character, making Windows PowerShell essentially ignore anything from there until the end of the line (a carriage return). Also, notice the care with which those constructs were formatted. You might also see formatting like this from some folks:
if ($those -eq $these)
{
#commands
}
It doesnβt matter where you place the curly brackets. However, what does matter is that you be consistent in your placement so your scripts are easier to read. Itβs also important to indent, to the exact same level, every line within the curly brackets.
The Windows PowerShell ISE lets you use the Tab key for that purpose, and it defaults to a four- character indent. Indenting your code is a core best practice. If you donβt, youβll have a tough time properly matching opening and closing curly brackets in complex scripts. Also, all of the other Windows PowerShell kids will make fun of you. Consider this poorly formatted script:
function mine {
if ($this -eq $that){
get-service
}}
Thatβs a lot harder to read, debug, troubleshoot and maintain. While the space after the closing parentheses isnβt necessary, it does make your script easier to read. The indented code isnβt necessary, but it makes your script easier to follow. Consider this instead:
function mine {
if ($this -eq $that){
get-service }
}
Placing a single closing curly bracket on a line by itself isnβt required by the shell, but itβs appreciated by human eyes. Be a neat formatter, and youβll have fewer problems in your scripts.
The Do While Construct
This is a looping construct in Windows PowerShell. Itβs designed to repeat a block of commands as long as some condition is True, or until a condition becomes True. Hereβs the basic usage:
7
Do {
# commands
} While ($this -eq $that)
In this variation of the construct, the commands within the curly brackets will always execute at least once. The While condition isnβt evaluated until after the first execution. You can move the While, in which case the commands will only execute if the condition is True in the first place:
While (Test-Path $path) {
# commands
}
Notice the second example doesnβt use a comparison operator such as -eq. Thatβs because the Test- Path cmdlet returns True or False to begin with. Thereβs no need to compare that to True or False in order for the expression to work.
The parenthetical expression used with these scripting constructs merely needs to evaluate to True or False. If youβre using a command such as Test-Path, which always returns True or False, thatβs all you need. As always, thereβs an βaboutβ topic in the shell that demonstrates other ways to use this construct.
The ForEach Construct
This construct is similar in operation to the ForEach-Object cmdlet. It differs only in its syntax. The purpose of ForEach is to take an array (or collection, which in Windows PowerShell is the same as an array) and enumerate the objects in the array so you can work with them one at a time:
$services = Get-Service
ForEach ($service in $services) {
$service.Stop() }
Itβs easy for newcomers to overthink this construct. Keep in mind that the plural English word βservicesβ doesnβt mean anything to Windows PowerShell. That variable name is used to remind us it contains one or more services. Just because itβs plural doesnβt make the shell behave in a special fashion.
The βinβ keyword on line two is part of the ForEach syntax. The $service variable is made up. It could easily have been $fred or $coffee and it would have worked in just the same way.
Windows PowerShell will repeat the constructβs commandsβthe ones contained within curly bracketsβ one time for each object in the second variable ($services). Each time, it will take a single object from the second variable ($services) and place it in the first variable ($service).
Within this construct, use the first variable ($service) to work with an individual object. On line three, the period indicates βI donβt want to work with the entire object, just one of its membersβthe Stop method.β
There are times when using ForEach is inevitable and desirable. However, if you have a bit of programming or scripting experience, you can sometimes leap to using ForEach when it isnβt the best
8
approach. The previous example isnβt a good reason to use ForEach. Wouldnβt the following code be easier:
Get-Service | Stop-Service
The point here is to evaluate your use of ForEach. Make sure itβs the only way to accomplish the task at
hand. Here are some instances where ForEach is probably the only way to go:
- When you need to execute a method against a bunch of objects and thereβs no cmdlet that performs the equivalent action.
- When you have a bunch of objects and need to perform several consecutive actions against each.
- When you have an action that can only be performed against one object at a time, but your script
may be working with one or more objects, and you have no way of knowing in advance.
Other Constructs
Windows PowerShell has several other scripting constructs, including Switch, For and so on. These are all documented in βaboutβ help topics within the shell and in the first PowerShell PowerPoint file on Blackboard. Sometimes, you can use the constructs covered here to replace those other constructs. For example, you can replace Switch with an If construct that uses multiple ElseIf sections. You can replace For with ForEach, or even with the ForEach-Object cmdlet. For example, having a loop that executes exactly 10 times:
1..10 | ForEach-Object -process {# code here will repeat 10 times # use $_ to access the current iteration # number}
Itβs up to you to select the best construct to get the job done. If youβre browsing the Internet for scripts, be prepared to run across any and all variations.
Functions
A function is a special kind of construct used to contain a group of related commands that perform a single, specific task. Generally speaking, you can take any Windows PowerShell script and βwrapβ it within a function:
function Mine { Get-Service Get-Process} Mine
This defines a new function called Mine. That basically turns Mine into a command, meaning you can run the function simply by entering its name. Thatβs what line five does. It runs the function.
Functions are typically contained within a script file. A single script can contain multiple functions and functions can contain other functions.
9
However, functions are scoped items. That means you can only use a function within the same scope in which it was created. If you put a function into a script, and then run that script, the function will only be available within the script and only for the duration of the script. When the script finishes running, the functionβlike everything else in the scriptβs scopeβgoes away. Hereβs one example:
function One {
function Two {
Dir }
Two }
One Two
Suppose you enter this into a single script file and run that script. The seventh line executes the function One, which starts on line one. Line five executes a function named Two, which starts on line two. The result will be a directory listing, which is on line three inside function Two.
However, the next line to execute will be line eight, and that will result in an error. The script doesnβt contain a function named Two. Function Two is buried within function One, and therefore exists only within the function One scope. Only things within function One can see Two. Attempting to call Two from anyplace else will result in an error.
Adding Parameters to a Script
Itβs rare to create a script thatβs intended to do exactly the same thing every time it runs. More frequently, youβll have scripts that contain some kind of variable data or variable behavior. You can accommodate these variations with parameters.
Parameters are defined in a special way at the top of the script. You can precede this definition with comments, but it must be the first executable lines of code within the script. Within the parameter definition area, each parameter is separated from the next by a comma. In keeping with the idea of neat formatting, it helps to place each parameter on a line of its own. Here is an example:
param (
[string]$computername,
[string]$logfile,
[int]$attemptcount = 5
)
This example defines three parameters. Within the script, these are simply used like any other variable. Youβll notice that on the fourth line, there is a default value assigned to the $attemptcount parameter. The default will be overridden by any input parameter, but will be used if the script is run without that parameter being specified.
10
Here are several ways in which the script might be run, assuming it was saved with the name Test.ps1:
.\test -computername SERVER .\test -comp SERVER -log err.txt -attempt 2 .\test SERVER err.txt 2 .\test SERVER 2 .\test -log err.txt -attempt 2 -comp SERVER
The script accepts parameters pretty much like any cmdlet. Variable names are used as the parameter names, specified with the usual dash that precedes all parameter names in Windows PowerShell. Hereβs a breakdown of how it works:
- On line one there is only one parameter specified β $logfile will thus be empty, and $attemptcount will contain 5, its default.
- On line two, all three parameters, though it is done using the shortened parameter names. As with cmdlets, you only need to type enough of the parameter name for Windows PowerShell to know which one youβre talking about.
- Line three also has all three parameters, this time indicated by position, without using parameter names. As long as values are in the exact order in which the parameters are listed in the script this will work fine.
- Line four shows what happens if youβre not careful. Here, $computername will contain ‘SERVER’ and $logfile will contain 2, while $attemptcount will contain 5. Thatβs probably not what was intended. When you donβt use parameter names, itβs harder to be accurate. Itβs also more difficult for someone else to decode what you meant, which makes it harder for them to troubleshoot any problems.
- Line five is a better example. Here, parameters are specified out of order, but thatβs fine because parameter names are used. As a general rule, I always use parameter names for the greatest degree of flexibility and accuracy.
Advanced Scripts
Windows PowerShell supports a technique for specifying additional information about parameters. This lets you declare a parameter as mandatory, accepting input from the pipeline and so forth. This technique is called Cmdlet Binding.
It doesnβt change the way the script uses parameters. It simply gives the shell a bit more information about the parameters. Youβll find this technique more commonly used in a function, but the syntax is valid within a script as well. Hereβs a simple example:
[CmdletBinding()] param (
[Parameter(Mandatory=$True)] [string]$computername,[Parameter(Mandatory=$True)] [string]$logfile,[int]$attemptcount = 5 )
11
The [CmdletBinding()] instruction must be the first executable line of code within the script. Itβs okay for comments to precede this, but nothing else. There are also [Parameter()] instructions added to two parameters. The [Paramater()] instructions indicate that these parameters are mandatory. If someone tries to run the script without specifying these parameters Windows PowerShell will prompt them for the information.
Notice that the last parameter doesnβt have any special instructions, and all three parameters still appear in a comma-separated list (meaning the first two parameters are followed by commas). There are a ton of other instructions you can specify for a parameter, which you can read about here or use get-help on the about_functions_advanced_parameters topic at the PowerShell prompt.
Answer the following questions and post the answers to Blackboard:
1. Described what is returned by each of the following commands: a. Get-Item env:\username
b. Get-Item env:\Computername
c. Get-ChildItem env:\userprofile
2. How would you change the commands in #1 to return only the value (look at the help information)?
3. How can you get the current date and time from the system within Powershell?
4. Describe the result of Get-WmiObject -Class for each of the following classes (see here or the help information for Get-WmiObject syntax).
a. Win32_ComputerSystem b. Win32_BootConfiguration c. Win32_BIOS
d. Win32_OperatingSystem e. Win32_TimeZone
f. Win32_LogicalDisk
f.1. Add β-Filter DriveType=3β g. Win32_Processor
h. Win32_PhysicalMemory i. Win32_Product
12



