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:
- How PowerShell commands are structured and how to use them
- How to discover commands and find documentation for using them
- How to interact with objects
- How to use the pipeline
- How to filter objects
- How to use variables and operators
- How to use PowerShell’s providers to manage files, certificates, and registry keys
- How to remotely manage systems with PowerShell
- How to find commands and modules on the PowerShell Gallery and pull them down for use
- How to write scripts using conditionals, loops, and multiple output streams, and how to handle errors.
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.
- instructions for installing Vagrant
- Instructions for installing Hyper-V
- Instructions for adding an external switch. This is necessary so that your machine can connect to the internet to download some prerequisites.
- Instructions for installing VirtualBox
Steps
- 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 beVagrantfile
, no extension.
- If it appended the
- Open a terminal, navigate to the folder where you saved the
VagrantFile
, and run the command below:vagrant up --provision
- 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:
vagrant suspend
vagrant resume
You can halt the environment, freeing up RAM:
vagrant halt
You can destroy the environment, freeing up disk space:
vagrant destroy
You can bring the environment back up after halting or destroying it using the same command as when you first brought it up:
vagrant up --provision
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:
Get-Item
Set-Item
New-Item
Remove-Item
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-Help
Select-Object
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.
- What command would you use to query for commands that manage Windows services?
- What is the command for querying the system for services?
- What parameters does that command take?
- What command would you run to check if the print spooler service is running?
- Is the print spooler service running?
- If it is running, what command would you use to stop it? (remember, you can look up commands with
Get-Command
whenever you want) - Make sure the service is stopped.
- Start the service again.
- 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.
- What properties are available for a service object?
- What type of service is the print spooler service?
- What is the start type for the print spooler service?
- Can the print spooler service stop?
- 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:
- We see its name,
Module
, but we already had that. - We see that it accepts input which is one or more strings of characters - the
[]
denotes that it accepts an array. - We see that it belongs to all parameter sets.
- We see that it is not dynamic.
- We see that it has one alias,
PSSnapin
, a holdover from when PowerShell commands were distributed as snapins instead of modules. - We see that it has attributes for belonging to all parameter sets and for having an alias, both of which we would expect given the values in
ParameterSets
andAliases
. - We see that it is not a switch parameter, which makes sense as it accepts input and is not a toggle.
Exercise X: File Methods
Create a new file using the
New-Item
command. You can retrieve information about a file using theGet-Item
command. We’ll cover why this isItem
and notFile
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?
- Retrieve information about
something.txt
using theGet-Item
command to the right. - What methods are available on a file object?
- What is the name of the method for copying the file?
- Make a copy of the file as
else.txt
using that method. - Retrieve information about
else.txt
. - Use the
Delete
method onsomething.txt
. - Retrieve information about
something.txt
again. - Did
Get-Item
error? Why? - Use the
Delete
method onelse.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:
Format-Table
displays the output in a table, which is what we’re used to seeing.Format-List
displays the output in a list of key-value pairs.Format-Wide
displays only one property of an object (and most objects have a default).Out-Gridview
isn’t strictly a formatting command, but it creates a useful popout window with the objects in it. In the gridview you can sort and filter through the output.
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:
- How is the output view from
Format-List
different from the default? - How is the
Format-Table
output view different from the default? - What displays when you use
Format-Wide
? - 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:
- First, we use
Get-Service
to retrieve the list of services on the machine. This will output service objects as we are familiar with. - 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. - We then call the command
ConvertTo-Json
which will take the output ofSelect-Object
as its input and convert it into a JSON string. That full string is then emitted as the output. Out-File
gets the string of JSON objects fromConvertTo-Json
and then writes that information out toresults.json
in the current directory. Note thatOut-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.
- What command would you run to get content from the
results.json
file? Hint: UseGet-Command
andGet-Help
here. - What command would you run to convert the content from JSON?
- What command would you run to sort objects on their properties?
- Get the content from
results.json
, convert them from JSON, and sort the results onStatus
. - 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
- How would you represent five is less than 10 in PowerShell?
- How would you represent 3 is greater than or equal to 5 in PowerShell?
- How would you check to see if the string “five” is greater than “three”? Is it?
- Use
Get-Service
to retrieve the list of services on your machine and then filter them such that theStartType
property is not equal toAutomatic
.
Exercise X: Matching Operators
- Which of the following strings matches the pattern “^P.ck.+”: “Peter”, “Piper”, “Picked”, “Peck”, “Pickled”, “Peppers”
- Which of those strings doesn’t match the following pattern: “(a|e|i|o|u).(a|e|i|o|u)”
- Use
Get-Service
to retrieve the list of services on your machine and then filter them such that you match theName
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
- How many results did you get?
Hint: you can pipe the output to
- 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 toWhere-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
- Use
Get-Service
to retrieve the list of services on your machine and then filter them such that you check whether the value of theStartType
is not in the following list:"Automatic","Manual"
- Use
Get-Service
to retrieve the list of services on your machine and then filter them such that you only return results where theServiceType
property contains"Win32OwnProcess"
. - Pipe those results to another filter, this time also filtering out all of the stopped services. How many results do you have now?
- 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:
- The first is the strange
$_
notation. This is the notation in PowerShell to represent the current object in the pipeline.Where-Object
is actually not processing all of the objects fromGet-Service
at the same time. Instead, it is checking each object it receives from the pipeline to see if it matches the filter criteria. This was true in the earlier examples too.- In other words, the
$_
notation is a shortcut to the object currently being processed in the pipeline.
- In other words, the
- We’re using the
and
operator here. Like the comparison operators we’ve been looking at,-and
is a special operator that is used in PowerShell, this time for logic. Other logical operators includeor
,xor
, andnot
(see table below).- We use the
and
operator to make sure we return only results where all three filter checks are true.
- We use the
- We placed all of the filters inside of curly braces (
{}
). Those denote a scriptblock, which is just a chunk of PowerShell code.
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
- 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”
- 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?
- Are the results the same (remember, you can pipe to
- 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
- Set the value of the variable
$Example
equal to the stringApple
. - What is the result of
$Example + 5
? - What is the result of
$Example - 1
?- What about
$Example.Length - 1
?
- What about
- What is the result of
$Example * 2
? - What is the result of
$Example / 5
?- What about
$Example.Length % 3
?
- What about
- Set the value of the variable
$Example
equal toGet-Date
. - What is the result of
$Example + 1
?- What about
$Example + 1000
? - What about
$Example + 10000000
?
- What about
- What methods are available on
$Example
? - Add 136 hours to
$Example
using the appropriate method.- What date is returned?
- Specify
-136
instead of136
, 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
- Run the following commands:
New-Item -Path ~/pwshopex -ItemType Directory
Push-Location -Path ~/pwshopex
- 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? - Get
~/pwshopex/test
- what properties and methods are available? What is the property for determining whether or not a file is read only? - Use the
Get-ItemProperty
command on~/pwshopex/test
to retrieve whether or not thetest
file is read only. - 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? - Use the
Set-Item
command to modify~/pwshopex/test
to have the textapple
. Note the error that causes this to fail; not all providers implement the same functionality. - For files, we have the
*-Content
commands. UseSet-Content
to modify~/pwshopex/test
to have the textapple
. Why does this error? - Use
Set-ItemProperty
to set the file back to read only being false. UseSet-Content
to modify~/pwshopex/test
to have the textapple
. - Use
Get-Content
to retrieve the text in~/pwshopex/test
. What properties and methods are available? What is the object type? - Use
Add-Content
to add the stringbanana
to~/pwshopex/text
. - Use
Get-Content
to retrieve the text in~/pwshopex/test
and pass the output through the pipeline toMeasure-Object
. Note that there are two objects - this is because PowerShell treats each line as a separate string. - Use
Clear-Content
to erase the text from~/pwshopex/text
. - Rename
~/pwshopex/text
tofile
. - Copy
~/pwshopex/file
to~/pwshopex/item
. - Use the command
Pop-Location
. - Use
Remove-Item
on~/pwshopex
.
Exercise X: Registry Keys
- Run the following commands:
New-Item -Path 'HKCU:\pwshop', 'HKCU:\pwshop\apple'
Push-Location -Path HKCU:\pwshop
- What properties and methods are available for the object representing
HKCU:\pwshop\apple
? - Use
New-ItemProperty to add the
Fruitproperty to
HKCU:\pwshop\apple, and set it to
$true`. - What properties are available for the object representing
HKCU:\pwshop\apple
? IsFruit
in the list of properties? - Use
Get-ItemProperty
onHKCU:\pwshop\apple
- what properties are returned? - Copy
HKCU:\pwshop\apple
toHKCU:\pwshop\biscuit
. - Set the value of
Fruit
onHKCU:\pwshop\biscuit
to$false
. - Return the list of all registry keys in
HKCU:\pwshop
. - Run the following commands:
Pop-Location
Remove-Item -Path 'HKCU:\pwshop' -Recurse
Exercise X: Certificates
- Run the following commands:
Push-Location -Path Cert:\CurrentUser\My
New-SelfSignedCert -DnsName 'example.pwshop.local'
- What properties and methods are available on the object you just created? What is it’s type?
- Can you use
Set-ItemProperty
orSet-Item
commands against this object? - Get the cert you created and pipe it to the following command:
Export-Certificate -FilePath "$env:HOMEDRIVE$env:HOMEPATH/pwshop.cer"
- 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"
- Delete the self-signed cert from
Cert:\CurrentUser\My
. - What command would you use to import a certificate?
Import the
pwshop.cer
certificate back intoCertL\CurrentUser\My
. - 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.
Get-Command -Module PowerShellGet
Exercise X: Modules
- 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.
- Use
Find-Command
to search the gallery for modules containing the appropriate command.- How many did it return?
- Install the
powershell-yaml
module to your machine usingInstall-Module
.- Make sure to set the
Scope
parameter toCurrentUser
.
- Make sure to set the
- Run
Get-Command
again to search for the appropriate command.- Do you get different results?
- Use
Get-Command
to return all of the commands whose noun isYaml
. - Save the module
PSYaml
to the path~
. - Get the child items at
~/PSYaml
recursively.- What is there?
- Use
Get-Command
to return all of the commands belonging to thePSYaml
module.- Did anything return?
- Run the following command to import the module into the current runspace:
Import-Module -Name ~\PSYaml\1.0.2\PSYaml.psd1
- Use
Get-Command
to return all of the commands belonging to thePSYaml
module again.- What are the results?
- 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(';')
) - Use
Remove-Module
to unloadPSYaml
from the current session. - Use the
Get-InstalledModule
command to check modules on your machine are installed from the gallery. - 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 elseif
s 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.
- Write an
If
statement to outputRed
if 1 is greater than 3. - Add an
elseif
statement to outputBlue
if 1 is less than 5. - Add an
else
statement to outputGreen
. - Change the
If
statement’s conditional operator to less than. - Add a blank line and then the following lines:
$Foods = 'Apple', 'Cookie', 'Spaghetti', 'Steak', 'Broccoli'
- `$Food = $Foods | Get-Random -Count 1
- 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"
- 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:
- The first expression (before the first
;
) defines the starting value. - The second expression defines the condition which, when false will cause the loop to terminate.
- The third expression defines what happens after each iteration of the loop.
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.
- 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 at0
and increment by one after each pass.- What is the output?
- Add a blank line and then the following code:
$Foods = 'Apple', 'Cookie', 'Spaghetti', 'Steak', 'Broccoli'
- Add a
ForEach
loop, iterating over the$Foods
collection and outputting the food each time. - Re-use your case statement from the previous exercise, placing it in the
ForEach
loop. - Add a blank line and then the following code:
$Food = $Foods | Get-Random
- Add a
while
loop which runs so long as$Food
does not equalBroccoli
. Each iteration the loop should output the value of$Food
and then set$Food
to a random entry in$Foods
again. - Modify the line before the
while
loop to the following code:$Food = 'Broccoli'
- Modify the
while
loop into ado-while
loop, ensuring it runs at least once. - Modify the
do-while
loop into ado-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:
~/pwshop/example.ps1
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.
- 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"
- Edit the script, placing the
Write-Error
line above theThrow
.- Which message(s) are displayed now?
- 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.')
- Add a
Try
to the script, placing theWrite-Error
andThrow
lines inside it. - Add a
Catch
statement after theTry
to catch theSystem.DivideByZeroException
.- In this block, set
$i
equal to0
.
- In this block, set
- Add a
Finally
statement which outputs$i
. - In the
Try
block, make theThrow
statement execute only if$i
is greater than10
. - At the beginning of the
Try
block, add the following line of code:$i = [1..20] | Get-Random -Count 1
- After the
If
statement add anElse
statement which sets$i
equal to itself divided by2
. - Modify the code in the
Else
statement to loop until$i
is less than1
.
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:
- Write a script to replace a manual task you currently have - clearing disk space, adding a user, restarting a service, finding out whether an app pool is running, whatever - just pick some task you currently do by hand or in the GUI and write a script to do it.
- Research and write a single advanced function using the resources below:
- Use Pester to write tests for one script or function:
- Use PowerShell Desired State Configuration to manage a service or application - doesn’t have to be for production, just find something to configure for desired state.
- Read the following books: