NAV
PowerShell Output

Introduction

This is the PowerShell 101 Workshop, pwshop. It’s designed to be a self-paced introduction to PowerShell fundamentals with an available instructor / facillitator.

Over the course of this workshop you will learn:

The workshop is designed to be worked through in sequential order, later exercises build on skills acquired during earlier exercises.

Setup

For this workshop we’re using a vagrant image - it is a Windows Server 2016 image with docker pre-installed. It is strongly advised that you set up this environment and test that it works prior to the workshop if at all possible. Once the lab environment is set up you should not need to download anything else.

Prerequisites:

You must have Vagrant and a hypervisor (Hyper-V or VirtualBox) installed.

Steps

  1. Download the Vagrantfile from this link into a folder on your machine.
    • If it appended the .txt extension to the file, remove it. The file should just be Vagrantfile, no extension.
  2. Open a terminal, navigate to the folder where you saved the VagrantFile, and run the command below:
    • vagrant up --provision
  3. This will download and spin up the lab machine for this workshop and get you ready to learn some PowerShell!

Controlling the Environment

You can pause and resume the environment:

You can halt the environment, freeing up RAM:

You can destroy the environment, freeing up disk space:

You can bring the environment back up after halting or destroying it using the same command as when you first brought it up:

Commands & Discoverability

When using PowerShell, the majority of the text you type into a console or script file will be PowerShell commands of some sort, instead of external executables. For this reason, it’s useful to become familiar with PowerShell’s Verb-Noun syntax, approved verbs, and some of the most common commands.

You can also retrieve the available verbs (though not their meanings) from the PowerShell prompt:

Get-Verb
Verb   Group
----   -----
Add    Common
Clear  Common
Close  Common
Copy   Common
Enter  Common
...

In Powershell every command follows the format Verb-Noun. For example:

Those are all real commands and they do roughly what you think they do - get an item, set an item’s value, create a new item, delete an item.

There’s a canonical list of approved verbs which PowerShell commands use. This helps to build a context that makes understanding a command without running it easier. If you know what the verb means and the noun is clear, you have a pretty good guess at what’s going on. You wouldn’t expect that a Get-SomeObject command would do anything but retrieve information, for example.

In addition to the verbs there’s a few core commands that you should know and we’re going to cover in this workshop:

Get-Command

Get-Command
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           Add-ProvisionedAppxPackage                         3.0        Dism
Alias           Add-ProvisioningPackage                            3.0        Provisioning
Alias           Add-TrustedProvisioningCertificate                 3.0        Provisioning
Alias           Apply-WindowsUnattend                              3.0        Dism
Alias           Disable-PhysicalDiskIndication                     2.0.0.0    Storage
Alias           Disable-StorageDiagnosticLog                       2.0.0.0    Storage
Alias           Enable-PhysicalDiskIndication                      2.0.0.0    Storage
Alias           Enable-StorageDiagnosticLog                        2.0.0.0    Storage
Alias           Export-VMCheckpoint                                2.0.0.0    Hyper-V
Alias           Flush-Volume                                       2.0.0.0    Storage
...

So we know that we’re going to need to run commands in Powershell. We also know that PowerShell use a predictable Verb-Noun structure for those commands. But what we don’t know yet is how to find a command we might want to run.

For that we use Get-Command, whose documentation can be found here.

We can use Get-Command to return the list of all available commands in our PowerShell session. We can also filter those commands by their type, name, verb, noun, or several other options.

When we do this, we’re passing a parameter to the command. This is the most common way we’re going to use commands at the prompt, by passing one or more parameters to the command.

With that in mind, our first exercise!

Exercise 1: Using Parameters

A: Single Parameter

Get-Command -Verb Trace
Get-Command -verb TrAce
CommandType     Name              Version    Source
-----------     ----              -------    ------
Cmdlet          Trace-Command     3.1.0.0    Microsoft.PowerShell.Utility

First, let’s use the Verb parameter to filter for only those commands with the Trace verb - it’s not used very much, so the list should be small. Note that you must get the verb exactly correct.

What happens if you instead specify traze or traces? What if you specify tra*?

B: Multiple Parameters

Get-Command -Noun Command -TotalCount 3
CommandType     Name                Version    Source
-----------     ----                -------    ------
Cmdlet          Get-Command         3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Invoke-Command      3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Measure-Command     3.1.0.0    Microsoft.PowerShell.Utility

We can also filter for only those commands whose noun is Command and use the TotalCount parameter to only return the first three commands our query finds. This will return all of the commands on our system that match - note that these commands include different sources.

What happens if you omit the TotalCount parameter? How many results do you get back?

What happens if you specify a verb in addition to the noun? How many results do you get back?

C: Filtering Parameters

Get-Command -Name *image*
CommandType     Name                            Version    Source
-----------     ----                            -------    ------
Function        Dismount-DiskImage              2.0.0.0    Storage
Function        Get-DiskImage                   2.0.0.0    Storage
Function        Mount-DiskImage                 2.0.0.0    Storage
Cmdlet          Add-WindowsImage                3.0        Dism
...

But what if we don’t know the exact noun being used? In that case, we can filter on Name and try to narrow our options down.

If, for example, we want to see what commands are available for interacting with images, we could use wildcards (*) in conjunction with the Name parameter.

What command would you run to narrow this down to commands for mounting and dismounting disk images? For just Windows images?

What parameter would you use to return only commands that would get images, not act on them?

Get-Help

Get-Help -Name Get-Help
NAME
    Get-Help

SYNOPSIS
    Displays information about Windows PowerShell commands and concepts.

...

In the previous section we looked at how to use commands and their parameters - but how do you discover what parameters are available for a particular command? And even if you know that a command has a parameter, how do you know what it accepts or expects?

Luckily, PowerShell has a pretty awesome built-in help system.

We can access that by running Get-Help and passing it a command or topic to get help on. Let’s first get help on Get-Help itself!

Get-Help -Name Get-Help -ShowWindow
Get-Help -Name Get-Help -Online

This returns information similar to Linux man pages - that is, it returns reference documentation for PowerShell commands. You can get the help directly in the console, but you can also get the help in a pop out window or open a link to the online documentation (if it exists).

What parameter would you add to show examples? To show the full help?

Exercise 2: Getting Help

A: Getting Help for Get-Command

Get-Help -Name Get-Command

Now that you know how to get help for a command, get the help for Get-Command in a pop-out window so you can reference it for the rest of this section.

What command did you use?

How many parameters does Get-Command have?

Which parameter would you use to limit the number of results? To ensure that it only returns results from a particular module?

What does the ListImported parameter do?

Exercise 3: Getting Commands and Help

A: Service Management

Often when investigating issues on a server you’ll need to see if a service is running or not.

  1. What command would you use to query for commands that manage Windows services?
  2. What is the command for querying the system for services?
  3. What parameters does that command take?
  4. What command would you run to check if the print spooler service is running?
  5. Is the print spooler service running?
  6. If it is running, what command would you use to stop it? (remember, you can look up commands with Get-Command whenever you want)
  7. Make sure the service is stopped.
  8. Start the service again.
  9. Restart the service with a single command.

Objects, Properties, & Methods

Properties

Get-Command -Noun Command
CommandType     Name                Version    Source
-----------     ----                -------    ------
Cmdlet          Get-Command         3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Invoke-Command      3.0.0.0    Microsoft.PowerShell.Core
Cmdlet          Measure-Command     3.1.0.0    Microsoft.PowerShell.Utility
...

So far we’ve been looking at single commands and their output. But why does Get-Command return something that looks like a table to the screen?

It does this because it’s returning an array of objects (as rows in the table output), each of which have properties (seen as the columns in the output).

So, looking at the results, the only properties for this command object we’re getting back are CommandType, Name, Version, and Source, right?

Nope! There’s several more properties, these are just the ones you see by default. As you’re about to see, displaying all of the properties would be a bit of a mess visually.

Get-Command -Name Get-Command | Get-Member -MemberType Properties
   TypeName: System.Management.Automation.CmdletInfo

Name                MemberType     Definition
----                ----------     ----------
CommandType         Property       System.Management.Automation.CommandTypes CommandType {get;}
DefaultParameterSet Property       string DefaultParameterSet {get;}
Definition          Property       string Definition {get;}
HelpFile            Property       string HelpFile {get;}
...

How do you discover all of the properties a command has? You can use Get-Member.

Notice that the first thing this command returned was a TypeName. This is the fully qualified name of the type of object you’re looking at. In this case, the shorthand is CmdletInfo.

The next thing you’ll see is a list of properties - CommandType, Definition, etc.

Get-Command -Name Get-Command | Select-Object -Property Name, HelpFile
Name        HelpFile
----        --------
Get-Command System.Management.Automation.dll-Help.xml

Okay, so now you can see what properties an object has - but how can you see the values of those properties?

We can do that with Select-Object, a command which will be a commonly used part of your toolkit for the rest of this workshop. Notice that we passed two properties to the Select-Object command: Name and HelpFile. If you want to display all of the properties you can do so by specifying a wildcard * instead of any property names.

Get-Command -Name Get-Command | Select-Object -Property *
HelpUri             : https://go.microsoft.com/fwlink/?LinkID=113309
DLL                 : C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll
Verb                : Get
Noun                : Command
...

This will display all of the same properties that an object had listed when you introspected it with the Get-Member command.

Exercise X: Service Properties

Remember that you can use Get-Service to retrieve services; do so to retrieve the print spooler service.

  1. What properties are available for a service object?
  2. What type of service is the print spooler service?
  3. What is the start type for the print spooler service?
  4. Can the print spooler service stop?
  5. Can it pause and continue?

Methods

Get-Command -Name Get-Command | Get-Member -MemberType Methods
   TypeName: System.Management.Automation.CmdletInfo

Name             MemberType Definition
----             ---------- ----------
Equals           Method     bool Equals(System.Object obj)
GetHashCode      Method     int GetHashCode()
GetType          Method     type GetType()
ResolveParameter Method     System.Management.Automation.ParameterMetadata ResolveParameter(string name)
ToString         Method     string ToString()

In addition to the properties, just about every object also has methods. Normally, in PowerShell, you will be able to find a command to manage an object (to start or stop a service, for example) - but, because PowerShell is built on top of .NET, you also have access to the methods directly.

You can find the methods for an object using Get-Member again, this time specifying Methods. Each of these methods is documented, but the method we want to focus on right now is ResolveParameter.

Looking at the documentation for it we find that it can be passed the name of a commands parameter and will return metadata about it.

(Get-Command -Name Get-Command).ResolveParameter('Something')
Exception calling "ResolveParameter" with "1" argument(s): "A parameter cannot be found that matches parameter name 'Something'."
At line:1 char:1
+ (Get-Command -Name Get-Command).ResolveParameter('Something')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ParameterBindingException

If we just pass an arbitrary string to the method PowerShell shows us an error. It looks like Something isn’t a valid parameter for Get-Command. So what we need to do is discover what valid parameters Get-Command does have. We can do that with our old friend Get-Help.

Get-Help -Name Get-Command -ShowWindow

Looking through the list of parameters, pick one - this example is going to use Module

(Get-Command -Name Get-Command).ResolveParameter('Module')
Name            : Module
ParameterType   : System.String[]
ParameterSets   : {[__AllParameterSets, System.Management.Automation.ParameterSetMetadata]}
IsDynamic       : False
Aliases         : {PSSnapin}
Attributes      : {__AllParameterSets, System.Management.Automation.AliasAttribute}
SwitchParameter : False

So that gives us back some interesting information about the Module parameter:

Exercise X: File Methods

Create a new file using the New-Item command. You can retrieve information about a file using the Get-Item command. We’ll cover why this is Item and not File shortly!

New-Item -Name 'something.txt' -Value 'just some text'
Get-Item -Path something.txt
# NOTE: The directory and LastWriteTime will be different for you,
# depending on when you're doing the workshop and in which folder.

    Directory: C:\code\personal\pwshop
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        9/18/2018   4:32 PM             14 something.txt

Running the ResolveParameter method on a command’s information is interesting but not particularly useful. What if we wanted to manage a file?

  1. Retrieve information about something.txt using the Get-Item command to the right.
  2. What methods are available on a file object?
  3. What is the name of the method for copying the file?
  4. Make a copy of the file as else.txt using that method.
  5. Retrieve information about else.txt.
  6. Use the Delete method on something.txt.
  7. Retrieve information about something.txt again.
  8. Did Get-Item error? Why?
  9. Use the Delete method on else.txt.

Output Views

Up to this point you may have noticed that sometimes PowerShell seems to output results as a table, sometimes as a list of properties and their values. Actually, neither of these is true - PowerShell always emits objects. What you’re seeing is a different format for the output.

There’s a few different commands you can do to change the output format:

Exercise X: Formats and Views

Get-Command -Verb 'Format'
Get-Command -Verb 'Format' | Format-List
Get-Command -Verb 'Format' | Format-Table -Property Name, Source
Get-Command -Verb 'Format' | Format-Wide
Get-Command -Verb 'Format' | Out-GridView

Run the following commands to the right and answer the following questions:

  1. How is the output view from Format-List different from the default?
  2. How is the Format-Table output view different from the default?
  3. What displays when you use Format-Wide?
  4. Sort the output in the gridview on Source. Does that change anything?

The Pipeline

Get-Command -Name Get-Command | Get-Member -MemberType Properties

In this section you’ve seen that PowerShell is all about objects which have properties and methods. With this foundational understanding of what’s being used you’re ready to look at the pipeline.

Actually, you’ve been using the pipeline since the last section. Whenever you see the pipeline operator - | - you can be reasonably sure you’re interacting with the pipeline.

In PowerShell you can use the pipeline operator to send the outputs of one command to the input of the next command.

In the earlier example for Get-Member (seen again on the right) the output of Get-Command is used as the input for Get-Member. Remember, the output of PowerShell commands is always one or more objects, not just plain text.

PowerShell lets you arbitrarily chain commands together via the pipeline, allowing you to manipulate and use the resulting objects however you need to.

Get-Service | Select-Object -Property Name, Status | ConvertTo-Json | Out-File results.json

For example, you could retrieve service statuses, convert the information to JSON, and write it to a file. We’ll go through each step one by one:

  1. First, we use Get-Service to retrieve the list of services on the machine. This will output service objects as we are familiar with.
  2. We’ll then use Select-Object to limit the properties of each object that we want to pass along the pipeline. Since all we care about is the name of the service and its state, that’s all we want to pass forward for the rest of the pipeline. This will output the service information we want as an array of objects.
  3. We then call the command ConvertTo-Json which will take the output of Select-Object as its input and convert it into a JSON string. That full string is then emitted as the output.
  4. Out-File gets the string of JSON objects from ConvertTo-Json and then writes that information out to results.json in the current directory. Note that Out-File does not, itself, emit any output. Because there is no output we cannot chain any further commands to the end of this pipeline.

Exercise X: Pipeline Chaining

In this exercise we’re going to reverse the flow from the earlier example. Make sure you actually run it in your prompt before starting this exercise.

  1. What command would you run to get content from the results.json file? Hint: Use Get-Command and Get-Help here.
  2. What command would you run to convert the content from JSON?
  3. What command would you run to sort objects on their properties?
  4. Get the content from results.json, convert them from JSON, and sort the results on Status.
  5. Notice anything strange about the status results now that you’ve converted the information back from JSON?

Filtering & Where-Object

One particularly good use case for the pipeline is to filter objects for further use. It is best practice, where possible, to “filter left” - that is, to filter objects when you’re querying for them rather than retrieving an unfiltered list and selecting the desired values afterward.

For example, your Active Directory / LDAP / SQL admins probably do not want you continually running queries to retrieve all of the available data so you can filter it locally. Normally, instead of returning all possible entries, you filter in your Get- command.

Sometimes, however, you can’t filter left. For example, if you wanted to retrieve all of the services on a computer which were running, how would you do that?

Notice that there’s no parameter for filtering on status. Instead, you’ll have to use the pipeline and the Where-Object command.

Get-Service | Where-Object -Property Status -eq Running

This passes all of the services discovered by Get-Service to the filtering command, Where-Object. That command inspects each object, looking to see if its Status property is equal to Running. Did it return any results where the services were not running?

Note the use of the eq operator. This is a special comparison operator which returns true if the value of the service’s Status property is equal to Running.

Comparison Operators

# This will evaluate to true:
1 -eq 1
# This will evaluate to false:
1 -eq 2
# This will evaluate to true:
1 -lt 2
# This will return 0, 1:
0, 1, 2 -lt 2
# This will evaluate to true (note the case insensitivity):
"Apple" -like "ap*"
# This will evaluate to false (adding the 'c' denotes case sensitivity):
"Apple" -clike "ap*"
# This will return "apply"
"Apple", "apply" -clike "ap*"
# This will evaluate to true (uses regex, not just wildcard):
"Apple" -match "p{2}"
# This will evaluate to true:
"Apple", "Banana", "Cherry" -contains "apple"
# This will evaluate to true:
"apple" -in "Apple", "Banana", "Cherry"
# This will output "Apply"
"Apple" -replace "e", "y"
# This will output "Acceptable"
"Apple" -replace "p{2}", "cceptab"
# This will output true:
1 -eq "1"
# This will output true:
1 -is [int]
# This will output false:
"1" -is [int]

It’s worth taking some time to discuss the available comparison operators in PowerShell. You’ll use these when building filters and when checking the truthiness of an assumption (is this object equal to that object? does this string match that pattern? etc). See the table below for the list of operators and what they mean.

Type Operators Description
Equality eq equals
ne not equals
gt greater than
ge greater than or equal
lt less than
le less than or equal
Matching like Returns true when string matches wildcard pattern
notlike Returns true when string does not match wildcard pattern
match Returns true when string matches regex pattern; $matches contains matching strings
notmatch Returns true when string does not match regex pattern; $matches contains matching strings
Containment contains Returns true when reference value contained in a collection
notcontains Returns true when reference value not contained in a collection
in Returns true when test value contained in a collection
notin Returns true when test value not contained in a collection
Replacement replace Replaces a string pattern
Type is Returns true if both object are the same type
isnot Returns true if the objects are not the same type

Exercise X: Equality Operators

  1. How would you represent five is less than 10 in PowerShell?
  2. How would you represent 3 is greater than or equal to 5 in PowerShell?
  3. How would you check to see if the string “five” is greater than “three”? Is it?
  4. Use Get-Service to retrieve the list of services on your machine and then filter them such that the StartType property is not equal to Automatic.

Exercise X: Matching Operators

  1. Which of the following strings matches the pattern “^P.ck.+”: “Peter”, “Piper”, “Picked”, “Peck”, “Pickled”, “Peppers”
  2. Which of those strings doesn’t match the following pattern: “(a|e|i|o|u).(a|e|i|o|u)”
  3. Use Get-Service to retrieve the list of services on your machine and then filter them such that you match the Name property against the following pattern: “^w.+svc”
    • How many results did you get? Hint: you can pipe the output to Measure-Object if you don’t want to count by hand
  4. Filter those results to show only the services that match the pattern and are also running. Hint: you can pipe the output of Where-Object to another call to Where-Object
    • How many results did you get now?

Exercise X: Containment Operators

Note that you can break longer lines of PowerShell up. If you hit enter after a pipeline operator PowerShell will expect further commands. If you hit enter again without adding more code it will error. So you could do something like this:

# It's not necessary to make everything line
# up like this, but it _is_ easier to read.
Get-Service |
  Where-Object -Property "Something" -eq        -Value "Else" |
  Where-Object -Property "Another"   -match     -Value "p{2}" |
  Where-Object -Property "Last"      -contains  -Value 1
  1. Use Get-Service to retrieve the list of services on your machine and then filter them such that you check whether the value of the StartType is not in the following list: "Automatic","Manual"
  2. Use Get-Service to retrieve the list of services on your machine and then filter them such that you only return results where the ServiceType property contains "Win32OwnProcess".
  3. Pipe those results to another filter, this time also filtering out all of the stopped services. How many results do you have now?
  4. Filter the results again, this time returning only the services with a display name that matches this pattern: "^Windows.*Service$"

Where-Object and FilterScript

Get-Service | Where-Object -FilterScript {
  $_.ServiceType -contains "Win32OwnProcess" -and
  $_.Status -ne "Stopped" -and
  $_.DisplayName -match "^Windows.*Service$"
}

The solution to the last exercise was complex, requiring the output of each filter to be passed along the pipeline to another filter.

There’s a simpler way to do this: use the FilterScript parameter of Where-Object.

In the example to the right you’ll notice some interesting things we haven’t seen before:

Logical Operator Description Example Result
and TRUE when both statements are TRUE. (1 -eq 1) -and (1 -eq 2) False
or TRUE when either statement is TRUE. (1 -eq 1) -or (1 -eq 2) True
xor TRUE when only one statement is TRUE. (1 -eq 1) -xor (2 -eq 2) False
not Negates the statement that follows. -not (1 -eq 1) False
! Same as not. !(1 -eq 1) False

You can use the FilterScript parameter instead of Property and Value, the way we’ve used Where-Object up until now. They are mutually exclusive parameters.

Exercise X: FilterScript

  1. Build and run a filter script which returns only those services which match at least one of the following:
    • The display name matches “Network”
    • The start type is in this list: “Manual”, “Disabled”
  2. Modify and run the filter script so it returns only those services which match both of those conditions.
    • Are the results the same (remember, you can pipe to Measure-Object)? How many did you get for each?
  3. Modify and run the filter script so it returns only those services which match one of those conditions.
    • How many results did you get?

Variables & Operators

So far in this workshop we’ve worked through using commands at the prompt and manipulating the output. But what if you need to store objects for later use?

This is where variables come in. In PowerShell variables are strings with a $ symbol prepended to them, such as $Credential or $Results.

You’ve already seen a special variable, $_, in the previous section. This is an automatic variable provided by PowerShell and which contains the current object in the pipeline. It has an alias, $PSItem.

$SomeVariable = 5
Write-Output $SomeVariable
$SomeVariable = 3
Write-Output $SomeVariable
$SomeVariable = Get-Service | Select-Object -First 1 -ExpandProperty Name
Write-Output $SomeVariable
5
3
AESMService

To assign a value to a variable we use the = symbol. You can reassign a variable to another value if you wish.

The value to the right of the = can be any valid PowerShell command.

This is useful for storing long values or for caching the results of commands. It becomes very useful when writing scripts.

Operators

There are several other operators available to manipulate objects in PowerShell. We’ve previously looked at comparison and logical operators.

You can add (+), subtract (-), multiply (*), divide (/), and calculate the remainder of a division operation (%).

Note that you can use the arithmetic operators on any object that supports them, such as String and DateTime, in addition to numbers.

Exercise X: Arithmetic Operators

  1. Set the value of the variable $Example equal to the string Apple.
  2. What is the result of $Example + 5?
  3. What is the result of $Example - 1?
    • What about $Example.Length - 1?
  4. What is the result of $Example * 2?
  5. What is the result of $Example / 5?
    • What about $Example.Length % 3?
  6. Set the value of the variable $Example equal to Get-Date.
  7. What is the result of $Example + 1?
    • What about $Example + 1000?
    • What about $Example + 10000000?
  8. What methods are available on $Example?
  9. Add 136 hours to $Example using the appropriate method.
    • What date is returned?
  10. Specify -136 instead of 136, using the same method.
    • What date is returned?

Assignment Operators

$Data = 2
Write-Output $Data
$Data += 5
Write-Output $Data
$Data -= 1
Write-Output $Data
$Data *= 3
Write-Output $Data
$Data /= 2
Write-Output $Data
$Data %= 7
Write-Output $Data
2
7
6
18
9
2

Now that you know the arithmetic operators you’ll be able to make sense of the assignment operators. You’re already familiar with =, which sets the variable on the left equal to the output of the PowerShell on the right.

There is also the += operator, which sets the variable on the left equal to itself plus the output of the PowerShell on the right. The -= operator sets the variable equal to itself minus the output of the PowerShell on the right, the /= operator to itself divided by the output of the PowerShell on the right, and so on.

Unary Operators

$a = 5
$a++
Write-Output $a
6

You can increment or decrement variables and properties using the unary operators ++ and -- respectively. Note that this is functionaly equivalent to += 1 and -= 1.

PowerShell Providers

Get-ChildItem -Path ~
# Note: the next two commands will only work on Windows systems.
Get-ChildItem -Path 'HKCU:\Keyboard Layout\'
Get-ChildItem -Path 'Cert:\CurrentUser'
    Directory: C:\Users
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         5/5/2018   2:04 PM                Admin
d-----        9/23/2018   4:04 PM                Michael Lombardi
d-r---         5/5/2018   4:58 PM                Public

    Hive: HKEY_CURRENT_USER\Keyboard Layout
Name                           Property
----                           --------
Preload                        1 : 00000409
Substitutes
Toggle

Name : SmartCardRoot
Name : Root
Name : Trust
...

Providers are one of the ways PowerShell makes our lives easier - they allow us to use the same set of commands to manage certificates, files, registry keys, and more - all without having to learn new commands for each.

We’ll start with the first command in this group that you’re likely to need: Get-ChildItem. This command will search inside a container and return all of the objects it finds. It is not recursive by default, but does have the Recurse parameter to enable that behavior.

The same command works across three completely different providers - it returned a list of files/folders, then registry keys, then certificate stores.

Similarly, the rest of the *-Item* commands (use Get-Command to retrieve the full list) can be used to interact with those items.

Exercise X: Files and Folders

  1. Run the following commands:
    • New-Item -Path ~/pwshopex -ItemType Directory
    • Push-Location -Path ~/pwshopex
  2. Notice that the command created a folder, not a file. What happens if you run the command, specifying ~/pwshopex/test as the path, but do not specify an item type?
  3. Get ~/pwshopex/test - what properties and methods are available? What is the property for determining whether or not a file is read only?
  4. Use the Get-ItemProperty command on ~/pwshopex/test to retrieve whether or not the test file is read only.
  5. Use the Set-ItemProperty command on ~/pwshopex/test to set the read only property to $true. Retrieve the item property again - is it set to true?
  6. Use the Set-Item command to modify ~/pwshopex/test to have the text apple. Note the error that causes this to fail; not all providers implement the same functionality.
  7. For files, we have the *-Content commands. Use Set-Content to modify ~/pwshopex/test to have the text apple. Why does this error?
  8. Use Set-ItemProperty to set the file back to read only being false. Use Set-Content to modify ~/pwshopex/test to have the text apple.
  9. Use Get-Content to retrieve the text in ~/pwshopex/test. What properties and methods are available? What is the object type?
  10. Use Add-Content to add the string banana to ~/pwshopex/text.
  11. Use Get-Content to retrieve the text in ~/pwshopex/test and pass the output through the pipeline to Measure-Object. Note that there are two objects - this is because PowerShell treats each line as a separate string.
  12. Use Clear-Content to erase the text from ~/pwshopex/text.
  13. Rename ~/pwshopex/text to file.
  14. Copy ~/pwshopex/file to ~/pwshopex/item.
  15. Use the command Pop-Location.
  16. Use Remove-Item on ~/pwshopex.

Exercise X: Registry Keys

  1. Run the following commands:
    • New-Item -Path 'HKCU:\pwshop', 'HKCU:\pwshop\apple'
    • Push-Location -Path HKCU:\pwshop
  2. What properties and methods are available for the object representing HKCU:\pwshop\apple?
  3. Use New-ItemProperty to add theFruitproperty toHKCU:\pwshop\apple, and set it to$true`.
  4. What properties are available for the object representing HKCU:\pwshop\apple? Is Fruit in the list of properties?
  5. Use Get-ItemProperty on HKCU:\pwshop\apple - what properties are returned?
  6. Copy HKCU:\pwshop\apple to HKCU:\pwshop\biscuit.
  7. Set the value of Fruit on HKCU:\pwshop\biscuit to $false.
  8. Return the list of all registry keys in HKCU:\pwshop.
  9. Run the following commands:
    • Pop-Location
    • Remove-Item -Path 'HKCU:\pwshop' -Recurse

Exercise X: Certificates

  1. Run the following commands:
    • Push-Location -Path Cert:\CurrentUser\My
    • New-SelfSignedCert -DnsName 'example.pwshop.local'
  2. What properties and methods are available on the object you just created? What is it’s type?
  3. Can you use Set-ItemProperty or Set-Item commands against this object?
  4. Get the cert you created and pipe it to the following command:
    • Export-Certificate -FilePath "$env:HOMEDRIVE$env:HOMEPATH/pwshop.cer"
  5. Open the newly created certificate file in the file explorer and compare its properties to the one in the store. You can do this with the following command:
    • & explorer.exe "$env:HOMEDRIVE$env:HOMEPATH/pwshop.cer"
  6. Delete the self-signed cert from Cert:\CurrentUser\My.
  7. What command would you use to import a certificate? Import the pwshop.cer certificate back into CertL\CurrentUser\My.
  8. Delete the cert from both the store and the file system.

Other Providers

Get-PSProvider

There are also several other providers, both builtin and available via additional modules. They can, like the three providers we’ve already explored, be interacted with through the Item commands.

Remoting

All of the PowerShell we’ve looked at so far relies on running on your local machine. Eventually, however, it’s probable that you will need to manage another machine. To do so without RDPing to it will require you to use PowerShell remoting.

Note: In order to remote to a machine you must have admin permissions on it.

You can interact with remote systems via PowerShell sessions, using the *-PSSession* commands.

Interactive Remoting

Enter-PSSession -ComputerName localhost
Exit-PSSession

Firstly, you can start interactive sessions - this is functionally the same as opening a PowerShell prompt on the target computer. In the example we connect to the localhost, but in a real environment you could specify the FQDN of a server or the name of a server on the same domain as the client machine. You can exit the session once you’re done. This is often useful if you need to look around on a target machine for multiple commands and explore a problem space.

Invoke-Command

$Host.Name
Invoke-Command -ComputerName localhost -ScriptBlock { $Host.Name }
ConsoleHost
ServerRemoteHost

But what if you just need to send a single command and get the results? If that’s the case, you can use the Invoke-Command command to target one or more remote machines and execute PowerShell code on them. The ScriptBlock parameter takes an arbitrary PowerShell block of code, similar to Where-Object. It will return the output of whatever commands are specified in that scriptblock.

Persistent Sessions

$Session = New-PSSession -ComputerName localhost
Invoke-Command   -Session $Session -ScriptBlock { "One" }
Invoke-Command   -Session $Session -ScriptBlock { "Two" }
Remove-PSSession -Session $Session
One
Two

You can also create a PowerShell session and save it to be used for multiple commands. This cuts down on the overhead cost of creating and tearing down PowerShell remoting sessions - just remember to close them when you’re done.

Multiple Sessions

New-PSSession -ContainerID ($env:Servers -Split ',') -OutVariable Sessions
 Id Name            ComputerName    ComputerType    State         ConfigurationName     Availability
 -- ----            ------------    ------------    -----         -----------------     ------------
  7 Session7        8adadc56b28d... Container       Opened                                 Available
  8 Session8        9ca3c01950af... Container       Opened                                 Available
  6 Session6        6b1fd3588ea0... Container       Opened                                 Available

So far the examples have looked at managing a single remote host, but you can also manage several at once. In this workshop we’ve prelaunched some docker containers and captured their container IDs for use as an environment variable. PowerShell remoting has a neat built-in option (for Windows PowerShell 5.1+) to be able to manage containers on the host, no networking required, which is what we’re using here.

Invoke-Command -Session $Sessions -ScriptBlock { "This is $env:ComputerName" }
This is 8ADADC56B28D
This is 9CA3C01950AF
This is 6B1FD3588EA0

Functionally, as far as running commands is concerned, all you need is an open PSSession - whether it’s via WinRM remoting, SSH, or another methods doesn’t effect how the commands are run.

There’s more to discuss with remoting, such as session configurations and implicit remoting, but those topics are beyond the scope of this workshop.

Modules & the PSGallery

Get-Module -ListAvailable
    Directory: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
ModuleType Version    Name                ExportedCommands
---------- -------    ----                ----------------
Manifest   1.0.0.0    AppBackgroundTask   {Disable-AppBackgroundTaskDiagnosticLog, Enable-AppBackgroundTaskDiagnosticLog, S...
Manifest   2.0.0.0    AppLocker           {Get-AppLockerFileInformation, Get-AppLockerPolicy, New-AppLockerPolicy, Set-AppL...
Manifest   1.0.0.0    AppvClient          {Add-AppvClientConnectionGroup, Add-AppvClientPackage, Add-AppvPublishingServer, ...
...

To this point in the workshop, everything we’ve worked on has only included commands which are ‘in the box’, so to speak - the commands that ship with PowerShell itself.

Behind the scenes, all of those commands actually reside in modules - logical containers for groups of commands. Modules are how PowerShell code largely gets shipped around - there are additional modules available, written by Microsoft, by third parties (like VMWare), and by the community.

There is also a centralized location where publically available modules can be discovered: the PowerShell Gallery. Here you can search for modules that might have commands or scripts which would be useful for you.

There’s a set of commands for interacting both with the gallery and with PowerShell modules which you’ve downloaded and installed locally.

Exercise X: Modules

  1. What command would you use to convert text from YAML format to PowerShell? Is that command available on your machine?
    • Remember the approved verbs list.
  2. Use Find-Command to search the gallery for modules containing the appropriate command.
    • How many did it return?
  3. Install the powershell-yaml module to your machine using Install-Module.
    • Make sure to set the Scope parameter to CurrentUser.
  4. Run Get-Command again to search for the appropriate command.
    • Do you get different results?
  5. Use Get-Command to return all of the commands whose noun is Yaml.
  6. Save the module PSYaml to the path ~.
  7. Get the child items at ~/PSYaml recursively.
    • What is there?
  8. Use Get-Command to return all of the commands belonging to the PSYaml module.
    • Did anything return?
  9. Run the following command to import the module into the current runspace:
    • Import-Module -Name ~\PSYaml\1.0.2\PSYaml.psd1
  10. Use Get-Command to return all of the commands belonging to the PSYaml module again.
    • What are the results?
  11. What is the value of the environment variable PSModule path? (Hint: you can split the variable into a readable list like this: $env:PSModulePath.split(';'))
  12. Use Remove-Module to unload PSYaml from the current session.
  13. Use the Get-InstalledModule command to check modules on your machine are installed from the gallery.
  14. Use Get-PSRepository to investigate the default gallery information.
    • What properties and methods are available?
    • What’s the publish location?

Note on Getting Started

When you’re first getting started, using these module commands may require you to install NuGet. PowerShell will try to do this for you automatically.

For a more in depth look at getting started with the gallery, including installing PowerShellGet on older versions of Windows, see the Microsoft docs.

Writing Scripts

Most of the workshop so far has had you writing PowerShell at the prompt, running each command one after the other. Frequently, you’re going to be in a position to want a shortcut to perform several actions in sequence which you can run yourself or turn over to coworkers.

You’re going to want to write scripts.

New-Item ~/pwshop -ItemType Directory
New-Item ~/pwshop/example.ps1
code ~/pwshop

This is actually pretty straightforward. First, we need to create a file with the extension ps1.

For this workshop we’re going to be looking at editing our scripts using Visual Studio Code. Running the code to the side will create the script file and open up VSCode to the appropriate folder.

Open the script file we created. It’s empty by default (because we didn’t specify a value).

Conditionals: If, ElseIf, & Else

$SpoolerStatus = Get-Service -Name spooler | Select-Object -ExpandProperty Status
If ($SpoolerStatus -ne "Stopped") { Stop-Service -Name Spooler -PassThru }

$SpoolerStatus = Get-Service -Name spooler | Select-Object -ExpandProperty Status
If ($SpoolerStatus -ne "Stopped") {
  Stop-Service -Name Spooler -PassThru
} ElseIf ($SpoolerStatus -eq "Stopped") {
  Start-Service -Name Spooler -PassThru
}
$SpoolerStatus = Get-Service -Name spooler | Select-Object -ExpandProperty Status
If ($SpoolerStatus -eq "Stopped") {
  Start-Service -Name Spooler -PassThru
} ElseIf ($SpoolerStatus -ne "Running") {
  "This message won't display"
} Else {
  Restart-Service -Name Spooler -PassThru
}
Status   Name               DisplayName
------   ----               -----------
Stopped  Spooler            Print Spooler

Status   Name               DisplayName
------   ----               -----------
Running  Spooler            Print Spooler

Status   Name               DisplayName
------   ----               -----------
Running  Spooler            Print Spooler

Sometimes in PowerShell you only want to execute a command if a certain condition is met. You can do this with an if statement.

An if statement includes a conditional (the code inside the parentheses) and a scriptblock (the code inside the curly braces). The scriptblock will execute if and only if the conditional evaluates to true.

You can use an elseif statement after an if statement. You would usually do this to execute code where the commands to execute depend on the status of some object. Note that the elseif is only evaluated if the if statement’s condition is false - if it evaluates to true, the elseif isn’t even evaluated.

If you add an else statement to the end of an if statement (with or without a preceding elseif) As with the elseif, this block only runs if all proceeding statements have conditionals which evaluate to false.

Conditionals: Switches

$Pet = Get-Pet -Family Lombardi
If ($Pet.Category -eq 'Dog') {
  'woof'
} ElseIf ($Pet.Category -eq 'Cat') {
  'meow'
} ElseIf ($Pet.Category -eq 'Snake') {
  'hiss'
} Else { 'honk' }

Case ($Pet.Category) {
  'Dog'   { 'woof' }
  'Cat'   { 'meow' }
  'Snake' { 'hiss' }
  Default { 'honk' }
}

If you find yourself writing long series of elseifs to account for numerous possible values, there’s a good chance you’re actually looking for a case statement. A case statement allows you to execute a different scriptblock depending on the value of the expression in the parentheses. The Default statement seen at the bottom is special - the scriptblock associated with it will run if and only if none of the other options are what the expression evaluates to.

Exercise X: Conditionals

& ~/pwshop/example.ps1

In the VSCode editor, open the example.ps1 file. Between each step below, run the file. You can run the script from the prompt using the code to the right.

  1. Write an If statement to output Red if 1 is greater than 3.
  2. Add an elseif statement to output Blue if 1 is less than 5.
  3. Add an else statement to output Green.
  4. Change the If statement’s conditional operator to less than.
  5. Add a blank line and then the following lines:
    • $Foods = 'Apple', 'Cookie', 'Spaghetti', 'Steak', 'Broccoli'
    • `$Food = $Foods | Get-Random -Count 1
  6. Add a blank line and then a case statement which will output the category of food (fruit, pastry, pasta, meat, vegetable) using the template below for the output (replacing fruit with the appropriate category):
    • "$Food is a fruit"
  7. Rerun the script a few times to see the different results.

Loops: ForEach

ForEach ($Service in (Get-Service -DisplayName "*windows*")) {
  $Service.DisplayName
}
Windows Audio Endpoint Builder
Windows Audio
Docker for Windows Service
...

In scripts you’ll often want to repeat an action several times. We do this by using loops.

For example, the foreach loop allows you repeat once for each item in a collection. In the example to the right we loop through the list of services whose display name includes windows in it. The $Service variable name is arbitrary, it would also have worked if we specified $a. It’s just good practice to use meaningful variable names.

Note that we didn’t have to define the collection before the loop. We could’ve assigned those results to a variable ahead of time and used that to the right of in.

Loops: For

For ($i = 0 ; $i -lt 10 ; $i++) {
  "I am $i"
}
I am 0
I am 1
I am 2
...

Sometimes you’ll want to use a loop that will execute a certain number of times. For that you can use a For loop.

Notice that the expression in the parentheses of the For loop is broken up by semicolons; these end the pipeline and execute the following command. They’re only necessary if you want to run multiple commands on a single line.

For For loops, this follows a special pattern:

Loops: While

$i = 1
While ($Truthy -ne $true) {
  "I am $i"
  $i *= 2
  If ($i -gt 8) { $Truthy = $true }
}
While ($Truthy -ne $true) {
  "I never display"
}
I am 1
I am 2
I am 4
I am 8

You can use a While loop to execute code if and only if a condition is true, and repeat only so long as the condition is true. Note that if the script gets to a While loop and the condition is false, it will not run the code in the loop, not even once.

Loops: Do-While, Do-Until

$i = 1
Do {
  "I am $i"
  $i *= 2
} While ($i -lt 0)
Do {
  "I am $i"
  $i *= 2
} Until ($i -gt 8)
I am 1
I am 2
I am 4
I am 8

You can use Do-While loops similarly to While loops, except that it will always execute at least once. A Do-Until loop is identical to a Do-While except that the loop will continue to execute until a condition is met instead of executing while a condition is true.

Exercise X: Loops

In the VSCode editor, open the example.ps1 file and clear it of any existing content. Between each step below, run the file.

  1. Add a for loop that write the value of $i times itself, executing only so long as $i is less than 5. $i should begin at 0 and increment by one after each pass.
    • What is the output?
  2. Add a blank line and then the following code:
    • $Foods = 'Apple', 'Cookie', 'Spaghetti', 'Steak', 'Broccoli'
  3. Add a ForEach loop, iterating over the $Foods collection and outputting the food each time.
  4. Re-use your case statement from the previous exercise, placing it in the ForEach loop.
  5. Add a blank line and then the following code:
    • $Food = $Foods | Get-Random
  6. Add a while loop which runs so long as $Food does not equal Broccoli. Each iteration the loop should output the value of $Food and then set $Food to a random entry in $Foods again.
  7. Modify the line before the while loop to the following code:
    • $Food = 'Broccoli'
  8. Modify the while loop into a do-while loop, ensuring it runs at least once.
  9. Modify the do-while loop into a do-until loop - how does this change the behavior?

Output Streams

[cmdletbinding()]
Param()

Process {
  Write-Output      "This is an output message"
  Write-Debug       "This is a debug message"
  Write-Error       "This is an error message"
  Write-Host        "This is a host message"
  Write-Information "This is an informational message"
  Write-Verbose     "This is a verbose message"
  Write-warning     "This is a warning message"
}

Now that you’re looking to write a script it’s worth knowing that PowerShell has mutliple output streams. Place the powershell code to the right into your script file.

To run the script file, call it from the powershell prompt:

What did you expect to see? Which output messages do you actually see?

Run it again, but this time specify the Verbose flag. You should see a new message. The verbose stream can be used to send messages about what is happening which aren’t neccessary for the user to see, but which are valuable context. In PowerShell it is best practice to only ever emit output objects from a script or command, not status messages.

Run it again with the Verbose flag, but this time assign the output to a variable called $Captured. Notice that the output message is missing from the text at the terminal - but if you look at $Captured, there the message is. Remember that what is assigned to a variable by the = operator is the output of a command or expression.

Notice also that the host message was not captured - this is because that text is not written to any stream, it goes only to the host program. Unlike other streams it can’t be redirected or captured. For this reason you are advised to avoid using Write-Host for the most part.

Run the script again, but this time specify the Debug flag. This time you should see a message and get asked to confirm whether or not you want to continue processing the script. This is very useful for working through scripts that are malfunctioning.

Run the script again, but this time specify Continue as the value for the InformationAction parameter. You should see the information stream message now.

Run the script again, but this time specify SilentlyContinue as the value for the ErrorAction parameter. You should not see the error message now. However, the script still errored! You just surpressed the error in the console.

Error Handling

Try {
  "Message 1"
  Write-Error "This is non-terminating"
  "Message 2"
  If (((Get-Random) % 2) -eq 0) {
    throw [System.ArgumentNullException]::New('a null reference was invalidly passed to a method')
  }
  "Message 3"
} Catch [System.ArgumentNullException] {
  "Message for caught exception"
} Finally {
  "Displays whether the terminating error is generated and caught"
}

In PowerShell, as in all coding, errors are inevitable. There’s a few things we need to be aware of in PowerShell.

The first is that there’s a difference between terminating and non terminating errors - some errors will prevent further execution of code, and we call these terminating errors.

For a really good overview and explanation of error handling, see this blog post by Kevin Marquette. For the purposes of this workshop we’re going to learn via a few examples but not go too deep into terminology.

You can use the Write-Error command to create a new error. You might do this when none of the PowerShell commands you’ve called have errored, but the script itself has a problem and will malfunction. You may not want this error to be terminating - for example, if you’re processing multiple objects and one of them fails, you may want to write an error and keep processing the rest of the objects.

If you want to throw a terminating error you can use the Throw command. You might want to do this in cases where a single failure will cause cascading issues in future parts of the script.

You can use a try-catch block to handle terminating errors in your PowerShell code. This allows you to manage your code and potential errors proactively - for example, if you don’t have permissions to modify a service, write a reasonable message to the console and request alternate credentials to continue the script.

You can add a finally block after a try or try-catch to run a block of code regardless of whether a terminating error is thrown. This is often used to close connections or cleanup the environment from a run.

Unfortunately, catching exceptions requires a bit of foreward thinking and identifying what the exception type will be. Luckily, Kevin Marquette has that covered too with a huge list of exceptions.

Open the example.ps1 file and replace its existing content with the code to the right, then run it multiple times. It will randomly determine a number and, if that random number is even, it will throw a terminating error. Note that, if the terminating is not thrown, Message 3 will display and the caught message will not. The first two messages and the message in the Finally block will always display.

Exercise X: Error Handling

In the VSCode editor, open the example.ps1 file and clear it of any existing content. Between each step below, run the file.

  1. Add the following code to the file (which message displays?):
    • [cmdletbinding()] Param()
    • Throw "This is a terminating error"
    • Write-Error "This is not a terminating error"
  2. Edit the script, placing the Write-Error line above the Throw.
    • Which message(s) are displayed now?
  3. Replace the Throw line with the following code:
    • Throw [System.DivideByZeroException]::New('there is an attempt to divide an integral or decimal value by zero.')
  4. Add a Try to the script, placing the Write-Error and Throw lines inside it.
  5. Add a Catch statement after the Try to catch the System.DivideByZeroException.
    • In this block, set $i equal to 0.
  6. Add a Finally statement which outputs $i.
  7. In the Try block, make the Throw statement execute only if $i is greater than 10.
  8. At the beginning of the Try block, add the following line of code:
    • $i = [1..20] | Get-Random -Count 1
  9. After the If statement add an Else statement which sets $i equal to itself divided by 2.
  10. Modify the code in the Else statement to loop until $i is less than 1.

What’s Next?

You’ve learned a lot during this workshop! At this point you’ve got most of the fundamentals of PowerShell in your hands. You know how to find commands to do what you want, how to figure out how they work, how to introspect objects for additional methods and properties, how to filter objects, how to use the pipeline, how to assign variables and operate on them, how to use different providers, how to remotely manage machines and how to find commands that aren’t available on your system, how to write scripts that use conditionals and loops and multiple output streams and how to handle errors.

This sets you up for a lot of success. Most of the rest of PowerShell is going to be a widening and deepening of the skills you’ve learned today - how to write better scripts, how to add documentation, how to write reusable code, how to package that code up for other folks to use.

To that end, here’s a few challenges and a lot of resources: