PowerShell Command Prompt Boot Camp

PowerShell Command Prompt Boot Camp

A PowerShell Command Line Boot Camp

This section will give a brief boot-camp / accelerated crash course to get up to speed on PowerShell quickly. It won't be very in depth, but it should provide enough info you can get by or at least search for more in-depth pages about a certain topic. It also won't teach you how to program scripts or some of the PowerShell best practices or gotchas - it's a basic "get started fast" guide. This overall guide was mainly just about the Command Line in general, but once you know the basics you want to start learning more - start here!

I assume you can complete the CLI Task Test, if not run through Zen's CLI Crash Course first. Most what you may know about the CMD/"DOS" Prompt may also be useful. This will just be a surface-level guide, not a deep-dive. Enough to help point you in the right direction and know what to Google if you get stuck. For the basics see the PowerShell Essentials page.

PowerShell Commands

PowerShell commands (officially called cmdlets) are generally always of the form Verb-Noun or Action-Subject, and the Noun or Subject will have several Verb/Action related commands, such as Get-Content, Set-Content, Add-Content, and Clear-Content to get the contents of a file, write a file, add to a file, and empty a file.

PowerShell commands/cmdlets always use a single dash - to indicate options/parameters/switches/arguments, such as -Identity or -Properties. Any value comes after, such as: -Identity jsmith. PowerShell commands/cmdlets and options/parameters are not case sensitive, but usually displayed using PascalCase so words forced together AreEasierToRead. Cmdlets and Parameters are often long so use Tab Completion.

PowerShell cmdlets come in various modules: many of which are included with Windows/PowerShell, come with other programs (like Active Directory or SQL Server), or can be downloaded from the PowerShell Gallery. Load a module with Import-Module (though often just using a cmdlet will auto-load the module in PowerShell 3.0+) and install a module with Install-Module. See all the modules installed on your system with: Get-Module -ListAvailable
  → Sometimes there are prefixes to the Nouns/Subjects to group commands, such as Get-ADUser, Set-ADComputer, Remove-ADGroup each having an "AD" prefix.


The Three Most Useful Commands

Because PowerShell is very regular and discoverable the 3 most useful commands to initially learn are listed here:


The Three Most Useful Concepts

Relatedly, the 3 most useful concepts in PowerShell are: Variables, Objects, and the Pipeline. We'll cover these in greater detail in the next sections.

Variables

Since everything is an Object (which will be discussed in the next section): a PowerShell command can return a lot of data, you can use Variables to store this data and access it again later; or create your own variables to avoid having to re-type the same thing over and over. There are some built-in and automatic variables. Think of a variable as a bucket with a label: the label is the name of the variable and the bucket holds the value. You retrieve the value by referencing the bucket by the name on the label. If you change the contents of the bucket: anywhere after it is referenced by name will use the new value.

Often a cmdlet will return more than one value (such as many files in a directory, many AD Users, many Processes, etc) in those cases the results will be an Array which is List or Collection of items. An Array can also be assigned to a variable in exactly the same way. However the variable name (eg. $ListOfFiles) will point to the Array, not one particular item in the array. To select just one item from the Array you use an Index number - which starts at 0, in [square brackets] after the Array variable name.

It's also worth briefly noting that variables have a Type in addition to a Name and a Value - the Type is the sort of Value the bucket holds. Common types are String (a sequence of characters, "such as a sentence in quotes"), Int (for Integer, a number without a decimal place: 0, 1, -6, 689, but not 1.5 or 3.14), Float (for floating point, a number with a decimal place: 0.0002, -0.5, 1.11178, 2.0, 3.14), Boolean (a value that is only either $True or $False - two special variables that represent True or False), DateTime (a Date and/or Time value, such as returned by Get-Date), and any other valid .NET type. Think of the Type as the Colour of the variable bucket: there's a name of a variable but also a colour denoting if it's holding text, numbers, dates, etc.

A final type of Variable worth mention is the Environment Variable. These are set either per-machine or per-user by the system but contain useful information, such as the name of the computer or currently logged in user, the location of the Windows folder, and more. In the CMD Command Prompt you access them with % as in %USERNAME% but in PowerShell you access them like any other variable using $ but in the ENV: virtual drive. So the current logged in user is $ENV:USERNAME, and they work in Strings too, such as "My computer is $ENV:COMPUTERNAME".

Objects

If PowerShell is all about Objects, what's an Object? An Object is "Structured Data + Methods to work with that Data". Think of an Object as a Large Bucket with several smaller Buckets inside plus the Tools to "do things with those buckets". The smaller buckets are Properties (the Data - basically a bunch of related variables) and the Tools are Methods (or Functions, that can do things with the Properties/variables). Objects are structured because every Object also has a Type and all Objects of the same Type follow the same structure, like a Template (having the same Properties and Methods available as the Template they were created from).

Object variables have their name on the Large Bucket that you can pick (like $UserObject) but the Property and Method names are set by the template Type for that sort of Object.
You can reference the Properties and Methods with "dot notation" - by using the Object's variable name, a dot ., and the Property or Method. Eg: $Object.Property or $Object.Method()
  → Properties are just variables and hold values, sometimes you can set them, other times you can only get them (if they are read-only).
  → Methods are functions that perform actions, possibly returning a value; you need to use (parentheses) to call a function, and pass any parameters inside the parentheses like: $Object.Method(parameter1, parameter2)

You can use Get-Member to see all the Properties and Methods an Object contains.
Can you convert an Int (number) to a String? Can you add years to a DateTime? Is there an Email Address Property on an AD User Object?
Use Get-Member to find out! Example: $UserObject | Get-Member, or Get-Member -InputProperty $Today, or even: 12345 | Get-Member


 

Often you need to access Properties, Methods, or even just evaluate a PowerShell expression, variable or command within another command or context. You can use sub-expressions to do this by enclosing the command/variable/expression in brackets like: (Some-Command) and then you can access Properties/Methods, like: (Some-Command).OneProperty

An Example of Variables with "double" and 'single' quotes:
PS C:\> $Today = Get-Date
PS C:\> # With "double quotes" it will resolve the $Today variable:
PS C:\> "It is $Today"
It is 02/24/2020 12:13:17
PS C:\> # With 'single quotes' it will print exactly as written:
PS C:\> 'It is $Today'
It is $Today

The Pipeline

The PowerShell Pipeline is what allows you to link commands (or Variables) together to process, filter, or otherwise do something with the values. The Pipeline is denoted by the vertical bar or Pipe character: |, which separates commands/cmdlets, expressions or variables. Each section of the Pipeline feeds the next: the Output of one command becomes the Input of the next. Almost all PowerShell commands/cmdlets and many scripts output Objects that can be accepted as input via the Pipeline by other commands/cmdlets/scripts - the Pipeline passes Objects. You can think of it as an unnamed Array of Objects where the current Object is represented by the special variable: $_ though often you don't need to reference individual elements of the Pipeline since by default commands will work on all elements of the Pipeline. Usually a command changes or filters (narrows) the Output as compared to the Input. You can have as many 'sections' of the Pipeline as you want, but unwieldy commands can be shortened by using Variables. Be careful mixing non-PowerShell (regular Windows Console CMD commands) and the PowerShell Pipeline: non-PowerShell commands output plain text, not objects!

Some common simple commands that work with the Pipeline are: Sort-Object, Measure-Object, Group-Object (usually just referenced by their aliases: Sort, Measure, and Group). By default the final result of the Pipeline is displayed on the screen, so you'll see the results.

Remember you can use Get-Command to see what other commands deal with "Objects", such as Get-Member *-Object

Other common Pipeline commands involve displaying information: Format-List, Format-Table (usually just fl and ft), Out-String or Out-GridView -- these almost always come at the end of the Pipeline:

Because of the regular and non-cryptic nature of PowerShell you can probably guess what most of these do even without using help with them. For example, the last one is literally "directory of C:\Windows, sorted by file Length, whose output goes into a GridView (a kind of pop-up list of data window)"

Saving/exporting (or reading/importing) data to/from files using Pipeline data is possible too: Set-Content, Add-Content, Out-File, Export-Csv, Get-Content, Import-Csv, ConvertTo-Html, and ConvertFrom-Json -- usually you want the raw data to be exported, so don't place these after Format-List or Format-Table

Remember, use Get-Help (maybe with -Full or -Examples) to look up what these commands do, what the Required or Optional parameters do (such as what -NoType does for Export-Csv -- remember you only need to specify enough to uniquely identify parameters).

 

Two of the most important, and common, Pipeline commands are Where-Object (or just Where, or even just a single question mark ? as an alias) which filters which Objects to pass along the Pipeline (that is: only pass along Objects which match some search/filter criteria). And Select-Object (or just Select) which filters which Properties of Objects are passed along the Pipeline (that is: only pass along Properties that are listed, no others).
  → Where filters Objects, Select filters Properties (of Objects)
Combined with other commands you can filter/choose which Objects or Properties to keep and which to discard, depending on what you want to do.

Where uses either the older/classic script-block format (which still allows more complex filters) or the newer (PS 3.0+) native format. In either case you choose aspects (usually Properties) of Objects and those Objects that match are passed along and those that don't are discarded.

Note, in PowerShell 'equals', 'not equals', 'greater than', and 'less than' are consistent in using -something because =, >, and < are used to assign variables or redirect output.
You cannot use also them to check if something is equal or greater than something. Use -eq, -ne, -gt, -lt, etc

Select uses names of Properties (including partial matches using wildcards, where * = any characters) to filter what results get passed along. You can list one or more Properties, or Expand a Property if it contains multiple values or SubProperties; or choose just the First or Last number of items, or only return Unique values from a collection/array of Objects. Generally all the Objects are passed along, just not all their Properties - use Where to filter whole Objects.

Note, because Select-Object is all about Objects it can get you into trouble if you to try to use it with the output from regular Windows Console CMD .EXE commands.
Non-PowerShell commands output plain-text, which isn't Objects. To filter plain-text strings use Select-String (alias is sls).
Where manage-bde.exe -status c: will show the BitLocker status of the C: drive, you CAN'T filter on the "Conversion Status:    Fully Decrypted" line with this:
manage-bde.exe -status c: | select "Conversion Status"
But you CAN with Select-String:
manage-bde.exe -status c: | select-string "Conversion Status"


Advanced Control Structures

PowerShell has many advanced control structures as well, but because this document is about the basic Command Line usage and not Programming/Scripting we'll just mention a few here. You will need to learn these (especially foreach and if) to create scripts or solve more advanced problems with PowerShell. For now, focus on being comfortable with PowerShell and the Command Line in general.


Foreach

The Pipeline cmdlet: use the Foreach-Object (or the aliases: foreach or even just the percent sign % by itself) to do something to everything in an Array/Collection in the Pipeline, where $_ represents the current Item/Object being processed, like with Where, for example: dir | foreach { write "Here is a file: $($_.Name)" }. Note: the default action of the Pipeline is to pass all objects to the next command, so you only need foreach if you want to accomplish something specific with each item.

The confusingly named PowerShell statement: foreach which is very similar to Foreach-Object (especially since alias for one is the same as the other - PowerShell figures out which) but the foreach doesn't use the Pipeline but names each item it works on (the $file variable in the example below) instead of using $_.

Which foreach method you use doesn't matter a lot, one will make more sense in some situations and the other in other situations. Use whichever makes the most sense at the time - ie. are you working with data in the Pipeline? Within .PS1 scripts the foreach statement is often used to do multiple things to each item in the array. Have a look at the example below - they both produce the same output.

An example of Foreach-Object and foreach statement:
PS C:\> $AllFiles = dir c:\windows
PS C:\> # With the Foreach-Object cmdlet you process via the pipeline (input and output):
PS C:\> $AllFiles | foreach { write "Here is a file: $($_.Name)" }
Here is a file: addins
Here is a file: ADFS
Here is a file: etc, etc
PS C:\> # With the foreach statement you declare a variable to work with the Array, no Pipeline:
PS C:\> foreach ($file in $AllFiles) { write "Here is a file: $($file.Name)" }
Here is a file: addins
Here is a file: ADFS
Here is a file: etc, etc
 


If ... Then

The conditional IF/ELSE statement, which uses Conditionals like Where does, is used for branching and making logical decisions (often called If ... Then ... statements). You can decide to do something IF a certain condition is true or ELSE do another thing.
A script that is just a collection of commands saves you from having to type them over and over, a script that uses variables is more flexible and easier to maintain and update, a script that uses loops (such as above) can do the same thing to all Objects in an Array without repeating lines - but a script that can make decisions can be very powerful (eg. Install X, if successful do Y, else do Z). An example of a simple if is below:

An Example of an IF / ELSE Condition:
PS C:\> $value = 35
PS C:\> # Use IF to decide what to print:
PS C:\> if ($value -lt 20) { write "$value is a small number" } else { write "$value is a big number" }
25 is a big number

Note: in the above two examples (for foreach and if) there's a Script Block in {curly braces} with commands that are run.
You can have multiple commands within Script Blocks, separated by semi-colons ; or within a .PS1 script by putting commands on separate lines.

Here is an example of a small script which has several lines within a script block, you can copy and paste it into a .PS1 file and run it:

Multi-line script blocks
# Tells you if a number is larger or smaller than 20
$MyArray = 5, 10, 15, 20, 25, 30, 35, 40
write "Starting..."
foreach ($Number in $MyArray) {
	# Several lines inside the foreach {script-block}
	if ($Number -lt 20) {
		write "$Number is smaller than 20"
	} elseif ($Number -eq 20) {
		# several lines inside this if/elseif script-block
		write "-=-=-=-=-=-=-"
		write "$Number IS Twenty!!"
		write "-=-=-=-=-=-=-"
	} else {
		write "$Number is larger than 20"
	}
}
write "All done."

Note that the above example also included ELSEIF which lets you stack several conditions into one IF statement (IF this, ELSEIF that, ELSEIF another ELSE final option).

 

Other control structures include For loops, Do While or Until loops, Switch statements, and Functions. Most of these are beyond the scope of this document, and probably not anything you'll need right away. But custom functions allow you to encapsulate common commands into a subroutine you can re-use - and keep your code tidy.


Functions

Functions are blocks of code that you can use and re-use within your code. You can define Functions on the command line or, more commonly, within scripts (.PS1 files) -- they are like mini scripts within scripts. You can define and pass parameters to functions, and they can return values (but they don't have to). Any value that is output within a Function is returned when the Function exits. This allows you to assign the output of a function to a variable, like: $var = My-Function.

In many languages you need an explicit return keyword, but it's optional in PowerShell - where any output in the Pipeline is returned. This often causes headaches for people coming from other scripting languages, so it's worth pointing it out (in case you are used to another programming/scripting language like JavaScript, Python, C/Java, etc).

To create a Function just use the function keyword, the name of the function, and put any optional parameters you want in (brackets). Then put your code in {curly braces} after - called a script-block. It's best to follow the standard Noun-Verb naming convention. There's a lot more you can do with functions, including supporting the help command or validating parameters with Advanced Functions which can also allow you to accept Pipeline input. Functions don't have to be long, they can be short and just save you from typing the same code over and over.

Two example functions:
PS C:\> # You can enter functions on one line in the Console, or across several lines in a script.
PS C:\> # See the current date:
PS C:\> Get-Date
Tuesday, June 9, 2020 10:26:07 PM
PS C:\> # Define a function with no parameters:
PS C:\> function Get-Tomorrow { (Get-Date).AddDays(1) }
PS C:\> # Test it:
PS C:\> Get-Tomorrow
Wednesday, June 10, 2020 10:26:20 PM
PS C:\> # Define a function with a parameter and then assign the results to a variable:
PS C:\> function Get-FutureDate ($Days) { (Get-Date).AddDays($Days) }
PS C:\> $NextWeek = Get-FutureDate -Days 7
PS C:\> $NextWeek
Tuesday, June 16, 2020 10:26:45 PM

If the above sections mean nothing to you, don't worry - unless you're used to Bash, Python, Javascript, C/C++, C#, VBScript, Pascal, etc it won't matter until you start scripting!
If you ARE interested in learning more about programming with PowerShell try PowerShell on Wikiversity, or if you're coming from Bash/Python/etc you can Learn PowerShell in Y Minutes.

 


Write vs Write-Host

Mentioning Write-Host and Write above it's worth expanding briefly on these, and also how to accept user input. Normally a script takes its input from a file (like a .CSV file), from another command, or parameters on the command line. But sometimes being able to ask the user for input can often be beneficial (and simpler) too, that's where Read-Host comes in.

Here's two versions of the same Function, one using Write-Host and one using Write to illustrate:

Write-Host vs Write in Functions:
PS C:\> # With Write-Host note that the message displays when the Function is called:
PS C:\> Function Add-Nums ($a, $b) { write-host "Adding: $a and $b."; $a + $b }
PS C:\> $c = Add-Nums 3 10
Adding: 3 and 10.
PS C:\> write "The answer is $c!"
The answer is 13!
PS C:\> # With Write instead, where the message is returned into $c
PS C:\> Function Add-Nums ($a, $b) { write "Adding: $a and $b."; $a + $b }
PS C:\> $c = Add-Nums 3 10
PS C:\> write "The answer is $c!"
The answer is Adding: 3 and 10. 13!

It's not that either is right or wrong, they both have their places. However, it's usually more correct to use Write and only return whatever should be returned from within a Function (and only use Write-Host to display diagnostic information.


Final PowerShell Points

A few other helpful things worth mentioning before finishing this PowerShell Boot Camp:

 

Your $PROFILE

After all that, you can also customize your $PROFILE - which is a built-in variable that points to a configuration file that PowerShell executes whenever it is started. You can see the path and filename by just typing the $PROFILE or open it up in an editor with something like: notepad $PROFILE. You can place configuration options and small "helper" functions to use on the command line.

Your $PROFILE is yours, it's saved in YOUR Windows Profile, so if you use any of the functions you add in anywhere scripts they won't be available on any other systems or running as any other users (such as scheduling a script to run under SYSTEM). You should only place functions/settings in there that you will use while administering a system from PowerShell, if multiple systems you'll need to copy it over.

Sample $PROFILE for PowerShell 5.1
# Some configuration for the built-in PSReadline module:
# Make Up and Down arrows act more like CMD's command history:
Set-PSReadlineOption -HistorySearchCursorMovesToEnd:$true
Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward
Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward

# Some helper functions for the command line:

Function Get-OffsetDate ($Days = -7) {
	# Returns the date $Days in the past/future (default = 7 days ago)
	# Example: dir | Where LastWriteTime -gt (Get-OffsetDate -5)
	(Get-Date).AddDays($Days)
}

Function Format-Date ($Date = (Get-Date)) {
	# Returns a date in just "YYYY-MM-DD" format (default = Today)
	# Example: $TwoWeekDateStamp = Format-Date (Get-Date).AddDays(14)
	(Get-Date $Date).ToString("yyyy-MM-dd")
}

Function Truncate-String ($String, $Length) {
	# Truncates/cuts a string to Length by cutting off characters on the RIGHT
	# Example: $FirstFiveChars = Truncate-String "0123456789ABCD" 5
	if (!$String) { 
		$null			# If no string then return null
	} else {
		# Return a portion of the String, the lesser of $Length or the String's .Length
		$String.substring(0, [System.Math]::Min($Length, $String.Length))
	}
}

Function Read-Input ($Prompt, $Default = "") {
	# Returns input like Read-Host except just pressing Enter returns a $Default value
	# Example: $FileName = Read-Input -Prompt "Enter a file name" -Default "data.csv"
	$Input = Read-Host "$Prompt, or accept [$Default]"
	if ($Input -eq "") { $Default } else { $Input }
}

Feel free to use any of these, but if you use them in scripts you'll need to copy and paste them into the script before calling them.

Of course this is just scratching the surface of PowerShell, but it should be enough to get you started!