Course AZ-040


My Samples

https://tinyurl.com/cbmctsamples


Module 1: Getting started with Windows PowerShell

Understand Windows PowerShell command syntax

https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands


Module 2: Windows PowerShell for local systems administration

Active Directory Domain Services administration cmdlets

The -Filter parameter does work with quotes around the entire filter but for consistency we should be using braces (the filter is a PowerShell expression and expressions should be in braces).

Get-ADObject -Filter 'ObjectClass -eq "contact"'
Get-ADObject -Filter {ObjectClass -eq "contact"}

It does require quotes around values inside the filter (either single or double, but not curly ).

Get-ADObject -Filter {ObjectClass -eq contact} # Error
Get-ADObject -Filter {ObjectClass -eq 'contact'}
Get-ADObject -Filter {ObjectClass -eq "contact"}


Network configuration cmdlets

Test-NetConnection has two party tricks that I find useful. First, it has a builtin traceroute. Second, it can connect to ports other than ICMP ECHO.

Test-NetConnection chcstud-fs -TraceRoute
Test-NetConnection chcstud-fs -Port 3389

Get-NetIpAddress allows filtering by IPv4/IPv6 address type.

Get-NetIPAddress -AddressFamily IPv4
Get-NetIPAddress -AddressFamily IPv6


Module 3: Working with the Windows PowerShell pipeline

Select, sort and measure objects - Sorting objects by a property

"It isn't possible in a single command to sort by one property in ascending order and another in descending order."

Yes, it is. It's been possible for the last four versions of this course which all said that it isn't. *sigh*

How? Use a hash table.

Get-Process `
| Sort-Object `
      @{expression={$PSItem.Name};descending=$true}, `
      @{e={$PSItem.NPM};ascending=$true}

https://craigb-mct.blogspot.com/2016/06/using-powershell-hash-tables-with.html


Filter objects out of the pipeline

No, the principle is filter left, format right.

https://www.youtube.com/watch?v=-LpsPtbcKR0


Skill question

If I run
Get-Service -Name "bits" | Start-Process
what is going to happen ?


Lab: Using PowerShell pipeline (the first one, Lab 3)

Exercise 2: Filtering objects

Task 5: Create a report that displays specified Control Panel items

What is this trying to achieve? List all items in the System and Security category and in only that category. One item (Power Options) is in two categories, so should be excluded.

Get-ControlPanelItem -Category "System and Security" `
| where { -not ($PSItem.Category -notlike "*System and Security*") } `
| sort name `
| ft name, category

Since the Category property is a collection (note the braces), there is a much better way of doing this - the Count property.

Get-ControlPanelItem -Category "System and Security" `
| where { $PSItem.Category.count -eq 1 } `
| sort name `
| ft name, category

Note that Get-ControlPanelItem is not in PowerShell 7.


Enumerate objects in the pipeline

PowerShell 7 adds a -Parallel processing option to ForEach-Object.

https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature/


Send and pass pipeline data as output - Passing pipeline data

Get-ADComputer -Filter * | Get-Process

This is a bad example because there is a parameter that matches (-Name) however Get-Process binds to -ComputerName first, which errors out (even though -ComputerName is not mandatory).

Why? Who knows? I can't find any information on a global rule for the order that pipeline binding uses the parameters.


Module 5: Querying management information by using CIM and WMI

WMI Explorer (Dec 2020)

https://github.com/vinaypamnani/wmie2/releases
https://devblogs.microsoft.com/scripting/weekend-scripter-announcing-wmi-explorer-2-0/


Query data by using CIM and WMI - Querying instances

In WQL the wildcards are % and _. The percent is the "0-or-more" wildcard (the * in Windows) and the underscore is the "exactly-1" wildcard (the ? in Windows).


Make changes by using CIM and WMI - Discovering methods

You cannot use Get-Member to discover methods. It only shows the methods of .NET classes, not of WMI classes.


Lab 5

Hints for the lab:
gcim -ClassName Win32_Account -Filter "LocalAccount = 'true'"
gcim -ClassName Win32_UserAccount -Filter "LocalAccount = 'true'"
#or
gcim -ClassName Win32_UserAccount -Filter "Domain='LON-CL1'"


Module 6: Working with variables, arrays, and hash tables

Manipulate variables - Working with strings

Note that the methods of the String class are always case-sensitive, regardless of the .NET culture.

[String] $S = "Monday"
$S.Contains("mon") # evaluates to False

Write-Host is bad

Don Jones famously wrote that every time you use Write-Host, you kill a puppy. Why is Write-Host so bad? Because it messes with the output streams.

Single-item arrays

PS > $A = "lon-dc1"
PS > $A.count
1
PS > $A[0]
l
PS > $A = @( "lon-dc1" )
PS > $A.count
1
PS > $A[0]
lon-dc1


Lab: Using variables, arrays, and hash tables in PowerShell (Lab 6)

Exercise 1: Working with variable types

Task 2: Use DateTime variables

Step 6: Either replace "haven't signed in" with "have signed in" in the Lab, or replace "-gt" with "-lt" in the Lab Answer Key. 

Note that none of the users in the domain except for Administrator have ever logged in, so the result set will probably be empty either way.
 

Module 7: Windows PowerShell scripting

Case convention

The convention for the scripting keywords is all lowercase. That is, foreach instead of ForEach, if and elseif and else instead of If and ElseIf and Else. Mixed case will, however, work fine.

Mandatory vs Read-Host

The model answers in the course often use a construct similar to the following for mandatory parameters. Note that the parens are required.
[string] $ComputerName = (Read-Host "Enter computer name")
A better way is to mark the parameter as mandatory.
[Parameter(Mandatory)] [string] $ComputerName


Lab: Using scripts with PowerShell (Lab 7)


Exercise 2: Processing an array with a ForEach loop

Goal: Create a script that sets the ip phone attribute for all members of the IPPhoneTest group. Set the ip phone number to givenname.surname@adatum.com.

Note that Active Directory Administrative Center displays the ipPhone attribute on the Account tab of a user. If you use Active Directory Users and Computers then you will need to enable Advanced Features and use the Attribute Editor tab.

Hint: Get-ADGroupMember returns an ADPrincipal object. This does not include most the attributes we need. Inside the foreach loop we will require a call to Get-ADUser.

Hint: The Set-ADUser cmdlet does not directly support changing the ipphone attribute. Use the -Replace parameter, which takes a hashtable for the attribute/value pair or pairs.
Set-ADUser -Identity "August" -Replace @{ ipPhone="august.toyle@adatum.com" }

Exercise 3: Processing items by using If statements

Goal: Create a script that reads a list of services from a text file. For each service in the list, if the service is not running then start it and display a message "Started service name", and if the service is running then display a message "Service name is already running".

The services.txt file should contain two lines: Spooler, W32Time

Exercise 4: Creating users based on a CSV file

Goal: Create a script that creates user accounts based on information from a csv file. There are no passwords provided, so create disabled accounts.

Extra Credit: Create enabled accounts with a complex password. Save the passwords to a text file.

The users.csv file contains four columns: First, Last, UserID, and Department.

Create the users with the following values.
Name: First Last
Path: OU=Department,DC=Adatum,DC=Com
User principal name: UserID@adatum.com
SAM account name: UserID
Display name: First Last
Given name: First
Surname: Last
Department: Department
Password: (Leave blank)

Exercise 5: Querying disk information from remote computers

Goal: Create a parameterised script that collects disk information from a remote computer.
PS E:\Mod07\Labfiles> .\AZ-040_Mod07_Ex5_LAK.ps1 -ComputerName lon-dc1

Hint: Get-CIMInstance Win32_LogicalDisk.

Exercise 6: Updating the script to use alternate credentials

Goal: Extend the previous script, add a switch parameter to make the script prompt us for a credential.
PS E:\Mod07\Labfiles> .\AZ-040_Mod07_Ex6_LAK.ps1 -ComputerName lon-dc1 -UseAlternateCredential

Hint: New-CimSession.


Module 10: Managing Microsoft 365 services with PowerShell

Manage Microsoft 365 user accounts, licenses, and groups with PowerShell - Managing groups

The MsolGroup cmdlets work with object IDs, not names.

For example:
$MktGrp = Get-MsolGroup | Where-Object {$_.DisplayName -eq "Marketing"}
$Catherine = Get-MsolUser | Where-Object {$_.DisplayName -eq "Catherine Richard"}
Add-MsolGroupMember -GroupObjectId $MktGrp.ObjectId -GroupMemberType "User" -GroupMemberObjectId $Catherine.ObjectId


Module 11: Using background jobs and scheduled jobs

Typo: In multiple places in this module, replace "-IncludeChildJobs" with "-IncludeChildJob".

PSScheduledJob module

Remove all references to the ScheduledTasks module. It was superseded many years ago by the PSScheduledJob module.

Get-Command -Module PSScheduledJob

Note that this module is not available in PowerShell 7.

Job results

Background job results are not persisted. When you close the PowerShell host, all unclaimed results are discarded.

Scheduled job results are persisted to disk.
$HOME\AppData\Local\Microsoft\Windows\PowerShell\ScheduledJobs\<jobname>