Managing Computers with Commands and Scripts - Windows PowerShell for Administration: The Personal Trainer (2015)

Windows PowerShell for Administration: The Personal Trainer (2015)

Chapter 1. Managing Computers with Commands and Scripts

PowerShell scripts are text files containing the commands you want to execute. These are the same commands you normally type at the PowerShell prompt. However, rather than type the commands each time you want to use them, you create a script to store the commands for easy execution.

Although scripts are useful, you’ll more often work with PowerShell directly at the PowerShell prompt. You can create some extensive one-liners that’ll let you do just about anything, and if one line won’t suffice, you can type multiple lines of commands just as you would in a script. Additionally, if you want to copy examples from a document, the PowerShell console allows you to copy and paste a series of commands in the same way as you copy and paste a single command. The only difference is that with a series of commands, PowerShell executes each command separately.

Getting More from Your Scripts and Profiles

Because scripts contain standard text characters, you can create and edit scripts using any standard text editor as well as the PowerShell Integrated Scripting Environment (ISE). When you type commands, be sure to place each command or group of commands that should be executed together on a new line or to separate commands with a semicolon. Both techniques ensure proper execution of the commands. When you have finished creating a PowerShell script, save the script file using the .ps1 extension. In most cases, you’ll be able to save scripts only to restricted areas of the file system if you start the PowerShell ISE or your text editor as an administrator.

When you save a script, you can execute it as if it were a cmdlet or external utility: simply type the path to the script, type the name of the script, and press Enter. When you do this, PowerShell reads the script file and executes its commands one by one. It stops executing the script when it reaches the end of the file. Any output from the script is written to the PowerShell console, unless you explicitly specify otherwise.

Don’t forget that your PowerShell profiles are some of the most powerful scripts you can create. Your profiles can contain aliases, functions, and variables that you can use at any time. When you are logged on locally, the profiles in $pshome and $home affect you. When you are using remoting, the profiles in $pshome and $home on the remote computer affect you.

Listing 1-1 shows an example profile. This profile defines several functions and several aliases. The Prompt function modifies the prompt so that it always shows the current path and the computer name. The GetWinRm function gets the status of the Windows Remote Management service. The GetS function lists configured services by their status, such as Running or Stopped. The Inventory function lists the properties of the Win32_OperatingSystem. Three aliases provide keystroke shortcuts to functions: gr for GetWinRm, gs for GetS, and inv for Inventory.

LISTING 1-1 An Example Profile

function prompt {"PS $(get-location) [$env:computername]> "}

new-alias gr getwinrm
new-alias gs gets
new-alias inv inventory
function getwinrm {
get-service -name winrm | format-list
}

function gets {param ($status)
get-service | where { $_.status -eq $status}
}

function inventory {param ($name = ".")
get-wmiobject -class win32_operatingsystem -namespace `
root/cimv2 -computername $name | format-list *
}

NOTE The third line from the bottom includes a back apostrophe (`). This character tells PowerShell the command is continued on the next line. If you have trouble typing a line with a continuation character, simply type the divided lines as a single line without the back apostrophe (`).

You are able to use variables, aliases, and functions in profiles because they are loaded into the global working environment. As you get used to using the additional elements you’ve created, you’ll increasingly want them to be available whenever you are logged on and working with Windows. In some cases, you’ll be able to copy your profiles to the computers you work with, such as with servers you manage, and you’ll then be able to take advantage of the features you’ve built into your profile. However, copying your profile to every possible computer you’ll work with in the enterprise isn’t practical. In these cases, you still can take advantage of your profile if you prepare ahead of time.

To prepare, you need to modify a copy of your profile so that its elements are declared as global explicitly. Listing 1-2 shows how you can accomplish this for the sample profile created previously. Next, you need to save the modified profile in a location that will be accessible on other computers you work with, such as a network share. Finally, on the computer you are working with, you can load the script into the global environment by running it. Be sure to specify the full path to the script, such as \\FileServer84\DataShare\wrstanek\profile.ps1.

LISTING 1-2 Example of a Modified Profile

function global:prompt {"PS $(get-location) [$env:computername]> "}

new-alias gr getwinrm -scope global
new-alias gs gets -scope global
new-alias inv inventory -scope global

function global:getwinrm {
get-service -name winrm | format-list
}

function global:gets {param ($status)
get-service | where { $_.status -eq $status}
}

function global:inventory {param ($name = ".")
get-wmiobject -class win32_operatingsystem -namespace `
root/cimv2 -computername $name | format-list *
}

Keep in mind that execution policy must be set to allow you to run scripts from remote resources. If execution policy requires remote scripts to be signed, you need to add a signature to the script before you can run it. Additionally, before PowerShell runs the script, you will likely see a security warning similar to the following:

Security Warning
Run only scripts that you trust. While scripts from the Internet can be useful, this script can potentially harm your computer.

Do you want to run \\192.168.1.252\wrs\profileold.ps1?
[D] Do not run [R] Run once [S] Suspend [?] Help (default is "D"):

To proceed and run the script, you need to type R and press Enter. For more information about execution policy and scripting signing, see Windows PowerShell: The Personal Trainer.

Creating Transcripts

The PowerShell console includes a transcript feature to help you record all your activities at the prompt. As of this writing, you cannot use this feature in the PowerShell application. Commands you use with transcripts include the following:

· Start-Transcript Initializes a transcript file and then creates a record of all subsequent actions in the PowerShell session

Start-Transcript [[-path] FilePath] [-force] [-noClobber]
[-append]

· Stop-Transcript Stops recording actions in the session and finalizes the transcript

Stop-Transcript

You tell PowerShell to start recording your activities using the Start-Transcript cmdlet. This cmdlet creates a text transcript that includes all commands that you type at the prompt and all the output from these commands that appears on the console. The basic syntax for Start-Transcript is

Start-Transcript [[-path] FilePath]

where FilePath specifies an alternate save location for the transcript file. Although you cannot use wildcards when you set the path, you can use variables. The directories in the path must exist or the command fails.

NOTE If you do not specify a path, Start-Transcript uses the path in the value of the $Transcript global variable. If you have not created this variable, Start-Transcript stores the transcript in the $Home\Documents directory as a PowerShell_transcript.TimeStamp.txt file, where TimeStamp is a date-time stamp.

Use the –Force parameter to override restrictions that prevent the command from succeeding. For example, –Force will override the read-only attribute on an existing file. However, –Force will not modify security or change file permissions.

By default, if a transcript file exists in the specified path, Start-Transcript overwrites the file without warning. Use the –noClobber parameter to stop PowerShell from overwriting an existing file. Alternatively, use the –Append parameter to add the new transcript to the end of an existing file.

When you want to stop recording the transcript, you can either exit the console or type Stop-Transcript. The Stop-Transcript cmdlet requires no additional parameters.

REAL WORLD When you start a transcript, PowerShell initializes the transcript file by inserting a header similar to the following:

**********************
Windows PowerShell Transcript Start
Start time: 20150211134826
Username : IMAGINEDLANDS\williams
RunAs : IMAGINEDLANDS\williams
Machine : CORPSERVER64 (Microsoft Windows NT 6.3.9600.0)
Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Process ID: 880
**********************
Transcript started, output file is C:\Data\PowerShell_transcript.20150211134826.txt

This header specifies the time the transcript was started, the user who started the transcript, the computer for which the transcript was created, and the full path to the transcript file. Note that the user name is provided in DOMAIN\UserName or MachineName\UserName format and that the machine information includes the Windows version and service pack. This information is helpful when you are troubleshooting, because it enables you to know if the script is running in the wrong user context or against a computer running an incompatible version of Windows. When you stop a transcript, PowerShell inserts a footer into the transcript file similar to the following:

**********************
Windows PowerShell Transcript End
End time: 20150211134958
**********************

This footer specifies the time the transcript was stopped. The difference between the start time and the stop time is the elapsed run time for a script or the total troubleshooting/work time when you are working at the prompt.

Creating Transactions

A transaction is a block of commands that are managed as a unit where either all the commands are successful and completed, or all the commands are undone and rolled back because one or more commands failed. You can use transactions when you want to be sure that every command in a block of commands is successful and to avoid leaving the computer in a damaged or unpredictable state.

Understanding Transactions

Whether you are working with relational databases, distributed computing environments, or PowerShell, transactions are some of the most powerful commands you can work with. Why? Because transactions ensure that changes are applied only when appropriate, and when you encounter any problems, the changes are undone and the original working environment is restored.

To restore the working environment, transacted commands must keep track of changes that were made as well as the original content and values. Because of this, commands must be designed specifically to support transactions, and not all commands can or do support transactions. In PowerShell, support for transactions must be implemented at two levels:

· Provider Providers that provide cmdlets must be designed to support transactions.

· Cmdlet Individual cmdlets, implemented on compliant providers, must be designed to support transactions.

In the core PowerShell environment, the only component that supports transactions is the Registry provider. On this provider, the Item-related cmdlets support transactions, including New-Item, Set-Item, Clear-Item, Copy-Item, Move-Item, and Remove-Item. You can also use the System.Management.Automation.TransactedString class to include expressions in transactions on any version of Windows that supports Windows PowerShell. Other providers can be updated to support transactions, and you can look for compliant providers by typing the following command:

get-psprovider | where {$_.Capabilities -like "*transactions*"}

At their most basic, transactions work like this:

1. You start a transaction.

2. You perform transacted commands.

3. You commit or undo transacted commands.

Transactions also can have subscribers. A subscriber is a sequence of transacted commands that is handled as a subunit within an existing transaction. For example, if you are using PowerShell to work with a database, you might want to start a transaction prior to changing data in the database. Then you might want to create subtransactions for each data set you are manipulating. Because the success or failure of each subtransaction determines the success or failure of the entire transaction, you must commit each subtransaction separately before you commit the primary transaction.

Cmdlets you can use with transactions include the following:

· Get-Transaction Gets an object that represents the current active transaction in the session or the last transaction if there is no active transaction. This allows you to view the rollback preference, the subscriber count, and the status of the transaction.

Get-Transaction

· Complete-Transaction Commits an active transaction, which finalizes the transaction and permanently applies any related changes. If the transaction includes multiple subscribers, you must type one Complete-Transaction command for every dependent subscriber to commit the transaction fully.

Complete-Transaction

· Start-Transaction Starts a new independent transaction or joins an existing transaction as a dependent subscriber. The default rollback preference is Error. Although there is no default time-out for transactions started at the command line, the default timeout for transactions started in a script is 30 minutes.

Start-Transaction [-Independent] [-RollbackPreference {Error | TerminatingError | Never}] [-Timeout Minutes]

· Undo-Transaction Rolls back the active transaction, which undoes any related changes and restores the original working environment. If the transaction includes multiple subscribers, the Undo-Transaction command rolls back the entire transaction for all subscribers.

Undo-Transaction

· Use-Transaction Adds a script block to the active transaction, enabling transacted scripting of compliant .NET Framework objects, such as instances of the System.Management.Automation.TransactedString class. You cannot use noncompliant objects in a transacted script block. To enter the active transaction, you must use the –UseTransaction parameter. Otherwise, the command is ineffective.

Use-Transaction [-TransactedScript] ScriptBlock
[-UseTransaction]

REAL WORLD Cmdlets that support transactions have a –UseTransaction parameter. As changes are made to PowerShell, you can find cmdlets that support transactions by using the following command: get-help * -parameter UseTransaction. If a cmdlet has this parameter, the cmdlet supports transactions. Note that default cmdlets, such as the Item cmdlets with the Registry provider, might not be listed in the help documents as having the –UseTransaction parameter.

When you use transactions to modify a computer’s configuration, keep in mind the data that is affected by the transaction is not changed until you commit the transaction. However, other commands that are not part of the transaction could make the same changes, and this could affect the working environment. Although most transactional systems, such as relational databases, have a feature that locks data while you are working on it, PowerShell does not have a lock feature.

In each PowerShell session, only one independent transaction can be active at a time. If you start a new, independent transaction while a transaction is in progress, the new transaction becomes the active transaction, and you must complete the new transaction before making any changes to the original transaction. You complete transactions by committing or rolling back changes.

With a successful transaction, all the changes made by the commands are committed and applied to the working environment. With an unsuccessful transaction, all the changes made by the commands are undone, and the working environment is restored to its original state. By default, transactions are rolled back automatically if any command in the transaction generates an error.

Using Transactions

To start a new independent transaction or join an existing transaction as a dependent subscriber, you type start-transaction at the PowerShell prompt or in a script. By default, if you use Start-Transaction while a transaction is in progress, the existing transaction object is reused, and the subscriber count is incremented by one. You can think of this as joining the original transaction. To complete a transaction with multiple subscribers, you must type a Complete-Transaction command for each subscriber.

NOTE PowerShell supports subscribers to transactions to accommodate environments when a script contains a transaction that calls another script that contains its own transaction. Because the transactions are related, they should be rolled back or committed as a unit.

With Start-Transaction, the –Independent parameter applies only when a transaction is already in progress in the session. If you use the –Independent parameter, a new transaction is created that can be completed or undone without affecting the original transaction. However, because only one transaction can be active at a time, you must complete or roll back the new transaction before resuming work with the original transaction.

In the following example and sample output, you start a transaction with automatic rollback set to Never and a timeout value of 30 minutes:

start-transaction –rollbackpreference "never" –timeout 30


Suggestion [1,Transactions]: Once a transaction is started, only commands that get called with the -UseTransaction flag become part of that transaction.

By adding the –RollbackPreference parameter, you can specify whether a transaction is automatically rolled back. Valid values are

· Error Transactions are rolled back automatically if any command in the transaction generates a terminating or nonterminating error. This is the default.

· TerminatingError Transactions are rolled back automatically if any command in the transaction generates a terminating error.

· Never Transactions are never rolled back automatically.

You can use the –Timeout parameter to specify the maximum time, in minutes, that a transaction can be active. When the timeout expires, the transaction is automatically rolled back. By default, there is no timeout for transactions started at the command line. When transactions are started by a script, the default timeout is 30 minutes.

After you’ve started a transaction, you can perform transacted commands in two ways. You can

· Add script blocks to the active transaction with Use-Transaction.

· Add an individual command to the active transaction using the command’s UseTransaction parameter.

PowerShell executes the script blocks and commands as you add them to the transaction, taking appropriate action if errors are encountered or if the timeout value is reached. You can obtain information about the transaction using Get-Transaction as shown in this example and sample output:

get-transaction

RollbackPreference SubscriberCount Status
------------------ --------------- ------
Never 3 Active

Here, Get-Transaction shows you the rollback preference is Never, the subscriber count is 3, and the status of the transaction is Active.

If PowerShell doesn’t automatically roll back the transaction, because of errors or a timeout expiration, you can manually commit or roll back the transaction. To commit the transaction, you type complete-transaction once for each subscriber. To undo a transaction completely for all subscribers, you type undo-transaction.

Consider the following example:

start-transaction

cd hkcu:\Software
new-item MyKey -UseTransaction
new-itemproperty -path MyKey -Name Current `
-value "Windows PowerShell" -UseTransaction

complete-transaction

Here, you start a transaction so that you can work safely with the registry. You access the HKCU\Software hive and create a new key called MyKey. Then you add a value to this key. As long as these operations did not generate an error, the transaction continues to be active, and you then apply the changes by completing the transaction.

Common Elements in Scripts

Now that we’ve discussed how to work with scripts, let’s look at common elements you’ll use in scripts, including

· Comments

· Initializing statements

· Conditional statements

· Control loops

Using Comments and Initializing Statements

Most scripts begin with comments that specify what a script is for and how it is used. PowerShell supports two types of comments:

· Single-line comments that begin with #. PowerShell interprets everything from the begin-comment mark to the end of the line as a comment. Here is an example:

$myVar = "$env:computername" #Get the computer name

NOTE Because values in strings are interpreted differently and # is not interpreted as a special character in a string, you can use # in single-quoted and double-quoted strings, and # will not be handled as the beginning of a comment.

· Multiple-line comments that begin with the <# delimiter and end with the #> delimiter. If you have a begin-comment delimiter, you must have a matching end-comment delimiter. PowerShell interprets everything between the begin and end comment tags as a comment. Here is an example:

<# --------------------------

ScriptName: EvaluateComp.ps1
Description: This script checks the working environment
of a computer to determine issues with drive space,
network connections, etc.

-------------------------- #>

Every script you create should have comments that include the following details:

· When the script was created and last modified

· Who created the script

· What the script is used for

· How to contact the script creator

· Whether and where the script output is stored

Not only are the answers to the questions of who, what, how, where, and when important for ensuring that the scripts you create can be used by other administrators, they can also help you remember what a particular script does, especially if weeks or months have passed since you last worked with the script. An example of a script that uses comments to answer these questions is shown as Listing 1-3.

LISTING 1-3 Sample Script Header

<# --------------------------
ScriptName: CheckDNS.ps1
Creation Date: 2/28/2015
Last Modified: 3/15/2015
Author: William R. Stanek
E-mail: williamstanek@aol.com
************************
Description: Checks DNS and IP configuration.
************************
Files: Stores output in c:\data\checkdns.txt.
-------------------------- #>

Keep in mind that you can also use comments to:

· Insert explanatory text within scripts, such as documentation on how a function works.

· Prevent a command from executing. On the command line, add # before the command to comment it out.

· Hide part of a line from interpretation. Add # within a line to block interpretation of everything that follows the # character.

After you add a header to your script, you might want to initialize the console so that the working environment appears the same way every time. For example, you might want to use Cls (or Clear-Host) to clear the console window and reset the screen buffer, and use the Start-Transcript cmdlet to record all output to a transcript file. If you start a transcript, be sure to add a Stop-Transcript cmdlet at the end of your script to end the transcript session.

You also might want your script to initialize the PowerShell window by setting the window size, text color, and window title. Here is an example:

if ($host.name -eq "ConsoleHost") {
$size=New-Object System.Management.Automation.Host.Size(120,60);
$host.ui.rawui.WindowSize=$size }

$myHostWin = $host.ui.rawui
$myHostWin.ForegroundColor = "Blue"
$myHostWin.BackgroundColor = "Yellow"
$myHostWin.WindowTitle = "Working Script"

Here, you get an instance of the System.Management.Automation.Host object so that you can set the window size to 120 characters wide by 60 lines high if you are working with the PowerShell console. Then you use properties of the $host.ui.rawui object to specify that you want to use blue text on a yellow background and a window title of Working Script. For more information on these object instances, see Windows PowerShell: The Personal Trainer.

NOTE When you dynamically reset the size of the PowerShell console, you must keep in mind the current display resolution and the console’s configured dimensions. You shouldn’t set the width and height of the console so that it is larger than the display size. Additionally, you’ll get an error if you try to set the width of the console so that it is greater than the buffer size. Because resizing the window won’t work with the PowerShell application, you may want to check the value of the $Host.Name property to ensure that you are working with the console and not the PowerShell ISE. $Host.Name is set to “Windows PowerShell ISE Host” for the PowerShell application and “ConsoleHost” for the PowerShell console.

When you are initializing the script, you also want to ensure the following:

· The script will run on a computer with a compatible version of PowerShell.

· The host application is indeed Windows PowerShell.

· Required PowerShell snap-ins, providers, and modules are available.

PowerShell includes a #Requires statement that you can use to validate the PowerShell version, the host application, and the availability of snap-ins. Using #Requires statements, you can do the following:

· Verify the PowerShell version, where N is the version number and n is the optional revision number. For example, to verify the PowerShell version is 4.0 or later, use 4 or 4.0.

#requires -Version N[.n]

#requires –version 4
#requires –version 4.0

· Verify the host application ID, where ShellID is the identifier for the host application. To verify the host application is the PowerShell console or PowerShell ISE, use a value of Microsoft.PowerShell.

#requires –ShellId ShellId

#requires –ShellId "Microsoft.PowerShell"

· Verify a specified snap-in—or, optionally, a specified version of the snap-in—is loaded in the current session, where PSSnapIn is the snap-in identifier and the optional N and .n values set the required version.

#requires –PsSnapIn PsSnapIn [-Version N[.n]]

#requires –PsSnapIn ADRMS.PS.Admin -Version 4

Whenever you use #Requires statements, the script runs only if the computer meets the criteria set in the #Requires statements. For additional discussion on this topic, see Windows PowerShell: The Personal Trainer.

As with most cmdlets and external utilities, you can pass arguments to scripts when you start them. You use arguments to set special parameters in a script or to pass along information needed by the script. Each argument should follow the script name and be separated by a space (and enclosed in quotation marks if necessary). In the following example, a script named Check-Computer in the current working directory is passed the arguments FileServer26 and Extended:

.\check-computer fileserver26 extended

Each value passed along to a script can be examined using the $args array. You reference the first argument using $args[0], the second using $args[1], and so on. The script name itself is represented by the $MyInvocation.MyCommand.Name property. The full file path to the script is represented by the $MyInvocation.MyCommand.Path property.

NOTE Because PowerShell stores arguments in an array, there is no limit to the number of arguments you can pass to a script. Further, regardless of the number of arguments you use, the last argument is always represented by $args[$arg.length - 1], and the total number of arguments used is represented by $args.count. If no arguments are passed to a script, the argument count is zero.

Using Conditional Statements

Now that you know how to work with and initialize scripts, let’s look at the selection statements used to control the flow of execution based upon conditions known only at run time. These statements include:

· If…Else, to execute a block of statements if a condition is matched (true or false) and to otherwise execute a second block of statements.

· If…ElseIf…Else, to execute a block of statements if a condition is matched (true or false). Otherwise, it’s used to execute a second block of statements if a second condition is matched. Finally, it’s used to execute a third block of statements if neither of the previous conditions is met.

· If Not, to execute a statement when a condition is false. Otherwise, the statement is bypassed.

· Switch, to perform a series of three or more conditional tests.

Using If, If…Else, and If…ElseIf…Else

Although some of the previous examples in this book used conditional execution, we haven’t discussed the syntax for these statements. If your background doesn’t include programming, you probably will be surprised by the power and flexibility of these statements.

The If statement is used for conditional branching. It can be used to route script execution through two different paths. Its basic syntax is

if (condition) {codeblock1} [else {codeblock2}]

Here, each code block can contain a single command or multiple commands. The condition is any expression that returns a Boolean value of True or False when evaluated. The Else clause is optional, meaning you can also use the following syntax:

if (condition1) {codeblock1}

The If statement works like this: If the condition is true, codeblock1 is executed. Otherwise, codeblock2 is executed (if the Else clause is provided). In no case will both the If and Else clauses be executed. Frequently, the expression used to control If statements involves a test for equality. In fact, the most basic type of string comparison is when you compare two strings using the equality operator (=), such as

if (stringA = stringB) {codeblock}

Here, you are performing a literal comparison of the strings; if they are identical, the related code block is executed. This syntax works for literal strings but is not ideal for use in scripts. Parameters, property values, and arguments might contain spaces, or there might be no value at all for a variable. In this case, you might get an error if you perform literal comparisons. Instead, use the comparison operators to perform more advanced comparisons. For example, you could use –eq, –like, –match, or –contains.

To learn about other advanced techniques, consider the following example and sample output:

if ($args.count -eq 0) {throw "No arguments passed to script"}
else {write-host "You passed args to the script"}


No arguments passed to script
At C:\Users\Bubba\dat.ps1:1 char:25
+ if ($args.count) {throw "No arguments passed to script"}
+ CategoryInfo : OperationStopped: (No arguments passed to script:String) [], RuntimeException
+ FullyQualifiedErrorId : No arguments passed to script

Here, if you don’t pass any arguments to the script, the script throws an error. Because the default error action for PowerShell is to halt execution, this is an effective way to stop processing a script when expected conditions are not met. The Else condition applies when you pass arguments to the script, and in this example, “You passed args to the script” is written to the output.

Alternatively, if a script requires an argument, such as a computer name, you could use the If…Else construct to catch the missing argument and prompt the user to enter it using the Read-Host cmdlet as shown in this example and sample output:

if ($args.count -eq 0) {
$compName = read-host "Enter name of the computer to check"
} else {
$compName = $args[0]
}


Enter name of the computer to check: FileServer84.imaginedlands.com

Here, if you don’t type an argument, the Read-Host cmdlet prompts you and then stores the value you specify in the $compName variable. Otherwise, the $compName variable is set to the value of the first argument you pass to the script.

Using any of the logical operators, such as –and, –or, –is, or –isnot, you can check two conditions. In the following example, the If condition is met only when the conditions on either side of the logical AND are true:

if (($args.count -ge 1) -and ($args[0] -eq "Check")) {
write-host "Performing system checks..."
} else {
write-host "Script will not perform system checks..."
}


.\check-sys.ps1 Check

Performing system checks...

PowerShell also allows you to use If…ElseIf…Else constructs to execute a block of statements if a condition is met. Otherwise, it allows you to execute a second block of statements if a second condition is met. Finally, it allows you to execute a third block of statements if neither of the previous conditions is met. The basic syntax is

if (condition1) {action1} elseif (condition2) {action2}
else {action3}

The following example uses the If…ElseIf…Else construct to perform actions depending on the value of the first argument passed to the script:

if (($args.count -ge 1) -and ($args[0] -eq "Check")) {
write-host "Performing system checks..."
} elseif (($args.count -ge 1) -and ($args[0] -eq "Test")) {
write-host "Performing connectivity tests..."
} else {
write-host "Script will not perform system checks or tests."
}


.\check-sys.ps1 Test

Performing connectivity tests...

When you want to execute a statement only if a condition is false, you can use If Not. The basic syntax is

if (!condition) {codeblock1} [else {codeblock2}]

or

if (-not (condition)) {codeblock1} [else {codeblock2}]

Here PowerShell evaluates the condition. If it is false, PowerShell executes the statements in codeblock1. Otherwise, codeblock1 doesn’t execute, and PowerShell proceeds to codeblock2, if present. The Else clause is optional, meaning you can also use the following syntax:

if (!condition) {codeblock1}

Consider the following example:

if (!$args.count -ge 1) {
read-host "Enter the name of the computer to check"
}

Here you execute the code block when the argument count is not greater than or equal to one (meaning the argument count is zero).

TIP A nested If is an If statement within an If statement. Nested Ifs are common in programming, and PowerShell scripting is no exception. When you nest If statements, you place the required If…Else or If…ElseIf…Else construct with its subconditions and subcodeblocks inside the code block of the original If…Else or If…ElseIf…Else construct.

Using Switch

Checking for multiple conditions using If…ElseIf…Else constructs is a lot of work for you and for PowerShell. An easier way to check three or more conditions is to use Switch constructs. Using Switch, you can check multiple conditions in a way that is clear and easy to understand. You can also add a default code block that is executed if none of the other conditions are met.

The basic syntax for a Switch construct is

switch (pipeline_expression) {

value1 { codeblock1 }
value2 { codeblock2 }
value3 { codeblock3 }
. . .
valueN { codeblockN }
default { default_codeblock}
}

The Switch construct is essentially an extended series of If statements that get the condition to test from a pipeline value you provide. If a value in the pipeline matches a specified value, the related code block is executed. After the code block is executed, PowerShell exits the switch. If there are additional values in the pipeline to process, PowerShell evaluates those values, each in turn, in the same way.

NOTE Each Switch construct can have only one default. If there is more than one default clause, an error results.

Switch constructs can be used with any valid data type. If the value to check is an array of numbers, strings, or objects, each element is evaluated in order against the switch conditions, starting with element 0. At least one element must be present that meets at least one condition, or PowerShell generates an error. In the following example and sample output, you define an array called $myValue, assign four values, and then process the values through a Switch construct:

$myValue = 4, 5, 6, 0

switch ($myValue) {
0 { write-host "The value is zero."}
1 { write-host "The value is one."}
2 { write-host "The value is two."}
3 { write-host "The value is three."}
default { write-host "Value doesn't match."}
}

Value doesn't match.
Value doesn't match.
Value doesn't match.
The value is zero.

This processing approach is the same one PowerShell uses if you insert continue clauses as the last statement in every code block. Rather than have PowerShell continue processing with the next value in the pipeline, you might want PowerShell to stop processing any additional values in the pipeline. To do this, you can add the break statement whenever you want PowerShell to exit the Switch construct. Typically, you add a break statement as the last statement in a code block. Thus, the basic syntax becomes

switch (pipeline) {
value1 { codeblock1 break}
value2 { codeblock2 break}
value3 { codeblock3 break}
. . .
valueN { codeblockN break}
default { default_codeblock}
}

In the following example, you use break statements to break out of the Switch construct:

$myError = "Green", "Red", "Yellow", "Green"

switch ($myError) {
"Red" { write-host "Critical error occurred. Break."; break}
"Yellow" { write-host "A warning occurred. Break."; break}
"Green" { write-host "No error occurred yet. No break." }
default { write-host "The values don't match expected parameters."}
}


No error occurred yet. No break.
Critical error occurred. Break.

By default, when you use Switch, PowerShell does not consider the letter case and looks for exact matches only. To control what determines matches, you can add the following flags:

· –regex Matches string values against a regular expression. If the value you are testing is not a string, this option is ignored. Don’t use this flag with the –wildcard and –exact flags.

· –wildcard Matches string values using wildcards. Indicates that the match clause, if a string, is treated as a wildcard string. If the value you are testing is not a string, this option is ignored. Don’t use this flag with the –regex and –exact flags.

· –exact Performs an exact match of string values. If the value you are testing is not a string, this option is ignored. Don’t use it with the –regex and –exact flags.

· –casesensitive Performs a case-sensitive match. If the value you are testing is not a string, this option is ignored.

· –file Takes input from a file rather than a statement. Each line of the file is read as a separate element and passed through the switch block, starting with the first line of the file. At least one element must be present that meets at least one condition, or PowerShell generates an error.

The following example switches based on the status of a service you specify by name in the first argument or when prompted:

if (!$args.count -ge 1) {
$rh = read-host "Enter the name of the service to check"
$myValue = get-service $rh
} else {
$myValue = get-service $args[0]
}
$serName = $myValue.Name

switch -wildcard ($myValue.Status) {
"S*" { write-host "The $serName service is stopped."}
"R*" { write-host "The $serName service is running."}
"P*" { write-host "The $serName service is paused."}
default { write-host "Check the service."}
}


Enter the name of the service to check: w32time
The W32Time service is running.

Using Control Loops

When you want to execute a command or a series of commands repeatedly, you use control loops. A loop is a method for controlling the logical flow and execution of commands. In PowerShell, you can perform controlled looping in several ways, including

· For looping

· ForEach looping

· While looping

· Do While looping

· Do Until looping

You can create a basic loop using the For statement. You use For loops to execute a code block for a specific count. The structure of For loops is as follows:

for (countStartValue; condition; countNextValue) { CodeBlockToRepeat }

where countStartValue is a statement that initializes the counter that controls the For loop, condition is a statement that specifies what value the counter must reach to stop looping, and countNextValue is a statement that sets the next value for the counter. Note that the elements in parentheses are separated with semicolons and that the next value for the counter is set after each iteration (and not before iteration begins).

You can set the next value for the counter using any assignment operator. In the following example and sample output, you initialize the counter to 1, loop as long as the counter is less than or equal to 10, and increment the counter by 1 after each iteration:

for ($c=1; $c -le 10; $c++){write-host $c}


1
2
3
4
5
6
7
8
9
10

In the following example and sample output, you initialize the counter to 10, loop as long as the counter is greater than or equal to 0, and decrement the counter by 1 after each iteration:

for ($c = 10; $c -ge 0; $c--) {Write-Host $c}


10
9
8
7
6
5
4
3
2
1
0

You can just as easily increment or decrement the counter by twos, threes, or more as shown in these examples:

for ($c=1; $c -le 100; $c += 2){write-host $c}
for ($c=1; $c -le 100; $c += 3){write-host $c}

for ($c = 20; $c -ge 0; $c -= 2) {Write-Host $c}
for ($c = 20; $c -ge 0; $c -= 3) {Write-Host $c}

Another type of loop is a ForEach loop. With ForEach loops, you iterate through each element in a collections of items. Although you can work with other types of collections, you’ll more typically work with collections of items in an array.

ForEach loops are similar to standard For loops. The key difference is that the number of elements in the collection determines the number of times you go through the loop. The basic syntax is

ForEach ( Item in Collection) { CodeBlockToRepeat }

where Item is a variable that PowerShell creates automatically when the ForEach loop runs, and Collection is the collection of items to iterate through. The collection can come directly from the pipeline. In the following example and sample output, you perform an action against each process in a collection of processes:

foreach ($p in get-process) {
if ($p.handlecount -gt 500) {
Write-Host $p.Name, $p.pm }
}


aolsoftware 14766080
csrss 1855488
csrss 22507520
explorer 34463744

Here, every running process is examined. The If statement checks for processes with greater than 500 file handles and lists their name and private memory set. This technique can help you find processes that are using a lot of resources on the computer.

In the following example and sample output, you perform an action against each file in a collection of files:

if (!$args.count -ge 1) {
$path = read-host "Enter name of the base directory to check"
} else {
$path = $args[0]
}

foreach ($file in Get-ChildItem -path $path -recurse) {
if ($file.length -gt 1mb) {
$size = [Math]::Round($file.length/1MB.ToString("F0"))
Write-Host $file, $size, $file.lastaccesstime }
}


A Catalog Section 1.pdf 8095845 11/23/2015 8:10:16 PM
A Catalog Section 2.pdf 12021788 11/23/2015 8:10:16 PM

Here, every file in a specified base directory and its subdirectories is examined. The If statement looks for files with a file size greater than 1 MB and lists their name, size in MB, and last access time. This technique can help you find large files that haven’t been accessed in a long time.

TIP With For and ForEach loops, you’ll sometimes want to break out of the loop before iterating through all of the possible values. To break out of a loop ahead of schedule, you can use the Break statement. The best place for this statement is within the code blocks for If, If…Else, and If…ElseIf…Else constructs.

Sometimes you’ll want to execute a code segment while a condition is met. To do this, you use While looping. The structure of this loop is as follows:

while (condition) {CodeBlockToRepeat}

With the While statement, the loop is executed as long as the condition is met. To break out of the loop, you must change the condition at some point within the loop. Here is an example of a While loop that changes the status of the condition:

$x = 0
$continuetoggle = $true
while ($continuetoggle) {
$x = $x + 1
if ($x -lt 5) {write-host "x is less than 5."}
elseif ($x -eq 5) {write-host "x equals 5."}
else { write-host "exiting the loop."
$continuetoggle = $false }
}

X is less than 5.
X is less than 5.
X is less than 5.
X is less than 5.
X equals 5.
Exiting the loop.

By placing the condition at the top of the loop, you ensure that the loop is executed only if the condition is met. In the previous example, this means the loop won’t be executed at all if continueToggle is set to False beforehand.

However, sometimes you want to execute the loop at least once before you check the condition. To do this, you can use the Do While construct, which places the condition test at the bottom of the loop, as in the following example:

do {CodeBlockToRepeat} while (condition)

In the following example, the code block is processed at least once before the condition is checked:

$x = 0
$continuetoggle = $true
do { $x = $x + 1
if ($x -lt 5) {write-host "x is less than 5."}
elseif ($x -eq 5) {write-host "x equals 5."}
else { write-host "exiting the loop."
$continuetoggle = $false }
}
while ($continuetoggle)


X is less than 5.
X is less than 5.
X is less than 5.
X is less than 5.
X equals 5.
Exiting the loop.

The final type of control loop available in PowerShell is the Do Until loop. With Do Until, you execute a loop until a condition is met instead of while a condition is met. As with Do While, you place the condition test at the end of the loop. The basic syntax is

do {CodeBlockToRepeat} until (condition)

The following loop is executed one or more times until the condition is met:

do {
$cont = read-host "Do you want to continue? [Y/N]"
} until ($cont -eq "N")


Do you want to continue? [Y/N]: y
Do you want to continue? [Y/N]: y
Do you want to continue? [Y/N]: n