PowerShell Beginners Guide To PowerShell
PowerShell Beginners Guide To PowerShell
Mike F. Robbins
This book is for sale at [Link]
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean
Publishing process. Lean Publishing is the act of publishing an in-progress ebook
using lightweight tools and many iterations to get reader feedback, pivot until you
have the right book and build traction once you do.
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Disclaimer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi
Get-Command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Contributing to the documentation . . . . . . . . . . . . . . . . . . . . . . . . . 39
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Next steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Next steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
At the rate I was going, I would have never learned PowerShell because I could only
spend a little time trying to understand it at work.
Most people with a family, including me, value work-life balance and try never
to miss out on family activities. However, I meet some people who are too busy
because of family responsibilities and always seem to know what happened in recent
television shows.
When someone says they don’t have time for something, they really mean that it’s
not important enough for them to make time for it. This thought process reminds
me of the funny pictures I’ve seen where Neanderthals are too busy pushing a cart
around with square wheels to make time to learn about round wheels. Being too
busy pointing and clicking in the GUI instead of making time to learn PowerShell
is inefficient, like the Neanderthal analogy. Instead of being funny, though, it’s just
sad.
Preface ii
I thought I was too busy to learn PowerShell. Are you too busy? If so, I challenge
you to make time to learn PowerShell. I’ve never met anyone who regretted learning
PowerShell.
My employer’s training budget was limited. I understood the importance of keeping
my skills up to date and that my career was my responsibility, not my employer’s. In
addition to the few company-sponsored conferences I attended, I used free and low-
cost resources like books, videos, podcasts, and blog articles to learn PowerShell. I
also attended free technology events, including user group meetings, which allowed
me to learn from and network with others in the industry. Many of these events are
streamed online and recorded. To maximize my time, I often multitask by reading
books or watching training videos while walking on the treadmill at the gym and
listening to podcasts during my commute.
We’re all in control of our own careers. A job and a career are two different things.
A job is what we do to pay the bills. A career is what we do to advance our skills
and knowledge.
When Microsoft released PowerShell 2.0 with remoting, it empowered automation
in on-premises data centers. That was the pivotal moment when I decided to make
time to learn PowerShell.
I dedicated an enormous amount of my personal time to learning PowerShell, often
staying up until the wee hours of the morning while still having to be at work early.
I not only learned PowerShell, but I also became proficient with it.
I competed in the beginner category of the Scripting Games in 2012, the last time Ed
Wilson hosted them. I led the competition during most events and finished in third
place. I competed in the advanced category in the 2013 Scripting Games, the first
year that PowerShell.org1 hosted them, and I won.
Once I figure something out, I help empower others by sharing my knowledge. In
2014, Microsoft awarded me their MVP award for my activities in the PowerShell
community. I was re-awarded every year until 2020, after I became the lead technical
writer for Azure PowerShell at Microsoft.
- Mike F. Robbins
1 [Link]
About this book
Before PowerShell, I began my career as an IT Pro, pointing and clicking in the GUI.
I wrote this book to save IT Pros from themselves by reducing the learning curve and
helping them avoid being reluctant to learn PowerShell.
Instead of a book that covers topics with fictitious scenarios, this book is a condensed
version targeting the specific topics I’ve found an IT Pro needs to know to succeed
with PowerShell in a real-world production environment. It’s a collection of what I
wish someone would have told me when I started learning PowerShell, along with
the tips, tricks, and best practices that I’ve learned while using PowerShell since 2007.
In every chapter, you’ll find a curated collection of links to specific help topics
if you want to know more about the information covered in that chapter. These
resources help you dive deeper into the topics discussed in the book and broaden
your understanding of PowerShell.
scholarship program (beginning June 3rd, 2020, and later). For more information
about OnRamp scholarships, see [Link]/onramp/1 .
When Mike’s not writing documentation for Microsoft, he can be found sharing
his thoughts and insights on his blog at mikefrobbins.com7 and interacting with his
followers on Twitter @mikefrobbins8 .
Lab environment
The examples in this book were specifically created and tested on Windows 11 and
Windows Server 2022 operating systems, utilizing Windows PowerShell version 5.1.
If you’re running a different operating system or version of PowerShell, your results
may vary from the ones presented in this book.
Disclaimer
I designed this book to provide accurate information. While I’ve made every effort
to ensure the accuracy and completeness of the information contained in this book, I
assume no responsibility for errors, inaccuracies, omissions, or any inconsistencies.
The code examples provided in this book are for learning and experimentation
purposes only.
You should use a lab environment to work through the code examples found in this
book. I do not guarantee that the code examples will work in all circumstances and
under all conditions. Before using the code examples in a production environment,
you should thoroughly test them.
Furthermore, the technology, software, and coding practices discussed in this book
constantly evolve, and there is no guarantee that the code and software practices
used will remain relevant or appropriate.
This book does not grant you any warranty, either express or implied. You are
responsible for any damage or adverse consequences that may result from using the
code examples found in this book.
Chapter 1 - Getting started with
PowerShell
This chapter focuses on finding and launching PowerShell and solving the initial pain
points that new users experience with PowerShell. Follow along and walk through
the examples in this chapter on your lab environment computer.
What is PowerShell?
Windows PowerShell is an easy-to-use command-line shell and scripting environ-
ment for automating administrative tasks of Windows-based systems.
• Windows PowerShell
• Windows PowerShell ISE
• Windows PowerShell (x86)
• Windows PowerShell ISE (x86)
Chapter 1 - Getting started with PowerShell 3
You only have two shortcuts if you’re running an older 32-bit version of Windows.
Those shortcuts don’t have the (x86) suffix but are 32-bit versions.
I recommend using the 64-bit version of Windows PowerShell if you’re running a 64-
bit operating system unless you have a specific reason for using the 32-bit version.
Depending on what version of Windows 11 you’re running, Windows PowerShell
may open in Windows Terminal1 .
The PowerShell ISE is no longer in active feature development. I recommend using
Visual Studio Code2 (VS Code) with the PowerShell extension3 to replace the ISE.
You must install VS Code and the PowerShell extension because they don’t ship
preinstalled with Windows. You only need to install them on the computer where
you create PowerShell scripts. You don’t need to install them on all the computers
where you run PowerShell.
For information about finding PowerShell on other versions of Windows, see Starting
Windows PowerShell4 .
Some commands run fine when you run PowerShell as an ordinary user. However,
PowerShell doesn’t participate in User Access Control (UAC). That means it’s unable
to prompt for elevation for tasks that require the approval of an administrator.
UAC is a Windows security feature that helps prevent malicious code from
running with elevated privileges.
When running as an ordinary user, PowerShell returns an error when you run a
command that requires elevation, such as stopping a Windows service.
Close the PowerShell console. When you relaunch it, right-click the Windows
PowerShell shortcut and select Run as administrator, as shown in Figure 1-3.
You’re prompted for credentials because you logged into Windows as an ordinary
user. Enter the credentials of your domain user who is a local administrator, as
shown in Figure 1-4.
Chapter 1 - Getting started with PowerShell 6
Search for PowerShell again, except this time right-click on it and select Pin to
taskbar as shown in Figure 1-6.
Chapter 1 - Getting started with PowerShell 8
When you need to run PowerShell with elevated permissions, right-click the Pow-
erShell shortcut pinned to your taskbar while pressing Shift and select Run as
administrator, as shown in Figure 1-7.
Chapter 1 - Getting started with PowerShell 9
You’ll never have to worry about finding PowerShell or whether it’s running elevated
as an administrator again.
$PSVersionTable
Name Value
---- -----
PSVersion 5.1.22621.2428
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.22621.2428
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion [Link]
If you’re running a version of Windows PowerShell older than 5.1, you should
update to Windows PowerShell 5.1. Microsoft distributes new versions of Windows
PowerShell as part of the Windows Management Framework (WMF). Depending on
the WMF version, a specific version of the .NET Framework is required. To upgrade
Windows PowerShell, see Upgrading existing Windows PowerShell5 .
PowerShell version 7 isn’t an upgrade to Windows PowerShell 5.1; it installs side-by-
side with Windows PowerShell. Windows PowerShell version 5.1 and PowerShell
version 7 are two different products. For more information about the differences
between Windows PowerShell version 5.1 and PowerShell version 7, see Migrating
from Windows PowerShell 5.1 to PowerShell 76 .
Execution policy
PowerShell execution policy controls the conditions under which you can run
PowerShell scripts. The execution policy in PowerShell is a safety feature designed
5 [Link]
upgrading-existing-windows-powershell
6 [Link]
powershell-7
Chapter 1 - Getting started with PowerShell 11
to help prevent the unintentional execution of malicious scripts. However, it’s not a
security boundary because it can’t stop determined users from deliberately running
scripts. A determined user can bypass the execution policy in PowerShell.
You can set an execution policy for the local computer, current user, or a PowerShell
session. You can also set execution policies for users and computers with Group
Policy.
The following table shows the default execution policy for current Windows operat-
ing systems.
Regardless of the execution policy setting, you can run any PowerShell command
interactively. The execution policy only affects commands running in a script. Use
the Get-ExecutionPolicy cmdlet to determine the current execution policy setting.
Check the execution policy setting on your computer.
Get-ExecutionPolicy
Restricted
Get-ExecutionPolicy -List
Chapter 1 - Getting started with PowerShell 12
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine Undefined
All Windows client operating systems have the default execution policy setting of
Restricted. You can’t run PowerShell scripts using the Restricted execution policy
setting. To test the execution policy, save the following code as a .ps1 file named
Get-TimeService.ps1.
When you run the previous command interactively, it completes without error.
PowerShell returns an error when you run the same command from a script.
.\Get-TimeService.ps1
Notice the error message tells you why the command failed: “Running scripts is
disabled on this system”.
Chapter 1 - Getting started with PowerShell 13
When you run a command in PowerShell that generates an error, read the error
message before retrying the command. The error message often tells you why the
command failed.
You change the execution policy with the Set-ExecutionPolicy cmdlet. LocalMachine
is the default scope when you don’t specify the Scope parameter. You must run
PowerShell elevated as an administrator to change the execution policy for the local
machine. Unless you’re signing your scripts, I recommend using the RemoteSigned
execution policy. RemoteSigned requires downloaded scripts to be signed by a trusted
publisher.
Before you change the execution policy, read the about_Execution_Policies7 help
topic to understand the security implications.
Change the execution policy setting on your computer to RemoteSigned.
Read the warning that’s displayed when you change the execution policy.
7 [Link]
Chapter 1 - Getting started with PowerShell 14
It’s also possible to change the execution policy for the current user without requiring
you to run PowerShell elevated as an administrator.
Set the execution policy for the current user to RemoteSigned. This step is unnecessary
if you successfully set the execution policy for the local machine to RemoteSigned.
Now that you’ve changed the execution policy to RemoteSigned, the Get-TimeService.ps1
script runs successfully.
Chapter 1 - Getting started with PowerShell 15
.\Get-TimeService.ps1
Summary
In this chapter, you’ve learned where to find and how to launch PowerShell. You’ve
also learned how to determine the version of PowerShell and the purpose of execution
policies.
Review
1. How do you determine what PowerShell version a computer is running?
2. When should you launch PowerShell elevated as an administrator?
3. What’s the default execution policy on Windows client computers, and what
does it prevent you from doing?
4. How do you determine the current PowerShell execution policy setting?
5. How do you change the PowerShell execution policy?
References
If you’re interested in learning more about the topics covered in this chapter, you
should read the following PowerShell help topics.
• about_Automatic_Variables8
• about_Execution_Policies9
• about_Hash_Tables10
8 [Link]
9 [Link]
10 [Link]
Chapter 1 - Getting started with PowerShell 16
Next steps
In the next chapter, you’ll learn about the discoverability of commands in PowerShell.
You’ll also learn how to download PowerShell’s help topics to view offline.
Chapter 2 - The help system
In an experiment designed to assess proficiency in PowerShell, two distinct groups
of IT professionals —beginners and experts— were first given a written examination
without access to a computer. Surprisingly, the test scores indicated comparable
skills across both groups. A subsequent test was then administered, mirroring the
first but with one key difference: participants had access to an offline computer
equipped with PowerShell. The results revealed a significant skills gap between the
two groups this time.
What factors contributed to the outcomes observed between the two assessments?
Experts don’t always know the answers, but they know how to figure out
the answers.
The outcomes observed in the results of the two tests were because experts don’t
memorize thousands of PowerShell commands. Instead, they excel at using the
help system within PowerShell, enabling them to discover and learn how to use
commands when necessary.
Mastering the help system is the key to being successful with PowerShell.
I’ve heard Jeffrey Snover, the creator of PowerShell, share a similar story on multiple
occasions.
Discoverability
Compiled commands in PowerShell are known as cmdlets, pronounced as “command-
let”, not “CMD-let”. The naming convention for cmdlets follows a singular Verb-
Noun format to make them easily discoverable. For instance, Get-Process is the
cmdlet to determine what processes are running, and Get-Service is the cmdlet to
Chapter 2 - The help system 18
I’m often asked: “How do you figure out what the commands are in PowerShell?”.
Both Get-Help and Get-Command are invaluable resources for discovering and under-
standing commands in PowerShell.
Get-Help
The first thing you need to know about the help system in PowerShell is how to use
the Get-Help cmdlet.
Get-Help is a multipurpose command that helps you learn how to use commands once
you find them. You can also use Get-Help to locate commands, but in a different and
more indirect way when compared to Get-Command.
When using Get-Help to locate commands, it initially performs a wildcard search for
command names based on your input. If that doesn’t find any matches, it conducts
a comprehensive full-text search across all PowerShell help topics on your system.
If that also fails to find any results, it returns an error.
Here’s how to use Get-Help to view the help content for the Get-Help cmdlet.
Beginning with PowerShell version 3.0, the help content doesn’t ship pre-installed
with the operating system. When you run Get-Help for the first time, a message asks
if you want to download the PowerShell help files to your computer.
Chapter 2 - The help system 19
Answering Yes by pressing Y executes the Update-Help cmdlet, downloading the help
content.
You only receive this message if you run PowerShell elevated as an administrator.
You also don’t receive it if you’re using the help function or man alias. If you don’t
receive this message, run Update-Help from an elevated PowerShell session running
as an administrator.
Once the update is complete, the help topic is displayed.
Take a moment to run the example on your computer, review the output, and observe
how the help system organizes the information.
• NAME
• SYNOPSIS
• SYNTAX
• DESCRIPTION
• RELATED LINKS
• REMARKS
As you review the output, keep in mind that help topics often contain a vast amount
of information, and what you see by default isn’t the entire help topic.
Parameters
When you run a command in PowerShell, you may need to provide additional
information or input to the command. Parameters allow you to specify options and
arguments that change a command’s behavior. The SYNTAX section of each help
topic outlines a command’s available parameters.
Get-Help has many parameters that you can specify to return the entire help topic or
a subset for a command. To view all the available parameters for Get-Help, see the
SYNTAX section of its help topic, as shown in the following example.
Chapter 2 - The help system 20
...
SYNTAX
Get-Help [[-Name] <[Link]>] [-Category {Alias | Cmdlet | Provider
| General | FAQ | Glossary | HelpFile | ScriptCommand | Function |
Filter | ExternalScript | All | DefaultHelp | Workflow | DscResource |
Class | Configuration}] [-Component <[Link][]>] [-Full]
[-Functionality <[Link][]>] [-Path <[Link]>] [-Role
<[Link][]>] [<CommonParameters>]
Parameter sets
When you review the SYNTAX section, you’ll notice that information appears to be
repeated six times. Each of those blocks is an individual parameter set, indicating the
Get-Help cmdlet features six distinct sets of parameters. A closer look reveals each
parameter set contains at least one unique parameter, making it different from the
others.
Parameter sets are mutually exclusive. Once you specify a unique parameter that
only exists in one parameter set, you’re limited to using the parameters contained
within that parameter set. For instance, you can’t use the Full and Detailed
parameters of Get-Help together because they belong to different parameter sets.
Each of the following parameters belongs to a different parameter set for the Get-Help
cmdlet.
• Full
• Detailed
• Examples
• Online
• Parameter
• ShowWindow
help Get-EventLog
The following output shows the relevant portion of the help topic.
...
SYNTAX
Get-EventLog [-LogName] <[Link]> [[-InstanceId]
<System.Int64[]>] [-After <[Link]>] [-AsBaseObject] [-Before
<[Link]>] [-ComputerName <[Link][]>] [-EntryType {Error
| Information | FailureAudit | SuccessAudit | Warning}] [-Index
<System.Int32[]>] [-Message <[Link]>] [-Newest <System.Int32>]
[-Source <[Link][]>] [-UserName <[Link][]>]
[<CommonParameters>]
The help syntax in the SYNTAX section of PowerShell help topics includes pairs of
square brackets ([ ]). These square brackets serve two different purposes depending
on their usage.
Positional parameters
For the Get-EventLog cmdlet, the first parameter in the first parameter set is LogName.
LogName is enclosed in square brackets, indicating it’s a positional parameter.
Since LogName is a positional parameter, you can specify it by either name or
position. According to the angle brackets following the parameter name, the value
for LogName must be a single string. The absence of square brackets enclosing both
the parameter name and datatype indicates that LogName is a required parameter
within this particular parameter set.
Get-EventLog [-LogName] <[Link]>
The second parameter in that parameter set is InstanceId. Both the parameter name
and datatype are entirely enclosed in square brackets, signifying that InstanceId is
an optional parameter. Furthermore, InstanceId has its own pair of square brackets,
indicating that it’s a positional parameter similar to the LogName parameter.
Following the datatype, an empty set of square brackets implies that InstanceId can
accept multiple values.
[[-InstanceId] <System.Int64[]>]
Switch parameters
A parameter that doesn’t require a value is called a switch parameter. You can easily
identify switch parameters because a datatype doesn’t follow their parameter names.
When you specify a switch parameter, its value is true; when you don’t specify a
switch parameter, its value is false.
The second parameter set includes a List parameter, which is a switch parameter.
When you specify the List parameter, it returns a list of event logs on the local
computer.
[-List]
Take a moment to run the example on your computer, review the output, and observe
how the help system organizes the information.
• NAME
• SYNOPSIS
• SYNTAX
• DESCRIPTION
• PARAMETERS
• INPUTS
• OUTPUTS
• NOTES
• EXAMPLES
• RELATED LINKS
By specifying the Full parameter with the Get-Help cmdlet, you’ll notice the output
includes several additional sections. Among these, the PARAMETERS section often
provides a detailed explanation for each parameter. However, the extent of this
information varies depending on the specific command you’re investigating.
...
-Detailed <[Link]>
Adds parameter descriptions and examples to the basic help display.
This parameter is effective only when the help files are installed
on the computer. It has no effect on displays of conceptual ( About_
) help.
Required? true
Position? named
Default value False
Accept pipeline input? False
Accept wildcard characters? false
-Examples <[Link]>
Displays only the name, synopsis, and examples. This parameter is
effective only when the help files are installed on the computer. It
Chapter 2 - The help system 25
Required? true
Position? named
Default value False
Accept pipeline input? False
Accept wildcard characters? false
-Full <[Link]>
Displays the entire help article for a cmdlet. Full includes
parameter descriptions and attributes, examples, input and output
object types, and additional notes.
This parameter is effective only when the help files are installed
on the computer. It has no effect on displays of conceptual ( About_
) help.
Required? false
Position? named
Default value False
Accept pipeline input? False
Accept wildcard characters? false
...
When you ran the previous command to display the help topic for Get-Help, you
probably noticed the output scrolled by too quickly to read it.
If you’re using the PowerShell console, Windows Terminal, or VS Code and need
to view a help topic, the help function can be useful. It pipes Get-Help to [Link],
displaying one page of help content at a time. As for the ISE, running help works the
same way as Get-Help. I recommend using the help function instead of the Get-Help
cmdlet because it provides a better user experience and it’s less to type.
While typing less may seem beneficial, it’s not always the best practice or intuitive
when saving commands in a script or sharing code with others. Using full cmdlet
and parameter names offers the benefit of self-documenting, making the code more
easily interpreted and understandable for anyone who reviews it.
Run each of the following commands in PowerShell on your computer.
Chapter 2 - The help system 26
Did you observe any variations in the output when you ran the previous commands?
The only difference is the last two commands display their output one page at a
time. When using the help function, press the Spacebar to navigate to the next page
of content or Q to quit. If you need to terminate any command running interactively
in PowerShell, press Ctrl+C.
In the previous example, the first line uses the Get-Help cmdlet, the second uses the
help function, and the third line omits the Name parameter while using the help
function. Since Name is a positional parameter, the third example takes advantage
of its placement instead of explicitly stating the parameter’s name.
To quickly find information about a specific parameter, use the help function with
the Parameter parameter. This approach is more concise, containing only the
parameter-specific help content instead of manually scanning the entire help topic
for details about a parameter.
In the following example, use the help function with the Parameter parameter to
return information from the help topic for the Name parameter of Get-Help.
Based on the following results, the Name parameter is positional and must be
specified in position zero when used positionally.
-Name <[Link]>
Gets help about the specified command or concept. Enter the name of a
cmdlet, function, provider, script, or workflow, such as `Get-Member`,
a conceptual article name, such as `about_Objects`, or an alias, such
as `ls`. Wildcard characters are permitted in cmdlet and provider
names, but you can't use wildcard characters to find the names of
function help and script help articles.
To get help for a script that isn't located in a path that's listed in
the `$env:Path` environment variable, type the script's path and file
Chapter 2 - The help system 27
name.
If you enter the exact name of a help article, `Get-Help` displays the
article contents.
If you enter any text that doesn't match any help article titles,
`Get-Help` displays a list of articles that include that text in their
contents.
Required? false
Position? 0
Default value None
Accept pipeline input? True (ByPropertyName)
Accept wildcard characters? true
The Name parameter expects the datatype for its value to be a single string as
identified by <String> next to the parameter name.
There are several other parameters besides Parameter that you can specify with
Get-Help to return a subset of a help topic. Run the following commands on your
computer to see how they work.
I typically use help <command name> with the Full or Online parameter. If you’re only
interested in the examples, use the Examples parameter; if you’re only interested in
a specific parameter, use the Parameter parameter.
Chapter 2 - The help system 28
When using the ShowWindow parameter, it opens the help topic in a separate
searchable window that can be placed on a different monitor if you have multiple
monitors. However, the ShowWindow parameter has a known bug that may
prevent it from displaying the entire help topic. The ShowWindow parameter also
requires an operating system with a Graphical User Interface (GUI). It returns an
error if you attempt to use it on Windows Server installed with the server core
installation option.
If you want the help topic in a separate window and have internet access, use the
Online parameter instead. The Online parameter opens the help topic with the
most up-to-date content in your default web browser, allowing you to search it and
navigate to other help topics.
help *process*
In this scenario, you aren’t required to add the * wildcard characters. When you omit
the wildcard characters, Get-Help automatically adds them behind the scenes. The
following example produces the same results as specifying the * wildcard character
on each end of process.
help process
However, you should always add them since that option works consistently. Oth-
erwise, you’re required to add them in certain scenarios and not in others. When
you specify a wildcard character within the value, they’re no longer automatically
appended behind the scenes.
The following command doesn’t return any results unless you add a * wildcard
character to the beginning, end, or both the beginning and end of pr*cess.
help pr*cess
PowerShell generates an error if you specify a value that begins with a dash without
enclosing it in quotes because it interprets it as a parameter name. No such parameter
name exists for the Get-Help cmdlet.
help -process
If you’re attempting to search for commands that end with -process, you must add
an * to the beginning of the value.
help *-process
When you search for PowerShell commands with Get-Help, it’s better to be vague
rather than too specific.
When you searched for process earlier, the results only returned commands that
included process in their name. But if you use help to search for processes, it won’t
find any matches for command names. As previously stated, when help doesn’t
find any matches, it performs a comprehensive full-text search of every help topic
on your system and returns those results. This type of search often produces more
results than expected, probably without the information you’re trying to locate.
Chapter 2 - The help system 30
help processes
about_WS-Management_Cmdlets HelpFile
about_Foreach-Parallel HelpFile
about_Parallel HelpFile
about_Sequence HelpFile
When using the help function to search for process, it returned 12 results. However,
when searching for processes, it produced 78 results. If your search only finds one
match, the help topic is displayed instead of listing the results.
help *hotfix*
NAME
Get-HotFix
SYNOPSIS
Gets the hotfixes that are installed on local or remote computers.
SYNTAX
Get-HotFix [-ComputerName <[Link][]>] [-Credential
<[Link]>] [-Description
<[Link][]>] [<CommonParameters>]
DESCRIPTION
> This cmdlet is only available on the Windows platform. The
`Get-Hotfix` cmdlet uses the Win32_QuickFixEngineering WMI class to
list hotfixes that are installed on the local computer or specified
remote computers.
RELATED LINKS
Online Version: [Link]
[Link]/get-hotfix?view=powershell-5.1&WT.mc_id=ps-gethelp
Chapter 2 - The help system 33
about_Arrays
Add-Content
Get-ComputerRestorePoint
Get-Credential
Win32_QuickFixEngineering class
REMARKS
To see the examples, type: "get-help Get-HotFix -examples".
For more information, type: "get-help Get-HotFix -detailed".
For technical information, type: "get-help Get-HotFix -full".
For online help, type: "get-help Get-HotFix -online"
It’s not commonly known that Get-Help can also find commands that lack help topics.
The more function is one of the commands that doesn’t have a help topic. To confirm
you can find commands with Get-Help that don’t include help topics, use the help
function to find more.
help *more*
The search only found one match, so it returned the basic syntax information you’ll
see when a command doesn’t have a help topic.
NAME
more
SYNTAX
more [[-paths] <string[]>]
ALIASES
None
REMARKS
None
The PowerShell help system also contains conceptual About help topics. You must
update the help content on your system for the About help topics to exist. If the
initial update of the help system fails, the About help topics won’t be available until
Chapter 2 - The help system 34
you run Update-Help and it completes successfully. For more information, see the
Updating help section of this chapter.
Use the following command to return a list of all About help topics on your system.
help About_*
When you limit the results to one About help topic, the content of the help topic is
displayed instead of returning a list of help topics.
help about_Updatable_Help
Updating help
Earlier in this chapter, you updated the PowerShell help topics on your computer the
first time you ran the Get-Help cmdlet. You should periodically run the Update-Help
cmdlet on your computer to obtain any updates to the help content. Update-Help
requires internet access by default.
In the following example, use the Update-Help cmdlet to update the PowerShell help
content on your computer.
Update-Help
If your computer doesn’t have internet access, use the Save-Help cmdlet on a computer
with internet access to download and save the updated help content. Then, use the
SourcePath parameter of Update-Help to specify the location of the saved updated
help content.
Get-Command
Get-Command is another multipurpose command that helps you locate commands. You
can also use Get-Command to learn how to use commands, but in a different and more
indirect way when compared to Get-Help.
How do you determine the syntax for Get-Command? You could use Get-Help to display
the help topic for Get-Command, as shown in the Get-Help section of this chapter.
You can also use Get-Command with the Syntax parameter to view the syntax for
any command. This shortcut helps you quickly determine how to use a command
without navigating through its help content.
Using Get-Command with its Syntax parameter offers a more programmatic view by
showing the type of the parameter, without listing the specific allowable values.
Chapter 2 - The help system 36
If you need more detailed information about how to use a command, revert to using
Get-Help.
...
Get-Command [[-Name] <[Link][]>] [[-ArgumentList]
<[Link][]>] [-All] [-CommandType {Alias | Function | Filter |
Cmdlet | ExternalScript | Application | Script | Workflow |
Configuration | All}] [-FullyQualifiedModule
<[Link][]>] [-ListImported]
[-Module <[Link][]>] [-ParameterName <[Link][]>]
[-ParameterType <[Link][]>]
[-ShowCommandInfo] [-Syntax] [-TotalCount <System.Int32>]
[<CommonParameters>]
<[Link][]>] [-ShowCommandInfo]
[-Syntax] [-TotalCount <System.Int32>] [-Verb <[Link][]>]
[<CommonParameters>]
...
The PARAMETERS section of the help topic for Get-Command reveals the Name, Noun,
and Verb parameters accept wildcard characters.
...
-Name <[Link][]>
Specifies an array of names. This cmdlet gets only commands that
have the specified name. Enter a name or name pattern. Wildcard
characters are permitted.
To get commands that have the same name, use the All parameter. When
two commands have the same name, by default, `Get-Command` gets the
command that runs when you type the command name.
Required? false
Position? 0
Default value None
Accept pipeline input? True (ByPropertyName, ByValue)
Accept wildcard characters? true
-Noun <[Link][]>
Specifies an array of command nouns. This cmdlet gets commands,
which include cmdlets, functions, and aliases, that have names that
include the specified noun. Enter one or more nouns or noun
patterns. Wildcard characters are permitted.
Required? false
Position? named
Default value None
Accept pipeline input? True (ByPropertyName)
Accept wildcard characters? true
-Verb <[Link][]>
Specifies an array of command verbs. This cmdlet gets commands,
which include cmdlets, functions, and aliases, that have names that
include the specified verb. Enter one or more verbs or verb
Chapter 2 - The help system 38
Required? false
Position? named
Default value None
Accept pipeline input? True (ByPropertyName)
Accept wildcard characters? true
...
In the following example, use * wildcard characters with the Name parameter of
Get-Command.
When using wildcard characters with the Name parameter of Get-Command, it returns
PowerShell commands and native commands, as shown in the following results.
If you use wildcard characters with the Name parameter of Get-Command, consider
limiting the results to PowerShell cmdlets, functions, and aliases with the Com-
mandType parameter.
Chapter 2 - The help system 39
A better option might be to use either the Verb or Noun parameter or both since
only PowerShell commands have verbs and nouns.
In the following example, use Get-Command to determine what commands exist on your
computer for working with processes. Use the Noun parameter and specify Process
as its value.
When you run Get-Command without any parameters, it returns a list of all the
commands on your system.
Summary
In this chapter, you’ve learned how to find commands with Get-Help and Get-Command.
You’ve also learned how to use the help system to figure out how to use commands
once you find them. In addition, you’ve learned how to update the help system on
your computer when new help content is available.
1 [Link]
2 [Link]
Chapter 2 - The help system 40
Review
1. Is the DisplayName parameter of Get-Service positional?
2. How many parameter sets does the Get-Process cmdlet have?
3. What PowerShell commands exist for working with event logs?
4. What’s the PowerShell command for returning a list of PowerShell processes
running on your computer?
5. How do you update the PowerShell help content stored on your computer?
References
To learn more about the information covered in this chapter, read the following
PowerShell help topics.
• Get-Help3
• Get-Command4
• Update-Help5
• Save-Help6
• about_Updatable_Help7
• about_Command_Syntax8
Next steps
In the next chapter, you’ll learn about objects, properties, methods, and the Get-Member
cmdlet.
3 [Link]
4 [Link]
5 [Link]
6 [Link]
7 [Link]
8 [Link]
Chapter 3 - Discovering Objects,
Properties, and Methods
PowerShell is an object-oriented scripting language. It represents data and system
states using structured objects derived from .NET classes defined in the .NET
Framework. By leveraging the .NET Framework, PowerShell offers access to various
system capabilities, including file system, registry, and Windows Management
Instrumentation (WMI) classes. PowerShell also has access to the .NET Framework
class library, which contains a vast collection of classes that you can use to develop
robust PowerShell scripts.
In PowerShell, each item or state is an instance of an object that can be explored
and manipulated. The Get-Member cmdlet is one of the primary tools provided by
PowerShell for object discovery, which reveals an object’s characteristics. This
chapter explores how PowerShell leverages objects and how you can discover and
manipulate these objects to streamline your scripts and manage your systems more
efficiently.
Prerequisites
To follow the specific examples in this chapter, ensure that your lab environment
computer is part of your lab environment Active Directory domain. You must also
install the Active Directory PowerShell module bundled with the Windows Remote
Server Administration Tools (RSAT). If you’re using Windows 10 build 1809 or later,
including Windows 11, you can install RSAT as a Windows feature.
Get-Member
Get-Member provides insight into the objects,properties, and methods associated with
PowerShell commands. You can pipe any PowerShell command that produces object-
based output to Get-Member. When you pipe the output of a command to Get-Member, it
reveals the structure of the object returned by the command, detailing its properties
and methods.
Consider a driver’s license as an analogy to illustrate this concept. Like any object, a
driver’s license has properties, such as eye color, which typically includes blue and
brown values. In contrast, methods represent actions you can perform on the object.
For instance, Revoke is a method that the Department of Motor Vehicles can perform
on a driver’s license.
Properties
To retrieve details about the Windows Time service on your system using PowerShell,
use the Get-Service cmdlet.
The results include the Status, Name, and DisplayName properties. The Status
property indicates that the service is Running. The value for the Name property is
w32time, and the value for the DisplayName property is Windows Time.
2 [Link]
administration-tools
Chapter 3 - Discovering Objects, Properties, and Methods 43
To list all available properties and methods for Get-Service, pipe it to Get-Member.
The results show the first line contains one piece of significant information. Type-
Name identifies the type of object that’s returned, which in this example is a
[Link] object. This is often abbreviated to the
last part of the TypeName, such as ServiceController, in this example.
TypeName: [Link]
Notice when you piped Get-Service to Get-Memnber, there are more properties than are
displayed by default. Although these additional properties aren’t shown by default,
you can select them by piping to Select-Object and using the Property parameter.
The following example selects all properties by piping the results of Get-Service to
Select-Object and specifying the * wildcard character as the value for the Property
parameter.
By default, PowerShell returns four properties as a table and five or more as a list.
However, some commands apply custom formatting to override the default number
of properties displayed in a table. You can use Format-Table and Format-List to
override these defaults manually.
Name : w32time
RequiredServices : {}
CanPauseAndContinue : False
CanShutdown : True
CanStop : True
DisplayName : Windows Time
DependentServices : {}
MachineName : .
ServiceName : w32time
ServicesDependedOn : {}
ServiceHandle :
Chapter 3 - Discovering Objects, Properties, and Methods 45
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Manual
Site :
Container :
Specific properties can also be selected using a comma-separated list as the value of
the Property parameter.
You can use wildcard characters when specifying property names with Select-Object.
In the following example, use Can* as one of the values for the Property parameter to
return all the properties that start with Can. These include CanPauseAndContinue,
CanShutdown, and CanStop.
Notice there are more properties listed than are displayed by default.
Status : Running
DisplayName : Windows Time
CanPauseAndContinue : False
CanShutdown : True
CanStop : True
Methods
Methods are actions you can perform on an object. Use the MemberType parameter
to narrow down the results of Get-Member to display only the methods for Get-Service.
Chapter 3 - Discovering Objects, Properties, and Methods 46
TypeName: [Link]
You can use the Stop method to stop a Windows service. You must run this command
from an elevated PowerShell session.
Query the status of the Windows Time service to confirm it’s stopped.
You might use methods sparingly, but you should be aware of them. Sometimes,
you’ll find a Get-* command without a corresponding Set-* command. Often, you
can find a method to perform a Set-* action in this scenario. The Get-SqlAgentJob
cmdlet in the SqlServer PowerShell module is an excellent example. No correspond-
ing Set-* cmdlet exists, but you can use a method to complete the same task. For more
information about the SqlServer PowerShell module and installation instructions, see
the SQL Server PowerShell overview3 .
Another reason to be aware of methods is many PowerShell users assume you can’t
make destructive changes with Get-* commands, but they can actually cause severe
problems if misused.
A better option is to use a dedicated cmdlet if one exists to perform an action. For
example, use the Start-Service cmdlet to start the Windows Time service.
By default, Start-Service, like the Start method of Get-Service, doesn’t return any
results. But one of the benefits of using a cmdlet is that it often provides additional
capabilities that aren’t available with a method.
In the following example, use the PassThru parameter, which causes a cmdlet that
doesn’t typically produce output to generate output. You must run this command
from an elevated PowerShell session.
To retrieve information about the PowerShell process running on your lab environ-
ment computer, use the Get-Process cmdlet.
3 [Link]
Chapter 3 - Discovering Objects, Properties, and Methods 48
When using the Get-Process command, you may notice that some properties dis-
played by default are missing when you view the results of Get-Member. This is
because many of the values shown by default, such as NPM(K), PM(K), WS(K), and CPU(s),
are calculated properties. You must pipe commands to Get-Member to determine their
actual property names.
TypeName: [Link]
You can’t pipe a command to Get-Member that doesn’t generate output. Because
Start-Service doesn’t produce output by default, attempting to pipe it to Get-Member
results in an error. You must run this command from an elevated PowerShell session.
Chapter 3 - Discovering Objects, Properties, and Methods 51
To avoid this error, specify the PassThru parameter with Start-Service. Adding the
PassThru parameter causes a cmdlet that doesn’t usually produce output to generate
output. You must run this command from an elevated PowerShell session.
TypeName: [Link]
Out-Hostis designed to show output directly in the PowerShell host and doesn’t
produce object-based output. As a result, you can’t pipe its output to Get-Member,
which requires object-based input.
Get-Command
Knowing the type of object a command produces allows you to search for commands
that accept that type of object as input.
Get-Command -ParameterType ServiceController
Active Directory
As mentioned in the chapter prerequisites, ensure you have RSAT installed
for this section. Additionally, your lab environment computer must be a
member of your lab environment Active Directory domain.
To identify the commands added to the ActiveDirectory PowerShell module after you
install RSAT, use Get-Command combined with the Module parameter. The following
example lists all the commands available in the ActiveDirectory module.
Get-Command -Module ActiveDirectory
By default, the Get-ADUser cmdlet retrieves a limited set of properties for user objects
and limits its output to the first 1000 users. This constraint is a performance
optimization designed to avoid overwhelming Active Directory with excessive data
retrieval.
Even if you only have a basic understanding of Active Directory, you may recognize
that a user account has more properties than those shown in the example.
TypeName: [Link]
TypeName: [Link]
The default configuration for retrieving Active Directory user account properties is
Chapter 3 - Discovering Objects, Properties, and Methods 58
You can explore the available properties by piping the $Users variable to Get-Member.
When querying Active Directory, filter the data at the source using the Properties
parameter to return only the necessary properties.
Chapter 3 - Discovering Objects, Properties, and Methods 59
Summary
In this chapter, you’ve learned how to determine what type of object a command
produces, what properties and methods are available for a command, and how to
work with commands that limit the properties returned by default.
Review
1. What type of object does the Get-Process cmdlet produce?
2. How do you determine what the available properties are for a command?
3. If a command exists for getting something but not for setting the same thing,
what should you check for?
4. How can certain commands that don’t return output by default be made to
generate output?
5. What should you consider doing when prototyping a command producing a
large amount of output?
Chapter 3 - Discovering Objects, Properties, and Methods 60
References
• Get-Member4
• Viewing Object Structure (Get-Member)5
• about_Objects6
• about_Properties7
• about_Methods8
• No PowerShell Cmdlet to Start or Stop Something? Don’t Forget to Check for
Methods on the Get Cmdlets9
Next steps
In the next chapter, you’ll learn about one-liners and the pipeline.
4 [Link]
5 [Link]
6 [Link]
7 [Link]
8 [Link]
9 [Link]
for-methods-on-the-get-cmdlets/
Chapter 4 - One-Liners and the
pipeline
When I started learning PowerShell, I initially relied on the Graphical User Interface
(GUI) for tasks that seemed too complex for simple PowerShell commands. However,
as I continued to learn, I improved my skills and moved from basic one-liners to
creating scripts, functions, and modules. It’s important to remember that feeling
overwhelmed by advanced examples online is normal. No one starts as an expert in
PowerShell; we all start as beginners.
For those who primarily use the GUI for administrative tasks, install the management
tools on your administrative workstation to remotely manage your servers. Whether
your server uses a GUI or the Server Core OS installation, this approach is beneficial.
It’s a practical way to familiarize yourself with remote server management in
preparation for performing administrative tasks with PowerShell.
As with the previous chapters, try these concepts in your lab environment.
One-Liners
A PowerShell one-liner is one continuous pipeline. It’s a common misconception
that a command on one physical line is a PowerShell one-liner, but this is’nt always
true.
For instance, consider the following example: the command extends over multiple
physical lines, yet it’s a PowerShell one-liner because it forms a continuous pipeline.
Line-breaking a lengthy one-liner at the pipe symbol, a natural breaking point in
PowerShell, is recommended to enhance readability and clarity. This strategic use of
line breaks improves readability without disrupting the flow of the pipeline.
Chapter 4 - One-Liners and the pipeline 62
Get-Service |
Where-Object CanPauseAndContinue -eq $true |
Select-Object -Property *
Name : LanmanWorkstation
RequiredServices : {NSI, MRxSmb20, Bowser}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
DisplayName : Workstation
DependentServices : {SessionEnv, Netlogon}
MachineName : .
ServiceName : LanmanWorkstation
ServicesDependedOn : {NSI, MRxSmb20, Bowser}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Automatic
Site :
Container :
Name : Netlogon
RequiredServices : {LanmanWorkstation}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
DisplayName : Netlogon
DependentServices : {}
MachineName : .
ServiceName : Netlogon
ServicesDependedOn : {LanmanWorkstation}
ServiceHandle :
Status : Running
ServiceType : Win32ShareProcess
StartType : Automatic
Site :
Container :
Name : vmicheartbeat
Chapter 4 - One-Liners and the pipeline 63
RequiredServices : {}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
DisplayName : Hyper-V Heartbeat Service
DependentServices : {}
MachineName : .
ServiceName : vmicheartbeat
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Manual
Site :
Container :
Name : vmickvpexchange
RequiredServices : {}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
DisplayName : Hyper-V Data Exchange Service
DependentServices : {}
MachineName : .
ServiceName : vmickvpexchange
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Manual
Site :
Container :
Name : vmicrdv
RequiredServices : {}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
DisplayName : Hyper-V Remote Desktop Virtualization Service
DependentServices : {}
Chapter 4 - One-Liners and the pipeline 64
MachineName : .
ServiceName : vmicrdv
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Manual
Site :
Container :
Name : vmicshutdown
RequiredServices : {}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
DisplayName : Hyper-V Guest Shutdown Service
DependentServices : {}
MachineName : .
ServiceName : vmicshutdown
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Manual
Site :
Container :
Name : vmicvss
RequiredServices : {}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
DisplayName : Hyper-V Volume Shadow Copy Requestor
DependentServices : {}
MachineName : .
ServiceName : vmicvss
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
Chapter 4 - One-Liners and the pipeline 65
StartType : Manual
Site :
Container :
Name : webthreatdefsvc
RequiredServices : {RpcSs, wtd}
CanPauseAndContinue : True
CanShutdown : True
CanStop : True
DisplayName : Web Threat Defense Service
DependentServices : {}
MachineName : .
ServiceName : webthreatdefsvc
ServicesDependedOn : {RpcSs, wtd}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Manual
Site :
Container :
Name : webthreatdefusersvc_644de
RequiredServices : {}
CanPauseAndContinue : True
CanShutdown : True
CanStop : True
DisplayName : Web Threat Defense User Service_644de
DependentServices : {}
MachineName : .
ServiceName : webthreatdefusersvc_644de
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : 240
StartType : Automatic
Site :
Container :
Name : Winmgmt
RequiredServices : {RPCSS}
Chapter 4 - One-Liners and the pipeline 66
CanPauseAndContinue : True
CanShutdown : True
CanStop : True
DisplayName : Windows Management Instrumentation
DependentServices : {}
MachineName : .
ServiceName : Winmgmt
ServicesDependedOn : {RPCSS}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Automatic
Site :
Container :
Natural line breaks can occur at commonly used characters, including comma (,) and
opening brackets ([), braces ({), and parenthesis ((). Others that aren’t so common
include the semicolon (;), equals sign (=), and both opening single and double quotes
(',").
Using the backtick (`) or grave accent character as a line continuation is controversial.
It’s best to avoid it if possible. Using a backtick following a natural line break
character is a common mistake. This redundancy is unnecessary and can clutter
the code.
The commands in the following example execute correctly from the PowerShell
console. However, attempting to run them in the console pane of the PowerShell
Integrated Scripting Environment (ISE) results in an error. This difference occurs
because, unlike the PowerShell console, the console pane of the ISE doesn’t auto-
matically anticipate the continuation of a command onto the next line. To prevent
this issue, press Shift+Enter in the console pane of the ISE instead of Enter when you
need to extend a command across multiple lines. This key combination signals to the
ISE that the command is continuing on the following line, preventing the execution
that leads to errors.
Name : w32time
RequiredServices : {}
CanPauseAndContinue : False
CanShutdown : True
CanStop : True
DisplayName : Windows Time
DependentServices : {}
MachineName : .
ServiceName : w32time
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : Win32OwnProcess, Win32ShareProcess
StartType : Manual
Site :
Container :
This next example doesn’t qualify as a PowerShell one-liner because it’s not one
continuous pipeline. Instead, it’s two separate commands placed on a single line,
separated by a semicolon. This semicolon indicates the end of one command and the
beginning of another.
Many programming and scripting languages require a semicolon at the end of each
line. However, in PowerShell, semicolons at the end of lines are unnecessary and not
recommended. You should avoid them for cleaner and more readable code.
Filter Left
This chapter demonstrates how to filter the results of various commands. For
instance, the Get-Service command is used with the Name parameter to display only
the Windows Time service.
Chapter 4 - One-Liners and the pipeline 68
It’s a best practice in PowerShell to filter the results as early as possible in the pipeline.
Achieving this involves applying filters using parameters on the initial command,
usually at the beginning of the pipeline. This is commonly referred to as filtering
left.
To illustrate this concept, consider the following example: Use the Name parameter
of Get-Service to filter the results at the beginning of the pipeline, returning only
the details for the Windows Time service. This method demonstrates efficient data
retrieval, ensuring you only return the necessary and relevant information.
It’s common to see online examples of a PowerShell command being piped to the
Where-Object cmdlet to filter its results. This technique is inefficient if an earlier
command in the pipeline has a parameter to perform the filtering.
The first example demonstrates filtering directly at the source, returning results
specifically for the Windows Time service. In contrast, the second example retrieves
all services and then uses another command to filter the results. This might seem
insignificant in small-scale scenarios, but consider a situation involving a large
dataset, like Active Directory. It’s inefficient to retrieve details for thousands of
user accounts only to narrow them down to a small subset. Practice filtering left —
applying filters as early as possible in the command sequence — even in seemingly
trivial cases. This habit ensures efficiency in more complex scenarios where it
becomes crucial.
Chapter 4 - One-Liners and the pipeline 69
Get-Service |
Select-Object -Property DisplayName, Running, Status |
Where-Object CanPauseAndContinue
Reversing the order of Select-Object and Where-Object produces the desired results.
Get-Service |
Where-Object CanPauseAndContinue |
Select-Object -Property DisplayName, Status
DisplayName Status
----------- ------
Workstation Running
Netlogon Running
Hyper-V Heartbeat Service Running
Hyper-V Data Exchange Service Running
Hyper-V Remote Desktop Virtualization Service Running
Hyper-V Guest Shutdown Service Running
Hyper-V Volume Shadow Copy Requestor Running
Web Threat Defense Service Running
Web Threat Defense User Service_644de Running
Windows Management Instrumentation Running
Chapter 4 - One-Liners and the pipeline 70
The Pipeline
As you’ve seen in many examples throughout this book, you can often use the output
of one command as input for another command. In Chapter 3, Get-Member was used
to determine what type of object a command produces.
Chapter 3 also showed using the ParameterType parameter of Get-Command to
determine what commands accepted that type of input. Depending on how thorough
help for a command is, it may include an INPUTS and OUTPUTS section.
The INPUTS section indicates that you can pipe a ServiceController or a String
object to the Stop-Service cmdlet.
The following output has been abbreviated to show the relevant portion of the help.
...
INPUTS
[Link]
You can pipe a service object to this cmdlet.
[Link]
You can pipe a string that contains the name of a service to this
cmdlet.
OUTPUTS
None
By default, this cmdlet returns no output.
[Link]
When you use the PassThru parameter, this cmdlet returns a
ServiceController object representing the service.
...
However, it doesn’t specify which parameters accept that type of input. You can
determine that information by checking the different parameters in the full version
of the help for the Stop-Service cmdlet.
Chapter 4 - One-Liners and the pipeline 71
Once again, only the relevant help is shown in the following results. Notice that the
DisplayName parameter doesn’t accept pipeline input. The InputObject parameter
accepts pipeline input by value for ServiceController objects. The Name parameter
accepts pipeline input by value for String objects and pipeline input by property
name.
...
-DisplayName <[Link][]>
Specifies the display names of the services to stop. Wildcard
characters are permitted.
Required? true
Position? named
Default value None
Accept pipeline input? False
Accept wildcard characters? true
-InputObject <[Link][]>
Specifies ServiceController objects that represent the services to
stop. Enter a variable that contains the objects, or type a command
or expression that gets the objects.
Required? true
Position? 0
Default value None
Accept pipeline input? True (ByValue)
Accept wildcard characters? false
-Name <[Link][]>
Specifies the service names of the services to stop. Wildcard
characters are permitted.
Required? true
Position? 0
Default value None
Accept pipeline input? True (ByPropertyName, ByValue)
Chapter 4 - One-Liners and the pipeline 72
When handling pipeline input, a parameter that accepts pipeline input both by
property name and by value prioritizes by value binding first. If this method fails,
it attempts to process pipeline input by property name. However, the term by value
can be misleading. A more accurate description is by type.
For instance, if you pipe the output of a command that generates a ServiceController
object to Stop-Service, this output is bound to the InputObject parameter. If
the piped command produces a String object, it associates the output with the
Name parameter. If you pipe output from a command that doesn’t produce a
ServiceController or String object, but does include a property named Name,
Stop-Service binds the value of the Name property to its Name parameter.
TypeName: [Link]
Now try string input. Pipe w32time to Get-Member to confirm that it’s a string.
'w32time' | Get-Member
Chapter 4 - One-Liners and the pipeline 73
TypeName: [Link]
The PowerShell help documentation illustrates that when you pipe a string to
Stop-Service, it binds to the Name parameter by value. Conduct a practical test to
see this in action: pipe the string w32time to Stop-Service. This example demonstrates
how Stop-Service processes the string w32time as the name of the service to stop.
Execute the following command to observe this binding and command execution in
action.
Notice that w32time is enclosed in single quotes. In PowerShell, it’s a best practice
to use single quotes for static strings, reserving double quotes for situations where
the string contains variables that require expansion. Single quotes tell PowerShell
to treat the content literally without parsing for variables. This approach not
only ensures accuracy in how your script interprets the string but also enhances
performance, as PowerShell expends less processing effort on strings within single
quotes.
'w32time' | Stop-Service
Create a custom object to test pipeline input by property name for the Name
parameter of Stop-Service.
$customObject = [pscustomobject]@{
Name = 'w32time'
}
$customObject | Get-Member
Chapter 4 - One-Liners and the pipeline 74
TypeName: [Link]
$customObject = [pscustomobject]@{
Service = 'w32time'
}
An error occurs while trying to stop the w32time service by piping $customObject to
Stop-Service. The pipeline binding fails because $customObject doesn’t produce a
ServiceController or String object and doesn’t contain a Name property.
$customObject | Stop-Service
Chapter 4 - One-Liners and the pipeline 75
When the output property names of one command don’t match the pipeline input
requirements of another command, you can use Select-Object to rename the property
names so they line up correctly.
In the following example, use Select-Object to rename the Service property to a
property named Name.
At first glance, the syntax of this example might appear complex. However, it’s
essential to understand that more than copying and pasting code is required to learn
the syntax. Instead, take the time to type out the code manually. This hands-on
practice helps you remember the syntax, and it becomes more intuitive with repeated
effort. Utilizing multiple monitors or split screen can also aid in the learning process.
Display the example code on one screen while actively typing and experimenting
with it on another. This setup makes it easier to follow along and enhances your
understanding and retention of the syntax.
$customObject |
Select-Object -Property @{name='Name';expression={$_.Service}} |
Stop-Service
There are instances where you might need to use a parameter that doesn’t accept
pipeline input. In such cases, you can still use the output of one command as the input
for another. First, capture and save the display names of a few specific Windows
services into a text file. This step allows you to use the saved data as input for
another command.
Chapter 4 - One-Liners and the pipeline 76
You can use parentheses to pass the output of one command as input for a parameter
to another command.
This concept is like the order of operations in Algebra. Just as mathematical opera-
tions within parentheses are computed first, the command enclosed in parentheses
is executed before the outer command.
PowerShellGet
PowerShellGet, a module included with PowerShell version 5.0 and above, provides
commands to discover, install, publish, and update PowerShell modules and other
items in a NuGet repository. For those using PowerShell version 3.0 and above,
PowerShellGet is also available as a separate download.
The PowerShell Gallery1 is an online repository hosted by Microsoft, designed as
a central hub for sharing PowerShell modules, scripts, and other resources. While
Microsoft hosts the PowerShell Gallery, the PowerShell community contributes most
of the available modules and scripts. Given the source of these modules and scripts,
exercise caution before integrating any code from the PowerShell Gallery into your
environment. Review and test downloads from the PowerShell Gallery in an isolated
test environment. This process ensures the code is secure and reliable, functions as
expected, and safeguards your environment from potential issues or vulnerabilities
arising from unvetted code.
Many organizations opt to establish their own internal, private NuGet repository.
This repository serves a dual purpose. First, it acts as a secure location for storing
modules developed in-house, intended solely for internal use. Secondly, it provides
a vetted collection of modules sourced externally, including those from public
repositories. Companies typically undertake a thorough validation process before
adding these external modules to the internal repository. This process is crucial to
1 [Link]
Chapter 4 - One-Liners and the pipeline 77
ensure the modules are free from malicious content and align with the security and
operational standards of the company.
Use the Find-Module cmdlet that’s part of the PowerShellGet module to find a module
in the PowerShell Gallery that I wrote named MrToolkit.
The first time you use one of the commands from the PowerShellGet module, you’ll
be prompted to install the NuGet provider.
To install the MrToolkit module, pipe the previous command to Install-Module.
Untrusted repository
You are installing the modules from an untrusted repository. If you trust
this repository, change its InstallationPolicy value by running the
Set-PSRepository cmdlet. Are you sure you want to install the modules from
'[Link]
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "N"):y
This capability dramatically simplifies the process of understanding and utilizing the
pipeline capabilities of PowerShell commands.
The information previously obtained by analyzing the help documentation can be
determined using this function.
ParameterName : InputObject
ParameterType : [Link][]
ValueFromPipeline : True
ValueFromPipelineByPropertyName : False
ParameterName : Name
ParameterType : [Link][]
ValueFromPipeline : True
ValueFromPipelineByPropertyName : True
Summary
In this chapter, you’ve learned about the intricacies of PowerShell one-liners.
You’ve also learned that the physical line count of a command is irrelevant to
its classification as a PowerShell one-liner. Additionally, you learned about key
concepts such as filtering left, the pipeline, and PowerShellGet.
Review
1. What is a PowerShell one-liner?
2. What are some of the characters where natural line breaks can occur in
PowerShell?
3. Why should you filter left?
4. What are the two ways that a PowerShell command can accept pipeline input?
5. Why shouldn’t you trust commands found in the PowerShell Gallery?
References
• about_Pipelines2
• about_Command_Syntax3
2 [Link]
3 [Link]
Chapter 4 - One-Liners and the pipeline 80
• about_Parameters4
• PowerShellGet: The BIG EASY way to discover, install, and update PowerShell
modules5
Next steps
In the next chapter, you’ll learn about formatting, aliases, providers, and comparison
operators.
4 [Link]
5 [Link]
powershell-modules/
Chapter 5 - Formatting, aliases,
providers, comparison
Prerequisites
The SQL Server PowerShell module is required by some of the examples shown in
this chapter. For more information about the SqlServer PowerShell module and
installation instructions, see the SQL Server PowerShell overview1 . It’s also used
in subsequent chapters. Download and install it on your Windows lab environment
computer.
Format Right
In Chapter 4, you learned to filter as far to the left as possible. The rule for manually
formatting a command’s output is similar to that rule except it needs to occur as far
to the right as possible.
The most common format commands are Format-Table and Format-List. Format-Wide
and Format-Custom can also be used, but are less common.
As mentioned in Chapter 3, a command that returns more than four properties
defaults to a list unless custom formatting is used.
1 [Link]
Chapter 5 - Formatting, aliases, providers, comparison 82
Status : Running
DisplayName : Windows Time
CanPauseAndContinue : False
CanShutdown : True
CanStop : True
Use the Format-Table cmdlet to manually override the formatting and show the output
in a table instead of a list.
Use the Format-List cmdlet to override the default formatting and return the results
in a list.
Notice that simply piping Get-Service to Format-List made it return additional prop-
erties. This doesn’t occur with every command because of the way the formatting
for that particular command is set up behind the scenes.
Chapter 5 - Formatting, aliases, providers, comparison 83
Name : w32time
DisplayName : Windows Time
Status : Running
DependentServices : {}
ServicesDependedOn : {}
CanPauseAndContinue : False
CanShutdown : True
CanStop : True
ServiceType : Win32OwnProcess, Win32ShareProcess
The number one thing to be aware of with the format cmdlets is they produce format
objects that are different than normal objects in PowerShell.
TypeName: [Link]
TypeName: [Link]
TypeName: [Link]
TypeName: [Link]
TypeName: [Link]
What this means is format commands can’t be piped to most other commands. They
can be piped to some of the Out-* commands, but that’s about it. This is why you
want to perform any formatting at the very end of the line (format right).
Aliases
An alias in PowerShell is a shorter name for a command. PowerShell includes a set
of built-in aliases and you can also define your own aliases.
The Get-Alias cmdlet is used to find aliases. If you already know the alias for a
command, the Name parameter is used to determine what command the alias is
associated with.
Multiple aliases can be specified for the value of the Name parameter.
You’ll often see the Name parameter omitted since it’s a positional parameter.
Chapter 5 - Formatting, aliases, providers, comparison 86
Get-Alias gm
If you want to find aliases for a command, you’ll need to use the Definition
parameter.
Providers
A provider in PowerShell is an interface that allows file system like access to a
datastore. There are a number of built-in providers in PowerShell.
Get-PSProvider
As you can see in the following results, there are built-in providers for the registry,
aliases, environment variables, the file system, functions, variables, certificates, and
WSMan.
Chapter 5 - Formatting, aliases, providers, comparison 87
The actual drives that these providers use to expose their datastore can be determined
with the Get-PSDrive cmdlet. The Get-PSDrive cmdlet not only displays drives exposed
by providers, but it also displays Windows logical drives including drives mapped to
network shares.
Get-PSDrive
Third-party modules such as the Active Directory PowerShell module and the
SQLServer PowerShell module both add their own PowerShell provider and PSDrive.
Import the Active Directory and SQL Server PowerShell modules.
Get-PSProvider
Notice that in the following set of results, two new PowerShell providers now exist,
one for Active Directory and another one for SQL Server.
Get-PSDrive
PSParentPath: [Link]\Certificate::LocalMachine\CA
Thumbprint Subject
---------- -------
FEE449EE0E3965A5246F000E87FDE2A065FD89D4 CN=Root Agency
D559A586669B08F46A30A133F8A9ED3D038E2EA8 OU=[Link]/CPS Incorp....
109F1CAED645BB78B3EA2B94C0697C740733031C CN=Microsoft Windows Hardware C...
Comparison Operators
PowerShell contains a number of comparison operators that are used to compare
values or find values that match certain patterns. The following table contains a list
of comparison operators in PowerShell.
All of the operators listed below are case-insensitive. Place a c in front of the operator
to make it case-sensitive. For example, -ceq is the case-sensitive version of the -eq
comparison operator.
Operator Definition
-eq Equal to
-ne Not equal to
-gt Greater than
-ge Greater than or equal to
-lt Less than
-le Less than or equal to
-Like Match using the * wildcard character
-NotLike Does not match using the * wildcard character
-Match Matches the specified regular expression
-NotMatch Does not match the specified regular expression
-Contains Determines if a collection contains a specified value
Chapter 5 - Formatting, aliases, providers, comparison 90
Operator Definition
-NotContains Determines if a collection does not contain a specific
value
-In Determines if a specified value is in a collection
-NotIn Determines if a specified value is not in a collection
-Replace Replaces the specified value
Proper case “PowerShell” is equal to lower case “powershell” using the equals
comparison operator.
True
It’s not equal using the case-sensitive version of the equals comparison operator.
False
False
Greater than, greater than or equal to, less than, and less than or equal all work with
string or numeric values.
5 -gt 5
Chapter 5 - Formatting, aliases, providers, comparison 91
False
Using greater than or equal to instead of greater than with the previous example
returns the Boolean true since five is equal to five.
5 -ge 5
True
Based on the results from the previous two examples, you can probably guess how
both less than and less than or equal to work.
5 -lt 10
True
The -Like and -Match operators can be confusing, even for experienced PowerShell
users. -Like is used with wildcard the characters * and ? to perform “like” matches.
True
True
$Numbers = 1..10
$Numbers -contains 15
False
$Numbers -contains 10
True
-NotContains reverses the logic to see if the $Numbers variable doesn’t contain a value.
$Numbers -notcontains 15
True
The previous example returns the Boolean true because it’s true that the $Numbers
variable doesn’t contain 15. It does however contain the number 10 so it’s false
when it’s tested.
$Numbers -notcontains 10
False
The -in comparison operator was first introduced in PowerShell version 3.0. It’s
used to determine if a value is in an array. The $Numbers variable is an array since it
contains multiple values.
Chapter 5 - Formatting, aliases, providers, comparison 93
15 -in $Numbers
False
In other words, -in performs the same test as the contains comparison operator except
from the opposite direction.
10 -in $Numbers
True
15 -in $Numbers
False
Just like the -contains operator, not reverses the logic for the -in operator.
10 -notin $Numbers
False
The previous example returns false because the $Numbers array does include 10 and
the condition was testing to determine if it didn’t contain 10.
15 is “not in” the $Numbers array so it returns the Boolean true.
15 -notin $Numbers
Chapter 5 - Formatting, aliases, providers, comparison 94
True
The -replace operator does just want you would think. It’s used to replace something.
Specifying one value replaces that value with nothing. In the following example, I
replace “Shell” with nothing.
Power
If you want to replace a value with a different value, specify the new value after the
pattern you want to replace. SQL Saturday in Baton Rouge is an event that I try to
speak at every year. In the following example, I replace the word “Saturday” with
the abbreviation “Sat”.
There are also methods like Replace() that can be used to replace things similar to the
way the replace operator works. However, the -Replace operator is case-insensitive
by default, and the Replace() method is case-sensitive.
Notice that the word “Saturday” wasn’t replaced in the previous example. This
is because it was specified in a different case than the original. When the word
“Saturday” is specified in the same case as the original, the Replace() method does
replace it as expected.
Chapter 5 - Formatting, aliases, providers, comparison 95
Be careful when using methods to transform data because you can run into un-
foreseen problems, such as failing the Turkey Test. For an example, see the blog
article titled Using Pester to Test PowerShell Code with Other Cultures2 . My
recommendation is to use an operator instead of a method whenever possible to
avoid these types of problems.
While the comparison operators can be used as shown in the previous examples, I
normally find myself using them with the Where-Object cmdlet to perform some type
of filtering.
Summary
In this chapter, you’ve learned a number of different topics to include Formatting
Right, Aliases, Providers, and Comparison Operators.
Review
1. Why is it necessary to perform Formatting as far to the right as possible?
2. How do you determine what the actual cmdlet is for the % alias?
3. Why shouldn’t you use aliases in scripts you save or code you share with others?
4. Perform a directory listing on the drives that are associated with one of the
registry providers.
5. What’s one of the main benefits of using the replace operator instead of the
replace method?
2 [Link]
Chapter 5 - Formatting, aliases, providers, comparison 96
References
• format-table3
• format-list4
• format-wide5
• about_Aliases6
• about_Providers7
• about_Comparison_Operators8
• about_Arrays9
Next steps
In the next chapter, you’ll learn about flow control, scripting, loops, and conditional
logic.
3 [Link]
4 [Link]
5 [Link]
6 [Link]
7 [Link]
8 [Link]
9 [Link]
Chapter 6 - Flow control
Scripting
When you move from writing PowerShell one-liners to writing scripts, it sounds a lot
more complicated than it really is. A script is nothing more than the same or similar
commands that you would run interactively in the PowerShell console, except they’re
saved as a .PS1 file. There are some scripting constructs that you may use such as a
foreach loop instead of the ForEach-Object cmdlet. To beginners, the differences can
be confusing especially when you consider that foreach is both a scripting construct
and an alias for the ForEach-Object cmdlet.
Looping
One of the great things about PowerShell is, once you figure out how to do something
for one item, it’s almost as easy to do the same task for hundreds of items. Simply
loop through the items using one of the many different types of loops in PowerShell.
ForEach-Object
ForEach-Objectis a cmdlet for iterating through items in a pipeline such as with
PowerShell one-liners. ForEach-Object streams the objects through the pipeline.
Although the Module parameter of Get-Command accepts multiple values that are
strings, it only accepts them via pipeline input by property name or via parameter
input. In the following scenario, if I want to pipe two strings by value to Get-Command
for use with the Module parameter, I would need to use the ForEach-Object cmdlet.
Chapter 6 - Flow control 98
'ActiveDirectory', 'SQLServer' |
ForEach-Object {Get-Command -Module $_} |
Group-Object -Property ModuleName -NoElement |
Sort-Object -Property Count -Descending
Count Name
----- ----
147 ActiveDirectory
82 SqlServer
In the previous example, $_ is the current object. Beginning with PowerShell version
3.0, $PSItem can be used instead of $_. But I find that most experienced PowerShell
users still prefer using $_ since it’s backward compatible and less to type.
When using the foreach keyword, you must store all of the items in memory before
iterating through them, which could be difficult if you don’t know how many items
you’re working with.
DistinguishedName : CN=WEB01,CN=Computers,DC=mikefrobbins,DC=com
DNSHostName : [Link]
Enabled : True
Name : WEB01
Chapter 6 - Flow control 99
ObjectClass : computer
ObjectGUID : 33aa530e-1e31-40d8-8c78-76a18b673c33
SamAccountName : WEB01$
SID : S-1-5-21-2989741381-570885089-3319121794-1107
UserPrincipalName :
Other times, you can get the same results while eliminating the loop altogether.
Consult the cmdlet help to understand your options.
DistinguishedName : CN=WEB01,CN=Computers,DC=mikefrobbins,DC=com
DNSHostName : [Link]
Enabled : True
Name : WEB01
ObjectClass : computer
ObjectGUID : 33aa530e-1e31-40d8-8c78-76a18b673c33
SamAccountName : WEB01$
SID : S-1-5-21-2989741381-570885089-3319121794-1107
UserPrincipalName :
As you can see in the previous examples, the Identity parameter for Get-ADComputer
only accepts a single value when provided via parameter input, but it allows for
multiple items when the input is provided via pipeline input.
For
A for loop iterates while a specified condition is true. The for loop is not something
that I use often, but it does have its uses.
In the previous example, the loop will iterate four times by starting off with the
number one and continue as long as the counter variable $i is less than 5. It will
sleep for a total of 10 seconds.
Do
There are two different do loops in PowerShell. Do Until runs while the specified
condition is false.
The previous example is a numbers game that continues until the value you guess
equals the same number that the Get-Random cmdlet generated.
Do While is just the opposite. It runs as long as the specified condition evaluates to
true.
Chapter 6 - Flow control 102
The same results are achieved with a Do While loop by reversing the test condition to
not equals.
Do loops always run at least once because the condition is evaluated at the end of the
loop.
While
Similar to the Do While loop, a While loop runs as long as the specified condition is
true. The difference however, is that a While loop evaluates the condition at the top
of the loop before any code is run. So it doesn’t run if the condition evaluates to
false.
Chapter 6 - Flow control 103
The previous example calculates what day Thanksgiving Day is on in the United
States. It’s always on the fourth Thursday of November. So the loop starts with
the 22nd day of November and adds a day while the day of the week isn’t equal to
Thursday. If the 22nd is a Thursday, the loop doesn’t run at all.
The break statement shown in the previous example causes the loop to exit on the
first iteration.
Continue is designed to skip to the next iteration of a loop.
Chapter 6 - Flow control 104
1
2
4
5
The previous example will output the numbers 1, 2, 4, and 5. It skips number 3 and
continues with the next iteration of the loop. Similar to break, continue breaks out
of the loop except only for the current iteration. Execution continues with the next
iteration instead of breaking out of the loop and stopping.
Return is designed to exit out of the existing scope.
$number = 1..10
foreach ($n in $number) {
if ($n -ge 4) {
Return $n
}
}
Notice that in the previous example, return outputs the first result and then exits out
of the loop. A more thorough explanation of the result statement can be found in
one of my blog articles: “The PowerShell return keyword”1 .
Summary
In this chapter, you’ve learned about the different types of loops that exist in
PowerShell.
1 [Link]
Chapter 6 - Flow control 105
Review
1. What is the difference in the ForEach-Object cmdlet and the foreach scripting
construct?
2. What is the primary advantage of using a While loop instead of a Do While or
Do Until loop.
3. How do the break and continue statements differ?
References
• ForEach-Object2
• about_ForEach3
• about_For4
• about_Do5
• about_While6
• about_Break7
• about_Continue8
• about_Return9
2 [Link]
3 [Link]
4 [Link]
5 [Link]
6 [Link]
7 [Link]
8 [Link]
9 [Link]
Chapter 7 - Working with WMI
WMI and CIM
Windows PowerShell ships by default with cmdlets for working with other tech-
nologies such as Windows Management Instrumentation (WMI). The WMI cmdlets
are deprecated and are not available in PowerShell 6+, but are covered here as you
may encounter them in older scripts running on Windows PowerShell. For new
development, use the CIM cmdlets instead.
There are several native WMI cmdlets that exist in PowerShell without having to
install any additional software or modules. Get-Command can be used to determine
what WMI cmdlets exist in Windows PowerShell. The following results are from
my Windows 11 lab environment computer that is running PowerShell version 5.1.
Your results may differ depending on what PowerShell version you’re running.
The CIM cmdlets still allow you to work with WMI so don’t be confused when
someone makes the statement “When I query WMI with the PowerShell CIM
cmdlets…”
As I previously mentioned, WMI is a separate technology from PowerShell and
you’re just using the CIM cmdlets for accessing WMI. You may find an old VBScript
that uses WMI Query Language (WQL) to query WMI such as in the following
example.
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
You can take the WQL query from that VBScript and use it with the Get-CimInstance
cmdlet without any modifications.
SMBIOSBIOSVersion : 090006
Manufacturer : American Megatrends Inc.
Name : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber : 3810-1995-1654-4615-2295-2755-89
Version : VRTUAL - 4001628
That’s not how I typically query WMI with PowerShell. But it does work and allows
you to easily migrate existing VBScripts to PowerShell. When I start out writing a
one-liner to query WMI, I use the following syntax.
SMBIOSBIOSVersion : 090006
Manufacturer : American Megatrends Inc.
Name : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber : 3810-1995-1654-4615-2295-2755-89
Version : VRTUAL - 4001628
If I only want the serial number, I can pipe the output to Select-Object and specify
only the SerialNumber property.
SerialNumber
------------
3810-1995-1654-4615-2295-2755-89
By default, there are several properties that are retrieved behind the scenes that are
never used. It may not matter much when querying WMI on the local computer. But
once you start querying remote computers, it’s not only additional processing time
to return that information, but also additional unnecessary information to have to
pull across the network. Get-CimInstance has a Property parameter that limits the
information that’s retrieved. This makes the query to WMI more efficient.
SerialNumber
------------
3810-1995-1654-4615-2295-2755-89
The previous results returned an object. To return a simple string, use the Expand-
Property parameter.
3810-1995-1654-4615-2295-2755-89
You could also use the dotted style of syntax to return a simple string. This eliminates
the need to pipe to Select-Object.
3810-1995-1654-4615-2295-2755-89
Chapter 7 - Working with WMI 110
Many people have security concerns when it comes to PowerShell, but the truth
is you have exactly the same permissions in PowerShell as you do in the GUI. No
more and no less. The problem in the previous example is that the user running
PowerShell doesn’t have rights to query WMI information from the DC01 server. I
could relaunch PowerShell as a domain administrator since Get-CimInstance doesn’t
have a Credential parameter. But, trust me, that isn’t a good idea because then
anything that I run from PowerShell would be running as a domain admin. That
could be dangerous from a security standpoint depending on the situation.
Using the principle of least privilege, I elevate to my domain admin account on
a per command basis using the Credential parameter, if a command has one.
Get-CimInstance doesn’t have a Credential parameter so the solution in this scenario
is to create a CimSession first. Then I use the CimSession instead of a computer
name to query WMI on the remote computer.
Chapter 7 - Working with WMI 111
The CIM session was stored in a variable named $CimSession. Notice that I also
specified the Get-Credential cmdlet in parentheses so that it executes first, prompting
me for alternate credentials, before creating the new session. I’ll show you another
more efficient way to specify alternate credentials later in this chapter, but it’s
important to understand this basic concept before making it more complicated.
The CIM session created in the previous example can now be used with the
Get-CimInstance cmdlet to query the BIOS information from WMI on the remote
computer.
SMBIOSBIOSVersion : 090006
Manufacturer : American Megatrends Inc.
Name : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber : 0986-6980-3916-0512-6608-8243-13
Version : VRTUAL - 4001628
PSComputerName : dc01
There are several additional benefits to using CIM sessions instead of just specifying
a computer name. When running multiple queries to the same computer, using a CIM
session is more efficient than using the computer name for each query. Creating a
CIM session only sets up the connection once. Then, multiple queries use that same
session to retrieve information. Using the computer name requires the cmdlets to set
up and tear down the connection with each individual query.
The Get-CimInstance cmdlet uses the WSMan protocol by default, which means the
remote computer needs PowerShell version 3.0 or higher to connect. It’s actually not
the PowerShell version that matters, it’s the stack version. The stack version can be
determined using the Test-WSMan cmdlet. It needs to be version 3.0. That’s the version
you’ll find with PowerShell version 3.0 and higher.
Chapter 7 - Working with WMI 112
wsmid : [Link]
[Link]
ProtocolVersion : [Link]
ProductVendor : Microsoft Corporation
ProductVersion : OS: 0.0.0 SP: 0.0 Stack: 3.0
The older WMI cmdlets use the DCOM protocol, which is compatible with older
versions of Windows. But DCOM is typically blocked by the firewall on newer
versions of Windows. The New-CimSessionOption cmdlet allows you to create a DCOM
protocol connection for use with New-CimSession. This allows the Get-CimInstance
cmdlet to be used to communicate with versions of Windows as old as Windows
Server 2000. This also means that PowerShell is not required on the remote computer
when using the Get-CimInstance cmdlet with a CimSession that’s configured to use
the DCOM protocol.
Create the DCOM protocol option using the New-CimSessionOption cmdlet and store
it in a variable.
For efficiency, you can store your domain administrator or elevated credentials in a
variable so you don’t have to constantly enter them for each command.
$Cred = Get-Credential
I have a server named SQL03 that runs Windows Server 2008 (non-R2). It’s the
newest Windows Server operating system that doesn’t have PowerShell installed by
default.
Create a CimSession to SQL03 using the DCOM protocol.
Chapter 7 - Working with WMI 113
Notice in the previous command, this time I specified the variable named $Cred as
the value for the Credential parameter instead of having to enter them manually
again.
The output of the query is the same regardless of the underlying protocol being used.
SMBIOSBIOSVersion : 090006
Manufacturer : American Megatrends Inc.
Name : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber : 7237-7483-8873-8926-7271-5004-86
Version : VRTUAL - 4001628
PSComputerName : sql03
The Get-CimSession cmdlet is used to see what CimSessions are currently connected
and what protocols they’re using.
Get-CimSession
Id : 1
Name : CimSession1
InstanceId : 80742787-e38e-41b1-a7d7-fa1369cf1402
ComputerName : dc01
Protocol : WSMAN
Id : 2
Name : CimSession2
InstanceId : 8fcabd81-43cf-4682-bd53-ccce1e24aecb
ComputerName : sql03
Protocol : DCOM
Retrieve and store both of the previously created CimSessions in a variable named
$CimSession.
Chapter 7 - Working with WMI 114
$CimSession = Get-CimSession
Query both of the computers with one command, one using the WSMan protocol
and the other one with DCOM.
SMBIOSBIOSVersion : 090006
Manufacturer : American Megatrends Inc.
Name : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber : 0986-6980-3916-0512-6608-8243-13
Version : VRTUAL - 4001628
PSComputerName : dc01
SMBIOSBIOSVersion : 090006
Manufacturer : American Megatrends Inc.
Name : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber : 7237-7483-8873-8926-7271-5004-86
Version : VRTUAL - 4001628
PSComputerName : sql03
I’ve written numerous blog articles about the WMI and CIM cmdlets. One of the most
useful ones is about a function that I created to automatically determine if WSMan
or DCOM should be used and set up the CIM session automatically without having
to figure out which one manually. That blog article is titled PowerShell Function to
Create CimSessions to Remote Computers with Fallback to Dcom1 .
When you’re finished with the CIM sessions, you should remove them with the
Remove-CimSession cmdlet. To remove all CIM sessions, simply pipe Get-CimSession
to Remove-CimSession.
Get-CimSession | Remove-CimSession
1 [Link]
fallback-to-dcom/
Chapter 7 - Working with WMI 115
Summary
In this chapter, you’ve learned about using PowerShell to work with WMI on both
local and remote computers. You’ve also learned how to use the CIM cmdlets to
work with remote computers with both the WSMan or DCOM protocol.
Review
1. What is the difference in the WMI and CIM cmdlets?
2. By default, what protocol does the Get-CimInstance cmdlet use?
3. What are some of the benefits of using a CIM session instead of specifying a
computer name with Get-CimInstance?
4. How do you specify an alternate protocol other than the default one for use
with Get-CimInstance?
5. How do you close or remove CIM sessions?
References
• about_WMI2
• about_WMI_Cmdlets3
• about_WQL4
• CimCmdlets Module5
• Video: Using CIM Cmdlets and CIM Sessions6
2 [Link]
3 [Link]
4 [Link]
5 [Link]
6 [Link]
second-hop-problem-with-cimsessions/
Chapter 8 - PowerShell
Remoting
PowerShell has many different ways to run commands against remote computers.
In the last chapter, you saw how to remotely query WMI using the CIM cmdlets.
PowerShell also includes several cmdlets that have a built-in ComputerName
parameter.
As shown in the following example, Get-Command can be used with the Parameter-
Name parameter to determine what commands have a ComputerName parameter.
Enable-PSRemoting
Chapter 8 - PowerShell Remoting 118
One-To-One Remoting
If you want your remote session to be interactive, then one-to-one remoting is what
you want. This type of remoting is provided via the Enter-PSSession cmdlet.
In the last chapter, I stored my domain admin credentials in a variable named $Cred.
If you haven’t already done so, go ahead and store your domain admin credentials
in the $Cred variable.
This allows you to enter the credentials once and use them on a per command basis
as long as your current PowerShell session is active.
$Cred = Get-Credential
[dc01]: PS C:\Users\Administrator\Documents>
Notice that in the previous example that the PowerShell prompt is preceded by [dc01].
This means you’re in an interactive PowerShell session to the remote computer
named dc01. Any commands you execute run on dc01, not on your local computer.
Also, keep in mind that you only have access to the PowerShell commands that exist
on the remote computer and not the ones on your local computer. In other words, if
you’ve installed additional modules on your computer, they aren’t accessible on the
remote computer.
Chapter 8 - PowerShell Remoting 119
TypeName: [Link]
When you’re done working with the remote computer, exit the one-to-one remoting
session by using the Exit-PSSession cmdlet.
[dc01]: Exit-PSSession
One-To-Many Remoting
Sometimes you may need to perform a task interactively on a remote computer.
But remoting is much more powerful when performing a task on multiple remote
computers at the same time. Use the Invoke-Command cmdlet to run a command against
one or more remote computers at the same time.
Chapter 8 - PowerShell Remoting 122
In the previous example, three servers were queried for the status of the Win-
dows Time service. The Get-Service cmdlet was placed inside the script block of
Invoke-Command. Get-Service actually runs on the remote computer and the results are
returned to your local computer as deserialized objects.
Piping the previous command to Get-Member shows that the results are indeed
deserialized objects.
TypeName: [Link]
Notice that the majority of the methods are missing on deserialized objects. This
means they’re not live objects; they’re inert. You can’t start or stop a service using a
deserialized object because it’s a snapshot of the state of that object the point when
the command ran on the remote computer.
That doesn’t mean you can’t start or stop a service using a method with Invoke-Command
though. It just means that the method has to be called in the remote session.
I’ll stop the Windows Time service on all three of those remote servers using the
Stop() method to prove this point.
recommend using the Stop-Service cmdlet instead of the stop method. I chose to use
the Stop() method to prove a point since many people are under the misconception
that methods can’t be called when using PowerShell remoting. They can’t be called
on the object that’s returned because it’s deserialized, but they can be called in the
remote session itself.
PowerShell Sessions
In the last example in the previous section, I ran two commands using the Invoke-Command
cmdlet. That means two separate sessions had to be set up and torn down to run those
two commands.
Similar to the CIM sessions discussed in Chapter 7, a PowerShell session to a remote
computer can be used to run multiple commands against the remote computer
without the overhead of a new session for each individual command.
Create a PowerShell session to each of the three computers we’ve been working with
in this chapter, DC01, SQL02, and WEB01.
Now use the variable named $Session to start the Windows Time service using a
method and check the status of the service.
Once the session is created using alternate credentials, it’s no longer necessary to
specify the credentials each time a command is run.
When you’re finished using the sessions, be sure to remove them.
Chapter 8 - PowerShell Remoting 125
Get-PSSession | Remove-PSSession
Summary
In this chapter you’ve learned about PowerShell remoting, how to run commands in
an interactive session with one remote computer, and how to run commands against
multiple computers using one-to-many remoting. You’ve also learned the benefits
of using a PowerShell session when running multiple commands against the same
remote computer.
Review
1. How do you enable PowerShell remoting?
2. What is the PowerShell command for starting an interactive session with a
remote computer?
3. What is a benefit of using a PowerShell remoting session versus just specifying
the computer name with each command?
4. Can a PowerShell remoting session be used with a one-to-one remoting session?
5. What is the difference in the type of objects that are returned by cmdlets versus
those returned when running those same cmdlets against remote computers
with Invoke-Command?
References
• about_Remote1
• about_Remote_Output2
• about_Remote_Requirements3
• about_Remote_Troubleshooting4
• about_Remote_Variables5
• PowerShell Remoting FAQ6
1 [Link]
2 [Link]
3 [Link]
4 [Link]
5 [Link]
6 [Link]
Chapter 9 - Functions
If you’re writing PowerShell one-liners or scripts and find yourself often having to
modify them for different scenarios, there’s a good chance that it’s a good candidate
to be turned into a function that can be reused.
Whenever possible, I prefer to write functions because they are more tool oriented.
I can put the functions in a script module, put that module in the $env:PSModulePath,
and call the functions without needing to physically locate where they’re saved.
Using the PowerShellGet module, it’s easy to share those modules in a NuGet
repository. PowerShellGet ships with PowerShell version 5.0 and higher. It is
available as a separate download for PowerShell version 3.0 and higher.
Don’t over complicate things. Keep it simple and use the most straight forward way
to accomplish a task. Avoid aliases and positional parameters in any code that you
reuse. Format your code for readability. Don’t hardcode values; use parameters
and variables. Don’t write unnecessary code even if it doesn’t hurt anything. It
adds unnecessary complexity. Attention to detail goes a long way when writing any
PowerShell code.
Naming
When naming your functions in PowerShell, use a Pascal case1 name with an
approved verb and a singular noun. I also recommend prefixing the noun. For
example: <ApprovedVerb>-<Prefix><SingularNoun>.
In PowerShell, there’s a specific list of approved verbs that can be obtained by
running Get-Verb.
1 [Link]
Chapter 9 - Functions 127
Verb Group
---- -----
Add Common
Approve Lifecycle
Assert Lifecycle
Backup Data
Block Security
Checkpoint Data
Clear Common
Close Common
Compare Data
Complete Lifecycle
Compress Data
Confirm Lifecycle
Connect Communications
Convert Data
ConvertFrom Data
ConvertTo Data
Copy Common
Debug Diagnostic
Deny Lifecycle
Disable Lifecycle
Disconnect Communications
Dismount Data
Edit Data
Enable Lifecycle
Enter Common
Exit Common
Expand Data
Export Data
Find Common
Format Common
Get Common
Grant Security
Group Data
Hide Common
Import Data
Initialize Data
Install Lifecycle
Invoke Lifecycle
Chapter 9 - Functions 128
Join Common
Limit Data
Lock Common
Measure Diagnostic
Merge Data
Mount Data
Move Common
New Common
Open Common
Optimize Common
Out Data
Ping Diagnostic
Pop Common
Protect Security
Publish Data
Push Common
Read Communications
Receive Communications
Redo Common
Register Lifecycle
Remove Common
Rename Common
Repair Diagnostic
Request Lifecycle
Reset Common
Resize Common
Resolve Diagnostic
Restart Lifecycle
Restore Data
Resume Lifecycle
Revoke Security
Save Data
Search Common
Select Common
Send Communications
Set Common
Show Common
Skip Common
Split Common
Start Lifecycle
Chapter 9 - Functions 129
Step Common
Stop Lifecycle
Submit Lifecycle
Suspend Lifecycle
Switch Common
Sync Data
Test Diagnostic
Trace Diagnostic
Unblock Security
Undo Common
Uninstall Lifecycle
Unlock Common
Unprotect Security
Unpublish Data
Unregister Lifecycle
Update Data
Use Other
Wait Lifecycle
Watch Common
Write Communications
In the previous example, I’ve sorted the results by the Verb column. The Group
column gives you an idea of how these verbs are used. It’s important to choose an
approved verb in PowerShell when functions are added to a module. The module
generates a warning message at load time if you choose an unapproved verb. That
warning message makes your functions look unprofessional. Unapproved verbs also
limit the discoverability of your functions.
A simple function
A function in PowerShell is declared with the function keyword followed by the
function name and then an open and closing curly brace. The code that the function
will execute is contained within those curly braces.
Chapter 9 - Functions 130
function Get-Version {
$[Link]
}
The function shown is a simple example that returns the version of PowerShell.
Get-Version
There’s a good chance of name conflict with functions named something like
Get-Version and default commands in PowerShell or commands that others may
write. This is why I recommend prefixing the noun portion of your functions to
help prevent naming conflicts. In the following example, I’ll use the prefix “PS”.
function Get-PSVersion {
$[Link]
}
Other than the name, this function is identical to the previous one.
Get-PSVersion
Even when prefixing the noun with something like PS, there’s still a good chance
of having a name conflict. I typically prefix my function nouns with my initials.
Develop a standard and stick to it.
Chapter 9 - Functions 131
function Get-MrPSVersion {
$[Link]
}
This function is no different than the previous two other than using a more sensible
name to try to prevent naming conflicts with other PowerShell commands.
Get-MrPSVersion
Once loaded into memory, you can see functions on the Function PSDrive.
If you want to remove these functions from your current session, you’ll have to
remove them from the Function PSDrive or close and reopen PowerShell.
If the functions were loaded as part of a module, the module can be unloaded to
remove them.
Chapter 9 - Functions 132
The Remove-Module cmdlet removes modules from memory in your current PowerShell
session, it doesn’t remove them from your system or from disk.
Parameters
Don’t statically assign values! Use parameters and variables. When it comes
to naming your parameters, use the same name as the default cmdlets for your
parameter names whenever possible.
function Test-MrParameter {
param (
$ComputerName
)
Write-Output $ComputerName
Why did I use ComputerName and not Computer, ServerName, or Host for my
parameter name? It’s because I wanted my function standardized like the default
cmdlets.
I’ll create a function to query all of the commands on a system and return the number
of them that have specific parameter names.
Chapter 9 - Functions 133
function Get-MrParameterCount {
param (
[string[]]$ParameterName
)
[pscustomobject]@{
ParameterName = $Parameter
NumberOfCmdlets = $[Link]
}
}
}
As you can see in the results shown below, 39 commands that have a Computer-
Name parameter. There aren’t any cmdlets that have parameters such as Computer,
ServerName, Host, or Machine.
ParameterName NumberOfCmdlets
------------- ---------------
ComputerName 39
Computer 0
ServerName 0
Host 0
Machine 0
I also recommend using the same case for your parameter names as the default
cmdlets. Use ComputerName, not computername. This makes your functions look and
feel like the default cmdlets. People who are already familiar with PowerShell will
feel right at home.
Chapter 9 - Functions 134
The param statement allows you to define one or more parameters. The parameter
definitions are separated by a comma (,). For more information, see about_Func-
tions_Advanced_Parameters2 .
Advanced Functions
Turning a function in PowerShell into an advanced function is really simple. One
of the differences between a function and an advanced function is that advanced
functions have a number of common parameters that are added to the function
automatically. These common parameters include parameters such as Verbose and
Debug.
I’ll start out with the Test-MrParameter function that was used in the previous section.
function Test-MrParameter {
param (
$ComputerName
)
Write-Output $ComputerName
What I want you to notice is that the Test-MrParameter function doesn’t have any
common parameters. There are a couple of different ways to see the common
parameters. One is by viewing the syntax using Get-Command.
ComputerName
function Test-MrCmdletBinding {
Write-Output $ComputerName
Drilling down into the parameters with Get-Command shows the actual parameter
names including the common ones.
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
SupportsShouldProcess
SupportsShouldProcess adds WhatIf and Confirm parameters. These are only needed
for commands that make changes.
function Test-MrSupportsShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param (
$ComputerName
)
Write-Output $ComputerName
Once again, you can also use Get-Command to return a list of the actual parameter
names including the common ones along with WhatIf and Confirm.
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
WhatIf
Confirm
Parameter Validation
Validate input early on. Why allow your code to continue on a path when it’s not
possible to run without valid input?
Always type the variables that are being used for your parameters (specify a
datatype).
Chapter 9 - Functions 138
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[string]$ComputerName
)
Write-Output $ComputerName
In the previous example, I’ve specified String as the datatype for the ComputerName
parameter. This causes it to allow only a single computer name to be specified. If
more than one computer name is specified via a comma-separated list, an error is
generated.
The problem with the current definition is that it’s valid to omit the value of the
ComputerName parameter, but a value is required for the function to complete
successfully. This is where the Mandatory parameter attribute comes in handy.
Chapter 9 - Functions 139
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$ComputerName
)
Write-Output $ComputerName
The syntax used in the previous example is PowerShell version 3.0 and higher com-
patible. [Parameter(Mandatory=$true)] could be specified instead to make the function
compatible with PowerShell version 2.0 and higher. Now that the ComputerName
is required, if one isn’t specified, the function will prompt for one.
Test-MrParameterValidation
If you want to allow for more than one value for the ComputerName parameter,
use the String datatype but add open and closed square brackets to the datatype to
allow for an array of strings.
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string[]]$ComputerName
)
Write-Output $ComputerName
}
Chapter 9 - Functions 140
Maybe you want to specify a default value for the ComputerName parameter if one
isn’t specified. The problem is that default values can’t be used with mandatory pa-
rameters. Instead, you’ll need to use the ValidateNotNullOrEmpty parameter validation
attribute with a default value.
function Test-MrParameterValidation {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
Write-Output $ComputerName
Even when setting a default value, try not to use static values. In the previous
example, $env:COMPUTERNAME is used as the default value, which is automatically
translated into the local computer name if a value is not provided.
Verbose Output
While inline comments are useful, especially if you’re writing some complex code,
they never get seen by users unless they look into the code itself.
The function shown in the following example has an inline comment in the foreach
loop. While this particular comment may not be that difficult to locate, imagine if
the function included hundreds of lines of code.
Chapter 9 - Functions 141
function Test-MrVerboseOutput {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
function Test-MrVerboseOutput {
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
When the function is called without the Verbose parameter, the verbose output won’t
be displayed.
When it’s called with the Verbose parameter, the verbose output will be displayed.
Chapter 9 - Functions 142
Pipeline Input
When you want your function to accept pipeline input, some additional coding is
necessary. As mentioned earlier in this book, commands can accept pipeline input
by value (by type) or by property name. You can write your functions just like the
native commands so that they accept either one or both of these types of input.
To accept pipeline input by value, specified the ValueFromPipeline parameter at-
tribute for that particular parameter. Keep in mind that you can only accept pipeline
input by value from one of each datatype. For example, if you have two parameters
that accept string input, only one of those can accept pipeline input by value because
if you specified it for both of the string parameters, the pipeline input wouldn’t know
which one to bind to. This is another reason I call this type of pipeline input by type
instead of by value.
Pipeline input comes in one item at a time similar to the way items are handled in a
foreach loop. At a minimum, a process block is required to process each of these items
if you’re accepting an array as input. If you’re only accepting a single value as input,
a process block isn’t necessary, but I still recommend specifying it for consistency.
function Test-MrPipelineInput {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline)]
[string[]]$ComputerName
)
PROCESS {
Write-Output $ComputerName
}
}
Chapter 9 - Functions 143
Accepting pipeline input by property name is similar except it’s specified with the
ValueFromPipelineByPropertyName parameter attribute and it can be specified for any
number of parameters regardless of datatype. The key is that the output of the
command that’s being piped in has to have a property name that matches the name
of the parameter or a parameter alias of your function.
function Test-MrPipelineInput {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
PROCESS {
Write-Output $ComputerName
}
BEGIN and END blocks are optional. BEGIN would be specified before the PROCESS block
and is used to perform any initial work prior to the items being received from the
pipeline. This is important to understand. Values that are piped in are not accessible
in the BEGIN block. The END block would be specified after the PROCESS block and is
used for cleanup once all of the items that are piped in have been processed.
Error Handling
The function shown in the following example generates an unhandled exception
when a computer can’t be contacted.
Chapter 9 - Functions 144
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
PROCESS {
foreach ($Computer in $ComputerName) {
Test-WSMan -ComputerName $Computer
}
}
There are a couple of different ways to handle errors in PowerShell. Try/Catch is the
more modern way to handle errors.
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
PROCESS {
foreach ($Computer in $ComputerName) {
try {
Test-WSMan -ComputerName $Computer
}
catch {
Write-Warning -Message "Unable to connect to Computer: $Compute\
r"
Chapter 9 - Functions 145
}
}
}
Although the function shown in the previous example uses error handling, it
also generates an unhandled exception because the command doesn’t generate a
terminating error. This is also important to understand. Only terminating errors
are caught. Specify the ErrorAction parameter with Stop as the value to turn a
non-terminating error into a terminating one.
function Test-MrErrorHandling {
[CmdletBinding()]
param (
[Parameter(Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string[]]$ComputerName
)
PROCESS {
foreach ($Computer in $ComputerName) {
try {
Test-WSMan -ComputerName $Computer -ErrorAction Stop
}
catch {
Write-Warning -Message "Unable to connect to Computer: $Compute\
r"
}
}
}
you can’t specify the ErrorAction on the command itself. In that scenario, you might
need to change the global $ErrorActionPreference variable, but if you do change it,
change it back immediately after trying the command.
Comment-Based Help
It’s considered to be a best practice to add comment based help to your functions so
the people you’re sharing them with will know how to use them.
function Get-MrAutoStoppedService {
<#
.SYNOPSIS
Returns a list of services that are set to start automatically, are not
currently running, excluding the services that are set to delayed start.
.DESCRIPTION
Get-MrAutoStoppedService is a function that returns a list of services
from the specified remote computer(s) that are set to start
automatically, are not currently running, and it excludes the services
that are set to start automatically with a delayed startup.
.PARAMETER ComputerName
The remote computer(s) to check the status of the services on.
.PARAMETER Credential
Specifies a user account that has permission to perform this action. The
default is the current user.
.EXAMPLE
Get-MrAutoStoppedService -ComputerName 'Server1', 'Server2'
.EXAMPLE
'Server1', 'Server2' | Get-MrAutoStoppedService
.EXAMPLE
Get-MrAutoStoppedService -ComputerName 'Server1' -Credential (Get-Credenti\
al)
Chapter 9 - Functions 147
.INPUTS
String
.OUTPUTS
PSCustomObject
.NOTES
Author: Mike F. Robbins
Website: [Link]
Twitter: @mikefrobbins
#>
[CmdletBinding()]
param (
#Function Body
When you add comment based help to your functions, help can be retrieved for them
just like the default built-in commands.
All of the syntax for writing a function in PowerShell can seem overwhelming
especially for someone who is just getting started. Often times if I can’t remember
the syntax for something, I’ll open a second copy of the ISE on a separate monitor
and view the “Cmdlet (advanced function) - Complete” snippet while typing in the
code for my function. Snippets can be accessed in the PowerShell ISE using the Ctrl
+ J key combination.
Summary
In this chapter you’ve learned the basics of writing functions in PowerShell to
include how to turn a function into an advanced function and some of the more
important elements that you should consider when writing PowerShell functions
Chapter 9 - Functions 148
such as parameter validation, verbose output, pipeline input, error handling, and
comment based help.
Review
1. How do you obtain a list of approved verbs in PowerShell?
2. How do you turn a PowerShell function into an advanced function?
3. When should WhatIf and Confirm parameters be added to your PowerShell
functions?
4. How do you turn a non-terminating error into a terminating one?
5. Why should you add comment based help to your functions?
References
• about_Functions3
• about_Functions_Advanced_Parameters4
• about_CommonParameters5
• about_Functions_CmdletBindingAttribute6
• about_Functions_Advanced7
• about_Try_Catch_Finally8
• about_Comment_Based_Help9
• Video: PowerShell Toolmaking with Advanced Functions and Script Modules10
3 [Link]
4 [Link]
parameters
5 [Link]
6 [Link]
cmdletbindingattribute
7 [Link]
8 [Link]
9 [Link]
10 [Link]
modules/
Chapter 10 - Script modules
Turning your one-liners and scripts in PowerShell into reusable tools becomes even
more important if it’s something that you’re going to use frequently. Packaging your
functions in a script module makes them look and feel more professional and makes
them easier to share.
Dot-Sourcing Functions
Something that we didn’t talk about in the previous chapter is dot-sourcing functions.
When a function in a script isn’t part of a module, the only way to load it into memory
is to dot-source the .PS1 file that it’s saved in.
The following function has been saved as Get-MrPSVersion.ps1.
function Get-MrPSVersion {
$PSVersionTable
}
.\Get-MrPSVersion.ps1
Get-MrPSVersion
Chapter 10 - Script modules 150
You can determine if functions are loaded into memory by checking to see if they
exist on the Function PSDrive.
The problem with calling the script that contains the function is that the functions
are loaded in the Script scope. When the script completes, that scope is removed and
the function is removed with it.
The function needs to be loaded into the Global scope. That can be accomplished by
dot-sourcing the script that contains the function. The relative path can be used.
. .\Get-MrPSVersion.ps1
. C:\Demo\Get-MrPSVersion.ps1
If a portion of the path is stored in a variable, it can be combined with the remainder
of the path. There’s no reason to use string concatenation to combine the variable
together with the remainder of the path.
$Path = 'C:\'
. $Path\Get-MrPSVersion.ps1
Now when I check the Function PSDrive, the Get-MrPSVersion function exists.
Script Modules
A script module in PowerShell is simply a file containing one or more functions that’s
saved as a .PSM1 file instead of a .PS1 file.
How do you create a script module? You’re probably guessing with a command
named something like New-Module. Your assumption would be wrong. While there
is a command in PowerShell named New-Module, that command creates a dynamic
module, not a script module. Always be sure to read the help for a command even
when you think you’ve found the command you need.
help New-Module
Chapter 10 - Script modules 152
NAME
New-Module
SYNOPSIS
Creates a new dynamic module that exists only in memory.
SYNTAX
New-Module [-Name] <[Link]> [-ScriptBlock]
<[Link]> [-ArgumentList
<[Link][]>] [-AsCustomObject] [-Cmdlet <[Link][]>]
[-Function <[Link][]>] [-ReturnResult] [<CommonParameters>]
DESCRIPTION
The `New-Module` cmdlet creates a dynamic module from a script block.
The members of the dynamic module, such as functions and variables, are
immediately available in the session and remain available until you
close the session.
Dynamic modules exist only in memory, not on disk. Like all modules,
the members of dynamic modules run in a private module scope that is a
child of the global scope. Get-Module cannot get a dynamic module, but
Get-Command can get the exported members.
RELATED LINKS
Online Version: [Link]
[Link]/new-module?view=powershell-5.1&WT.mc_id=ps-gethelp
Export-ModuleMember
Get-Module
Import-Module
Remove-Module
about_Modules
REMARKS
To see the examples, type: "get-help New-Module -examples".
For more information, type: "get-help New-Module -detailed".
For technical information, type: "get-help New-Module -full".
For online help, type: "get-help New-Module -online"
In the previous chapter, I mentioned that functions should use approved verbs
otherwise they’ll generate a warning message when the module is imported. The
following code uses the New-Module cmdlet to create a dynamic module in memory.
This module demonstrates the unapproved verb warning.
function Return-MrOsVersion {
Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object -Property @{label='OperatingSystem';expression={$_.Captio\
n}}
}
} | Import-Module
Chapter 10 - Script modules 154
WARNING: The names of some imported commands from the module 'MyModule' include
unapproved verbs that might make them less discoverable. To find the commands w\
ith
unapproved verbs, run the Import-Module command again with the Verbose paramete\
r. For a
list of approved verbs, type Get-Verb.
Just to reiterate, although the New-Module cmdlet was used in the previous example,
that’s not the command for creating script modules in PowerShell.
Save the following two functions in a file named MyScriptModule.psm1.
function Get-MrPSVersion {
$PSVersionTable
}
function Get-MrComputerName {
$env:COMPUTERNAME
}
Get-MrComputerName
An error message is generated saying the function can’t be found. You could also
check the Function PSDrive just like before and you’ll find that it doesn’t exist there
either.
You could manually import the file with the Import-Module cmdlet.
Chapter 10 - Script modules 155
Import-Module C:\MyScriptModule.psm1
$env:PSModulePath
C:\Users\mike-ladm\Documents\WindowsPowerShell\Modules;C:\Program Files\Wind
owsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules;C:\
Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\
The results are difficult to read. Since the paths are separated by a semicolon, you
can split the results to return each path on a separate line. This makes them easier
to read.
C:\Users\mike-ladm\Documents\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules
C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\
The first three paths in the list are the default. When SQL Server Management
Studio was installed, it added the last path. For module autoloading to work, the
MyScriptModule.psm1 file needs to be located in a folder named MyScriptModule directly
inside one of those paths.
Not so fast. For me, my current user path isn’t the first one in the list. I almost never
use that path since I log into Windows with a different user than the one I use to run
PowerShell. That means it’s not located in my normal Documents folder.
The second path is the AllUsers path. This is the location where I store all of my
modules.
The third path is underneath C:\Windows\System32. Only Microsoft should be storing
modules in that location since it resides within the operating systems folder.
Once the .PSM1 file is located in the correct path, the module will load automatically
when one of its commands is called.
Chapter 10 - Script modules 156
Module Manifests
All modules should have a module manifest. A module manifest contains metadata
about your module. The file extension for a module manifest file is .PSD1. Not all files
with a .PSD1 extension are module manifests. They can also be used for things such as
storing the environmental portion of a DSC configuration. New-ModuleManifest is used
to create a module manifest. Path is the only value that’s required. However, the
module won’t work if RootModule isn’t specified. It’s a good idea to specify Author
and Description in case you decide to upload your module to a NuGet repository
with PowerShellGet since those values are required in that scenario.
The version of a module without a manifest is 0.0. This is a dead giveaway that the
module doesn’t have a manifest.
The module manifest can be created with all of the recommended information.
$moduleManifestParams = @{
Path = "$env:ProgramFiles\WindowsPowerShell\Modules\MyScriptModule\MyScript\
Module.psd1"
RootModule = 'MyScriptModule'
Author = 'Mike F. Robbins'
Description = 'MyScriptModule'
CompanyName = '[Link]'
}
New-ModuleManifest @moduleManifestParams
If any of this information is missed during the initial creation of the module manifest,
it can be added or updated later using Update-ModuleManifest. Don’t recreate the
manifest using New-ModuleManifest once it’s already created because the GUID will
change.
Chapter 10 - Script modules 157
function Get-MrPSVersion {
$PSVersionTable
}
function Get-MrComputerName {
$env:COMPUTERNAME
}
In the previous example, only the Get-MrPSVersion function is available to the users
of your module, but the Get-MrComputerName function is available to other functions
within the module itself.
If you’ve added a module manifest to your module (and you should), then I
recommend specifying the individual functions you want to export in the Func-
tionsToExport section of the module manifest.
FunctionsToExport = 'Get-MrPSVersion'
It’s not necessary to use both Export-ModuleMember in the .PSM1 file and the Function-
sToExport section of the module manifest. One or the other is sufficient.
Chapter 10 - Script modules 158
Summary
In this chapter you’ve learned how to turn your functions into a script module in
PowerShell. You’ve also learned some of the best practices for creating script modules
such as creating a module manifest for your script module.
Review
1. How do you create a script module in PowerShell?
2. Why is it important for your functions to use an approved verb?
3. How do you create a module manifest in PowerShell?
4. What are the two options for exporting only certain functions from your
module?
5. What is required for your modules to load automatically when a command is
called?
References
• How to Create PowerShell Script Modules and Module Manifests1
• about_Modules2
• New-ModuleManifest3
• Export-ModuleMember4
1 [Link]
2 [Link]
3 [Link]
4 [Link]