2: Intermediate

In this section we will cover some intermediate topics and build on the previous section. We will cover the pipe, variables, error handling, and a look at some operators.

The Pipeline

The pipeline or “pipe” is an extremely powerful tool, and without it your scripts won’t be able to do many interesting things. The basic idea is for PoweShell to “pipe” the objects from the output of one command into the input of another. The way you do this is like you saw in the examples in the basic section of the guide. Simply put a | character between your two commands. There are a few caveats to this which we will cover. For now, let’s pick some commands to pipe and then go over what the pipe does in this example.

In the section about command names there was a really long command, and in order to get that command I used PowerShell and the pipe, let me show you how. I will note that there are more efficient ways to pull this information, but for an example it is a good one. So what’s going on with this command, let’s break it down:

Get-Command | Select-Object Name | Sort-Object {$_.Name.Length} -Descending | Select-Object -First 1

So breaking it down we have:

Get-Command |

Get-Command is pulling a list of all commands on the system and then the | is used to pipe the objects to the next command

Select-Object Name |

Select-Object is taking the objects piped from Get-Command and in that list it is selecting the one we care about, in this case “Name” the | is then taking those objects and sending them to…

Sort-Object {$_.Name.Length} -Descending |

Sort-Object is taking the object list and using “{$_.Name.Length}” (more on this later) to get the length of characters in the name, and then sorting that descending (largest to smallest) and sending it through the | to…

Select-Object -First 1

Select-Object is taking the sorted objects and selecting the “First” 1, the first in this case being the command with the longest name length. So when you combine all those together, from Get Command pulling every command there is, to the final Select-Object spitting out one single command with the longest character length.

As you can see, the | is very important, and is honestly the key to doing anything interesting with PowerShell. Now you know you can use the pipe to send objects from one command to another, but how do you KNOW that you can use the pipe on a command? It’s your friend Get-Help to the rescue.

Get-Help Select-Object -Full

This will be much more verbose with the “-Full” parameter, There’s a section called “INPUTS” that will say plainly “You can pipe any object to Select-Object”. If we were to run the Get-Help -Full command against Get-Help itself and looked at the input section you might see “You can’t send objects down the pipeline to Get-Help.” So not all commands accept the pipe, and for commands that do it can sometimes not make much sense for the two commands to share objects

Get-Process | Where-Object {$_.Name -Like "Notepad"} | Stop-Service

Get-Process can pipe to Where-Object with no issues, and pull a list of processes that that match the “Notepad” name criteria. Where-Object can pipe to Stop-Service with no issue either, but when we run the command we get an error like:

Stop-Service: Cannot find any service with service name 'notepad'.

So just because you can pipe two commands together doesn’t mean the objects from one will be useful for the next command in the pipe.

Variables

Variables in PowerShell are important for more than just saving you time spent typing, they are the best way to ensure code is easy to understand, reusable, and consistent. Using variables is 100% necessary for many of the higher level scripting concepts as well.

Variables are simply named objects that you create yourself, or the system has already created with environment variables (more on this later). They are designated with the $ like this $VariableName. Let’s jump into something interesting with variables as I think you all get the variable concept.

Let’s set a variable called $Path to be equal to a location on a hard drive (C:\test) then let’s create another variable called $TestPath for testing if the $Path exists (using the command Test-Path) and then return the results of the Test-Path command with $TestPath

$Path = 'C:\test'
$TestPath = Test-Path $Path
$TestPath
False

This script is fairly straightforward, and short enough that you can skip the variables and just do Test-Path ‘C:\test’ to get the same “False” but what if you wanted to do something based on the result, or perhaps have the $Path be a prompt that would ask the user for input on the location to check There are a million reasons to use variables, so it’s best to include them in every script you write. Some scripts start out simple, but might evolve over time, and variables give you the flexibility to change things up without re-writing your entire script.

Using variables to pass properties

Something you have seen me use in this guide is having a period after a variable then followed up by a property like $_.Name, I’ll go deeper into $_ later, but let’s look at an example that I think will shed light on what is going on. If I wanted to get a list of processes on a device and return only the process names I could do something like Get-Process | Select-Object Name and that works as expected. Let’s look at another way to do the same thing that will be used over and over in your scripts.

$GetProcess = Get-Process
$GetProcess.Name

What’s happening here is we are setting a variable to be equal to the Get-Process command, and then using that variable with the “.Name” to return the Name property of the processes. What that means is we can do something like $GetProcess.Id to return the process Id or $GetProcess.CPU for cpu usage information etc.

Let’s go back to the “$_” I said I would bring up later to see what it means, and how you can use it. Those two characters will soon become one of your best scripting friends. Let’s start by telling you what $_ means, it’s really just an alias for $PSItem so that explains why it looks weird, but what does $PSItem mean? It’s one of those variables that come with PowerShell and it simply means the current object in the pipeline, that’s it. So what does $_.Name mean? Whatever the current object is give me the name property of it. Let’s go back to an example with your new understanding.

Get-Process | Where-Object {$_.Name -Like "Notepad"}

Get-Process is piping every process as an object to Where-Object which is then using $_ ($PSItem) to get the current object as it goes through the list then getting the Name property with .Name.

Environment Variables

PowerShell comes with some built in variables that it populates with information for you. These variables are stored in a virtual drive called “Env:” which is important to note as there might be cases of you needing to access the location, set your working directory to the drive, or even add/modify variables there.

To get a list of current environment variables run:

Get-ChildItem Env:

Look through the list and you will see some familiar items jumping out at you. The Temp and Windows directories, the “Home” path, computer name, and current logged in user. There’s some handy things in there and you didn’t have to do any work!

Environment variables are global, that means if you open two PowerShell windows both will have access to the environment variables, but if you set $Variable = 1 in one window and $Variable = 2 in another, both of those are only available in the session in which they were set. Accessing or setting an environment variable is always done by prepending it with “Env:” as that tells PowerShell to look on the virtual drive where those are stored. Environment variables come into play a lot when working with scripts that might be pushed out to multiple devices, like through a remote management and monitoring platform, so we will cover more of this in the advanced section of the guide.

Scripting Prep

This section will deal with some scripting preparation like error handling, quotation marks, and looking at operators

Error Handling

A very important aspect of scripting is troubleshooting. Unless you get it perfect every time, at some point, on some device, your script will fail and you are going to want to know why. This goes doubly true for scripting from a management platform. So start by checking for errors early and often!

The error action preference variable $ErrorActionPreference is a global variable available in all of your scripts. You can put this at the top of your script to control the default error action for all errors. There’s a few options depending on what you want to do. Setting the variable to “SilentlyContinue” will do what the name says, PowerShell will attempt to make it passed any errors and continue on without without spitting an error out to the console. If you set it to “Stop” your script will try to stop the first time it encounters an error.

$ErrorActionPreference = "SilentlyContinue"

That can be set globally like above, or on a per command basis:

Get-Content 'C:\Temp\NotHere.txt' -ErrorAction Stop

-ErrorAction is available in all cmdlets

Another best practice technique is to use try/catch blocks in order to capture all errors and output them to standard out with the error name and the line number. If you put all of your code in the spot between try/catch (#Insert code here) the catch will see any errors and write them out for you:

try {
    #Insert code here
} catch {
    Write-Host "Error Message: $($_.Exception.Message) on line $($_.InvocationInfo.ScriptLineNumber)"
}

It’s also possible (and a good practice) to add additional try/catch blocks within the main one you see above for additional error handling and delineation.

Where do errors live? Interestingly, there’s several streams in PowerShell (they can be seen in more detail here https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7 ) So errors go to the error stream. In the script above you see the $_.Exception and you know from this guide that means take the current object ($_) and then get the Exception property of it. So errors are properties of objects just like everything else in PowerShell.

Let’s say I were to run a command that doesn’t exist, but I have the ” SilentlyContinue” value set, PowerShell will try to run my command but not display anything and it won’t display an error either. So where did my error go?

$ErrorActionPreference = "SilentlyContinue"
Get-Atrasfgha

No error, no anything. So let’s run the following command and see if we can get the error out:

$Error[0]
Get-Atrasfgha: The term 'Get-Atrasfgha' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

That variable $Error is another one of the built in ones you don’t have to create yourself. The brackets after it with the zero inside [0] are used for an array (yep, more on this later) and in PowerShell by default arrays start with 0. So what that $Error[0] is doing is pulling the most recent error that PowerShell has seen. $Error[0] will always be the most recent error, PowerShell just moves all of the other errors up by one for each error.

Most PowerShell errors are non-terminating

What that means is your script usually won’t stop just because it runs into an error. It will keep running without a care in the world. This can be good or bad depending on what you are trying to do with your script. That’s why it’s important to use the -ErrorAction parameter when needed and to make use of Try / Catch blocks to help you catch errors, and so you can assume errors and write code to react to possible errors taking action based on the error itself.

Quotation marks

In PowerShell (and in life you savage) single and double quotes are slightly different things. The simple explanation is single quotes ‘ ‘ tell PowerShell whatever is between the quotes is a “literal string”, that means PowerShell will use the data exactly as entered with no logic. Take this as an example:

$Name = 'Jeremy'
$WhatIsMyName = 'My name is $name'
$WhatIsMyName

The result of that output is not what we want

My name is $name

Let’s try that again with double quotes

$Name = 'Jeremy'
$WhatIsMyName = "My name is $name"
$WhatIsMyName
My name is Jeremy

So just remember that if you want to use quotes that have variables in them or do any logic operations on the data between the quotes you will want to use double quotes ” ” single quotes ‘ ‘ are only used for exactly what is between them as written.

Operators

Before we get to the actual logic of scripts, you will want to have an understanding of operators. Most of you already know even if you aren’t aware of it directly.

We will start with some operators for math:

+ - * / %

That is addition, subtraction, multiplication, division, and % is for remainders or “modulus”. What PowerShell can do with math operators and the caveats there would make this guide extra long, so if you run into an issue using a math operator it’s best to check this Microsoft document.

https://docs.microsoft.com/enus/powershell/module/microsoft.powershell.core/about/about_arithmetic_operators

Comparison operators you have seen several times throughout this document, and they are the most common operators you will probably use while scripting.

TypeOperatorsDescription
Equality-eqequals
-nenot equals
-gtgreater than
-gegreater than or equal
-ltless than
-leless than or equal
Matching-likeReturns true when string matches wildcard pattern
-notlikeReturns true when string does not match wildcard pattern
-matchReturns true when string matches regex pattern; $matches contains matching strings
-notmatchReturns true when string does not match regex pattern; $matches contains matching strings
Containment-containsReturns true when reference value contained in a collection
-notcontainsReturns true when reference value not contained in a collection
-inReturns true when test value contained in a collection
-notinReturns true when test value not contained in a collection
Replacement-replaceReplaces a string pattern
Type-isReturns true if both object are the same type
-isnotReturns true if the objects are not the same type

There are many more operators than that, but again would make this guide very long, so feel free to browse them yourself here https://docs.microsoft.com/enus/powershell/module/microsoft.powershell.core/about/about_operators

Summary

This marks the end of the intermediate section of the guide. Much like the section before this one, this is all a primer for the advanced topic section where we will get into actual scripting.

We covered:
The pipeline
Variables and environment variables
Error handling
And an example and reference for operators

Go to section 3: Advanced