Ready to Start Your Career?

Comprehensive PowerShell Tutorial

nginIzz 's profile image

By: nginIzz

February 24, 2016

Comprehensive PowerShell Tutorial - Cybrary
First, a look at some code:
Add-PSSnapin Microsoft.Exchange.Management.Powershell.E2010$header = "<style type='text/css'>`n" + `"td { width: 200px }`n" + `"th { text-align: left }`n" + `"</style>"$d = get-date$nondefault = Get-Mailbox -Database 'Mailbox Db0' | Where-Object {$_.UseDatabaseQuotaDefaults -ne $true} | Select-Object Name,  IssueWarningQuota, ProhibitSendQuota | ConvertTo-html -Title "Mailboxes in Mailbox DB2" -Head $header$emailFrom = "PSscript@nginIzz.script"$emailTo = "noone@test.nocom"$subject = "Inactive Accounts " + $d$smtpServer = "mySMTP-NetBIOS"$message = new-object System.Net.Mail.MailMessage $emailFrom, $emailTo$message.Subject = $subject$message.isbodyhtml = $true$message.Body = $inactive$smtp = New-Object Net.Mail.SmtpClient($smtpServer)$smtp.Send($message)
Parts of this may seem a little scary, but it's actually straightforward. There's only one major change I make for many of my scripts (other than possibly doing a Import-Module ActiveDirectory) and that's the command captured in the $nondefault variable. Let's start at the top...By adding this PowerShell Snapin, we're now working with Exchange 2010 within PowerShell. NOTE: Snapins have been superseded in v2 with modules.$header is static for nice formatting within email using css definitions, by the way. You could change the 200 to something smaller or larger depending on your needs, but the rest is there to keep the column alignment when sending off the email.I'd like to bring your attention to the trailing ` at the end of each line. This basically tells PowerShell we are not finished with this command construct and continue to the next line for the 'rest of the code'.$d here gets a date time in the format of mm/dd/yyyy hh:mm:ss - this can obviously be changed, but I'm showing you the default format. This works nicely for me when I'm running jobs to check for 'stale' email accounts, inactive or disabled AD accounts and so on.Let's skip the command issued into the $nondefault variable for now and look at the email construct.$emailFrom is straightforward. We're assigning a string value of the from address to be used in our email block. The same logic applies to the next three (3) lines.$message = New-Object System.Net.Mail.MailMessage $emailFrom, $emailToHere's where the .NET underlying classes peak out. We are declaring a new email object and passing in the 'from' and 'to' fields.You should be aware that we're working with an object class/instance, and therefore, we aren't using standard variables - rather object variables within the System.Net.Mail.MailMessage Class Instance to push in and set values.$message.Subject is assigned the string value we constructed three lines up$message.isBodyHtml = $trueI do this because formatting in PowerShell is great for stdout and for text files, but for emails, HTML is the only way I can obtain a nicely formatted/aligned message body.$message.Body is assigned the object output from the constructed cmdlet, which I will dissect shortly.$smtp = New-Object Net.Mail.SmtpClient($smtpServer) creates yet another object instance from .NET as a mini-SMTP client, passing in the Server to communicate with, and finally we fire off the email by passing our $message object into the client and 'pushing send':$smtp.Send($message)
$nondefault = Get-Mailbox -Database 'Mailbox Db0' | Where-Object {$_.UseDatabaseQuotaDefaults -ne $true} | Select-Object Name,  IssueWarningQuota, ProhibitSendQuota | ConvertTo-html -Title "Mailboxes in Mailbox DB2" -Head $header
Once you fully understand what is going on with the pipes, cmdlets, object manipulation and selection, you'll be able to accomplish just about anything within PowerShell.Pipes are very similar to what we've seen for over two decades in the *nix realm, working left to right. The main difference here is we can pipe an entire object (rather than just strings of data) at a time. This works quite nicely, actually.Get-Mailbox -Database 'Mailbox Db0'This command alone selects our Exchange database (some may know as information store). This would return every mailbox object contained within this particular database.Where-Object {$_.UseDatabaseQuotaDefaults -ne $true}Here we begin to filter the objects returned by the first cmdlet construct, looking for any mailboxes where the property 'UseDatabaseQuotaDefaults' is not equal to true.$_ = Current Object :: REMEMBER THIS, YOU WILL USE IT A LOT [similar to this->]Select-Object Name, IssueWarningQuota, ProhibitSendQuotaNow, we're selecting the properties we want returned from the command. Here, I'm just looking for the Name of the mailbox owner and the values in IssueWarning and ProhibitSend quotasConvertTo-Html -Title "Mailboxes in Mailbox DB0" -Head $headerThis is where the magic happens for the HTML formatting. We convert the output of the final pipe to HTML, title the HTML 'page' and 'assign' the header of the HTML document the csss construct we put together earlier.NOTE: I've not had luck with any ConvertTo-Html commands without having both the Title and Head set explicitly within the command as an argument. Although we don't need a title, per se, in an outgoing email, I always keep it there to ensure all necessary arguments are set.This concludes the 'email portion'.
PowerShell BASICSI always like to tackle something hard, understand how it is constructed and then dig into the language basics/details. Personally, I learn better by working an example above and then moving back to variables, arithmetic operators, logical operands, loops, etc.PowerShell reminds me so much of when I learned C, or any other language, but working with it, I've definitely seen the .NET language base. If you know C, C++, C#, PowerShell will be a breeze. It's not necessary, and in the process of learning PowerShell, you'll actually learn more about how .NET programming works.You definitely need some underlying coding in one of the popular OO (Object Oriented) languages like any of the C variants, Perl, Java, or PHP. Even having the basics of DOS, vbScript, or BASH will give you a foot forward.PowerShell is based on CMDLETs. All of the cmdlets in PowerShell are simply organized as verb-noun, such as GET-COMMAND. Many of the cmdlets you will see in PowerShell start with one of the following: Get, New, Remove, Set. This is not all inclusive, of course, but you will see these verbs many times.Cmdlets operate based on objects. Input to a cmdlet is an object, and the cmdlet is going to spit an object out. As I have said in threads here before, PowerShell is an Object-Oriented (OO) Language. Microsoft has built PowerShell on top of the .NET framework and primarily based on C#. If you have any extensive knowledge into the .NET languages and structure, you will recognize objects, class instantiations, and syntax throughout your journey with PowerShell.As I said above, cmdlets operate on objects. Both the input and output of cmdlets are objects. Each object has distinct definition, or class, which defines what is contained in each occurrence of an object.
Quote:Get-Process produces objects belonging to the .NET class System.Diagnostics.Process...Get-Process can return any number of process instances, each representing a single process.
Just like other OO languages, class instances have members, which include Properties (an attribute of a class instance), Methods (some defined function of the class instance), and Events (triggered by an object and detectable with Register-ObjectEvent).You can get help, like a man page, on ANY topic in PowerShell by calling Get-Help About_<topic>. Well, we don't know what the topic name is at this point, so let's look at every 'man page' defined:
Get-Help About_*
That's right, you'll see RegEx and SQL-like syntax a lot in PowerShell. Another great tool to get started with cmdlets is Get-Command. This will return every cmdlet, and alias for that cmdlet, so that we can dig deeper. As an extension of the Get-Command, we may know the command, but not what all is inside the cmdlet itself. Consider the following:
Import-Module active*Get-ADUser SomeUserID -Properties *| Get-Member
Two things to note here. In v2, Microsoft has replaced 'PSSnapin's with 'Module's. In v1, we would have done something like:Add-PSSnapin <some PowerShell Snap-in>now we run the Import-Module command to load a specific module. PowerShell does work retroactively, so Add-PSSnapin still works (e.g. Loading the Exchange 2010 snapin).Secondly, the Get-Member cmdlet gives us a peak inside the Get-ADUser cmdlet. By running the above command against a known user (SomeUserID), we'll get a list of ALL properties on that user, whether set or not. Some of the properties include, PasswordLastSet, IsActive, PasswordExpired, LastLogonDate, and so on.I'd like to add that when Microsoft opened the door for the Get-Module cmdlet andsince PowerShell is based on C#, MS and outsiders alike jumped at the opportunity to build modules for everyday operations. I suggest you look into this on your own at Explore .psn1 (Script Modules) files and .psd1 (Manifest Modules). The latter is exactly how it sounds, documentation / copyright / and other cool module information on which it manifested.Down to the basics of any language - Variables, Operations and Operands, Loops, and Functions.Variables can hold any .NET data type. Something as simple as $i = 5 which defaults to assigning a System.Int32 datatype to $i all the way to something much more complex as System.Security.AccessControl.FileSystemAccessRule for ACL management. Google that one...I am sure everyone on here will have a field day with the possibilities. Goodbye xcacls, Hello PowerShell.What if we didn't want $i to be of type System.Int32, and we want it to be purely a string? You guessed it, we simply cast the data type:
[string] $i
Something a little more practical is where a variable holds a string of numbers and we want to convert or assign it as a number:
$str = "1024"$i = [int] $str
You get the idea. A good reference for casting and the classes in which are aliased (int is for System.Int32 and so on) is is filled with arithmetic operators that any C, C++, or C# coder should understand. I will run through them very quickly here.+ (addition), - (subtraction), * (multiplication, / (division), and % (modulo). I will not go into details on operators, but want to make them available for quick reference here.Likewise, you should recognize the assignment operators.=, +=, -=, *=, /=, and %=You ask about comparison? Well, this one through me off the first time, but it is familiar to other scripting languages like vbScript and BASH.-eq (equal), -ne (not equal), -lt (less than), -gt (greater than), -le (less than or equal), -ge (greater than or equal), -like (wildcard match), -notlike (wildcard nonmatch), -match (reg ex match), -notmatch (reg ex nonmatch), band (boolean AND), -bor (boolean OR), -bxor (boolean XOR [exclusive or]), and -bnot (boolean NOT). These comparison operators are just that, for comparison and will return either a true or false.You will eventually want to look into 'logical operators', but I will not cover that here.Some other notable operators are the standard unary operators of ++ and -- (e.g. $i++ and $i--). Play around with the about files mentioned earlier for further explanation and uses.Conditions and Loops appear to be standard from previous languages, and again if you are familiar with c# this will be nothing new.Quickly, to run through Conditional & Looping syntax:If:
if ($i -eq 3) {Write-Host '$i is indeed equal to 3'}else {Write-Host 'Sorry, $i is not equal to 3'}
$i = 1..4 | Get-Random -count 1switch ($i) {1 { Write-Host 'The number chosen is 1' }2 { Write-Host 'The number chosen is 2' }3 { Write-Host 'The number chosen is 3' }4 { Write-Host 'The number chosen is 4' }default { Write-Host 'Some other number has been chosen' }}
For loop:
for ($i=0; $i -lt 10; $i++) {$i}
$i = 0do {$i$i++} until ($i -gt 9)
$i = 0do {$i$i++} while ($i -le 10)
$i = 0while ($i -le 10) {$i$i++}
I hope this will assist in getting you started and sparking your interest in the powerful new admin language used by those of us forced into the Windows world. And, since I mentioned functions earlier, this first one will demonstrate function use as well as other topics discussed.I'll be more than happy to dissect this code if requested in pieces or in whole. The purpose of this script is to run a check via task scheduler (every 5 minutes in my case) to see if a new DHCP lease has been handed out.
Remove-Item C:ScriptsDHCPLeases_diff.html$a = (netsh dhcp server scope show clients 1)$lines = @()#start by looking for lines where there is both IP and MAC present:foreach ($i in $a){if ($i -match "d{1,3}.d{1,3}.d{1,3}.d{1,3}"){If ($i -match "[0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}"){$lines += $i.Trim()}}}$csvfile = @()#Trim the lines for uneeded stuff, leaving only IP, Subnet mask and hostname.foreach ($l in $lines){$Row = "" | select Hostname,IP$l = $l -replace '[0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}', ''$l = $l -replace ' - ',','$l = $l -replace 's{4,}',''$l = $l -replace '--','-'$l = $l -replace '-D-','-'$l = $l -replace '[-]{1}d{2}[/]d{2}[/]d{4}',''$l = $l -replace 'd{1,2}[:]d{2}[:]d{2}',''$l = $l -replace 'AM',''$l = $l -replace 'PM',''$l = $l -replace 's{1}',''$l = $l + "`n"$l = $l -replace '[,][-]',','$Row.IP = ($l.Split(","))[0]#Subnet mask not used, but maybe in a later version, so let's leave it in there:#$Row.SubNetMask = ($l.Split(","))[1]$Row.Hostname = ($l.Split(","))[2]$csvfile += $Row}#let create a csv file, in case we need i later..$csvfile | sort-object Hostname | Export-Csv "Out_List.csv"#Create the HTML formating$a = "<style>"$a = $a + "body {margin: 10px; width: 600px; font-family:arial; font-size: 12px;}"$a = $a + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"$a = $a + "TH{border-width: 1px;padding: 2px;border-style: solid;border-color: black;background-color: rgb(179,179,179);align='left';}"$a = $a + "TD{border-width: 1px;padding: 2px;border-style: solid;border-color: black;background-color: white;}"$a = $a + "</style>"#And create HTML file...$csvfile | sort-object Hostname | ConvertTo-HTML -head $a | Out-File -Append "DHCPLeases_diff.html"function FilesAreEqual{param([System.IO.FileInfo] $first, [System.IO.FileInfo] $second)$BYTES_TO_READ = 8;if ($first.Length -ne $second.Length){return $false;}$iterations = [Math]::Ceiling($first.Length / $BYTES_TO_READ);$fs1 = $first.OpenRead();$fs2 = $second.OpenRead();$one = New-Object byte[] $BYTES_TO_READ;$two = New-Object byte[] $BYTES_TO_READ;for ($i = 0; $i -lt $iterations; $i = $i + 1){$fs1.Read($one, 0, $BYTES_TO_READ) | out-null;$fs2.Read($two, 0, $BYTES_TO_READ) | out-null;if ([BitConverter]::ToInt64($one, 0) -ne[BitConverter]::ToInt64($two, 0)){return $false;}}$fs1.Close();$fs2.Close();return $true;}if (FilesAreEqual c:ScriptsDHCPLeases.html c:ScriptsDHCPLeases_diff.html) {return $false;}else {$emailFrom = "PSscript@h4ck3rIZZ.script"$emailTo = ""$subject = "New DHCP Lease"$body = Get-Content ("C:ScriptsDHCPLeases_diff.html")$smtpServer = "mySMTP-NetBIOS"$message = new-object System.Net.Mail.MailMessage $emailFrom, $emailTo$message.Subject = $subject$message.isbodyhtml = $true$message.body = $body$smtp = New-Object Net.Mail.SmtpClient($smtpServer)$smtp.Send($message)Copy-Item c:scriptsDHCPLeases_diff.html c:scriptsDHCPLeases.html}

I wrote this just over 3 years ago for another site, but wanted to share. I'll start with something I see myself doing over time and again for automation. I realize this is a bit scattered. But hopefully, this will help someone understand the fundamentals. Let me know of any changes to formatting or requests for additional content, or removal of content. I will be more than happy to help and share where I can.
Schedule Demo