Tuesday, October 23, 2018

Mastering the Basics of PowerShell

Table of Contents

INTRODUCTION

In my opinion, the most important thing about learning PowerShell, scripting or programming language, or anything at all, is to start with a good foundation, or you'll waste a lot of time.

PowerShell is a rocket ship. Can you just get into a rocket ship and learn as you go? Maybe, but there's a very, very high chance that you will crash and burn. Learn how to operate the ship before you attempt to fly.

This lesson does not delve deep into the features and capabilities of PowerShell. This lesson will teach you everything you need to know to get this rocket ship off the ground. You can take it to the next country, planet, or galaxy. That part is up to you.

PowerShell is different from other shells, as it is object oriented. Old shells process, parse, and produce text. PowerShell processes objects or text, parses text, and produces objects or text.

Let's compare the difference between these two ideas:

  1. PowerShell scripts (.ps1 files) with cmdlets
  2. Batch scripts (.bat or .cmd files) with commands

The old command prompt and batch scripts work this way:

You use command line programs that typically end with .exe. These are not commands, they are compiled programs. Try entering cd.exe /? or whoami.exe /? on the old command prompt. As you can see, the question mark argument /? is available in almost all command line programs, or "commands," in the old Windows shell (Command Prompt).

In PowerShell, Cmdlets are like the old command line programs. They are not functions or commands. They are actually programs, usually written in C# following strict specifications, to work intuitively and as expected in PowerShell.

Back to the TOC

EXECUTIVE SUMMARY

To learn the basics of PowerShell, you need to know four basic things:

First, you need to know how to help yourself learn. PowerShell provides three cmdlets that you will use consistently during your experience with the language: Get-Help, Get-Command, and Get-Alias. You will always use these cmdlets as long as you're reading or writing PowerShell.

Second, you will need to realize that learning about and examining cmdlets, and learning about and examining cmdlet output, are two entirely separate things. This is very, very easy to mix up. If you can keep these two ideas separate, you'll be able to learn the language much more quickly.

You'll use System.Object's .GetType() method and the Get-Member cmdlet to inspect cmdlet return values. In addition to the help based cmdlets in the previous paragraph, you'll always be using .GetType() and Get-Member as well.

Third, you're going to need to learn a very simple concept called the "pipeline." It's as simple as saying that one cmdlet can send its output to the next cmdlet. Then, that cmdlet can send its output to another cmdlet, and on and on, as long as you need it to go on.

The fourth and final concept needed to Master the Basics of PowerShell, is to know the difference between processing text output and processing object oriented output. Old shells like CMD.EXE, BASH, and Cisco's IOS shell, only handle text based output, whereas PowerShell has the additional ability to handle object oriented output. If you understand how this handling of object oriented data is different than handling text based data, you'll understand the most fundamental way that PowerShell is different from other shells.

Back to the TOC

LEARN ABOUT CMDLETS

The first cmdlets you should know in PowerShell are Get-Help, Get-Command, and Get-Alias. If you ever have questions about any cmdlet in PowerShell, you can search online on MSDN, TechNet, or Microsoft Docs for help. You can also just use PowerShell.

Get-Help

Type Get-Help into PowerShell to read about how Get-Help works:

Get-Help
If you want help on a specific cmdlet like Test-Connection or Get-Help itself, you can use this syntax:
Get-Help Get-Help
or
Get-Help Test-Connection
If you use the -Online parameter, PowerShell will open the Web page for that help file.
Get-Help Test-Connection -Online
The same information you'll online should also be in PowerShell, but if you'd like to view it in a Web browser instead of a console window, the -Online parameter is for you. Now, if you want to get details on all of Test-Connection's parameters, it looks like this:
Get-Help Test-Connection -Parameter *
or one of its parameters:
Get-Help Test-Connection -Parameter Count
Most cmdlets' help information comes with examples. Use the -Examples parameter to see them.
Get-Help Test-Connection -Examples
Finally, if Get-Help doesn't seem to provide much information, it may need to be updated. If so, run PowerShell as Administrator and use this cmdlet:
Update-Help

Get-Command

To see a list of cmdlets, aliases, or functions, you should use the cmdlet Get-Command. Here are some examples:

Get-Command
or
Get-Command get-*
or
Get-Command *hash*

Get-Alias

Another extremely useful thing to know, is how to learn about aliases. Use Get-Alias to enter a command and see its aliases, or to enter an alias to get its corresponding command (definition).

Get-Alias gal
Get-Alias -Definition Get-Alias
Do you have any experience with Windows Command Prompt CMD.EXE or Unix/Gnu/Linux BASH? The designers of PowerShell implemented aliases for cmdlets. This made it so that people from those backgrounds could use the same syntax they used in their old shells, for many PowerShell equivalent commands. For example, the commands DIR in Windows or ls in *nix, show you the contents of your current directory. In PowerShell, DIR and ls are both aliases for the Get-ChildItem cmdlet. Get-ChildItem also has another alias, gci. Let's inspect this in PowerShell:
gal -definition set-location
gal -definition get-childitem
gal ls
gal dir
gal clear
gal cls
gal man

Another note on the help commands in PowerShell

Note: If you're unfamiliar with the *nix man command, skip past this section. One more interesting note about getting help with cmdlets in PowerShell, is that there is a cmdlet named Get-Help, a function help, and an alias man. It would seem that Get-Help is the cmdlet, while help and man should be aliases, but this is not the case. Do this:

Get-Command Get-Help,Help,man
It seems that while Get-Help is a cmdlet (compiled executable program), help is a function (a procedure with a return value, which likely utilizes the Get-Help cmdlet in its definition - wherever that may be defined [I don't know]). Here man is not an alias to the Get-Help cmdlet, but for the help function. I think it is done this way because Get-Help does not paginate (wait for you to scroll), while help does. I believe man is an alias to help because the man command in *nix does paginate by default. Next lets focus on the output of a cmdlet instead of the cmdlet itself.

Back to the TOC

LEARN ABOUT RETURN OBJECTS' DATA TYPES AND DATA MEMBERS

So, now that you know how to use cmdlets and get help on them, you need to be able to analyze the information those cmdlets provide. The way most cmdlets work is that they return information. This information is mostly in the form of objects, and usually not just text. These objects have a data type or class. If you're familiar with programming, you know that classes have data members. Data members are things that belong to objects, which are usually properties or methods. Properties are pieces of information about the object's state, and methods are the actions that these objects may perform. In simple terms, getting help with cmdlets and getting help with cmdlet output is different, and this is easily confused by beginners. First, we'll use the -Full parameter on Get-Help to find the output type of the Get-Date cmdlet. Cmdlets return return objects, a.k.a. return values. These returned objects are described and defined by their type of data, a.k.a. data type, a.k.a. class. When I say "defined by," I'm referring to data members, a.k.a. properties and methods, where properties are data that describe the object's state, and methods are actions that objects can perform.

Get-Help Get-Date -Full | more
Now scroll to the OUTPUTS section of the help page. You'll see that the outputs for the Get-Date cmdlet are either System.DateTime or System.String, based on how you use the command. Briefly, go ahead and click the links for the DateTime and/or String data type. On the left-hand side of these Web pages, you can see the Properties, Methods, and other data members of these .Net Library data types. Another way to get this same information is to capture the Get-Date output into a variable like this:
$_outputFromGetDate = Get-Date
Now, the output from Get-Date is stored in the variable $_outputFromGetDate. Variables in programming are very similar to mathematics. If you say a=1 and b=1 then a+b=2 but PowerShell variables can hold much more than simple numbers. In PowerShell, variables always begin with $. Let's check what datatype this information has like this:
$_outputFromGetDate.GetType()
Now to more quickly and easily get the data members with the Get-Member cmdlet:
$_outputFromGetDate | Get-Member
Actually, you don't even have to store the output into a variable. Check this out even quicker/simpler:
(Get-Date).GetType()
Get-Date | Get-Member
Now, one last comment on this last way to analyze PowerShell output; many times the output from a cmdlet will not be a single object but an array or other collection type of output. Here's how to deal with that. Let's take a look at the output from Get-WmiObject.
Get-WmiObject -Class Win32_LogicalDisk
You'll likely notice multiple objects are seen on the screen. One for each logical drive in Windows. Now If we check the data type, we see array.
(Get-WmiObject -Class Win32_LogicalDisk).GetType()
Well, that's not helpful. So lets simply reference an index of that array and get the type of that object like this:
(Get-WmiObject -Class Win32_LogicalDisk)[0].GetType()
Okay, that's better. We're looking at the ManagementObject datatype. If all the indices in the array have the same type you can just use Get-Member as usual:
Get-Get-WmiObject -Class Win32_LogicalDisk | Get-Member
If types are different in the array, just reference the index you wish to analyze. Now that you understand that getting help for cmdlet output is different than getting help for the cmdlet itself, you should be able to use this information on anything and everything you encounter in PowerShell. Next let's look at how to navigate the PowerShell pipeline for processing objects vs processing text in PowerShell.

Back to the TOC

OBJECT ORIENTED PIPELINE FLOW

Okay, now let's look at typical PowerShell pipeline flow by analyzing data on a typical cmdlet's return object passed down a pipeline. I'm just going to tell you right now that the pipeline flow (I made up the term "pipeline flow") is:

CmdletVerb-CmdletNoun | Select-Object | Where-Object | Sort-Object | Format-Object
You probably don't know what that means right now but after I explain it below, you'll get it. Lets use Get-WmiObject again but let's use the alias gwmi so we don't have to type as much.
gwmi win32_logicaldisk
When we get data back from a cmdlet, it's usually an array or collection. We can think of this as a database table (if you're unfamiliar with that, it's like an well structured spreadsheet). What we see on the screen is each object separated by a blank line. This is not string output, these are object's properties. So like in SQL if you want to SELECT * FROM TABLENAME (select all columns in the table), let's make sure we're getting all properties from gwmi win32_logicaldisk by using the Select-Object cmdlet (alias is select).
gwmi win32_logicaldisk | select *
As you can see, by default PowerShell doesn't always give you all the properties of an object when you use a cmdlet. Let’s specify which properties (or columns like in SQL) we want as in "SELECT DeviceId,FreeSpace,Size from TABLENAME" would do for us in SQL:
gwmi win32_logicaldisk | select DeviceId,FreeSpace,Size
Ok now let's not get every single object, let's single out the C:\ drive. In SQL it would look something like this "SELECT * FROM TABLENAME WHERE DeviceId LIKE "%C%". To do this, we use Where-Object (aliases are where or ?). Note that we need to use the special builtin automatic variable called $PSItem (same as $_). This variable contains current return object(s) from the command pipeline. You can learn more about $PSItem/$_ with this command (don't read it now, read it some other time):
man about_Automatic_Variables
Here's an example of Where-Object (using the ? character as an alias):
gwmi win32_logicaldisk | select DeviceId,FreeSpace,Size | ? {$_.DeviceId -like "*c*"}
Did you notice the -like comparison operator? It's not a parameter it's an comparison operator. This comparison operator can be used with the asterisk as a wildcard. If you like to use regular expressions (I highly recommend using them) you can use the -match comparison operator instead. Here's how to learn more about comparison operators and regular expressions:
man about_Comparison_Operators
man about_Regular_Expressions
So lets actually single out both the c drive and the h drive like this:
  
gwmi win32_logicaldisk | select DeviceId,FreeSpace,Size | ? {$_.DeviceId -like "*c*" -or $_.DeviceId -like "*h*"}
or
gwmi win32_logicaldisk | select DeviceId,FreeSpace,Size | ? {$_.DeviceId -match "[ch]"}
Ok now lets move onto the next step in the pipeline flow. Lets sort our output with Sort-Object (alias sort):
gwmi win32_logicaldisk | select DeviceId,FreeSpace,Size | ? {$_.DeviceId -match "[a-e]"} | sort FreeSpace
or
gwmi win32_logicaldisk | select DeviceId,FreeSpace,Size | ? {$_.DeviceId -match "[a-e]"} | sort DeviceId -descending
Alright, the last step of the pipeline flow when working with cmdlet output as object oriented data is to format the data. For the most part, you'll want to either see the data as a table or as a list using the Format-Table (ft) or Format-List (fl) cmdlets like this:
gwmi win32_logicaldisk | select DeviceId,FreeSpace,Size | ? {$_.DeviceId -match "[a-e]"} | sort DeviceId -descending | fl
or
gwmi win32_logicaldisk | select DeviceId,FreeSpace,Size | ? {$_.DeviceId -match "[a-e]"} | sort DeviceId -descending | ft
Up until this point, that's all you really need to ever know to use PowerShell as a basic user. I'll show you how to process output as text though just because it's nice to know.

Back to the TOC

TEXT-ORIENTED PIPELINE FLOW

It's almost always better to work with object oriented data but here's how to work with text if you need to. You can always use the old FINDSTR.EXE utility from the old Command Prompt days in PowerShell. But PowerShell provides Select-String (sls) as a native cmdlet specifically used to work with PowerShell. So to use FINDSTR.EXE, you have to use the /? argument because it's an old shell command, not a cmdlet so let's see it:

FINDSTR /?
Anyway, let's use FINDSTR on our logical disk output.
gwmi win32_logicaldisk | FINDSTR /I "c:"
This is not helpful because it's formatted as a list. Let's format it as a table:
gwmi win32_logicaldisk | ft | FINDSTR /I "c:"
That's better, but what are the fields? Let's expand our regex (very limited support within FINDSTR.EXE):
gwmi win32_logicaldisk | ft | FINDSTR /I "deviceid c: h:"
Okay now lets use Out-String and Select-String. Out-String converts cmdlet output from object oriented data to text data. Select-String can parse strings (not objects) for text we're looking for using regex by default or simple search using the -SimpleMatch parameter. SimpleMatch will not treat your string as a regex. Here we go:
gwmi win32_logicaldisk | ft | Out-String | Select-String "c:"
What the heck? Select-String didn't select a specific line. Why? Because Out-String converts the entire output of a cmdlet into one big large string with line separators intact. What we want is an array of strings split upon a newline character. To do that, lets use the -Stream parameter on Out-String like this:
gwmi win32_logicaldisk | ft | Out-String -Stream | Select-String "c:"
Ok cool that works as expected. Now here are some other examples:
gwmi win32_logicaldisk | ft | Out-String -Stream | Select-String -SimpleMatch "c:" # This doesn't see "c:" as a regex
gwmi win32_logicaldisk | Out-String -Stream | Select-String 'device|free|size' # Notice, no Format-Table (ft)
gwmi win32_logicaldisk | ft | Out-String -Stream | Select-String "device|[ch]:" 

Back to the TOC

CONCLUSION

Many times, it will be easier to process objects instead of parsing text. Also, if you're storing your cmdlet output into a variable, you can only utilize properties and methods of the System.String data type on text based output whereas you can use more meaningful properties and methods of different datatypes when you just process the data using the object-oriented way. With this knowledge, you can learn about or read almost any PowerShell code you come across. After reading this, you likely will not have a complete understanding of PowerShell but you do now have a very solid foundation. If you want to learn more, I would first consult technet or msdn. Otherwise there are tons and tons of unofficial but extremely helpful examples, guides, and tutorials online.

What I recommend that you do next is check out the Microsoft Docs page for PowerShell for deeper learning. Here is a page that expands upon this guide. Here's Microsoft's official page for PowerShell example scripts and tutorials. And finally, here's the first page for the reference on the entire PowerShell language.
Note: at the top of those pages, you can change the version of PowerShell to whatever you're working with.

Back to the TOC

No comments:

Post a Comment