Tiny PowerShell Projects
Tiny PowerShell Projects
1. welcome
2. 1_Introduction
3. 2_Automating_email_address_creation
4. 3_Create_a_user_(the_easy_way)
5. 4_Bugs!_(troubleshooting_common_script_issues)
6. 5_The_power_unlock
7. 6_Manage_groups_like_a_boss
8. 7_One-click_Exchange_account_fix
9. 8_Unified_user_creation
10. 9_Making_LOTS_of_users!
11. 10_Inventory_expert
12. index
welcome
Thank you for purchasing the MEAP for Tiny PowerShell Projects.
This book has been written for those IT Professionals with little to no coding
experience. But even those with a firm understanding of the language can
hopefully find some useful scripts that will drastically improve their ability to
automate and optimize their environment.
PowerShell, perhaps more than any other language, is the perfect language
for the IT Professional to learn. It has power, flexibility and an ease of entry
that is not found in any other tool.
Rest assured; this is not the nature of Tiny PowerShell Projects. I have never
considered myself a software engineer. I first experienced PowerShell, nearly
ten-years ago, while working as a remote support technician for a large
company. My first PowerShell course was taught like it was any other
programming language. We covered variables, if statements and loops, I
spent five days in a live training program, but frankly I found myself in
debugging hell because I’d do something like forget to end my closing brace.
I quickly found that I was being buried in the newness of it all. Relieved
when I could type a string of commands into the interpreter and not see a
screen full of red staring back at me. I wasn’t getting anything out of it that
was useful. I could construct some code and make it run, for the most part.
But it was a long and arduous process, and I still had no idea how, when, or
why to use PowerShell in my daily life.
My goal for Tiny PowerShell Projects is to flip this paradigm. I will start with
the why, then show you the when and only then, will I detail the how. Tiny
PowerShell Projects is designed to produce useful scripts and demonstrate
use cases that you can take out of the book and put directly into practice in
your environment. Then, I will break down the script section by section
covering the language and logic that make it work. It is quite simply the book
I wish I had had when learning PowerShell.
Feedback on this project has already been incredible and inspiring. I hope
that you’ll be leaving comments in the liveBook Discussion forum. Your
question, comment or suggestion may help future readers as they too discover
this powerful automation and optimization tool at their fingertips.
Thanks again for your interest and for purchasing the MEAP!
—Bill Burns
In this book
Congratulations! In this book, you have just found a resource that will not
only make your life easier; it will make you more efficient and more
marketable. PowerShell is your Swiss Army knife for automating all the
things. It is a remarkable tool for IT professionals of many hues – it has been
embraced by systems administrators (for automating their daily tasks),
security practitioners (for obtaining, searching and collating logs,
configuration files and other data sources) and even software developers (for
creating build scripts, CI/CD actions and additional tools like prerequisites
checkers or package downloaders for their products).
After reading this book, you will be able to automate simple tasks similar to
the ones described in the book in the form of “tiny projects”, make sense of
other people’s PowerShell code (“other people” may very well be yourself a
year after you created a particular script!) and find and quash bugs in
PowerShell scripts efficiently. To achieve this, each chapter starts with a
PowerShell script that solves a typical IT administrator’s daily challenge. The
script is then dissected to demonstrate how it works and why it works that
way, and extended by adding more code to refine the logic or add more use
cases. You can download all scripts from the book’s website.
Input formats and arguments are not standardized, making passing data
from one command to another difficult and sometimes even impossible
without some clever scripting in between.
Output of native commands is simple text geared towards being visually
interpreted by the human in front of the console, not processed by
another command. Some commands localize their output according to
the OS and/or user language settings, while others use English
regardless of the system language.
Even numbers (decimal and thousand separators) and time/date values
are subject to localization – again, without a standard in place to at least
make output formats consistent across commands on the same machine.
Accessing remote systems and running code on them usually requires
potentially insecure protocols like RPC or SMB.
PowerShell script code is just unformatted text, and you can use your favorite
text editor to create and edit PowerShell scripts and even modules. However,
programmers and scripters traditionally use “integrated development
environments” (IDE) which combine code editing with syntax highlighting,
code completion, as well as the ability to run and debug the code being edited
without leaving the editor. Windows PowerShell includes a feature-rich
Integrated Scripting Environment (ISE) that has helped generations of
enterprise scripters create and debug their PowerShell code with ease. The
standard go-to for developing scripts for open-source PowerShell is
Microsoft’s free and versatile code editor, Visual Studio Code, or VSCode
for short.
Imagine you have been tasked with migrating a DNS service to a different set
of IP addresses. Standing up a new DNS and transferring the zone data is
easy. However, if your servers and maybe even clients have static IP
configurations, you’d normally have to
Figure 1.1 Modifying IP settings the old way – by visiting each node and disturbing the user on it.
On a handful of systems this is an inconvenience, but once their number rises
into multiple digits, it becomes a daunting task – so much so that network
people often go to great lengths to keep DNS servers’ IP addresses constant
over long periods of time. Imagine that, after you are done with the DNS
address change, you open the next ticket and it requires you to change the
default gateway of all those systems to a new value for each subnet. An old-
fashioned admin would have to repeat the whole exercise.
PowerShell, with its unique capability to both juggle data and interact with
systems, allows you to
Figure 1.2 A PowerShell script pulls the data, reaches out to the nodes and documents in real
time.
If the IT infrastructure manager assigned two team members to perform these
tasks at different sites, who do you think will get the bigger raise at the end of
the year – the old-school “sneakernet” admin who took days to complete the
task and inconvenienced the users by taking over their terminals, or his
PowerShell-savvy colleague who performed the same work remotely, in
parallel, in a fraction of the time, and without user disruption?
Each project is then dissected, explaining the code that makes the script
work, detailing critical programming concepts like variables, functions,
branching, loops, structured error handling, arrays, and methods. You don’t
need to have any kind of programming background to see the value of
implementing the projects in your environment and to use them as written.
If, however, you have some experience with PowerShell, the projects in this
book will be immediately useful and you can enhance your skills with
practical hands-on exercises. You’ll see, through the application of these
projects, many more potential opportunities to automate and customize your
own domain, making your administration task lists less about the repetitive
tasks and more about the true added value that you bring to your
organization.
If you have no desire to take a deeper dive into PowerShell and how it works,
you’ll still get value from this book in the form of the fifteen specific and
useful PowerShell scripts you can implement today to improve your
productivity and proficiency in the language. It’s our goal not to waste
anyone’s time with scripts that do not make a tangible improvement to your
environment. You won’t find a “hello world” script in the book. That being
said, the beginning scripts will ease new PowerShell users into the language
and syntax at a pace designed not to overwhelm them. More advanced users
should read the script at the beginning of the chapter. If you understand
exactly what the script does and how it functions, feel free to skip the
dissection of the script in the rest of the chapter. Many times, future scripts
will build upon the basics we learned in earlier chapters to achieve more
complicated solutions.
But should you see the power and flexibility native to the language, this book
will serve as a valuable jumping-off point illustrating not only what can be
done to improve your life as a System Admin, Exchange Admin, Security
Analyst, or other IT professional, whether you are working on premises or in
the cloud. It will also show you how to recognize the most advantageous
times to implement a scripted solution.
In general, anyone, even a power user, who has a repetitive task could benefit
from the automation and configuration capabilities of PowerShell.
Ironically it is the group of Professionals that have the most to gain from
mastering PowerShell that are the most resistant to using it. This, we believe,
comes from the simple fact that today’s System Administrators are not
usually coders. If you have ever been put off learning PowerShell or have felt
intimidated, or don’t know where to start, this book is for you! You have
come to the right place. Each tiny project is useful and your hands-on
experience will help you overcome your fear and dive into the amazing world
of scripting and automation!
We will try to keep as much of the script code in this book executable on
both PowerShell families as possible. In the rare cases where the PowerShell
family being used makes a difference, we will explicitly point that out.
The list of examples like this is endless, and so are the possibilities to make
yourself more valuable to your organization and ultimately on the job market
by embracing automation. Jeffrey Snover, the inventor of PowerShell,
emphasized this as a key part of PowerShell’s value proposition very early on
– a universal automation paradigm which, once mastered, will make you
more valuable wherever your professional journey may take you.
Finally, there is another aspect to this that is often overlooked when speaking
about automation, yet it has proven to be impactful time and time again. IT
people are generally not very good at documenting their work, and the few
exceptions there are just prove the rule. Truth is, IT people are usually not
very keen to spend time documenting their work, as this provides no instant
gratification typical to other parts of an IT professional’s workday. By
scripting out your IT management processes in a language that is both
powerful enough to achieve every required objective and verbose enough to
be read and understood by humans, you codify the organizational knowledge
about these processes by the very act of automating them, with an occasional
comment thrown into the code to clarify the business intent behind it. And
PowerShell is uniquely suited to provide the technological basis for this sort
of knowledge preservation by process automation!
For most of the chapters, we assume that your machine is joined to an Active
Directory domain, and that your account possesses sufficient privileges on
your own and the other systems involved (these privileges will be detailed in
the respective chapters). Chapters 7 and 9 deal with Microsoft Exchange. In
order to use the examples from these chapters, you need access to an on-
premises Exchange environment – any version from 2013 onwards will do.
At the time of writing, the classic Exchange version is near their end of
support, to be replaced by Exchange Subscription Edition (SE). The
examples in this book will work with that version as well.
1.8 Summary
PowerShell is a task automation and configuration tool built by
Microsoft on top of .NET Framework (Windows PowerShell 1.0
through 5.1) and .NET (PowerShell 6.x and 7.x). Therefore, everything
in PowerShell is an object derived from a .NET class.
PowerShell utilizes cmdlets that adhere to common guidelines for
naming, input, parametrization and output, to make it immediately
useful to IT professionals with little to no coding experience.
Cmdlets are lightweight PowerShell commands that mostly perform
atomic tasks but can consume each other’s output by means of a rich
object-oriented pipeline.
PowerShell will allow you to complete many common or repetitive tasks
with ease, making you many times more efficient than those IT
professionals who have yet to embrace this skillset.
This book will contain a series of Tiny PowerShell Projects that you can
use in your environment today. They will give you immediate and
tangible value and teach you the language at the same time.
There are significant differences between the feature complete Windows
PowerShell 5.1 and ever-evolving open-source and cross-platform
PowerShell 7.x.
2 Automating email address
creation
This chapter covers
Using Variables and Cmdlets
Outputting Variable contents with Write-Host
Concatenating strings
Modifying strings
Adding comments to your code
Automation is a way of taking simple, repetitive things off your list. Besides
freeing up the 15 minutes you didn’t have in the first place, it also helps keep
your attention focused on the more important matters. Your organization gets
more value out of your time, and you are less likely to suffer from burnout!
If you already have a firm grip on the foundations, please feel free to give the
script a quick look and progress to more advanced chapters.
Nearly every company has email that they use for a way of communicating
with their employees. Thus, an email address is almost always something that
you will need to create when onboarding a new employee, put into company
directory, print on a business card and so on. Most organizations have
conventions, or “policies”, that prescribe how an email address of a person
will be derived from their personal and work-related metadata, such as first
and last name, business unit, or department.
A very widespread example of such a policy is “First letter of the first name,
followed by the last name, followed by the @ sign that separates the private
part from the domain part, followed by the SMTP domain name the company
or business unit utilizes for email delivery.“
If we are given a list of employee names and the task of creating email
addresses for them, for each entry on the list we would need to:
Even a skilled typist, which many System Administrators are not, would take
several seconds to perform this function manually. The opportunity for typos
is quite significant when faced with a large list. How often do we insert a
“.com” at the end of a domain name by rote (even if our own organization
top-level suffix is a “.org” or “.edu”)?
If the lists of employees’ names are not provided on paper but in digital form
(documents, spreadsheets or database query results), digital artifacts may
creep in – usually in form of whitespace or unprintable characters. These
need to be cleaned up because the SMTP standard does not allow them in
email addresses.
This brings us to our very first tiny PowerShell project! This script will:
Allow us to assign a First and Last name to variables. You can just copy
and paste this from your list.
Allow us to assign a domain name (that we only have to type once, so
the likelihood of confusing a .org with a .com goes nearly to zero).
Clean up any white space, so you don’t need to worry about cleaning
your list before you copy and paste. A leading or trailing space, or even
several, will not make it into the email address output.
Find the first initial of the first name.
Compose the email address as described above (first letter of first name,
full last name, @ symbol and domain).
Write out the full email address.
The rest of the chapter will walk you through what each step of the script
does and how it works.
After installing all the components, start VSCode (figure 2.1), and open the
.ps1 script file by using the File à Open File… command from the menu (or
Ctrl + O). Navigate to the folder where you saved the script and select the
desired .ps1 file. If you forgot to install the PowerShell extension, VSCode
will recognize the PowerShell language and prompt you to install the
extension. Without the extension, VSCode will not be able to offer you any
language-specific features such as syntax highlighting, interactive terminal,
or debugger.
Both in VSCode and in PowerShell ISE, if you have not actually highlighted
any code, the “Run Selection” command will run the one line in which the
cursor is currently placed, provided it contains executable PowerShell code.
ISE will even select the entire line before running the code; VSCode just runs
it in the currently selected terminal without highlighting anything.
Of course, you don’t need to open your script in an editor if you just intend to
run it. In fact, using a development environment for simply executing scripts
in production is considered bad practice by many enterprise scripters. Just
open the PowerShell of your choosing and enter the path of the desired script
file, followed by <ENTER>!
Note
If your script path contains spaces, or if you have used the “Copy as Path…”
function of the Windows Explorer context menu to copy and paste the path, it
will be enclosed in (double) quotes. From the PowerShell point of view, this
represents a valid string, and simply placing it on a line will result in the
content of the string, i.e. without the surrounding quotes, being output to the
console. To make your intent of invoking the script unmistakably clear,
prepend the path with the ampersand symbol (figure 2.3).
In this chapter, the underlying type of all objects we will use is [string]
which is essentially a sequence of characters in a certain order. However,
.NET and PowerShell make a simple string much more intelligent than just
this.
To create a string value, we can just put the desired sequence of characters
between two double quotes. This is what lines 1-3 of our project code do:
$FirstName = " John "
$LastName = " Doe "
$Domain = "Tiny-PowerShell-Projects.COM "
You can achieve the same by putting your text between single quotes, or
apostrophes:
$FirstName = ' John '
The quotes must match – you cannot start a string with a single quote and end
it with a double! The “other” quote can be part of the string:
'We used to call John "Johnny" in private'
"Let's do this!"
To include the limiting quote character in the string, just type it twice:
"We used to call John ""Johnny"" in private"
'Let''s do this!'
Later in this chapter, we will look at one use case where the double quotes
make all the difference. But for this tiny project you may use whatever
quotation you prefer.
The only property a PowerShell string has is .Length, which returns the
number of characters in the string. This is a “read-only” property; any attempt
to assign a value to it will result in an error. In addition to that, the [string]
type comes equipped with 48 methods that provide a variety of operations a
string can perform on itself. You can recognize methods by the parentheses
directly following the name. Some methods require parameters (which you
put in the parentheses in a certain order); others allow for invocation without
specifying any parameters. In this tiny project we will make use of four of the
[string] methods.
The above code returns just “Doe”, without any leading or trailing
whitespace characters. We will only need the first letter of the first name, so
we are not really interested in the whitespace that may be at the end of the
string. To just remove the whitespace at the beginning, we will use the
.TrimStart() method on $FirstName.
Look closely at the second line: $FirstName contains a string. Calling its
.TrimStart() method also yields a string - $FirstName.TrimStart() has
got all the methods and properties that $FirstName has! This is why we can
apply the .Substring() method directly to the result of the .TrimStart()
method by chaining them together!
Have you noticed what we did in the last line? We assigned the “lowercased”
value of $EmailAddress back to the same variable!
It does exactly what the name suggests: write the contents of the variable
supplied to the cmdlet out to the PowerShell shell, or the “host”, as we call
any application capable of executing PowerShell code. All PowerShell
cmdlets follow the Verb-Noun naming pattern, with the noun always in
singular. If one day your PowerShell journey leads you to creating your own
cmdlets, remember that
There are sanctioned verbs that are accepted when loading cmdlets into
PowerShell, any other verbs will cause a warning.
The singular naming convention for the nouns is not enforced, but
plurals are frowned upon.
If, for example, your cmdlet is designed to count leaves on a tree, the name
Count-Leaves will be in violation of both rules, but only the unsanctioned
verb Count will trigger a warning. To get a list of acceptable verbs, just use
Get-Verb in your shell!
All cmdlets come from modules. The Write-Host cmdlet we use in the project
code is part of the Microsoft.PowerShell.Utility module shipped with
PowerShell by default. One such module, Microsoft.PowerShell.Core, is
special in that it’s always loaded automatically without actually being
registered as module, which lets many PowerShell users think of the about 60
cmdlets contained there as “built-in cmdlets”. This view is not wrong.
The most important PowerShell cmdlet, and one of the “Core” cmdlets, is
Get-Help <name of a cmdlet>
At the very least, it will confirm the existence of the cmdlet within your
PowerShell session and deliver the basic information about the cmdlet’s
purpose, the supported arguments (or parameters) and their syntax (figure
2.5).
If you never used Get-Help on this machine before, start with Update-Help
(as long as the computer has Internet connectivity). This will tell PowerShell
to download help files for all registered modules and all cmdlets therein.
A cmdlet may have multiple allowed ways to supply arguments to it, so-
called “parameter sets”, which is why you see multiple syntax blocks in the
output above. When reading the output, notice the square brackets around
some, but not all of the parameters – they signify an optional parameter that
you can, but are not required to, supply. In the first syntax block above, all
parameters are optional. Calling Get-Help without any parameters will output
a general description of the PowerShell help system.
Parameters of PowerShell cmdlets always start with a dash, followed by the
name of the parameter, a space, and the value that you would like to pass to
this particular parameter. A space is also required before the dash, separating
parameters from the cmdlet and from each other. Similar naming conventions
as for nouns in cmdlets’ names apply to parameter names, but none of them
are enforced.
Some of the parameters accept values without being explicitly named. For
Get-Help, the first such parameter is -Name, which is why we were able to
write just
Get-Help Get-Help
instead of
Get-Help -Name Get-Help
is equivalent to
Write-Host -Object $EmailAddress
In the last line, the comment contains perfectly valid PowerShell code, but
this code will not be executed because it is “commented out”!
If your intended comment does not fit on a single line, you can spare the
effort of putting a hash sign in front of each line and just use a multiline
comment:
<#
This line is commented out.
So is this line.
In the next chapters we will learn how comments can help significantly
streamline your scripting process, in addition to the obvious benefit of having
the code annotated for posterity!
If you cannot be 100% sure (and, for some reason, would not like to make
sure) that the first part of the concatenated string will, in fact, be a string, you
should resort to more deterministic methods of composing a string value. One
that is considered very typical for PowerShell is called string expansion and
is especially valuable if parts of the resulting string are static:
$name = "jdoe"
$domain = "tiny-powershell-projects.com"
$result = "The address is: $name@$domain"
The most important requirement here is that the resulting string be enclosed
in double, not single, quotes. In such a string, once a variable (i.e. a dollar
sign followed by a variable name) is encountered, it is replaced by, or
“expanded into” that variable’s current value! If the variable parts of the
strings are more complex expressions, enclose them in $() like this:
$name = " jdoe "
$domain = "Tiny-PowerShell-Projects.COM"
$result = "The address is: $($name.Trim())@$($domain.ToLower())"
If you would like to include a dollar sign or a double quote in the string,
“escape” them by prepending these characters with the backtick symbol `.
The same goes for backtick itself:
"This is a `"dollar sign`": `$"
"We used a `"backtick`" here: ``"
Another way to compose a string, one especially useful when dealing with
numbers and dates, is the formatted output operator directly inherited from
.NET:
"The address is: {0}@{1}" -f $name.Trim(), $domain.ToLower()
The -f operator inserts its first argument into the string at position {0}, the
second argument at position {1} and so on. This is a very powerful tool we
will be using later in the book. If you are curious to know more about the -f
operator, refer to the about-page: https://learn.microsoft.com/en-
us/powershell/module/microsoft.powershell.core/about/about_operators?
#format-operator--f .
The script code for this chapter provides examples of using both alternatives
to solve this chapter’s task of creating an email address.
will perform this operation for the letter ä. To replace multiple special
characters by their ASCII counterpart, repeat the above line as needed. The -
replace operator uses regular expressions to find the character in the string.
If you don’t know what regular expressions are and how to use them, do not
be disappointed – we will not be using them in this book very often. But in
this particular scenario, regular expressions allow us to shorten the code by
looking for multiple special characters at once, provided they all have the
same ASCII replacement:
$EmailAddress = $EmailAddress -replace '[íìïî]','i'
In this case, .Replace() is the .NET method for replacing text in a string
which is defined as a part of the [string] type, very much like .Trim() or
.Substring(). This method does not use regular expressions, which means
that you have to perform the replacement for each individual character.
2.8 Summary
PowerShell stores information in objects called variables.
Variables can hold different types of data. PowerShell does not require
you to enforce a particular data type for a variable. In fact, the data type
a variable holds can change during the script’s execution.
A string, simple as it looks at first glance, is a rich .NET object sporting
an important property (.Length) and lots of methods that make handling
strings easier.
To cancel out whitespace at the beginning, at the end or at both ends of a
string, the methods .TrimStart(), .TrimEnd() and .Trim() can be
used.
To convert the whole string to capitals or to small letters, the methods
.ToUpper() and .ToLower() are used.
Putting together a string from several partial strings is as simple as
placing plus signs between the parts. There are, however, other methods
of achieving this.
For carving out a part of the string, use .Substring() – this method
requires parameters passed to it: the starting point within the string and
the desired substring length, separated by a comma!
Comments, that is, lines of text starting with a #, help improve
readability of your code by humans – including yourself six months in
the future! Multiline comments (text between <# and #>) allow including
long text passages or commenting out long portions of script code.
There may be other characters than just whitespace supplied in the initial
data that require attention, like international characters not permitted in
email addresses. The replace operator helps locate them and rectify the
syntax.
3 Create a user (the easy way)
This chapter covers:
Using Read-Host for user supplied data
Creating Menus with Read-Host and Write-Host
PowerShell pipe mechanism
Importing Modules
Active Directory cmdlets New-ADUser and Get-ADUser
Filtering AD Users with Where-Object
Introduction to Race Conditions.
Logging command history quickly and easily.
Press F5
Enter a First Name
Enter a Last Name
Warning
This code is frequently broken to span multiple lines. This is done at the pipe
(|) operator. We’ll learn more about the pipeline mechanism later in this
chapter! This will run just fine in PowerShell 7.x. However, this may prove
problematic with older versions of PowerShell. If you plan on running this
code utilizing PowerShell 5.1, you can fix this by moving every line that
starts with a pipe (|) to the end of the line before it.
First, we’ll look at the code that works in both the legacy Windows
PowerShell 5.1 as well as the newer PowerShell 7.x:
This is shown in figure 3.3 line 9 and 10. The line currently reads:
Note
In order to use this code in your environment you will need access to an
Active Directory Domain Controller. These PowerShell cmdlets come native
to the Active Directory Features you install on your Domain Controller.
To utilize these Active Directory cmdlets without logging into your Domain
Controller (remote or console) you can install the Microsoft Remote Server
Administration Tools (RSAT). Directions on where to download and how to
install this can be found in the README.md included with the Code for
Chapter 3.
3.2 Comments
Frequently, when we are scripting or writing any kind of code, we want a
way to remind ourselves, or anyone else reading our code what we are doing
and why. The easiest way to do this is with a comment. Comments are ways
that we can insert an explanation or annotation into our script without it being
executed.
A single line comment is initiated with the pound or hash sign (#). Anything
to the left of the hash is generally ignored by PowerShell.
3.3.1 Read-Host
In the last Tiny PowerShell project we used the cmdlet Write-Host. This
took our data and wrote it to the console for the operator to read. Read-Host
does the opposite. The console prompts the operator for information. When
we use a variable combined with a Read-Host, we can then store this data for
use later in our scripts.
The string in quotes behind the Read-Host is not required. The script will
execute just fine without it. But you leave the operator of your script clueless
as to what information they are being asked to store. It’s a good idea to give
your operator some clue about the what the data will be used for upfront.
Without the string at the end the operator simply sees a blinking cursor and
the script may hang entirely as the operator is not aware that he/she has
anything they need to do at this point.
3.3.2 Menus
With the use of both Read-Host and Write-Host statements it’s easy for your
operator to utilize menus or flags that can be used to help make more
complex decisions in your code.
Clear-Host
$First_Name=Read-Host "What is the user’s First Name?"
Write-Host "You entered: $First_Name `n Is that right?"
$Question=Read-Host "(Y)es, or (N)o"
Note
In Visual Studio Code, when you type the code above as shown, you will see
a red squigly line under the variable called $Question. It may say "The
Variable 'Question' is assigned and never used.” This is an expected error and
can safely be ignored.
The commands are similar in many ways. Both commands type a message on
the screen for the operator to see.
Write-Host "Message A"
Write-Output "Message B"
These commands do nearly the same thing. Both commands display a
message to the console for the operator of your script to read.
3.3.4 Pipe
PowerShell allows us to send the output of one command as the input for
another command through the use of the pipe operator (|) character. Let’s
look at the pipeline mechanic in practice utilizing the Read-Host and Write-
Host cmdlets. Before, we used the Read-Host cmdlet to input a value into a
variable we created, we could then validate this input by outputting the value
of the $Name variable to the console with the Write-Host cmdlet.
$Name=Read-Host "What’s your Name?"
Write-Host $Name
Utilizing the Pipeline Mechanic, we can skip the variable entirely and simply
pipe the value the user inputs from the Read-Host directly into the console
with the Write-Host.
Read-Host "What’s your name?"|Write-Host
Write-Output, however, can store information inside the variable. You can
feed output to your user and then utilize that same output in another function
(say like a log) without having to assign the data to a variable re-capture or
re-create it.
Note
3.3.6 Tee-Object
Tee-Object makes a second copy and redirects output. It sends the output in
two different directions like a (T) intersection. The data is branched in two
different directions. We’ll illustrate this concept in the code below.
To start we will use the Write-Output cmdlet to write out the text
“***IMPORTANT MESSAGE***, this could represent any important
message or log able event. We then pipe this Output to the Tee-Object
Cmdlet. The Tee-Object will make a branch in the code. One branch will be
sent to the output, the other will be captured as a variable called
$second_place.
We will then write out the value of the $second_place variable to validate
that the “***IMPORTANT MESSAGE***” was sent to both places.
Clear-Host
Write-Output "***IMPORTANT MESSAGE***"|Tee-Object -Variable second_place
Write-Host "The first place it went was to the screen. The second place it
Write-Host "The variable contains: "$second_place
3.4 Get-ADUser
Utilizing Active Directory is a huge part of any System Administrator’s job.
Microsoft has made this significantly easier with a number of built-in cmdlets
expressly for navigating Active Directory.
Import-Module
Unless you are running your PowerShell on your Domain Controller it’s
likely that you may see the error message: The term ‘get-aduser’ is not
recognized as a name of a cmdlet, function, script file or executable…
Because of the sheer bulk and flexibility of the PowerShell language, not all
cmdlets are loaded into memory for direct use by default when you install
PowerShell 7 (or utilize older built-in versions).
After the module was imported it’s now listed in the Get-Module command
along with a subset of the cmdlets that are included in the module.
3.5 New-ADUser
New-ADUser is a cmdlet in the ActiveDirectory module that allows for
PowerShell users to quickly and easily create a new user in your domain.
New-ADUser -Name “John Doe” -GivenName “John” -Surname “Doe” -SamAccountName
This creates the user John Doe in your active directory forest.
3.5.1 Where-Object
There are times where you may be using PowerShell to search an enormous
amount of data. For instance, you might be searching your entire domain for
a specific user, or a user with a specific value in any given field. For example,
you might want to query all of your domain to see which of your users are
currently locked out. Depending upon the size of your domain you may be
searching hundreds or even tens of thousands of users. What would be really
helpful is a way to have PowerShell filter all of those results to only give you
the information you care about.
Note
The more you utilize PowerShell the more you will see the strange variable
defined only as ($_). Simply put the $_ is an automatic variable. This can
essentially be used as a placeholder for “Each instance of”. In this code:
Get-ADUser -filter *|Where-Object {$_.SamAccountName -eq “jdoe”}
3.6 Start-Sleep
There are times where it might take several moments for an action to occur. If
you queried an Active Directory structure like we did above, depending on
the size of the domain, it could take several seconds to even a minute or two
to pull all of the results. Creating a new user can sometimes take several
moments to complete. Then, because that account needs to replicate across all
of your domain controllers it may take several seconds for all of your Active
Directory clients to be aware of the new user. If we were to create a user then
immediately query active directory for that user we might create what’s
known as a race condition.
A race condition is basically when two things are happening at the same time
and you’re unsure of which one might finish first. Will the DCs replicate
their Active Directories before PowerShell returns a user from its query?
If replication is slower than the query PowerShell might respond that the user
doesn’t exist. Because when it queried the Domain Controller it was speaking
to, the user account had not yet replicated to its database. If the administrator
had simply waited for replication to finish, he or she would have received a
different answer.
Start-Sleep is like a pause button. It puts your script into a timed wait
where it doesn’t attempt to execute the next command until it has waited the
amount of time you requested.
3.7.1 Read-Host
Using the Read-Host cmdlet we prompt the operator of our script to enter the
First and Last Name of the user they want to create in Active Directory. This
allows for more dynamic use of the script because we no longer need to edit
the script before we run it modifying the variables beforehand. Now the
operator supplies those values each time the script is run.
Because there is a possibility for the operator to hit a space before or after the
values he or she enters we also utilize the Trim() method we learned in the
last Tiny PowerShell project to remove any whitespace for each of these
variables.
With the values of First and Last name supplied to us by the operator we can
use the Substring method and string concatenation we learned in the last
Tiny PowerShell Project to create a username that takes the first character of
the $First_Name and concatenates it to the $Last_Name
Figure 3.22 Using a variable for a log location in our Tiny PowerShell Project code.
3.7.4 Write-Output
So far there is not a lot of data being passed to the operator. A long period of
silence can cause a Windows Admin to begin to wonder if the process is
working or if the process is hung.
Therefore, it’s important to try to give the operator some indication the
process is not hung. We can do this by writing our steps to the console. In
addition, we want to make sure we capture the commands that are being run
in the log. Because we don’t want to duplicate our effort, we can utilize the
Write-Output and the pipe mechanic combined with the Tee-Object cmdlet
to simultaneously send output to the log and output for the operator.
We send our first message to the Operator and the Log confirming that we
are creating a User for the $User_Name we created earlier.
Figure 3.23 Using the Write-Output and Tee-Object cmdlets in our Tiny PowerShell Project
code.
3.7.5 New-ADUser
Utilizing the Active Directory cmdlets we can create new Active Directory
users with a single line of Code. We create a new user in Active Directory
taking the information supplied to us by the Operator of the script. We’re
able to propagate the Given Name (first name), Surname (last name),
SamAccountName (User Name), Display Name (User Name) and with the
pipe mechanic and Tee-Object cmdlet we can send the output of the New-
ADUser cmdlet to our screen and to the log file.
3.7.7 Get-ADUser
The very best way to confirm that the new user was created in Active
Directory is to check Active Directory for our expected user. We can query
all of Active Directory with the -filter *. But this could give us hundreds,
or thousands of users returned. We don’t want to have to sort through that
haystack looking for our new needle.
Instead, we can have PowerShell do the work for us. By utilizing the pipe
mechanic and the Where-Object cmdlet we can have PowerShell pull all of
the users in Active Directory and then provide us only the User Object that
matches the username we supplied it. Utilizing another pipe mechanic and a
Tee-Object cmdlet we can send this information simultaneously to the
operator and the log to confirm both at time of creation and for anyone later
reviewing the log that the user was successfully created.
Creating user accounts are one of the most common tasks that an IT
Professional are likely to get. Each user on the system will need an account to
use it, there’s just no way around that. Today we have begun to automate this
process. Imagine how much more time you’d have to work on more
challenging issues if you didn’t look over at the stack of new hires needing
accounts and know that it was going to eat up a couple of hours out of your
day?
In addition to the speed in which you created these users, creating new users
with a script also makes you far more consistent. There are lots of moving
pieces in creating a user account, and while none of them are particularly
hard, it does leave a lot of room for simple mistakes. Forgetting to put in a
field, typing a username incorrectly, typos in general. All of these common
mistakes can simply be eliminated from your environment by consistently
using scripted solutions.
Figure 3.26 Get-ADUser and Tee-Object from Tiny PowerShell Project code.
3.8 Summary
We can store user supplied information in variables using Read-Host
With combinations of Read-Host and Write-Host we can make menus
that allow for more granular control of our scripts.
The PowerShell pipe mechinic takes the output of a command or series
of commands and inputs that into a nother command or series of
commands without having to store the intermediate values as variables.
Write-Host has no output, but Write-Output does.
Tee-Object can be used to easilly duplicate and send messages to
multiple sources.
Active Directory has its own set of PowerShell cmdlets to help us
manage our domain. We can get access to all of these by using the
Import-Module command.
Start-Sleep can be used to help us elimnate race conditions
4 Bugs! (troubleshooting common
script issues)
This chapter covers:
Introduction to debugging
Navigating the debugging tool provided by PowerShell and VS Code
Identifying and correcting syntax errors using messages and markings
Finding and correcting logical errors using the Break and Write-Host
Cmdlets
Now that you’ve got a few scripts in your bag of tricks it’s important for you
to learn a new skill, debugging. The fact is, writing the code is the easy part.
Debugging is the hard part, figuring out why you’re getting result A instead
of B.
Even if you have no real desire to write original scripts, this chapter is
important! Because there are any number of environmental differences on
one domain to the next. The scripts provided in the Tiny PowerShell Project
book are designed to run out of the box in most environments. But you can
still run into issues implementing them and this chapter will give you the
tools you need to empower you to get around your implementation problems
and see the value from these Tiny PowerShell projects in your own
environment.
The execution policy mentioned in the error message in Figure 4.1 is a safety
feature that details the conditions under which PowerShell can execute a
script on your system. By implementing an execution policy, you can
prevent all scripts or unsigned scripts from being run on your PC, thus
helping to prevent the execution of malicious scripts.
In our case, however, we know that this is not a malicious script so we can
remove this restriction. We utilize the Set-ExecutionPolicy cmdlet to change
the execution policy on our system.
Set-ExecutionPolicy -ExecutionPolicy Unrestricted
This will unrestrict your computer from running unsigned scripts. You can
re-enable this safety feature by running the Set-ExecutionPolicy cmdlet
again.
Set-ExecutionPolicy -ExecutionPolicy Restricted
Running the above command will re-enable the Execution Policy restriction
on your PC.
Figure 4.2 Using the information in an error message to find and resolve the issue
Error messages like those explored in figures 4.1 and 4.2 help us correct
errors and learn more about PowerShell as we develop our scripts. Next, let’s
look at how we can further de-bug scripts in PowerShell utilizing VSCode
and PowerShell error messages to get from broken to working.
Unlike all of the other code in this book, this code is intentionally left
broken. On the whole, it should look very much like some of the other
scripts we have been working with this far and through the rest of the book.
But when you run it, as written, it will have several syntax errors and even a
logical error.
Don’t worry if you can’t understand what the code is doing. We’ll get to
loops and if statements later in the book. For now, we’re just trying to debug
the code so it runs.
If you’re familiar with the song 100 bottles of beer on the wall, you should
get a kick out of this script (Figure 4.1). When we’ve finished debugging the
code and finally get it to run it will print out the full lyrics to the screen for
the whole song; Of course it won’t do it just yet.
The “project” in this Tiny PowerShell Project is not about what the script will
do for us; but rather how to make you, the reader, a more capable scripter and
able to debug common issues when you see them.
Figure 4.5 Examples of Syntax issues that don’t seem to make sense.
Looking at the output of the “Problems” tab we see that PowerShell is
informing us there is a missing curly brace somewhere in our code. But
there’s obviously an issue with this code as we clearly have the closing brace
included. For some reason VS Code is not seeing it correctly. We can step
our way through the issues detected by VS Code to see if anything jumps out
at us (Figure 4.6).
Looking at between line 11 and line 14 we can see the culprit. There is an
extra quotation mark at the end of line 11. VS Code saw the $song variable
as:
$song="`t$i $object of beer on the wall.`n"
Line 12-14 is no longer seen as part of the $song variable. The quote at the
end of line 14 is seen as the start of a new sting. So, everything beyond this
point is seen as a string. That’s the reason that it didn’t see the closing curly
brace on line 18.
We can correct the code so that it looks like this:
$i="100"
do{
$object="bottles"
$objec2="bottles"
If($i == 1){
$object="bottle"
}
If ($i2 -eq 1){
$object2="bottle"
}
$song="`t$i $object of beer on the wall.`n
$i $object of beer.`n
Take one down pass it around.`n
$i2 $object2 of beer on the wall.`n`n"
$i2=$i-1
Write-Host $song
$i--
} until ($i -lt 1)
When we run this version of the code there are significantly less syntax issues
(Figure 4.7). But now we’re stuck in an infinite loop! We can stop the
execution by hitting the red square (stop) button on VS Code.
When we fix the code, we no longer see any VS Code identified issues, nor
do we see any underlined red in the window. However, the code still will not
run successfully. When we run it we see two error messages:
What are these errors if they’re not errors in syntax? These are logical errors.
Add Breakpoint
Let’s also call each of our variables to make sure that they hold the value we
expect them to.
$i
$object
$object2
$i2
Let’s check to see what PowerShell has for these variables. First, we will add
Write-Host statements to check the values in our variables. Often when a
script fails it is because the value within the variables is not what we think it
should be. Validating the input and output of these variables can be a critical
first step to debugging.
Depending on when you run this code you can see a very strange behavior.
Sometimes when you run this code you might see a value 99 returned for
$i2. Other times when you run it you might see no value for variable $i2
(Figure 4.11).
When you’re debugging it’s a good idea to always clear your variable values,
so you don’t have old values you’re not expecting show up.
We can fix the issue we’re seeing with the null value for $i2, by simply
moving line: “$i2=$i-1” somewhere above our break. So, let’s move it to
just above the section we first use $i2.
$i="100"
do{
$object="bottles"
$object2="bottles"
$i2=$i-1
If($i == 1){
$object="bottle"
}
If ($i2 -eq 1){
$object2="bottle"
}
Write-Host "variable i is: $i"
Write-Host "variable object is: $object"
Write-Host "variable object2 is: $object2"
Write-Host "variable i2 is: $i2"
$song="`t$i $object of beer on the wall.`n
$i $object of beer.`n
Take one down pass it around.`n
$i2 $object2 of beer on the wall.`n`n"
Write-Host $song
$i--
} until ($i -lt 1)
Those starting values look great now (Figure 4.12). But we’re still seeing a
couple of errors in the output. First look at this one (Figure 4.13):
The error is telling us on line 6 the term “=” is not recognized. This is in our
if statement where we’re checking to see if the value of $i is equal to 1. But
if you remember from chapter 3, PowerShell, unlike some languages like C,
uses -eq to denote if a value is equal not the “==” we used here. Let’s fix
that!
$i="100"
do{
$object="bottles"
$object2="bottles"
$i2=$i-1
But what happens when we step through the breakpoints? When we utilize
VSCode’s debugger, we can set breakpoints manually. VSCode also inserts
automatic breakpoints to help us identify issues with our code. We can use
the Step tool to step through those breakpoints to see if our code executes
(Figure 4.15).
Programming Shorthand
There are many common techniques that programmers run into over and
over. To save themselves time, effort and typing a kind of shorthand was
created for a lot of these very common things. Here are a few that will be
useful for you to know at this stage.
PowerShell is seeing the value as a System.String or (String) [Chapter 2].
Just like you can’t subtract a number from a word (cat -1, for example,
doesn’t make any sense), you can’t subtract an integer from a string.
PowerShell is not seeing the value of $i as the number 100, but rather as the
word “100”.
Strings are defined by putting them in quotes. We can remove the quotes
from line 1 and PowerShell should now see the value of $i as the number
100.
With the loop issue resolved, we can remove the Write-Hosts displaying the
starting values of the variables: i, i2, object, and object2.
$i=100
do{
$object="bottles"
$object2="bottles"
$i2=$i-1
If there’s one constant in the world of IT it’s the fact that users will lock
themselves out and come to you, the admin, to unlock them. Typically, this
involves opening up Active Directory Users and Computers and then
searching for the user, opening their account clicking on a tab and checking a
box.
It doesn’t take up a ton of time. But it is something that can be made much
easier with PowerShell. Now, imagine, for a moment that you have a
network issue in one of your remote locations. Some service has stopped and
all of your users in a particular geographic area are locked out.
At this point you can be reactive and wait for tickets or phone calls to roll in
and unlock the users one by one. Or with the use of this script, you can see
with a single click that there is a significant number of users from any given
network location that are locked out.
This simple inclusion in a script that you’d be using to make your life easier
can take you from reactive to IT superstar.
This process is okay if you have one or two accounts you might want to
unlock, but it’s tedious and reactive. You don’t have a good way of
proactively seeing who on your domain is currently locked out, so it’s
difficult to pick out trends.
Find all of the users in your whole domain who are currently locked
Pull each user’s name, username, and location into a numbered list
Prompt the user which user they’d like to unlock
Unlock the corresponding user.
Confirm the user is unlocked.
Arrays are part of what makes PowerShell more flexible and powerful than
many other scripting languages. We’ve already discussed variables and how
useful they can be for storing things like, strings, integers, and objects for
later use.
Arrays do the same thing. But unlike variables which store only one value.
Arrays are capable of storing many different values at the same time!
Variables vs Arrays
Just like in PowerShell you denote a variable by starting with the dollar sign
($), arrays are instantiated with the @ and enclosed in parentheses. @().
Each object inside is differentiated by a comma.
On the surface a variable and an array may be hard to distinguish, but it will
quickly become evident how different they are (Figure 5.4).
We can assign the same values to the array and variable above. When we
write them out the output appears to be identical.
However, when we try to use the data within the array and variable they
behave very differently (Figure 5.5).
Figure 5.5 Although they look similar PowerShell treats Arrays and Variables very different
This distinction makes the data stored in the array much more available to us
for manipulation.
But how do we access the values in the array? Like the shoebox example,
let’s say you wanted to get a specific pair of shoes from the rack full of
shoeboxes. How can we make sure that we get exactly the box that we
want? The easy way is to number the boxes, then when we want the shoes in
box four, for example, we can ask for the shoes specifically in box four.
Fortunately, arrays work in a very similar way. When an array is created
each object in the array is numbered as well. These numbers are known as
indexes. We can reference any index of an array by putting the index number
inside square brackets: [some number]. This tells PowerShell exactly which
index to retrieve and will return the value of the data in that index.
Just like in the above example, if we wanted to retrieve the value in the fourth
index of an array, we would type the name of the array and then the index
number in square brackets (Figure 5.6).
This would retrieve the fourth index of the array, in our case the string ‘red’.
Now, you might be wondering, if we wanted the value of the fourth index of
the array, why did we use:
$array_colors[3]
a= first term
n= index
d= common difference
$array=[5,10,15,20,25,30,35,40]
If we wanted the 3rd index of the array, we would need to use the formula:
a+(n-1)*d
5+2*5=15
We got the same answer, but with zero based indexing we didn’t have to
subtract 1 from n. For the computer this is a calculation saved. Today’s
modern computers can execute between 150,000,000 to 200,000,000
calculations per second. The IBM 603 developed in 1946, however, could
make as many as 6,000 calculations per hour. So, you can see why in the
early days of computers you would want to save as many calculations as you
could.
Referencing the values of the indexes of the array makes it far easier to
access and re-arrange those values. Let’s say for, example, we wanted to
change the order of the colors in our output. If we wanted to reverse the
colors in the array it’s simple (Figure 5.6).
$array_colors=@('green', 'yellow', 'blue', 'red', 'black')
Write-Host $array_colors
Write-Host $array_colors[4..0]
Splits
There are times you want to break up a string and select only the parts or part
you want. If there is a specific pattern to the string you can use the split()
method to basically cut the string into parts (Figure 5.7).
However, your boss doesn’t like the output. Your boss would prefer that the
report listed the user logged in followed by the computer that they were
logged into. At this point, you can try to regenerate the list, which may be
difficult or time consuming or we can use PowerShell to do the work for us.
Here we can import the list into an array. Since each one of these items is in
quotes it indicates to PowerShell that these objects should be strings. So
instead of having one value in a variable, we have five string values in an
array. Each one of these values is a separate element of the array. Since they
are string objects we can use the split() method and then split on the colon
that divides each of the PCs and users. This will automatically store the new
elements in a new array. We can then flip the order of the indexes of this
new array and write this to our console with a write-host (Figure 5.9).
$array=@("PC01:gwashington","PC02:jadams","PC03:tjefferson","PC04:jmaddison"
write-host $array[0].Split(":")[1,0]
write-host $array[1].Split(":")[1,0]
write-host $array[2].Split(":")[1,0]
write-host $array[3].Split(":")[1,0]
write-host $array[4].Split(":")[1,0]
Figure 5.10 Modifying individual elements of and array (the hard way)
This code works. But it was only 5 elements. Imagine if there were on
hundred, or a million, that’s a lot of typing. There must be an easier way!
This is where loops come in. A loop sets up a section of our code that
executes over and over until a condition is met. It can run once or an infinite
number of times, until the condition is met. What if, instead of listing out
each of the elements in our $items we told PowerShell to do the same thing
for each one until there were no more left? That is exactly what a for loop
does!
For Loops
The first type of loop we’re going to explore is called the for loop. Where for
each object in the list we are going to do the same things to it (Figure 5.10).
$array=@("PC01:gwashington","PC02:jadams","PC03:tjefferson","PC04:jmaddison"
foreach ($item in $array){
write-host $item.split(":")[1,0]
}
The structure of a for loop (Figure 5.11) should include at least these three
things to make it work correctly.
There are many types of loops, all of which essentially do the same thing, by
performing one or more actions to one or more targets. We’ll cover more of
the different loop types in future chapters. The difference between loops is
largely how the loop determines if and how many loops need to be done. The
foreach loop does the same action to each target until there are no more
targets. Other loops, like do while and do until loops, use conditional logic to
determine if the loop still needs to run or if it has already met the condition to
stop the loop.
5.5 PSObjects
So far, we have covered a number of ways to store information. We can store
information in a variable and in an array. But what if we need to store
multiple pieces of information about a single type of object? In the Tiny
PowerShell Project code we are looking at a user. Then we are pulling the
user’s SamAccount name, the user’s full name, and the user’s location from
Active Directory. We can pull all of these into variables; we can even store
all of this information in an array. But how do we do this if we want to make
sure that all of the values are associated to each other?
By utilizing a PSObject we can take a group of objects and collate them into
a single object. Once we have created the PSObject, we can use the cmdlet
called “Add-Member” to add a “NoteProperty” to the item. A NoteProperty
is a generic property that PowerShell can create and assign to an object.
When you create a NoteProperty you assign it a name and a value and
PowerShell assigns that property to the object.
PowerShell gives us a single cmdlet that does all that work for us.
Unlock-ADAccount jdoe
This single command performs all these steps and unlocks jdoe’s account
(Figure 5.13).
However, there are times, when there is a legitimate reason to unlock all
accounts; for example, an Exchange Server issue might cause a large number
of accounts to be locked. Instead of unlocking all these accounts manually,
or even with the script we developed in this chapter, you can unlock them all
at once. Sometimes the efficiency of unlocking a large number of locked
accounts outweighs the potential security risk of unlocking all of your
accounts with a single click.
It is, after all, for this very power and flexibility we’ve turned to PowerShell
in the first place.
This task manually, could have taken an entire team of people hours to
accomplish. Congratulations, you’re a superstar.
5.8 Summary
There are many ways to store information with PowerShell. Arrays are
powerful data structures designed to hold many elements of information
and treat each element as a separate data piece.
Referencing an array is as easy as calling the whole array, like you
would a variable, or by referencing a specific element or multiple
elements from the array by utilizing the square braces.
Array indexes start at 0 and the indexes count up from there. The first
value of the first element of the array, in PowerShell, can be called by
accessing index 0.
Loops are ways your program or script can perform the same action or
actions on multiple targets until an end condition is met.
Loops can run zero to an infinite number of times depending on the
conditions you set for the start and end of the loop.
The foreach loop will perform the same action or actions on each
element in a list until it performs the tasks on all of the elements of the
list, at which point the loop terminates.
PSObjects are objects that can be used to help you keep groups of
related information together.
PSObjects can use the Add-Member cmdlet to add custom properties to
the object known as NoteProperties.
Using Active Directory Cmdlets, arrays, PSObjects and Read-Host
menus you can create a script that can unlock any locked user on your
domain with simple menu selection.
6 Manage groups like a boss
This chapter covers:
The basics of utilizing conditional logic in our scripts
Identifying the Active Directory Groups any User in our Domain is a
member of
Using an existing user as template to copy group membership to anyone
with a single script
It’s common in today’s environments to manage file access with the concepts
of least privilege and Role Based Access Controls (RBAC). This prevents
anyone without a need to access specific company data from accessing the
data. This is exactly what Active Directory groups were created for.
But managing groups and making sure that new users have all the file access
they need can be cumbersome at best and a true nightmare at worst. This
Tiny PowerShell Project will throw admins a lifeline in this regard.
Traditionally, when you want to create a new user in a department you look
for the groups that they will need to be a member of. Few things are more
frustrating to users and their managers than waiting days to get their new
employee a login then waiting weeks before that employee can actually start
doing what they were hired to do.
Prompts the user for the login name of the user they wish to copy from
Prompts the user for the login name of the user they wish to copy to
Confirms the selections are correct (this is not required for the cmdlets
to work, but since we’re dealing with security relevant objects like
Security Groups it’s a good idea to make sure there’s a chance to catch
any mental flubs)
Looks up the template user in Active Directory and copies all of the
groups they are a member of into an array so we can import these same
groups to the target user.
Copies groups in the array to the target user.
In several places in this chapter’s script you see the `n within the string. This
is PowerShell shorthand for a new line. This makes the output much easier to
read (Figure 6.2).
Figure 6.2 How and why to use new lines within strings.
Humans use conditional logic all the time, it’s so ingrained in our everyday
behavior we rarely even realize we’re using it.
Imagine we were teaching a computer to drive a car. We can tell the car to:
Drive
Stop
Turn
If we wanted the car to turn right at the second intersection. We might give it
instructions like
Much like teaching a car to choose the correct actions for different traffic
signals, you might want a way to treat inputs, outputs or objects differently
from each other. You wouldn’t treat the local administrator account the same
way you would a typical user account. You wouldn’t treat a Domain
Controller the same way you would a workstation.
But with the use of conditional logic, we can have the script begin to make
decisions on how it behaves based upon the inputs, outputs or objects it
interacts with.
Where-Object
Going back to the car example: What if we gave the car some ability to
understand conditional logic? Not only would we give it the basic commands
above, but we would give it two more abilities.
Drive
Stop
Turn
If the light is green, go
If the light is red, stop
Now as the car drives to the first intersection it’s able to act differently
depending upon the color of the light. If the light is green it will go, if the
light is red it will stop (Figure 6.3).
6.1.1 If
The most basic conditional logic we can use is known as the “if statement”.
If a condition is met, then perform an action. In the car example we use this
exact statement “If the light is green, go.” You can use the same statement in
your code.
To use the if statement, you use the structure shown in Figure 6.4. Start the
line with if followed by two parentheses. The statement you are comparing
goes into the parenthesis. If the statement is true anything within the
following curly braces will be executed.
6.2.1 Get-ADUser
We have used the cmdlet Get-ADUser before. But Active Directory stores so
much information about each user it would be difficult to explain all of the
information stored by Active Directory in a single chapter. We have already
used this command to find users who are locked out by utilizing the
.lockedout attribute. Now, we’re going to explore the . MemberOf attribute.
Memberof
This information is great. We can now copy each of the groups jdoe is a
member of to any other user in our domain. But wouldn’t it be nice if there
was a cmdlet that let us do that instead of having to do it one at a time in
Active Directory Users and Groups?
6.2.2 Add-ADGroupMember
Utilizing the Add-ADGroupMember cmdlet we can quickly and easily add
new users to existing groups (Figure 6.8).
If you’re still new with concepts like loops and if/else statements take a
moment here to make sure you understand the branches your code will take
to complete the task of copying group membership from one user to another.
Using the power of the IF statement we proceed with the remainder of the
script only if our user enters a “y” or “yes” (or anything that actually starts
with the letter “y”).
If the user does not enter something that starts with the letter “y”, the script
jumps to the Else statement where it prompts the user to re-try the script
when ready (Figure 6.11).
Figure 6.11 Utilizing the If/Else statements from Tiny PowerShell project code.
6.3.3 Create a variable containing the groups
MemberOf is an attribute of the objects output by the Get-ADUser cmdlet, if
you provide the MemberOf as an argument to the properties parameter when
you use Get-ADUser (Figure 6.12). Utilizing this, we are able to store the
value of each of the groups that our “$Copy_From” user is a member of and
store it in our $groups variable (Figure 6.13).
Figure 6.12 Illustrating how the MemberOf attribute is handled on objects returned from Get-
ADUser.
Figure 6.13 Using Get-ADUser to store groups a given user is a member of
6.3.4 Loop through the list
Using a Foreach loop we select each group in our list of groups and then
utilize the Add-ADGroupMember to add the user in the “$Copy_To” variable
to the group (Figure 6.14).
6.4 Summary
PowerShell derives power and flexibility from conditional logic.
Utilizing tests and comparisons you can branch your code to treat
different results in different ways.
When utilizing an If statement, your statement will either evaluate true
and the actions within the If statement are performed or false, where
they are not.
By utilizing the Else statement, you can perform actions on statements
that do not meet the conditional logic in the if-statement.
The Get-ADUser cmdlet has access to all of the values stored for any
user in your Active Directory database. Many of these values are not
returned by default but instead are accessed by specifying the name of
the attribute in the -properties parameter of the Get-ADUser cmdlet.
Groups the user is a member of are stored in the Memberof attribute in
the Active Directory user database.
You can add additional group membership to any user by using the Add-
GroupMember cmdlet available in the ActiveDirectory module.
Looping through a list of these groups can quickly and easily add a
targeted user to any number of Active Directory Groups.
7 One-click Exchange account fix
This chapter covers:
How to build a function that we can utilize multiple times in our script.
A new type of loop: the Do-Until loop perfect for status updates.
Built in Exchange functions that lets us create, remove, backup and
import user mailboxes.
At the time of writing this, email has been and is the king of internal
communication. With the rise of social media and the ever-present text and
phone communication options email has lost some of its ride or die status; yet
it’s clear that this form of communication is not going anywhere soon. As a
system administrator it’s likely you’ll be tasked with some common
Exchange related tasks.
Currently there are three main types of Exchange solutions offered to your
typical enterprise.
On-Prem
As a Service
Hybrid
One thing they all have in common? You, as a system administrator, are
going to be interfacing with them. Even with your email as a service and
hybrid solutions utilizing Office 365, management of your email systems is
up to you.
Both EAC and ECP are Web-Frontend GUIs, but you can fully control every
aspect of your Exchange system through the Exchange Management Shell
built on Windows PowerShell. This means that with the use of PowerShell
scripts you can administer your entire Exchange environment faster, easier
and more efficiently. Typical administrators using the GUI will be forced to
modify mailboxes one at a time. Utilizing the skills you’ve already learned
such as variable arrays, conditional logic and loops you can administer
hundreds or thousands with a script and let the computer do the work.
One of the most common fixes for broken exchange mailboxes is simply to
disable and re-enable the mailbox. This essentially re-creates the user’s
mailbox and fixes all kinds of issues from mailbox corruption to database
integrity issues. It is not, however, without its downsides. Primary among
these is the fact that when this is done, the user’s old email is no longer
associated with the new mailbox.
If your email users are anything like mine, they use their email boxes as their
own personal time machines going back and pulling that one critical email
out of the message ether from fifteen years ago. Thus, the thought of losing
their stores of historical emails is as terrifying as the latest Friday the 13th
movie; and much more real.
The easiest way to resolve this is to back up the user’s .pst file (Offline
Outlook Data File). This holds the user’s emails, calendar items and other
things. With the .pst file saved we can safely re-create the user’s mailbox and
re-import the .pst and restore all of the historical data.
The typical issue with this is that backing up a .pst file, especially for a large
mailbox, is a long process. Relying on a user to back up their own .pst file
can work in some situations; but destroying a user’s email box and relying on
them to back up their own messages can lead to nightmare scenarios where
the data is gone for good and you’re dealing with an enraged user bent on
making you feel their pain.
This can be fine, however, time consuming for individual users. But if you
have a significant corruption of a database affecting many users, this process
could take several days before you’re able to restore your users. PowerShell
to the rescue!
No more wasted time! While the script is running the system admin is free to
address the many other issues that come into play with a major Exchange
issue, including communication of the outage and release of status reports,
and he/she is able to give status updates to upper management without ever
having the ETA affected.
In the last chapter we looked at how we might program a car to drive itself.
But the process of driving is fairly complicated. You must press the
accelerator, or the brake. You must steer and utilize turn signals. Let’s take a
quick look at how we might instruct a car to drive (Figure 7.2).
Let’s look to see what it takes to add a turn-signal to the script (Figure 7.3).
Turn Right
Turn Left
Turn None
We can use a function called “Turn,” shown in Figure 7.4, to do exactly this!
In our script for this Tiny Project, we utilize the Do-Until Loop. The Do-
Until Loop runs in a loop until an exit condition is met. If we have a situation
where we want to exit a loop when an error is detected, we would have to add
an IF statement and a Break command to exit the loop if an error was
detected. The Do-Until Loop has this built in. It’s kind of a combination of a
loop and an If statement.
In the example below (Figure 7.7) we’ll get the system time, just looking at
the current second, then sleep for one second. If the second equals 15 the
loop will end. This means that if we run this loop at the top of the minute, the
loop will run 15 times checking the first second then the second and the third
until it gets to the 15th second. If start the loop on the 16th second it will run
59 times. After each loop the Do-Until loop will check to see if the exit
condition is met and if it hasn’t it will run the loop again.
Get-Date
To accomplish this, we’ll need a way for PowerShell to get the current
second from the system time. Fortunately, there’s a cmdlet already build for
this functionality called Get-Date. The Get-Date cmdlet gets a DateTime
object from your system and is capable of formatting it in many ways.
By default, Get-Date returns the Day of the Week, Month, Date, Year, Hour,
Minute, and Second. But we can easily isolate any of these elements, to
retrieve only the current second, we use the dot notation to request only the
seconds parameter from the returned DateTime object.
Figure 7.7 Do-Until loop example
There are times when using conditional logic where we need to check for
multiple conditions. The logic we are trying to apply might be useful if,
some, none or all of the conditions are true.
Let’s look at the conditional logic we are using to end our do loop, shown in
Figure 7.8.
Queued
InProcess
Completed
Failed
We don’t want to exit our do loop until the process of backing up the .pst has
finished. But we don’t want to proceed with disabling the mailbox if the .pst
backup has failed. So, we really care about only two of the four possible
results of the $status variable (Completed and Failed). We can set either of
these conditions as a way out of our do-until loop with the inclusion of the “-
or” statement in our Conditional Logic.
Now that we understand the structure of the loop let’s look at what we can
use the loops to perform. In our Tiny PowerShell project code, we are using
the do-until loop to wait until the status of the ImportMailbox or
ExportMailbox command is complete. But how do we check the status to see
if the condition is met? We can use PowerShell to query the Exchange server
and check.
We could write multiple functions that do very specific things. Or we can try
to make a more dynamic and universal function that allows us to perform
different functions depending upon the types of parameters we pass it when
calling the function. Using these parameters and conditional logic we can
construct a very specific Exchange server command.
7.3 Select-Object
Select-Object is a cmdlet that allows us to select an object or part of an object
and return that value. PowerShell and scripting in PowerShell is all about
learning how to manage the objects you care about. Select-Object can be used
to select specific number, types and parts of objects all of which make our
lives as PowerShell scripters easier.
Select-Object -Property
There are many times where the results of a command are simply more than
we care about. If, for example we were only interested in a complete list of
email addresses in our Active Directory Database, running a Get-ADUser
cmdlet and having the user’s Name, SamAccountName, DistinguishedName
and all of the other values that are returned by default from that cmdlet
(Figure 7.9) would be something we would need to filter out.
Figure 7.10 Trying to filter for more than one attribute with a dot notation.
Utilizing Select-Object, however we can select multiple Active Directory
attributes with the -Property parameter (Figure 7.11). Here we can select for
one or more attributes on the object with ease.
Figure 7.11 Using Select-Object to select more than one property from a returned cmdlet.
Select-Object -ExpandProperty
Some object properties are a collection of values or nested objects. When you
try to display them, they may not display properly. We can utilize our
MemberOf Active Directory attribute as an example. The MemberOf
parameter in the Get-ADUser cmdlet returns the groups the user is a member
of. But these could be many, many groups. The -property parameter of the
Select-Object cmdlet will not return them all. Instead, it will truncate the
output to indicate that there are more objects that are not being displayed.
Utilizing the -ExpandProperty parameter, however, will return all of these
values (Figure 7.12).
Notice that there are a few strange characters in the Where-Object part of our
Get-MailboxExportRequestStatus function? There are tiny backticks in front
of the $ and both quotation marks. These are known as escape characters. See
the sidebar below for more information on escape characters if you’re
unfamiliar with how they work.
Escape Characters
New-MailboxExportRequest
Get-MailboxExportRequest
Disable-Mailbox
Enable-Mailbox
New-MailboxImportRequest
Get-MailboxImportRequest
We will touch on how we can use these scripts to perform our mailbox repair.
But our use case will only scratch the surface of what the Exchange modules
are capable of. Let’s see how we can utilize these specific Exchange cmdlets
to repair a user mailbox with a single script. Then, if you’re interested in
learning more you can check out the further details of the Exchange modules
at https://docs.microsoft.com/en-us/powershell/module/exchange.
7.4.1 New-MailboxExportRequest
New-MailboxExportRequest uses the native PowerShell backend to create a
.pst copy of a user’s mailbox. This is simply a way to back up an existing
user’s mailbox/calendars/contacts and folders (Figure 7.15).
Now that we have sent the Exchange Server the request to back up a user’s
mailbox we can check the status of the request by utilizing the Get-
MailBoxExportRequest. This will provide you details on where in the process
the ExportRequest is. (It takes about 5 minutes for the average mailbox to
complete this step. But extremely large mailboxes could take much longer).
There are a number of status that can be returned by this cmdlet. The typical
statuses you might expect to see include:
Queued
InProgress
Completed
Failed
There are additional statuses that can be returned each of these can be found
in the cmdlet’s help file or online help sections. Since this is not a book that
intends to deep dive into Exchange specific troubleshooting the four listed
above are the statuses we will be focusing on.
The two statuses that we’re interested in for our script are the “Completed”
and “Failed”. We have our do-until loop in our function to check for these
two key statuses. If an Export is Queued we will simply wait, which our do-
until loop does quite well. If it’s InProgress, once again we are good to wait.
Thus, we will also terminate our loop if it returns a “Failed” status. Our script
is looking for this value and will write an error message to our user and end
the script before any damage is done.
7.4.3 Disable-Mailbox
The Disable-Mailbox (Figure 7.16) cmdlet disables a current user’s mailbox.
The user account remains but the mailbox is no longer associated with the
user’s Active Directory attributes. The mailbox does still exist and can be re-
connected by utilizing a Connect-Mailbox cmdlet.
7.4.6 Get-MailboxImportRequest
Much like when we used the MailboxExportRequest we will want a way to
monitor the status of the import request. In general, a failure at this step is
less of an issue than a failure on the automated backup process. Since the .pst
file has already been backed up, if for some reason, the automated import
fails, we can import manually.
Queued
InProgress
Completed
Failed.
If we get a failed status returned our code is set to inform us of the failure and
tell us the location of the .pst created so the System Admin can then re-import
the .pst file manually to the new mailbox.
7.5 Putting it all together
Here’s how all of the tools work together to enable us to automatically repair
a user’s Exchange Database corruption with a script.
It utilizes a new loop function called a do-until loop. The loop checks for the
status and then waits until the status is either “Completed” or “Failed”. The
function then returns the “Completed” or “Failed” result once the
backup/import is complete.
Utilizing a function saves us from having to type the code twice (once for
backup and once for import). It also makes the script easier to update because
if we later make a change to this; we only have to change it once to affect any
number of times we later utilize the function.
Figure 7.20 Get user information from Tiny PowerShell project code
The .pst file from the old mailbox is then imported into the new mailbox
utilizing the New-MailboxImportRequest cmdlet (Figure 7.29). This ensures
that the new mailbox now includes all of the old mailbox’s email, folders,
calendars and contacts for a seamless transition.
Once more utilizing the function we created we are able to monitor the status
of the mailbox import request using the Get-MailboxImportRequest. The
function once again checks every five seconds for the status to change to
“Completed” or “Failed” (Figures 7.30, 7.31, and 7.32).
7.6 Summary
Functions are a list of PowerShell statements that are assigned a name.
These lines of code can then be called in your Main script once or
multiple times.
Functions can save a lot of repetitive code in your script by just writing
it once and calling it each time you want to perform those statements.
Functions can be simple or very dynamic; capable of loops and
conditional logic that help you branch your code to make a single
function perform a variety of actions.
There are many types of loops used in PowerShell.
While loops all do the same thing, running PowerShell statements
repeatedly on one or more targets, different types of loops can be run to
determine targets and exit conditions.
A new type of loop is known as the do-until loop, where the statements
executed on targets until a specified exit condition is achieved through
the evaluation of conditional logic.
Conditional logic does not need to be a singular statement but can
include multiple evaluations with the use of the -or within the condition.
There are times when constructing complex strings you need specific
key characters to be ignored. You can do this with an escape character.
PowerShell uses the backtick (`) as an escape character.
When you load the Exchange Module in your PowerShell session you
can access to many Exchange related cmdlets. We used the New-
ExportMailboxRequest, New-ImportMailboxRequest, Get-
MailboxImportRequest, Get-MailboxExportRequest, Disable-Mailbox,
and Enable-Mailbox cmdlets to backup a user’s mailbox information,
repair and then import the old data back.
The New-ExportMailboxRequest and New-ImportMailboxRequest
cmdlets export a user’s mailbox information into a .pst file and then
import this .pst file to a new mailbox, respectively.
The Get-MailboxImportRequest and Get-MailboxExportRequest
cmdlets check the current status of mailbox import request and export
request, respectively. They return one of many statuses including, but
not limited to, Queued, InProgress, Completed, or Failed.
The Disable-Mailbox cmdlet removes the association with the Exchange
mailbox and database from the user’s Active Directory user account and
database.
The Enable-Mailbox cmdlet creates and enables a mailbox in Exchange
and makes all of the associations in the Active Directory user account
and database for use on the Domain.
8 Unified user creation
This chapter covers:
Using conditional logic to call a function.
Using Exchange cmdlets on any PC in your domain utilizing
PSSessions.
Creating unique usernames to prevent duplicates
Unifying several scripts to create a user, email and default groups with a
few questions.
Testing and optimizing your searches.
Up until now we have dealt with scripts that do very specific things. They
might help us create an email address, create a user, or transfer group
memberships. Each of these scripts are useful in their own right, but we’re
interested in automation. Why not create one script that does all of these
things?
Fortunately, there are guards we can put on our scripts that will help us
control for these unintended consequences. The first of these guards is the
Mandatory attribute within the param keyword.
When we use the param keyword to set the Parameter attribute on our script
or function we can set a Mandatory attribute to tell PowerShell if our
expected parameter(s) are mandatory, or if, like in the script above, they can
safely be ignored.
When the script is called, if any of these parameters are omitted the script
will prompt the user for these missing values. So there is no foreknowledge
of the parameters required.
8.3.1 New-PSSession
In order for us to create a persistent connection to a remote computer we first
need to use the New-PSSession cmdlet.
In the next chapter we’ll discuss remoting into computers. When we do this,
we often use the Invoke-Command to run a specified command or cmdlet on
a remote computer. This utilizes an unmanaged PSSession known as a
Temporary session. PowerShell creates the session, executes the command
and terminates the session all without any input from us.
Note
This code would create persistent connections to each of these three remote
computers.
This object can be saved to a variable for later use in managing the
connection, reconnecting, or removing the session without having to look up
the session information.
8.3.2 Get-PSSession
We can see the status and information of each of our PSSessions by running
the Get-PSSession cmdlet.
Note
Because books have a finite width, I have spanned several lines by breaking
up what could be a single line of code into multiple lines of code. As notated
in the README.md and in Chapter 3 [figure 3.1], in PowerShell 7 you can
natively span lines at any pipe (|).
But due to width concerns I have broken the lines not at pipes (|), but instead
at cmdlet parameters. We can use the backtick (`) to tell PowerShell that our
command naturally follows on the next line.
Get-ADuser -Identity jdoe -Properties MemberOf
We will make use of these backticks (`) to span lines in the next section.
Figure 8.13 Tiny PowerShell project code: Connect-Exchange function using PSSessions.
Note
If for example we asked our user for their name, we don’t have any idea what
name they might enter. We could write 26 -like operators
(($name -like “a*”) -or ($name -like “b*”) -or Etc…))
This would do the same thing. It would look at the value of the variable
$name and if anything was in there it would return a true value. If, however,
the value was empty (or null, covered later in this chapter) the statement
would return false.
8.4 If not
When you are using conditional logic, often you want to make sure that
something is not equal to a value instead of testing for a specific value. If
you were patching computers in your domain and it required a reboot, you
would want to make sure you avoided patching your servers, so your critical
systems didn’t go down unexpectedly. You might have a handful of servers,
but you likely have hundreds, perhaps thousands of computer systems.
Wouldn’t it be nice to have a way to filter on the thing you don’t want and
not the thousands of things you do want?
Fortunately, PowerShell makes this easy. First you can simply modify the
comparison operator. Many of them have a “not” value:
Not -not
In the example below there are 104 listed computers. The PCs are named by
their six-digit serial number. Servers are called Server1, Server2, and
Server3. Instead of filtering for all of the possible combinations of six-digit
serial numbers, it would be much easier to simply grab everything that wasn’t
a server.
When we check we see that all three servers have been removed and the
count is now 101, meaning that all the rest of the systems in the list are
unchanged.
In our Tiny PowerShell script, we define the variable that will contain the
FQDN for our Exchange server. This is best done as a variable, because it’s
possible that you may upgrade or move your Exchange server well before
this script stops being useful in your environment. If this value was hard
coded in your script you would need to find every instance of this value in
your script and change it. By capturing it as a variable, you will only need to
change this value in one logical place if it ever changes.
Next our script will use the If -Not logic to see if the PSSession has already
been connected. It is a drain on our networking and computing resources to
establish multiple remote sessions. Utilizing the If -Not logic we can
establish this connection to our Exchange server only if a connection is not
already present. This will save us time and resources when executing this
script.
8.5 Get-Credential
Very often, you will need to provide a system your password to authenticate
to the resources you are trying to access. We have learned so far how to
prompt our user for input and capture that input as a variable. Most of the
time this is fine, but when dealing with sensitive data we will likely want to
re-consider this practice.
Below is an example of how using the Read-Host cmdlet would potentially
expose our user password to a number of people.
When you create a secure string, you create an encrypted PowerShell object
known as a credential object. This credential can be converted from a text
with the ConvertTo-SecureString cmdlet or, as we just saw, the
AsSecureString method available to a PowerShell string object.
8.6.1 Measure-Command
One way to determine which process takes more computing time is to use the
Measure-Command cmdlet. Much like it sounds it simply returns the total
time that the command took to run. The syntax is to simply enclose the
command or groups of commands you wish to run within curly braces. By
default, the Measure-Command cmdlet returns a TimeSpan object. The
format of the default output is: Days:Hours:Minutes:Seconds:Milliseconds.
Figure 8.20 Example of Left and Right filter with Measure-Command cmdlet
Obviously, best practice is to use the better performing left filter. That being
said, in our Tiny PowerShell Project code we do not use this practice when
detecting for the existence of our Exchange connection.
if (-Not(Get-PSSession|Where-Object {$_.ConfigurationName -eq “Microsoft.Exc
Doing this, however, when the PSSession is not connected throws an error
seen below.
There is a way to suppress this error in the left filter above. We will explore
the -ErrorAction parameter in later chapters of this book.
If you create a variable but do not assign it a value, the value of that variable
is $null. However, if you create a variable and assign it even an empty string,
the value is no longer absent; it is empty; so, this is no longer Null.
Below we’ll see several examples of Null values and non-Null values.
This is because $null is what’s known as a scalar value. What this essentially
means is that the value of $null is a singular value. When you place a scalar
value in the left-hand side of the comparison operator it compares the whole
object on the right-hand side to the value on the left. Meaning that the whole
value on the left must be null to be evaluated as null.
If the value does come back as one already in use on our domain we use a do-
while loop checking each iteration of SamAccountNames until one evaluates
as NULL, so we can safely assign this SamAccountName to our new user
without fear of duplicating a username.
8.7 Summary
The New-PSSession cmdlet allows you to create a persistent connection
to a remote computer.
The Get-PSSession cmdlet will show you a list of all of the remote
PSSessions connected to your current PSSession.
Import-PSSession allows you to import cmdlets, aliases and functions
from a remote PSSession you are connected to directly into your current
PSSession.
Because persistent remote connections require both computing and
networking resources, it’s best practice to remove PSSessions connected
to your current PowerShell Session when you no longer need access to
these resources.
You can close existing PSSessions with the Remove-PSSession cmdlet.
You can target the PSSession name, group ID or even an object variable
you may have stored.
There are situations where it’s more efficient to use conditional logic to
search for something that may not be present, or filter for something you
don’t want, rather than list all of the things you do want. PowerShell
allows for this with its many -not comparison operators: -not, -ne, -
notlike, and -notmatch grant significant flexibility to your conditional
logic.
The Get-Credential cmdlet is a way to prompt a user for their username
and password without exposing the input as clear text. If you are
prompting a user for this information, you should always make sure to
protect this Personal Identifiable Information (PII) at the highest level
possible.
PowerShell commands are read from top to bottom and left to right.
Therefore, it is more efficient to filter as far to the left of your statement
as possible.
You can use the Measure-Command cmdlet to measure the length of
time your command, or series of commands, takes to complete.
Optimizing for performance will make all of your scripts run better and
faster, saving you even more time.
The null value in PowerShell represents an empty value or absence of
data.
It can be useful to check for the null value when using conditional logic.
Be aware that the null value is a scalar value and should almost always
be placed in the left-hand side of the conditional logic expression to
make sure that the whole condition is indeed null.
9 Making LOTS of users!
This chapter covers:
Additional Parameter options to collect credential objects and check for
null values
Building a Windows dialogue box to explore for a file using the GUI
Using the While loop
A new way to check for empty or null strings
Reading data from a Comma Separated Value list (CSV)
In the last chapter, we took a big step forward in making our lives easier. We
now have a script to create a user in our domain from start to finish with a
single click and some simple values. This is great! But companies typically
have some sort of onboarding procedure to bring batches of employees in at
the same time. Thus, as an IT Professional, it’s far more common that you are
creating new users in batches.
We have a script that will create a new user, but do we want to run it over and
over, perhaps dozens of times? Isn’t there an easier way? There is!
Accept a path for a specific type of input file: a Comma Separated Value
(CSV) list.
Accept a username and prompt for a PSCredential to authenticate into
our Exchange server.
Check to ensure the username and password is not simply null. A
username and password are required to authenticate to the PSSession on
our Exchange server.
Check to see if there is an established connection to the Exchange
Server and if not, create one using the PSCredentials provided.
Check to see if a path to a CSV file was provided; if not, create a
Windows Dialog Box to navigate to the file using the Windows GUI.
Read each CSV file line, pull values from specific headers, and assign
them to variables within our script: FirstName, LastName, and
CopyFromUserSamAccountName.
Using these variables, the script functions as it did in the previous
chapter, creating unique, valid user names, assigning email addresses,
and copying relevant group membership for each user in the CSV.
When the last user in the CSV is created the script removes the
PSSession to the Exchange server.
)
function Connect-Exchange {
param (
$ExchangeServer,
$ExchangePassSecure
)
$Session = New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri http://$ExchangeServer/PowerShell/ `
-Authentication Kerberos -Credential $ExchangePassSecure
Import-PSSession $Session -DisableNameChecking
Return $Session
}
#Variable for the FQDN to your Exchange Server
$ExchangeServer="exchange01.ForTheITPro.com"
Function Get-File(){ #C
[System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”)
| Out-Null
$FileDialogBox = New-Object System.Windows.Forms.OpenFileDialog
$FileDialogBox.filter = “csv (*.csv)| *.csv”
$FileDialogBox.ShowDialog() | Out-Null
return $FileDialogBox.filename
}
while ([string]::IsNullOrEmpty($CSVLoc){ #D
Write-Output "Navigate to the CSV file to create your users:"
$CSVLoc=Get-File
}
$LastLength=$LastName.Length
if ($LastLength -ge 8){
$LastName=$LastName.Substring(0,7)
}
$UserName=$FirstName.Substring(0,1).ToLower()+$LastName.ToLower()
$dup=Get-ADUser -filter {SamAccountName -eq $UserName}
if ($null -ne $dup){
$i=1
do{
$TestName=$UserName+$i
$dup=Get-ADUser -filter {SamAccountName -eq $TestName}
$i++
}until ($null -eq $dup)
$UserName=$TestName
if ($UserName.Length -ge 8){
$UserName=$UserName.Substring(0,8)
}
}
$LogFile="C:\Logs\$UserName.log"
Write-Output "Creating user: $UserName"|Tee-Object $LogFile -Append
New-ADUser -Name $UserName -GivenName $FirstName -Surname $LastName `
-DisplayName $UserName -SamAccountName $UserName -Enabled $false
|Tee-Object $LogFile -Append
Start-Sleep 5
$Groups=(Get-ADUser -Identity $CopyFromUserSamAccountName -Properties Me
$Groups|Add-ADGroupMember -Members $UserName
Write-Output "$UserName added to: $Groups"|Tee-Object $LogFile -Append
(Get-ADUser -Identity $UserName).SamAccountName|Enable-Mailbox
Write-Output "$(Get-Date) $UserName Account successfully created."|Tee-O
}
Remove-PSSession $Session #G
9.2.1 Mandatory
Suppose we wanted to allow our users to specify a path for our logs to be
stored. We may want to create this as a parameter that can be passed to our
script. That way, if our user has a folder they like to keep all of their logs in,
it saves them the time of having to move all of these logs manually.
param(
[Parameter (Mandatory=$true)]
$LogPath
)
Write-Output "All logs for this script will be sent to $LogPath"
9.2.3 System.Management.Automation.PSCredential
Figure 9.4 Using the Parameters in the Tiny PowerShell Project code.
Unlike what we did in the previous chapter, however, we are not going to
establish an Exchange session for each user we plan on adding to the
Domain. By putting this function outside of the loop we can establish the
connection once, use it for many users, then disconnect it once.
To see how this works, let’s take a quick look at this code with a statement
both inside and outside the loop.
$list=@(1,2,3,4,5)
Write-Host "This is outside the loop." #A
foreach ($number in $list){
Write-Host "This is within the loop" #B
}
It is fully possible to prompt our user for the path to this value and read in the
response with our Read-Host cmdlet. But what if the returned string has a
typo?
We can add in some error checking to make sure that the path exists and
contains the file. But what if the user types another incorrect path? The idea
of having two small errors in file paths that could be on a remote share or
several files, or several dozen files deep is not too improbable.
We could loop through our prompt and our error checking, constantly
prompting our user for paths. What if the mistake is not a typo but simply
confusion, the user typing C:\tmp instead of C:\temp? Or if it were a drive
mapping that is disconnected or unmapped? J:\... would not mean anything to
our script if “J” were not mapped. This lack of visibility can add to a lot of
frustration and wasted time.
What if instead of doing all of this we simply built a Windows Dialog Box
that allowed the user to explore their system and select the file with a click?
This capability would instantly clear up some of the above issues or at least
give our users a better place to start troubleshooting instead of assuming the
issue is a bug within our script.
This means that PowerShell can use the .Net Framework just like other
programming languages. We are going to use this flexibility to create a
Windows Dialogue box and use it to allow our users to select the exact CSV
they need.
On the surface, this line of code doesn’t do much. But what did it do? Well,
PowerShell by default doesn’t load a lot of .Net assemblies. The WinForms
assembly is not one of the few loaded by default, so we have to tell
PowerShell we want to use it.
What is an Assembly?
However, our needs are more simple than this at the moment. We simply
want Windows to give us a standard dialog box with an “open” and “cancel”
box. This will meet our needs at the moment.
This can be done with a filter method on the WinForms Assembly dialog
box. We also want to make sure that we capture the selected file as a variable
we can use.
In our script, we create a function that creates this dialog box, filters for .CSV
files, and then returns the filename out of our function into a variable called
$CSVLoc
Function Get-File(){
[System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”)
| Out-Null
$FileDialogBox = New-Object System.Windows.Forms.OpenFileDialog
$FileDialogBox.filter = “csv (*.csv)| *.csv” #A
$FileDialogBox.ShowDialog() | Out-Null
return $FileDialogBox.filename #B
}
The while loop executes the commands within the loop while the conditional
logic within the while statement evaluates as true. It can be thought of as
“While something is true, do something.” Once the while statement’s
conditional logic no longer evaluates as true the loop no longer runs.
$byte=8 #A
while ($byte -lt 256) { #B
Write-Output $byte #C
$byte=$byte*2
}
This type of logic can be useful for math, but it can be even more useful to
check to see if a condition exists. Let’s look at some input validation.
Definition
Input validation is simply the process that we employ to test the input
received by our application for compliance and compatibility. Input
validation can help make sure that the type of data we are expecting is what
we receive. In many cases, the type of data we receive can be important.
We’ve already discussed data types like strings and integers in previous
chapters. Input validation can help us make sure we get the type of data we
expect and can prevent significant errors later in our code.
We can use the While loop to make sure that the user enters a path for us to
use later in our script.
$path=Read-Host "What is the path to the file?"
while ($path -eq ""){
Write-Output "Path cannot be empty."
$path=Read-Host "What is the path to the file?"
}
$path
We use the While loop to re-prompt our user for the file location whenever a
null or empty value is returned for the location of the CSV file.
while ([string]::IsNullOrEmpty($CSVLoc)) { #A
Write-Output "Navigate to the CSV file to create your users:"
$CSVLoc=Get-File #B
}
9.5 IsNullOrEmpty
As you may have noticed in the while loop above we are using more .Net.
This time, we are using a string method IsNullOrEmpty. This method will
test to see if the string value of the object is either null or empty.
But as a part of our input validation, what happens if our user closes the
window or hits cancel before they select the CSV? In this case, the value
would be null but instead empty.
Instead of writing nested if/else statements we can put a single check within
our While loop to continue to prompt our user for a valid CSV location until
they successfully explore to the file and click on “open”.
while ([string]::IsNullOrEmpty($CSVLoc)) { #A
Write-Output "Navigate to the CSV file to create your users:"
$CSVLoc=Get-File
}
9.6 Import-CSV
In its simplest form, a CSV is a file that contains values separated by
commas. By using the Import-CSV cmdlet we instruct PowerShell to create a
collection of custom objects from a CSV file. Because of the file type,
programs that open the CSV file type recognize the structured data instead of
simply displaying the raw (comma-separated format). This allows for tables
to be easily constructed and manipulated.
Raw CSV files can be read in any text editor, but the real power and
flexibility of the file type is when you use spreadsheet programs like Excel to
create or modify them.
"President, Order, Years
George Washington, 1, 8
John Adams, 2, 4
Thomas Jefferson, 3, 8
James Madison, 4, 8"|out-file c:\temp\presidents.csv
By writing the output and separating the values by commas and the rows by
lines we’re able to use PowerShell to create a CSV.
Figure 9.11 The CSV file we created can easily be opened in Excel.
Figure 9.12 Opening the CSV in Excel no longer contains any commas within the data.
Excel can be used to manipulate the data, perform calculations, separate text,
merge cells and all of the many powerful functions people use Excel for
every day. I was able to update my meager president list by copying and
pasting the data found at the Library of Congress. I then broke down the
starting and ending years into separate columns and used a calculation
function to generate the term. Then resaved the data as presidents2.csv.
Figure 9.14 Importing the CSV brings the data structure into PowerShell.
9.6.1 How we use it in our script
To generate a new user we need to supply our script with three basic pieces
of information: A first name, a last name, and a user we want to copy
permissions from. Because of the ubiquities of Excel in business today,
likely, the first and last names of the users we’re being asked to create are
already in an Excel format.
We can easily create a CSV in Excel that includes the FirstName, LastName,
and CopyFromUser information for each of the users we are going to create.
PowerShell reads the data and creates properties for each of them. The
“Term”, “Name”, “StartingYear”,… etc. are each treated as properties for
each object within our collection.
We can use the dot notation to reference just the values in a single column.
$PresInfo=Import-Csv C:\temp\presidents2.csv
$PresInfo.name
By looping through the data we can isolate each cell and manipulate the
contents of any given cell. Say, for example, we only cared about the
President’s name and term of office. We could modify the CSV file
eliminating these columns. But there may be a time we need this data. Or we
may not have permission to modify the original file. With PowerShell, we
can isolate and display the values we want and order them in any way we
wish.
$PresInfo=Import-Csv C:\temp\presidents2.csv
foreach ($Row in $PresInfo){
$name=$Row.name
$term=$Row.term
Write-Output "$name served a term of $term years in office."
}
A First Name
A Last Name
A User to Copy Group Membership From
By collecting this information for each new user we want to create and
storing it in a CSV file we can use this file as the input source of this
information for our script.
9.8 Summary
In our previous chapter we developed a script that allowed us to create
Windows Domain accounts with ease. Using the script required that the
operator provide the script a First Name, Last Name, and User to Copy group
membership from. This is great when creating a single user! But running the
script over and over and manually providing the First Name, Last Name, and
User to Copy from was tedious. Instead of entering this information one at a
time, we can create a CSV file with this information for every user we want
to create leveraging Excel’s many powerful spreadsheet tools. With the use of
a CSV file that includes a First Name, Last Name, and User to copy
permissions from, we can create dozens, hundreds, or even thousands of
users on our domain with a single script.
If you’ve been in the IT industry long, you realize there is a lot of data to
manage. Each computer has dozens of components and characteristics that
are valuable. If a new virus, for example, affects only specific versions of
Windows 10 it can be a mad dash to figure out exactly how many computers
in your company are at risk and need to be mitigated and accounted for.
Hard Drive space can fill up rapidly, and critical data may be irrevocably lost
once a drive is full. As PCs age, you may need to upgrade RAM to keep the
system functional. Serial numbers are often used by vendors for support, and
for remote computers, in the past, gathering all of the required data meant a
phone call at best and perhaps even a trip to the location at worst to get this
information and more. Worse, if you didn’t gather all of the data or recorded
it incorrectly this could mean a costly re-work.
Wouldn’t it be nice if we could simply run a script and gather all of this
information and more from any computer, a list of computers (say all of the
PCs in a particular geographic location), or all of the active computers on
your domain? This is exactly the need we’re going to address in this very
chapter!
while ([string]::IsNullOrEmpty($LookUpType)) {
Write-Host "Do you want to query a (s)ingle PC, a (l)ist of PCs,`
or (a)ll PCs on your domain:"
$LookUpType=Read-Host "s, l, a: or any other key to cancel?"
}
switch ($LookUpType[0]) { #A
"a" {$ObjList=(Get-ADComputer -filter *|Where-Object `
{$_.enabled -eq $true}).name }
"s" {$ObjList=Read-Host "Enter the name of the PC" }
"l" {$TargetList=Get-File;
$ObjList=Get-Content $TargetList }
Default {write-host "No valid LookUpType found, please try again and`
select : a, s, or l"; exit}
}
foreach($PC in $ObjList){
$Alive=Test-Connection -Count 1 $PC -Quiet #B
if ($Alive){
$Online="True"
$base= Get-CimInstance -computername $PC -ClassName ` #C
Win32_ComputerSystem -ErrorAction SilentlyContinue
$os = Get-CIMInstance -computername $PC -class `
Win32_OperatingSystem -ErrorAction SilentlyContinue
$vol = Get-CIMInstance -computername $PC -class Win32_Volume|
Where-Object {$_.DriveLetter -eq "C:"}`
-ErrorAction SilentlyContinue
$net = Get-CIMInstance -computername $PC -class `
Win32_NetworkAdapterConfiguration |
where-object { $_.IPAddress -ne $null } -ErrorAction `
SilentlyContinue
}else{
$Online="False"
}
$DeviceInfo= @{ #D
Online=$Online
SystemName=$PC
OperatingSystem=$os.name.split("|")[0]
SerialNumber= $os.SerialNumber
Version=$os.Version
Architecture=$os.OSArchitecture
Organization=$os.Organization
Domain=$base.Domain
RAM_GB=$base.TotalPhysicalMemory/1GB
IPAddress=($net.IPAddress -join (", "))
Subnet=($net.IPSubnet -join (", "))
MACAddress=$net.MACAddress
DiskCapacity_GB= $vol.Capacity/1GB
FreeCapacity_GB=$vol.FreeSpace/1GB
}
while ($SavePath -notlike "*.csv"){
Write-Error "The save path must end in a csv file name `
`n Example: c:\temp\report.csv"
$SavePath=Read-Host "Enter the full path and name of the csv to save`
the data to"
}
However, when you introduce more than three choices it gets complex and
ugly quickly.
$Fruit=Read-Host "Pick a fruit"
if ($Fruit -eq "banana"){
write-host "a $fruit is yellow"
}elseif ($Fruit -eq "apple") {
write-host "an $fruit is red"
}elseif ($Fruit -eq "orange") {
write-host "an $fruit is orange"
}elseif ($Fruit -eq "grape") {
write-host "a $fruit is green"
}elseif ($Fruit -eq "cherry") {
write-host "a $fruit is red"
}elseif ($Fruit -eq “kiwi”) {
write-host “a $fruit is green”
}else{
write-host “I don’t know what color a/an $fruit is.”
}
The Switch statement is a more concise way to check for multiple conditions
without nested if/else statements.
$Fruit=Read-Host “Pick a fruit”
switch ($Fruit) {
“banana” {write-host “a $fruit is yellow”}
“apple” {write-host “an $fruit is red”}
“orange” {write-host “an $fruit is orange”}
“grape” {write-host “a $fruit is green”}
“cherry” {write-host “a $fruit is red”}
“kiwi” {write-host “a $fruit is green”}
Default {write-host “I don’t know what color a/an $fruit is.”}
}
Multiple values for the condition can be defined and checked within the
switch statement. Each subsequent value checked is listed within the curly
braces of the switch statement. The first value that evaluates as true is then
executed.
Figure 10.3 Checking condition against multiple values in a switch statement.
Note
When using switch statements it’s best practice, to establish a default action.
A default action will happen when none of the other conditions resolve to
true.
switch ($question){
"Yes" {write-host "You typed Yes"}
"No" {write-host "You typed No"}
"Go Back" {write-host "You typed Go Back"}
Default {write-host "I didn't understand."}
}
This is a simple example of a menu. It prompts our user for a Yes, No, or Go
Back response. If it doesn’t get the answer it expects, it tells the user that it
didn’t Understand.
switch ($question[0]){
"Y" {write-host "You typed Yes"}
"N" {write-host "You typed No"}
"B" {write-host "You typed Back"}
Default {write-host "I didn't understand."}
}
Figure 10.7 Example of the switch statement with only the first character.
If, however, this is not passed as a parameter when initializing the script, we
set up a While Loop, and as long as the $LookUpType variable is null or
empty it will prompt our user to supply this information.
If the user enters “a” or any string that starts with an “a” the script will
perform an Active Directory query for the names of all enabled computers on
the domain and set the $ObjList variable equal to this list.
If the user selects “s” or any string that starts with an “s” the script will
prompt the user for the name of the PC they wish to check and set the
$ObjList variable equal to this value.
If the user selects “l” or any string that starts with an “l” the script will create
a variable $TargetList and then run the Get-Content function. This function
will open up a GUI menu that will allow the user to select the .txt file
containing the list of PCs. The script will then read the contents of this file
and set the $ObjList variable equal to this content.
If the user presses any other key or combination of keys, the script will
prompt the user that no valid LookUpType was found and exit the script.
Figure 10.8 Using the switch statement in our Tiny PowerShell project.
10.3 Test-Connection
When you are dealing with objects at a large scale, performance becomes an
issue we must pay special attention to. If a script takes a few seconds to run
on each system or object, it can add minutes, hours, or even days to a large
enough collection. One thing that commonly adds several seconds is a
remote computer that is not reachable remotely. PowerShell will attempt to
reach the system and wait until the command times out before moving on.
Below we can see the impact this has on the runtime of our scripts
Measure-Command
Here we will run a simple get-content cmdlet across two remote computers.
One computer, PC-01, is turned on and responsive. The other computer, PC-
02, is turned off and unreachable remotely.
Figure 10.9 Measuring the response time for a remote query of an online vs an offline computer.
Two seconds might not seem like a lot. But if you have just 30 computers
unavailable, that would add more than a minute to our total time to complete.
If you have an enterprise with thousands or tens of thousands of systems, this
can add considerable time to your request.
This is where the Test-Connection cmdlet can make our lives easier. Like a
ping, Test-Connection sends out an Internet Control Message Protocol
(ICMP) echo request packet to a destination or list of destinations. It is a
quick way to see if a system is online and reachable before attempting to
remotely access the system or its resources.
You can control the number of requests sent and received with the count
parameter.
Ping vs Test-Connection
You may be wondering, “Can’t I already do all that with a ping? Why
would I use Test-Connection?” The reason is, that the test-connection can
return a Boolean value as well based on the results of the echo request using
the quiet parameter. With a traditional ping request, you would need to do
additional conditional logic based on the response to determine if the ping
was a success or a failure. This function is built into the Test-Connection
cmdlet and requires no additional conditional logic or overhead.
Figure 10.11 Using the Test-Connection -Quiet to get a True or False response.
10.3.1 The structure of the Test-Connection cmdlet
The syntax for the Test-Connection cmdlet includes several useful
parameters:
For example, the following statement will send a single ICMP echo request to
your host computer, also known as a loopback test.
Test-Connection -count 1 -TargetName $env:COMPUTERNAME
When we insert a Test-Connection into the same code we ran in Figure 10.9,
we see that our time to execute is improved.
Figure 10.12 Use of a Test-Connection can improve script speeds by avoiding having to timeout
commands.
10.3.2 How do we use this in our Tiny PowerShell Script?
After we have established the list of computers we are going to query in our
script, we then use a foreach loop to query each one for its hardware
properties. However, because we don’t want to waste time querying PCs that
will not be able to answer (especially since we need to query each box more
than once), we first send a single ICMP echo request to the computer with the
Test-Connection cmdlet to see if it is responsive.
We use the -Quiet parameter of the cmdlet to return only a True response if it
is responsive or a False if it is unreachable. We then set an if statement to
check the value of this response and, if the value is True, we query the system
for its hardware.
Figure 10.13 Illustrating how we use the Test-Connection cmdlet in our Tiny PowerShell Project.
10.4 Get-CimInstance
Computers store significant amounts of data, and a significant amount of this
data is about the computer itself. This includes file permissions, registries,
and information about its hardware, which is perfect for us! Accessing this
data is easy and can be done remotely using PowerShell with the Get-
CimInstance cmdlet.
When accessing the data within the CIM structure it’s important to know how
it is grouped. You won’t, for example, be able to access the remote system’s
IP address from the same location as its Operating System version. This
information is organized into data structures known as Classes.
10.4.1 Get-CimClass
Classes are the way that the data is structured. This keeps things that are
alike grouped so you don’t need to parse through the thousands of things your
computer is keeping track of to find the thing you need. However, because of
this grouping, you must know which class you need to query to get the data
you want.
You can get a list of the available CIM classes your system knows about by
running the Get_CimClass cmdlet.
Get-CimClass
After running that command you start to understand what I mean when I say
your computer stores significant amounts of data about itself! It’s probably
an interesting use of your time to spend a little time digging around these
classes to see what kinds of hidden information gems you might discover.
In general, it’s probably a good idea to restrict your CIM Class search to
Win32 as well. These are the classes, in general, that hold the most valuable
information for System Administrators.
Get-CimClass |Where Get-CimClass|Where-Object {$_. CimClassName -match "Win3
But knowing which Classes are available on our system is only part of the
battle. We still need to know how to access this data. This is where the Get-
CimInstance cmdlet comes in.
Figure 10.14 Querying system’s operating system, version, and build with CIM classes.
You can also use dot notation to pull a specific property stored within a CIM
Class. This allows us to pull the full class, store it as a variable, and then use
the Dot notation to access specific aspects of the Class without having to
constantly re-query the CIM Instance.
Figure 10.15 Saving the CIM Class as a variable and using Dot notation to access the properties.
10.4.3 How do we use this in our Tiny PowerShell Script?
Once we’ve determined the computer is reachable on the network with the
Test-Connection cmdlet, we then query the remote PC via the Get-
CimInstance cmdlet, storing the class info from:
Figure 10.16 Assigning Get-CimInstance return values to variables in our Tiny PowerShell Script
Once we have the values for the four CIM instances stored in variables, we
pull specific parts of the CIM Class out into other variables for our Hashtable
(You’ll learn about Hashtables in the next section). Using those four CIM
Classes we can retrieve the values for the:
Operating System
Operating System Version
Operating System Architecture
Serial Number
Organization
Domain
RAM
IP Address IPv4 and IPv6
Subnet Mask
MAC Address
HDD Disk Capacity
HDD Disk Free Space
Figure 10.17 Pulling specific information from the CIM Class in our Tiny PowerShell Script.
10.5 Hashtables
A hashtable is simply a data storage structure like a variable or array. Each
data item is stored in a key/value pair, sometimes referred to as a Dictionary
value. A key/value pair, quite simply, is a unique string paired with another
value. If we wanted to create a pairing of presidents with their vice
presidents our key/value pairs might look something like this:
Vice Presidents
“George Washington” “John Adams”
Once again, the syntax is very similar to accessing an array. But unlike when
accessing an array, we do not need to know which position the data was
entered in beforehand. The hashtable’s algorithm can find it for us much
quicker than we could search an array.
Let’s set up a hashtable and an array with this information:
$VicePresidentsHashtable=@{
'George Washington'="John Adams"
'John Adams'="Thomas Jefferson"
'Thomas Jefferson'="Aaron Burr"
}
$VicePresidentsArray=@("John Adams", "Thomas Jefferson", "Aaron Burr")
Using the Measure-Command we can see how long it takes to find Aaron
Burr in the array.
Measure-Command -Expression {$VicePresidentsHashtable['Thomas Jefferson']}
Measure-Command -Expression {$VicePresidentsArray[2]}
Figure 10.19 Comparing the speed of data access between an array and a hashtable.
This impact is even larger if you don’t already know the position of the data
you want to pull within the array. Let’s say you wanted to pull Aaron Burr
from both the hashtable and the array. To access the answer from the
hashtable you just need to know the key but to access the answer from the
array you’ll need to look at all the entries and select the one you want with a
Where-Object cmdlet.
Measure-Command -Expression {$VicePresidentsHashtable['Thomas Jefferson']}
Measure-Command -Expression {$VicePresidentsArray|Where-Object {$_ -eq "Aaro
Figure 10.20 Speed comparison between arrays and hashtables when the array’s index value is
unknown.
10.5.3 How do we use this in our Tiny PowerShell Project?
After we have pulled the CIM Class data from a PC we create a hashtable
called $DeviceInfo. We then use the four variables containing the classes we
stored to make key/value pairs for each hardware category we care about
before exporting the whole table of keys and values to our CSV.
Figure 10.21 The hashtable $DeviceInfo used in our Tiny PowerShell Project.
10.6 Export-CSV
In previous chapters, we already explored some of the powerful uses of a
CSV (Comma-Separated Values) data format. For instance, this data type can
be read by text editors and with Microsoft Excel, which makes it perfect for
reports. But in those previous chapters, we were Importing-CSV files for use
within our script. Now that we have this wonderfully structured data from
our hashtable, you might wonder, “How do we save this data while retaining
its structure?” That is where the Export-CSV cmdlet comes in.
-Path: This is the location of the file you’d like to save to. This
parameter is the only one of the parameters we’ll be covering that is
required.
-Append: Adds data to the bottom of the file, instead of overwriting it.
-Delimiter: The default is a comma, but this can be changed.
Figure 10.22 using the Export-CSV cmdlet in our Tiny PowerShell Project
However, because the path parameter is looking for a .csv file, we have to
make sure that the $SavePath includes the .csv file type. Thus, before writing
to our csv, we put in a guard to ensure that the $SavePath meets our
formatting needs.
We set up a while loop that checks the value of the $SavePath and, while the
value within this variable does not include a .csv extension, it will prompt
and re-prompt our user to re-enter the save path with a file that ends in the
.csv file extension.
Figure 10.23 Guard statement to make sure the $SavePath variable includes a .csv extension.
10.7 Summary
Switch allows you to compare the value of a variable against several
options and execute the code associated with the first match without
using multiple if/else statements.
Using Read-Host and Switch statements, you can make simple menus
that allow your user more control over what can execute within your
script.
The Test-Connection cmdlet allows you to send out ICMP echo requests
to remote computers to check to see if they are online before attempting
to remotely access them, improving the efficiency of your scripts in
most cases.
The Test-Connection cmdlet is similar to the ping command, but if you
use the -quiet parameter it will return a Boolean value instead of
response metrics, making conditional logic based upon the responses
much faster and easier.
Computers store massive amounts of data, and much of this can be
accessed remotely with the Get-CimInstance cmdlet.
The CIM (Common Information Model) data is broken up into CIM
Classes; these classes can be displayed with the Get-CIMClass cmdlet.
Hashtables are a quick and efficient data storage type used in
PowerShell to store unique key/value pairs.
These Key/Value pairs can be referenced by calling the name of the
hashtable followed by the unique key name in square brackets.
The Export-CSV cmdlet allows you to export data from PowerShell into
a structured data file known as a Comma-Separated Values file. This
data type can be read by text editors and with Microsoft Excel, which
makes it perfect for reports.
welcome
Thank you for purchasing the MEAP for Tiny PowerShell Projects.
This book has been written for those IT Professionals with little to no coding
experience. But even those with a firm understanding of the language can
hopefully find some useful scripts that will drastically improve their ability to
automate and optimize their environment.
PowerShell, perhaps more than any other language, is the perfect language
for the IT Professional to learn. It has power, flexibility and an ease of entry
that is not found in any other tool.
Rest assured; this is not the nature of Tiny PowerShell Projects. I have never
considered myself a software engineer. I first experienced PowerShell, nearly
ten-years ago, while working as a remote support technician for a large
company. My first PowerShell course was taught like it was any other
programming language. We covered variables, if statements and loops, I
spent five days in a live training program, but frankly I found myself in
debugging hell because I’d do something like forget to end my closing brace.
I quickly found that I was being buried in the newness of it all. Relieved
when I could type a string of commands into the interpreter and not see a
screen full of red staring back at me. I wasn’t getting anything out of it that
was useful. I could construct some code and make it run, for the most part.
But it was a long and arduous process, and I still had no idea how, when, or
why to use PowerShell in my daily life.
My goal for Tiny PowerShell Projects is to flip this paradigm. I will start with
the why, then show you the when and only then, will I detail the how. Tiny
PowerShell Projects is designed to produce useful scripts and demonstrate
use cases that you can take out of the book and put directly into practice in
your environment. Then, I will break down the script section by section
covering the language and logic that make it work. It is quite simply the book
I wish I had had when learning PowerShell.
Feedback on this project has already been incredible and inspiring. I hope
that you’ll be leaving comments in the liveBook Discussion forum. Your
question, comment or suggestion may help future readers as they too discover
this powerful automation and optimization tool at their fingertips.
Thanks again for your interest and for purchasing the MEAP!
—Bill Burns
In this book