School of PowerShell

Soldato
Joined
25 Mar 2004
Posts
15,912
Location
Fareham
Lesson 1 - Basics

This is going to be very basic indeed, but I need to get some ground rules down. This lesson will cover the different ways that PowerShell can be used, and getting to grips with some of the discovery elements.

Introduction

Welcome to the School of PowerShell.

In this forum thread I will attempt to explain some basics of PowerShell. This will be aimed at users who hold sysadmin positions, and perhaps have dabbled with VBScript or Batch in the past, but want to progress to PowerShell.

PowerShell is being integrated with all major Microsoft products these days. Some more than others, for example it's very well integrated with Exchange.

I can see this trend continuing, and if you know some PowerShell, it will almost certainly make your life easier if you are working in Microsoft land.

PowerShell is very much about understanding certain principles, and also how to bend it to your will (which you will sometimes need to do!). There is usually more than one way to achieve something in PowerShell.

Opening PowerShell

Can't use if if you can't open it right?

There are a few ways to open PowerShell:

  1. You can go into a normal CMD prompt and type PowerShell.exe, this will convert your CMD prompt window into a PowerShell window.
  2. Hit Windows Key-R to open the Run window, then type PowerShell.exe, this also opens PowerShell.
    [*]My favourite one is just to bind the shortcut to my taskbar/start menu and run from there every time. You can find the shortcuts under Start Menu\Programs\Accessories\Windows PowerShell.

I strongly recommend number 3 as it fundamentally changes the way that the shell responds.

Shortcut pinned to the task bar looks like this:

RkUEW.png

Versions of PowerShell

PowerShell has had 3 different versions now. V1, V2, and most recently V3. I will generally cover V2 as that's the one I've used the most. V2 comes with default Windows 7 installations, which most people will be using.

You can find out what version you are running by typing $Host.version into the PowerShell window. The number under Major is your version.

y4MhT.png

If you don't have PowerShell installed you can download it from here:

http://support.microsoft.com/kb/968929

Using PowerShell

There are two ways of using PowerShell:

  1. Interactively
  2. Via .PS1 Scripts

I will generally cover Interactive use - that is using PowerShell directly via the window. PS1 scripts have their place though. Especially if you want to configure scheduled tasks.

Tabbed completion

I'm not going to go into too much detail here, but PowerShell supports tabbed completion. That means you can begin typing a Command, Function name, File name etc and PowerShell will complete it for you.

This is great as you can discover new commands, ensure correct spelling, and also save time! :)

A basic command, and one I may refer to throughout these lessons, is Get-Service. This command gets the statuses of running services on the local machine.

If you begin typing the command and then hit tab, it will attempt to autocomplete the command for you.

For example typing this, then hitting tab:

Get-Ser

0aggI.png

Autocompletes the entry for me:

7UKpX.png

Copy/Pasta

Copying and Pasting text in PowerShell is simple, but does depend on how you loaded PowerShell a little.

If you converted a CMD prompt window to PowerShell.exe then yours will work more like copying/pasting in CMD prompt.

If yours looks like mine, then it's different again. I will only cover copying/pasting in one that looks like my screenshots in this thread for now.

To copy text just left click once in the window, this starts highlighting, then drag the highlight over the text you want to copy using the mouse.

Copying text:

TYV6l.png

To complete the copy process, just Right click once, this stops the highlighting process. The text will now be copied to the clipboard.

Pasting text is as simple as Right clicking within the PowerShell Window.

In short:

Left click Copies
Right click Pastes.

Self-Help

PowerShell has an excellent self help system. I learnt just about everything I know by using the in-built help commands, or Googling to be honest.

To get help on a command simply prefix the command you want more info on with Get-Help. I normally add the -Detailed switch on the end for more detail as well.

For example this will return help info on the Get-Service command:

Get-Help Get-Service -Detailed

mvmRz.png

Running previous commands

One of the simplest, yet most useful things in PowerShell, is to be able to go back through command history and run previous commands.

You can do this in PowerShell in the same way you can with Command Prompt, by using the Up and Down arrows to cycle through commands you've run in the same session.

I'll leave it here for this lesson. Some very basic things so far, but I don't want to pile too much one one go.
 
Last edited:
Lesson 2 - Moar Interesting Stuff (Variables)

I've covered some very basic things in Lesson 1. Lesson 2 should be a bit more interesting. :)

Variables

Variables in PowerShell are prefixed with a $ symbol.

I use the alternate caps style names for Variables, so if I wanted to create a Variable for Running Services, I might call it $RunningServices for example. It's good practice to do this for any programming really in my opinion.

You generally use Variables if you want to store something for later usage.

You can assign a Variable to almost anything. For example here I assign a Variable named $WindowsServices to the command Get-Service:

$WindowsServices = Get-Service

4cBA7.png

By running this, the Variable $WindowsServices now contains the results of Get-Service. This is a "point-in-time" snapshot of the data. Recalling the Variable later will not re-issue the Get-Service command.

You will also note it didn't feed anything back to me when I ran it. This is expected behavior.

If I want to see the results of the command in the Variable, I can simply type the Variable into the window, and hit RETURN to display the information, like this:

f66eh.png

PowerShell does not forget


PowerShell will "remember" anything you run, until you either manually clear the Variables, or close the window and open a new one. This is because it stores the results of Variables in RAM. If you are storing lots of data, PowerShell can eat a lot of RAM!

I've been caught out a few times where something would work in my open window, but would not in a new one. This was due to having a Variable with a value I was not anticipating.

You can use PowerShells ability to remember things to debug, or to simply allow you to play with raw data without having to re-issue a command that can take a while to run.

Clearing Variables

Based on the above, sometimes if you are looping through arrays, or values, you will want to clear Variables to ensure it's not just remembering previous data.

You can do this in a few ways, which I will discuss here:

1. Clear-Variable

Issue a Clear-Variable command, specifying the Variable you want to clear. This keeps the Variable but wipes any data it contains. Note I do not need to specify the $ on the name of the Variable I am clearing:

Clear-Variable -Name "WindowsServices"

NbR1N.png

2. Remove-Variable

Like Clear-Variable, but it obliterates the Variable totally.

Remove-Variable -Name "WindowsServices"

2BnaO.png

3. Re-define the Variable

PowerShell remembers the last value assigned to a Variable, you can take advantage of this and re-define the Variable with another value.

For example, if I declare the same Variable name has two values, only the most recently executed one will apply:

$Variable1 = "10"
$Variable1 = "20"


mzIcu.png

Take care with this, you may also need to re-define the Data Type assigned to the Variable as well. See later section for more info.

4. Set the Variable to $Null

There are certain "protected" Variables, which I will cover more in the next step.

You can set a value to be $Null and it will essentially be a null, or empty value:

$Variable1 = $Null

NCQjm.png

Protected Variables

You can't use certain Variables for your own scripts or commands, for example these ones are protected:

$True
$False
$Null
$Host
$Error

If you try, you will get an error:

hEI2M.png

There is probably an exhaustive list of these somewhere, these are just a few I know off the top of my head.

Data Types

Variables are stored as Data Types. PowerShell is not very strict when it comes to Data Types, and it does it's best to guess what Data Type applies to the values you are using unless you explicitly declare it.

There are certain Data Type prefixes that you can use with PowerShell, as follows:

[bool] (short for System.Boolean)
[byte] (short for System.Byte)
[char] (short for System.Char)
[int] (short for System.Integer)
[long] (short for System.Long)
[decimal] (short for System.Decimal)
[single] (short for System.Single)
[double] (short for System.Double)
[string] (short for System.String)
[datetime] (short for System.DateTime)

The most common one you will come across is [string], but they all can have their uses.

To declare a data type you can just prefix the Variable name with the Data Type you want to use. I don't often need to do this, but you may want to force the data type sometimes, especially for mathematical calculations.

[string]$Variable2 = "This is a string"

g9y8X.png

You can declare a Data Type and then give it data which is in reality something else, PowerShell will attempt to convert the data you have entered, if it fails it will give you an error.

This example shows what happens to a decimal number when you use different Data Types:

fqiNi.png

You can also directly cast text or numbers into a Data Type, I sometimes do this if I am just testing a conversion works OK.

As you can see it gives an error if I try to use the UK DateTime format, my computer only resolves the DateTime correctly if I use the American system! :mad:

o6I6X.png

Discovering Variable Data Types

Not sure what Data Type a Variable is? simples!

In this example I declare a new Variable called $Variable, with an [int] Data Type, and a value of 10. I then show you how you can verify this is an Integer:

$Variable.GetType()

qEP8T.png

You can also use Get-Member to find out more about a variable.

Get-Member

Get-Member, or gm for short, can be used to retrieve additional information about what you can do with Variables.

This command will list the Data Type, and further information about the methods you can use with the variable, or the properties attached to it.

You can use Get-Member in a couple of ways that I know of.

1. Specify an Input object:

Get-Member -InputObject $Variable

iIslQ.png

2. Pipe a variable to Get-Member. I will cover the Pipeline in more detail in the next Lesson.

$Variable | Get-Member

0Mw9o.png
 
Last edited:
Lesson 3 - More p|pes than Super Mario

The Pipeline

I would consider the Pipeline in PowerShell a pretty fundamental tool for any serious PowerShell user.

You can pipe one section to another for various reasons, sometimes you may want to use the Pipeline to Retrieve a list of objects (using a Get- cmdlet) and then run an action against them (using a Set- cmdlet for example).

But that is not the only use for the Pipeline, you can also use it to do basic Selecting, Sorting, Filtering, and Expressions to name but a few.

A lot of the Pipeline operations draw certain Parallels with SQL querying, so you should feel fairly at home if you use SQL.

Selecting

I will go back to my favorite example, Get-Service, to show you how you can use the Pipeline to your advantage.

I will also tie this example in with some of my previous lesson material to show how you can use these elements practically.

First of all I declare a new Variable, $Services and make it equal to Get-Service.

$Services = Get-Service

uoJef.png

Next I check what fields the variable has using Get-Member, so I can decide what columns to return.

$Services | gm

As you can see the highlighted values here are the properties that belong to the object, I can return any one of these using a Select statement.

S3g89.png

To do so, simply Pipe the variable into a Select statement, giving Property names in a comma separated list of Properties you want returned.

$Services | Select ServiceName, CanStop

ZUhDb.png

Sorting

Once you have a list of items, sorting it is dead simple. Just Pipe into Sort <PropertyName> to sort on that property.

$Services | Select ServiceName, CanStop | Sort ServiceName


sY7oJ.png

Default sort direction is Ascending. If you want to sort the other way, just add -Desc onto the Sort command.

$Services | Select ServiceName, CanStop | Sort ServiceName -Desc

Rnvh2.png

$_

Before I go onto Filtering and Expressions, there is a basic principle that must be understood. $_ is the value from the Previous Pipeline element. $_.<PropertyName> is the specific Property value from the Previous Pipeline element.

This might make more sense when used in practice, see below for more info.

Filtering

You can filter elements from a previous Pipeline element using Where-Object combined with $_.Property names, and operators. This sounds complicated but bear with me and you will see that it's fairly straight-forward.

I will cover Operators more later in this Lesson.

Let's say I want to return only the Service that is called "EventSystem" from the $Services Variable I declared earlier, I can do this by filtering in the PipeLine for it. -eq stands for "Equals" in this example:

$Services | Where-Object {$_.ServiceName -eq "EventSystem"}

Yxsi7.png

In reality, you can shorten Where-Object to Where, or even just ?. These all do exactly the same thing:

$Services | Where-Object {$_.ServiceName -eq "EventSystem"}
$Services | Where {$_.ServiceName -eq "EventSystem"}
$Services | ? {$_.ServiceName -eq "EventSystem"}

I favor the use of ? myself, less code so it's cleaner.

Expressions

Expressions are probably the most complicated thing i've tried to explain so far. You use expressions if you want to pipe a Select statement, and either change a value to append or modify it, or add a property to your returned values that does not actually exist in the original property list.

This sounds really complicated, and I won't lie, it took me a little time to get to grips with it, but now I understand them better I use them where appropriate.

Let's pretend I am returning the "ServiceName" and "CanStop" properties from my previous example, simple right?

$Services | Select ServiceName, CanStop

bTNs8.png

Now, imagine you need to also return the current Date/Time as a returned field as well. Not so simple. But this is where the true power of Expressions comes in.

Pay close attention to the differences between the above command and this one.

$Services | Select ServiceName, CanStop, @{Name="DateTime";Expression={Get-Date}}

ydkBN.png

Now I have returned a column named "DateTime" (which is the name I gave it), which has the value of Get-Date, which gets the current Date and Time.

This is a very difficult topic to understand, just know that you can do almost anything with Expressions to declare Properties on the fly through the pipeline.

Here is another example to demonstrate that you can add multiple Expressions, this one counts how long each ServiceName string is, and returns the count in another column labelled Count:

$Services | Select ServiceName, CanStop, @{Name="DateTime";Expression={Get-Date}}, @{Name="Count";Expression={$_.ServiceName.Length}}

oIigr.png

Operators

Operators are a way that you can compare one value with another one, and for conditional logic.

Some opeators I use a lot. This list is not exhaustive:

-eq (Equals)
-ne (Not Equals)
-lt (Less Than)
-le (Less Than or Equals)
-gt (Greater Than)
-ge (Greater Than or Equals)
-match (Regex text match)

I used the -eq Operator in the previous Filtering section of this Lesson. To use the Operators is simple, let's say you want to compare two numbers, A and B, and you want to see which number is bigger, you can do so using an
operator.

Operator checks will generally return a True or False value, which you can use in your script logic. Some examples here of Operator checks:

dAtPU.png

I often use Operators in IF statements and Filtering, rarely outside of these though.
 
Last edited:
Lesson 4 - Arrays and Lists

.Net

PowerShell is fairly similar to .Net, a lot of the principles are the same, and it uses a lot of the framework of .Net as well. You can do almost anything you can do with .Net using PowerShell.

One of the things that PowerShell does better than .Net in my opinion is Arrays, and I will show you why the Arrays in PowerShell are so powerful.

Arrays

Arrays generally come in two flavors, one dimensional, and multidimensional. One dimensional arrays are really just lists of single items, multidimensional lists allow you to assign multiple fields or properties per item.

Simple Arrays

You can create a simple array in PowerShell on the fly, by declaring a variable, and passing it in multiple values using comma separation. Take this for example:

$Array1 = "Item1","Item2","Item3","Item4"

When you echo the Variable back, you will see all 4 values returned on different lines, one for each element in the array:

C9WKE.png

You can count the number of objects in the Array using the .Count method:

Get-Member -InputObject $Array1

(Note the variable is a System.Object[] Type, the square brackets dictate this is a collection of values, or an Array)

463kG.png

$Array1.Count

W2GAN.png

Other ways to create an Array

You can create a quick an dirty Array of objects by declaring a new variable as an Empty array, and then adding items into it.

To declare a new Array, you need to create a new Variable as an array type:

$Array2 = @()

O0jQ5.png

you can then add values to it like this:

$Array2 += "ArrayItem1"
$Array2 += "ArrayItem2"
$Array2 += "ArrayItem3"


This is basically saying on each line, $Array2 is itself + the new value in addition, so the additions are cumulative as you can see here:

3280b.png

Retrieving values from an Array

In the above examples, I am returning all of the values in an Array, but what if you want to just return say the 2nd item in the Array, or the last one? Simples!

Arrays are -0 Indexed, that means the 1st Item in the Array is Index number 0, the 2nd is number 1, etc. To retrieve the 2nd item in the Array you simply enter the index number you want within the square brackets after the Array Variable:

$Array2[1]

BSA26.png

To get the last Item you enter this, -2 would be the 2nd to last Item:

$Array2[-1]

J9B27.png

Importing Lists

PowerShell has some great Import functions, my most used one is probably Import-Csv, but I also use Get-Content on text files to import Simple lists as well.

Get-Content

Let's assume you want to import a list of Servers for processing for example, if you create a text file which contains the Server Names, you can import this into PowerShell really simply.

Text File in C:\Temp\ServerList.txt:

ntpNr.png

$ServerList = Get-Content "C:\Temp\ServerList.txt"


pIrtm.png

You can then interact with this in the same way you would the Arrays we created above.

$ServerList[0]

Ga8wx.png

Another cool thing you can do, is to retrieve a subset of the items in the Array. Let's say you wanted items 2 through 4, you can do this as follows:

$ServerList[1..3]


noXvh.png

This sometimes has it's uses.

Import-Csv

My favorite import command, you can create a multidimensional list of items in Excel, save it as a .csv file, and then Import it right into PowerShell.

To demonstrate this I will use a scenario that may be faced in the real world.

In this scenario I need to move a few users in Exchange to another mailbox database, the move requests must be flexible to allow for different target databases, and the BadItemLimit must be customisable per object as well.

To tackle this I would create a new .csv file called C:\Temp\Mailboxes.csv"

9EY6Z.png

I would then Import it into PowerShell:

$Mailboxes = Import-Csv "C:\Temp\Mailboxes.csv"

j126M.png

Next I would process a for each Loop against the list, I will cover Looping more in the next Lesson. This script would create a Move Command for each user and echo back the command that would be run:

Code:
foreach ($Entry in $Mailboxes)
{
	Write-Host -ForeGroundColor "Yellow" "Processing user ... $($Entry.Mailbox)"
	$MoveCommand = "Move-Mailbox -Identity $($Entry.Mailbox) -TargetDatabase $($Entry.TargetDatabase) -BadItemLimit $($Entry.BadItemLimit)"
	Write-Host $MoveCommand
}

j8c5S.png

$($Variable.SubProperty)

You may have noticed that when I was putting the values literally into the strings, I declared the variables as $($Variable.PropertyName). This is important when expanding a sub property in a string.

The Variable $Entry which I declared above, will contain each line of the Csv file I imported. If I echo back $Entry it was the last known value assigned to it, or the last line in the .csv file:

CMDL6.png

You can, at any time, return or use any of the values within this variable as sub properties, this for example just returns the "Mailbox" field of the $Entry Variable:

$Entry.Mailbox

EIBv7.png

If you want to use the sub property in a string, you need to expand it like I did, or it won't work properly.

This is a non-working example:

"The current user is $Entry.Mailbox"

1ygEe.png

This is a working example:

"The current user is $($Entry.Mailbox)"

V7QI9.png
 
Last edited:
Lesson 5 - Loops, If/Else and Switches

Using Loops in PowerShell allows you to take a list of items, and iterate through the list, within the iteration you are able to do many things at a more granular level.

There are a few basic loops that I use, ostensibly the list of common ones looks like this:

  • foreach x in y
  • do x while y
  • do x until y

I will go into a little more depth on each loop to show how they can be used.

foreach x in y


a foreach loop is useful if you want to iterate through a list of items and do "something" regardless of whether a condition is met or otherwise. The best example of this I can think of this is running a loop against a list of files in a folder and deleting all files older than a certain date.

I know of two ways of writing a foreach loop, and I use them both commonly.

By declaring $y = 1..10 I am saying that the variable $y is the values 1 through 10. You can see this if you echo back the value of $y as well.

Rv8L0.png

The first way is like this:

Code:
$y = 1..10
foreach ($x in $y)
{
	Write-Host $x
}

7gJ5T.png

What is this doing?

I have declared $y above the foreach block, but $x is a variable which is created on the fly, and it becomes each line of the $y variable in turn. I always use a unique name here to represent this. $x and $y are just for demonstration.

The second way is like this:
Code:
$y = 1..10
for ($i = 0; $i -lt $y.count; $i++)
{
	Write-Host $y[$i]
}

8kLZs.png

What is this doing?

This looks more complicated than the previous method, and I would agree that it is.

The for section is split into three different subsections, like this:

for (Counting Variable ; When to iterate until ; Increment)

So by saying this:

$i = 0; $i -lt $y.count; $i++

I am saying:

  • $i is a new variable with an initial value of 0
  • Iterate whilst the value of $i is less than the number of items in the $y collection (10).
  • $i++ directly translates into add +1 to the value of $i each time.

As $i starts at 0 (Arrays are 0 indexed which means $var[0] is generally the first item in the collection), and it's running whilst $i is less than 10, as soon as $i hits 10 the loop will end.

$y[$i] returns the nth item in the $y collection, where the actual position (i.e. 2nd) is actually n -1, or if you wanted the 2nd item positionally you would use $y[1] NOT $y[2].

pu21G.png

Both methods do the same thing essentially, the first way is better if you don't care how far into the foreach you are positionally, the second I find better if I need to know what number I am up to.

do x while y

do while loops have their uses too, I must admin I use foreach almost exclusively, but I have been known to use the do while and do until loops as well if the situation calls for it.

do while loops are better than do until loops if you want to run the loop until a condition is met.

for example this code will run whilst the value of $i is less than or equal to 7.

Code:
$i = 0
do
{
	$i++
	Write-Host $i
}
while ($i -le 7)

7VDmZ.png

What is this doing?

I am declaring a new variable called $i and assigning it a value of 0. Within the do loop codeblock, I am incrementing the value of $i by +1 each iteration and then echoing that value back to the host screen.

As soon as the value of $i goes above 7 it will exit the loop on the end of the next run, that means it will actually echo back the number 8 before it quits.

do x until y

The do until loop is better if you want to meet a condition and then quit.

for example this code will run until the value of $i is 7.

Code:
$i = 0
do
{
	$i++
	Write-Host $i
}
until ($i -eq 7)

Ym1Y9.png

What is this doing?

I am declaring a new variable called $i and assigning it a value of 0 just like previously, then during the codeblock incrementing by +1 and echoing back the value to the host. The main difference here is in the conditional stamenent.

I have said that it's running until $i equals 7, so $i will increment until this is hit, then it will exit.

Both do while and do until have their uses, just be careful of getting stuck into an infinite loop where the loop never ends because the conditions never get met!

IF/ELSE/ELSEIF Statements

IF/ELSE statements can be used when you want to check for a condition and do something based on the results.

IF Statement

The simplest form of this is the if statement. This is an example of one which is based off the previous half of this Lesson:

Code:
$y = 1..10
foreach ($x in $y)
{
	Write-Host $x
	
	if ($x -eq 5)
	{
		Write-Host "$x has been found" -ForeGroundColor "Yellow"
	}
}

Bp0ji.png

What is this doing?

I'm utilising a simple foreach loop like in the previous examples, within that I am using an if statement to check if the value of $x is equal to 5, if it is I write that it's been found in Yellow text back to the host.

IF/ELSE Statement

Building on the previous statement, an ELSE statement can be appended which allows you to perform an action if the value does not match the if statement.

Code:
$y = 1..10
foreach ($x in $y)
{
	Write-Host $x
	
	if ($x -eq 5)
	{
		Write-Host "$x has been found" -ForeGroundColor "Yellow"
	}
	else
	{
		Write-Host "5 has not been found" -ForeGroundColor "Red"
	}
}

What is this doing?

Same as above, except if the value is not 5 it writes into the host that "5 has not been found" in red text.

yqRDz.png

IF/ELSE/ELSEIF Statement

The IF/ELSE/ELSEIF statement allows you to define much more granular categorisation than the other statement types, but should be used with care.

Code:
$y = 1..10
foreach ($x in $y)
{
	Write-Host $x
	
	if ($x -eq 5)
	{
		Write-Host "$x has been found" -ForeGroundColor "Yellow"
	}
	elseif ($x -eq 6)
	{
		Write-Host "$x has been found" -ForeGroundColor "Yellow"
	}
	else
	{
		Write-Host "5 or 6 has not been found" -ForeGroundColor "Red"
	}
}

qTvrs.png

What is this doing?

If the value of $x is 5 or 6 then it returns that it's been found, otherwise it writes in red text that these values are not found.

The ELSEIF portion of the code allows you to have multiple IF statements in the same code section, or allows you to handle more than one eventuality.

When ELSEIF is used, the ELSE section becomes your default action if none of the previous IF statements are triggered.

Think smart, not hard.

The above Statement could actually be written like this and would work the same, on fewer lines of code:

Code:
$y = 1..10
foreach ($x in $y)
{
	Write-Host $x
	
	if (($x -eq 5) -or ($x -eq 6))
	{
		Write-Host "$x has been found" -ForeGroundColor "Yellow"
	}
	else
	{
		Write-Host "5 or 6 has not been found" -ForeGroundColor "Red"
	}
}

3W7GO.png

It's always worth thinking about cutting down the number of lines being written to streamline the scripts being written, it helps to make the code easier to understand as well.

Switch Statements

This is the last topic I will cover in this Lesson. Switch statements should be used in place of multiple IF/ELSE/ELSEIF statements. Switches can be used for decision based logic which can have many results, whilst keeping the code clean.

This is an example of how you can use the Switch statement:

Code:
$y = 1..10
foreach ($x in $y)
{
	Write-Host $x
	
	Switch ($x)
	{
		1 {Write-Host "1 is the first number"}
		2 {Write-Host "2 is the second number"}
		4 {Write-Host "4 is a good number"}
		7 {Write-Host "777"}
		default {Write-Host "que?"}
	}
}

zoP5a.png

What is this doing?

By using the Switch statement against the $x variable, I have defined certain actions to take if certain values are met. If the numbers 1, 2, 4, or 7 are encountered then different pieces of text will be returned to the host.

The default action defines what to do if nothing is matched implicitly. The default action is optional and can be dropped if you are not interested in doing anything by default, much like you don't have to use ELSE with an IF Statement.

Writing this same logic into an IF/ELSE/ELSEIF Statement would take many more lines of code. The Switch Statement definitely has it's uses and should not be ignored.

This completes this lesson. I think I can start covering writing actual scripts next lesson, a lot of the stuff to date is ground work really so that there will be understanding behind the scripts.
 
Last edited:
Lesson 6 - Writing Scripts

This is what all of the previous Lessons have been working towards. In this Lesson I will demonstrate how to structure code, add comments, debug, and save your code as a PowerShell script file (.ps1).

Thinking Logically

To solve a problem with a script you need to be able to solve it without one first of all. I've lost count of the number of times people have asked "how can I do this with a script?", when I then ask "how would you do this without one?" they often have no idea.

The script is a means to an end, or a way to make something that may otherwise take a long time to do manually, much faster. You still need a clear idea of the problem and how to solve it though.

When I start writing a script I am always thinking in my head about how I would approach the problem without one, or how I want to step through the elements, which checks I want to do, and even how I want the results to look.

If this is not all straight in my head, I work at it outside of the script until it is.

One other key piece of advice, don't use a script unless it's something that will take you the same amount of time, or less than doing the task manually. The only exception to this is when writing the script will teach you something valuable. Then it becomes a trade off between time and knowledge.

I extrapolate the time spent doing manual tasks with on-going things too. If there is a task you have to do every week, and you can script it in twice the amount of time it takes you to do a single manual run, then it's worthwhile as you don't need to spend time on it every week.

Text Editors

There are multiple Text Editors out there for PowerShell, but my preference is NotePad++ (http://notepad-plus-plus.org/). I like it as I don't need assistance from the GUI when writing scripts, but it makes organization and editing easier than standard Notepad or using the PowerShell window directly.

There are certainly others out there though, so don't get stuck on my favorite if you want to try others out.

NotePad++

To create a new PowerShell based script in NotePad++ just create a new document, then enable PowerShell language support by going to Language > P > PowerShell. Doing this will add various highlighting/colours to your code, this makes writing the code a little easier too.

Save the document in whatever location you want with your chosen name:

VbUM3.png

The document will now be saved as a .ps1 file.

Structuring Code

I like to structure my code using indents where appropriate to reflect where sections begin and end.

I also insert Blank lines between elements to better separate them logically, giving a little spacing is good for readability, otherwise it would be like having a book with no spacing in the paragraphs.

Writing this:

Code:
$y = 1..10

foreach ($x in $y) {Write-Host $x}

Is the same as this one:

Code:
$y = 1..10

foreach ($x in $y)
{
	Write-Host $x
}

The main difference here is that the second one is much easier to read, and if you add more code is much simpler to understand or update later.

Multi-Line approach

Using the Multi-Line approach improves readability significantly. Sure you can sometimes get away with it but using consistent coding techniques will make your life much easier.

If I wanted to add another line into this code to return the current date and time this is how the two variants above would look.

Without splitting the content:

Code:
$y = 1..10

foreach ($x in $y) { Write-Host $x ; Write-Host (Get-Date).ToString() }

With splitting the content:

Code:
$y = 1..10

foreach ($x in $y)
{
	Write-Host $x
	Write-Host (Get-Date).ToString()
}

The first example here is already starting to look complicated by comparison.

Layouts on other elements

This is my preferred method for layout, with all loops separated onto multiple lines, and the same for all If statements/Switch statements.

For example, if I introduce the IF statements we used previously in Lesson 5 you can see how this might look with or without indents.

Without Indents:

Code:
$y = 1..10

foreach ($x in $y)
{
Write-Host $x

if ($x -eq 5)
{
Write-Host "$x found" -ForeGroundColor "Yellow"
}
}

With Indents:

Code:
$y = 1..10

foreach ($x in $y)
{
	Write-Host $x
	
	if ($x -eq 5)
	{
		Write-Host "$x found" -ForeGroundColor "Yellow"
	}
}

Both will work the same, but the second one is much easier to grasp.

Coding can be an art form, well formed, well structured code is much nicer to look at than ugly but functional code.

Commenting code

I strongly recommend commenting code. This has lots of benefits, you will understand the code if you come to look at it later better. Other people looking at your code will also understand it better as well.

Don't go overboard on comments though, not every line needs a comment, and sometimes it's easier to comment a section rather than lines within a section.

To designate a line as a Comment, you simply put a # on the front of the line. For example this is me adding too many comments to my code, for demonstration purposes:

Code:
#Declare $y as 1 through 10
$y = 1..10

#Loop through each value of $y
foreach ($x in $y)
{
	#Echo back to host the current value
	Write-Host $x
	
	#Check if $x is equal to 5
	if ($x -eq 5)
	{
		Write-Host "$x found" -ForeGroundColor "Yellow"
	}
}

It looks fairly obvious in NotePad++ when you have comments too:

rdjDT.png

Put comments where you think you or others might find them handy in the future. I often refer to my own comments if it's from a script I wrote months or years ago.

Debugging

PowerShell will return to you if an error occurs, it will also generally tell you what line it occured on, and what command it was trying to run at the time. All of this will help you fix the problem.

Sometimes though it's useful for you to know roughly where it got to before it had a problem, or to see if a particular element you coded is firing or not (IF statement for example).

To make this easier I recommend inserting Write-Host elements in desired locations, with text that you can understand. My script above already contains some for demonstration purposes.

This is a simple but powerful tool, and has saved me time previously.

If I add a line into my script which forces an error I can show why this is useful, take this for example:

Code:
#Declare $y as 1 through 10
$y = 1..10

#Loop through each value of $y
foreach ($x in $y)
{
	#Echo back to host the current value
	Write-Host $x
	
	#Check if $x is equal to 5
	if ($x -eq 5)
	{
		Write-Host "$x found" -ForeGroundColor "Yellow"
		$Error = 0
	}
}

JWPxH.png

$Error is protected so this will always cause an exception.

The exception details give more info, it says it's failed at Line 9 char 15 - unfortunately it does not consider the blank lines or the fact that some elements are multi-lined.

However the exception detail gives the error location as $Error <<<< = 0 - that means this section of code failed to execute properly. We can replicate this error easily by simply running this line ourselves:

Code:
$Error = 0

This will fail every time.

Verification

You can't always foresee every problem that may come up with a script, until a problem actually occurs, especially if you are dealing with variable data. But sometimes you need to insert some common sense into your scripts to ensure that it doesn't try and do silly things like divide a number by zero.

Simple example, but let's pretend you are writing a script that looks at your local hard drives, perhaps you are looking at running a report for free drive space, or some kind of alerting system. This is a very basic example:

Code:
[array]$Drives = Get-WMIObject -Computer "localhost" -Class "Win32_LogicalDisk"

foreach ($Drive in $Drives)
{
	Write-Host $Drive.Name
	Write-Host (($Drive.FreeSpace / $Drive.Size) * 100)
}

This runs fine now, but what if one of your drives was dead but still attached? or your command was failing to pull back any drive info at all? it would probably fail.

You should put in some basic checks at stages of your script to sanity check what you are doing rather than blasting through it.

The above example could be changed to this:

Code:
[array]$Drives = Get-WMIObject -Computer "localhost" -Class "Win32_LogicalDisk"

if ($Drives.Count -gt 0)
{
	foreach ($Drive in $Drives)
	{
		Write-Host $Drive.Name
		Write-Host (($Drive.FreeSpace / $Drive.Size) * 100)
	}
}

This is saying that if the count of the number of elements in $Drives is greater than 0 then proceed with the section of code that actually does the work.

Try/Catch/Finally

I use this quite a lot, but with a Try/Catch/Finally block, you can attempt to run something, and if it fails you can handle it yourself.

For example you have a script that goes through machines in your AD and tries to connect to them for WMI (perhaps the disk checking command above). If this fails to run the machine may be down, or WMI may not be working remotely.

In this case I would recommend the use of the Try/Catch/Finally scriptblock. This will let you handle this properly. The only caveat is that you sometimes need to specify on the commands that the -ErrorAction is set to "Stop" or it may not go to the Exception block properly.

This is a good example of using Try/Catch/Finally to stop the script trying to process a machine it can't contact for disk info:

Code:
#List of Computers with comma separation
$Computers = "localhost","192.168.1.1"

#Loop through Computers
foreach ($Computer in $Computers)
{
	Write-Host "Computer : $Computer" -ForeGroundColor "Magenta"

	Try
	{
		#Attempt to get Drive Info
		Write-Host "Got to Try Block"
		
		[array]$Drives = Get-WMIObject -Computer $Computer -Class "Win32_LogicalDisk" -ErrorAction "Stop"
		
		Write-Host "Processing Disk Info..."
		
		foreach ($Drive in $Drives)
		{
			Write-Host $Drive.Name
			Write-Host (($Drive.FreeSpace / $Drive.Size) * 100)
		}		
	}
	Catch [Exception]
	{
		#If failure then go here
		Write-Host "Got to Exception Block"
		
		Write-Host "$_" -ForeGroundColor "Green" -BackGroundColor "Black"
	}
	Finally
	{
		#This runs no matter what. The Finally section can be ommited though.
		Write-Host "Got to Finally Block"
	}	
}

IXgQp.png

As you can see from the screenshot, on the 2nd machine (which was my router), it failed and subsequently did not try to process anything else in the Try block beyond the point of failure. A very useful trick!

Running PowerShell script files

To run a .ps1 file, you have to do two things:

1. Save the script as a .ps1 file.
2. Allow your computer to execute .ps1 files.

Saving the .ps1 file is just as simple as described at the start of this Lesson. Allowing execution requires you to run this command from a PowerShell window which is running as Admin:

Code:
Set-ExecutionPolicy RemoteSigned

See this article for more info:

http://technet.microsoft.com/en-us/library/ee176961.aspx

Once done you can execute the .ps1 files by calling them from the Window:

Code:
cd .\Temp
.\Script.ps1

oHx5S.png

This changes to the directory and then runs the .ps1 file. I normally just run interactively by copying/pasting my code into the window directly until I am ready to run the script as a scheduled task or similar, then I put it into a .ps1 file.

Exiting scripts/Loops

Reserved for later.

Logging

Reserved for later.

That's it for this lesson, I will reserve the next lesson for some real world scripts that might be useful, if there is anything you want to see, then I will be happy to see what I can pull together, as long as it's simple enough for everyone reading these lessons.

I will revisit this Lesson later and append the sections about Exiting scripts/loops, and Logging.
 
Last edited:
Thanks Lucero, I will add some about that, but in a later lesson I reckon. People won't need to be able to necessarily run .ps1 scripts right away, I am trying to approach the subject from the view of a novice who needs to learn the fundamentals first.

Lesson 2 added. I will add some more info to it tomorrow about identifying data types assigned to variables, and Get-Member as I think that's relevant too.
 
As you can see it gives an error if I try to use the UK DateTime format, my computer only resolves the DateTime correctly if I use the American system! :mad:

That's pretty annoying :mad: I wonder why powershell/.net doesn't honour locale settings. FWIW [noparse][datetime]::parse("26/10/2012")[/noparse] will use the computer's locale date format
 
I should really make more of an attempt to learn PowerShell as I'm sure it would be useful, so I'm going to be following this.

I've worked on projects where I've seen it abused, which is probably why I haven't made an effort with it before.
Once worked on a project with this guy and he tried to get PowerShell in all over the place, including lots of places where it really wasn't needed.

Hopefully you can change my opinions on it :)
 
Thanks guys for all of the comments, if you have any questions or comments I am always happy to receive them. I have just added Lesson 3, which covers the Pipeline.

I think in Lesson 4 I will cover foreach loops, IF statements, and perhaps Switch statements as well.

After that I may be ready to go onto actual Script writing in Lesson 5 I hope, unless another topic strikes me as better before then :)
 
Back in action, been a hectic few days for me so not felt like writing another Lesson.

Lesson 4 is now added though, this one is all about Arrays, and importing Lists/Csv files. As usual comments/questions are welcomed.
 
This is really very good. Thanks for taking the time, Eulogy.

You're most welcome :)

I will be writing Lesson 5 when I get time over the weekend, focusing on Loops (foreach, do while etc).

After that, I think I will get a bit more into actually writing functional scripts, testing, debugging, benchmarking etc (Lesson 6).

I will probably keep the last post for some musings etc, interesting notes.
 
This is really helpful so far. I only discovered import-csv the other week and it is so useful. I had some trouble echo'ing out the variables so now I know to expand them properly.
 
Back
Top Bottom