PHASE 5 · MODULE 3

PowerShell Scripting for IT Support

Automate repetitive tasks, manage Active Directory, and build reusable scripts that save hours of manual work.

Learning Objectives

Module Breakdown

Module 1: PowerShell Foundations

🕐 ~2.5 hours Core Skills

Module 2: Active Directory & File Operations

🕐 ~2 hours IT Support Focus

Module 3: Functions, Automation & Scheduling

🕐 ~2 hours Advanced

Module 1: PowerShell Foundations

What is PowerShell?

PowerShell is a cross-platform task automation framework from Microsoft, consisting of a command-line shell and scripting language. Unlike traditional shells that work with text, PowerShell works with .NET objects, enabling powerful piping and object manipulation.

PowerShell Versions

Windows PowerShell 5.1: The legacy version built into Windows 10/11. Still widely used in enterprise environments. PowerShell 7+ (pwsh): The modern cross-platform version built on .NET Core. Supports Linux, macOS, and Windows. All examples below work in both versions unless noted.

Your First Scripts: Variables and Data Types

# Variable declaration (no $ needed for cmdlet parameters!) $computerName = "WORKSTATION-01" $userCount = 42 $isOnline = $true $departments = @("Sales", "IT", "HR", "Finance") # String interpolation Write-Host "Checking $computerName -- $userCount users online, status: $isOnline" # Arrays and hashtables $servers = @("SRV-DC01", "SRV-APP01", "SRV-FILE01") $config = @{ Environment = "Production" Region = "US-East" MaxRetries = 3 }

Working with Objects

Everything in PowerShell is an object. Cmdlets return objects with properties you can access and methods you can invoke.

# Get processes as objects and filter by property Get-Process | Where-Object { $_.CPU -gt 100 } | Select-Object Name, CPU, Id # View available properties and methods Get-Process | Get-Member # Call a method directly (Get-Date).AddDays(7).ToString("yyyy-MM-dd") # Format output as a table Get-Service | Where-Object { $_.Status -eq 'Running' } | Sort-Object DisplayName | Select-Object -First 10 DisplayName, Status, StartType
Key Syntax Notes

The $_.Property syntax refers to the current object in a pipeline. Get-Member (alias gm) shows all properties and methods available on any object. Use Select-Object (alias select) to choose which properties appear in your output.

Conditional Logic

# If / Else if / Else $serviceStatus = (Get-Service -Name wuauserv).Status if ($serviceStatus -eq 'Running') { Write-Host "Windows Update service is running." -ForegroundColor Green } elseif ($serviceStatus -eq 'Stopped') { Write-Host "Windows Update service is stopped. Attempting to start..." -ForegroundColor Yellow Start-Service wuauserv } else { Write-Host "Service status is: $serviceStatus" -ForegroundColor Red } # Switch statement (cleaner for multiple conditions) $exitCode = $LASTEXITCODE switch ($exitCode) { 0 { Write-Host "Success!" -ForegroundColor Green } 1 { Write-Host "Warning -- non-fatal error" -ForegroundColor Yellow } 2 { Write-Host "Critical failure" -ForegroundColor Red } default { Write-Host "Unknown exit code: $exitCode" } } # Comparison operators reference # -eq (equal), -ne (not equal), -gt (greater than), -lt (less than) # -ge (>=), -le (<=), -like (wildcard match), -match (regex) # -contains, -in, -not, -and, -or

Loops for Batch Operations

# For loop -- iterate through a list of computers $servers = @("SRV-DC01", "SRV-APP01", "SRV-FILE01") foreach ($server in $servers) { if (Test-Connection -ComputerName $server -Quiet -Count 2) { Write-Host "$server is ONLINE" -ForegroundColor Green } else { Write-Host "$server is OFFLINE" -ForegroundColor Red } } # While loop -- process until condition changes $maxAttempts = 5 $attempt = 0 $connected = $false while ($attempt -lt $maxAttempts -and -not $connected) { $attempt++ Write-Host "Attempt $attempt of $maxAttempts..." $connected = Test-Connection -ComputerName "SRV-DC01" -Quiet -Count 1 Start-Sleep -Seconds 2 } if ($connected) { Write-Host "Connection established!" -ForegroundColor Green } # ForEach-Object (pipeline version) -- essential for production scripts Get-Process | Where-Object { $_.WorkingSet64 -gt 100MB } | ForEach-Object { Write-Host "$($_.Name) is using $([math]::Round($_.WorkingSet64/1MB)) MB RAM" }

Exercise 1: User Account Age Report

Write a script that queries Active Directory for user accounts, finds those older than 90 days, and outputs their SamAccountName, LastLogonDate, and Created date. If the AD module is not available, simulate with sample data.

# Simulation without AD module (use Replace-AdventureWorks for real AD) $simulatedUsers = @( @{SamAccountName = "jsmith"; LastLogon = (Get-Date).AddDays(-150); Created = (Get-Date).AddDays(-200)}, @{SamAccountName = "ajones"; LastLogon = (Get-Date).AddDays(-30); Created = (Get-Date).AddDays(-60)}, @{SamAccountName = "bwilliams"; LastLogon = (Get-Date).AddDays(-365); Created = (Get-Date).AddDays(-400)}, @{SamAccountName = "mdavis"; LastLogon = (Get-Date).AddDays(-95); Created = (Get-Date).AddDays(-120)} ) $cutoffDate = (Get-Date).AddDays(-90) $inactiveUsers = $simulatedUsers | Where-Object { $_.LastLogon -lt $cutoffDate } Write-Host "Inactive users (no login in 90+ days):" -ForegroundColor Yellow $inactiveUsers | ForEach-Object { Write-Host " $($_.SamAccountName) -- Last logon: $($_.LastLogon.ToString('yyyy-MM-dd')) | Created: $($_.Created.ToString('yyyy-MM-dd'))" } # Real AD equivalent (uncomment when AD module is available): # Import-Module ActiveDirectory # Get-ADUser -Filter {Enabled -eq $true} -Properties LastLogonDate, Created | # Where-Object { $_.LastLogonDate -lt (Get-Date).AddDays(-90) } | # Select-Object SamAccountName, LastLogonDate, Created | # Export-Csv -Path "C:\Reports\StaleAccounts.csv" -NoTypeInformation

Module 2: Active Directory & File Operations

Active Directory Cmdlets

The ActiveDirectory module provides over 200 cmdlets for managing AD. These are available on Domain Controllers and machines with RSAT (Remote Server Administration Tools) installed.

TaskPowerShell CmdletExample
List all users in a departmentGet-ADUserGet-ADUser -Filter {Department -eq 'IT'}
Create a new userNew-ADUserNew-ADUser -Name ...
Reset a passwordReset-ADAccountPasswordReset-ADAccountPassword ...
Disable an accountDisable-ADAccountDisable-ADAccount -Identity jsmith
Enable an accountEnable-ADAccountEnable-ADAccount -Identity jsmith
Add user to groupAdd-ADGroupMemberAdd-ADGroupMember -Members jsmith -Groups 'IT-Support'
Find disabled accountsGet-ADUser with filterGet-ADUser -Filter {Enabled -eq $false}
Find expired passwordsGet-ADUser + PasswordExpiredGet-ADUser -Filter {PasswordExpired -eq $true}

Complete AD Management Scripts

# Script 1: Bulk create users from CSV Import-Module ActiveDirectory $users = Import-Csv "C:\Scripts\NewHires.csv" foreach ($u in $users) { $params = @{ Name = "$($u.FirstName) $($u.LastName)" SamAccountName = $u.Username UserPrincipalName = "$($u.Username)@contoso.com" Path = $u.OU AccountPassword = (ConvertTo-SecureString $u.Password -AsPlain -Force) Enabled = $true PasswordNeverExpires = $false } New-ADUser @params Write-Host "Created user: $($u.Username)" } # Script 2: Lock out and report stale accounts (90-day rule) $staleThreshold = (Get-Date).AddDays(-90) $staleUsers = Get-ADUser -Filter {Enabled -eq $true} -Properties PasswordLastSet, LastLogonDate | Where-Object { $_.PasswordLastSet -lt $staleThreshold -and $_.LastLogonDate -lt $staleThreshold } foreach ($user in $staleUsers) { Disable-ADAccount -Identity $user.SamAccountName Write-Host "Disabled stale account: $($user.SamAccountName) (last logon: $($user.LastLogonDate))" } $staleUsers | Measure-Object | Select-Object -ExpandProperty Count

File System Operations

# Common file operations Get-ChildItem "C:\Logs" -Recurse | Where-Object { $_.Length -gt 100MB } | Sort-Object Length -Descending | Select-Object FullName, Length, LastWriteTime # Find and delete old log files (older than 30 days) $deleteBefore = (Get-Date).AddDays(-30) Get-ChildItem "C:\Temp\Backups" -File | Where-Object { $_.LastWriteTime -lt $deleteBefore } | Remove-Item -Force Write-Host "Cleaned up $(@(Get-ChildItem 'C:\Temp\Backups' -File | Where-Object { $_.LastWriteTime -lt $deleteBefore }).Count) old backup files" # Copy and archive $dateStamp = Get-Date -Format "yyyyMMdd" Compress-Archive -Path "C:\Data\Sensitive" -DestinationPath "C:\Archive\backup-$dateStamp.zip" -Force # Parse event logs Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625} -MaxEvents 20 | Select-Object TimeCreated, Message | ConvertTo-Xml -As String -Depth 2 | Out-File "C:\Reports\EventLogExport.xml"

Error Handling with Try/Catch

# Basic error handling pattern try { $svc = Get-Service -Name "Spooler" -ErrorAction Stop Restart-Service -Name "Spooler" -ErrorAction Stop Write-Host "Print spooler restarted successfully." -ForegroundColor Green } catch { Write-Host "Failed to restart Print Spooler: $($_.Exception.Message)" -ForegroundColor Red # Log to event log or file Write-EventLog -LogName Application -Source PowerShell -EntryType Error ` -EventId 5000 -Message "Script error restarting spooler: $($_.Exception.Message)" } # Finally block ensures cleanup runs regardless of outcome try { $result = Invoke-Command -ComputerName $targetPC -ScriptBlock { Get-Process } } catch { Write-Host "Remote connection failed: $($_.Exception.Message)" } finally { Write-Host "Operation completed at $(Get-Date)." }

Exercise 2: New Employee Onboarding Script

Create a PowerShell script that takes FirstName, LastName, Department, and Manager as parameters. It should create an AD user, add them to the appropriate security group, assign an O365 license placeholder, and send a confirmation email body as a string.

#Requires -Module ActiveDirectory param( [Parameter(Mandatory=$true)][string]$FirstName, [Parameter(Mandatory=$true)][string]$LastName, [Parameter(Mandatory=$true)][string]$Department, [Parameter(Mandatory=$true)][string]$Manager ) $upan = "$FirstName.$LastName".ToLower() $ouPath = switch ($Department) { "Sales" { "OU=Sales,OU=Users,DC=contoso,DC=com" } "IT" { "OU=IT,OU=Users,DC=contoso,DC=com" } default { "OU=General,OU=Users,DC=contoso,DC=com" } } try { $newUser = New-ADUser -Name "$FirstName $LastName" -SamAccountName $upan -UserPrincipalName "$upan@contoso.com" -Path $ouPath -Enabled $true -ChangePasswordAtLogon $true Write-Host "User created: $upan in $ouPath" -ForegroundColor Green Write-Host "Temporary password assigned -- user must change at next login." -ForegroundColor Yellow } catch { Write-Host "Error creating user: $($_.Exception.Message)" -ForegroundColor Red }

Module 3: Functions, Automation & Scheduling

Writing Reusable Functions

# Function with parameters and validation function Test-ServiceHealth { param( [Parameter(Mandatory=$true)][string[]]$ComputerName, [string[]]$Services = @("spooler", "wuauserv", "bits"), [int]$TimeoutSec = 5 ) foreach ($comp in $ComputerName) { foreach ($svc in $Services) { try { $status = Get-Service -Name $svc -ComputerName $comp -ErrorAction Stop $emoji = if ($status.Status -eq 'Running') { '[OK]' } else { '[!!]' } Write-Host "$emoji $comp : $svc = $($status.Status)" } catch { Write-Host "[XX] $comp : $svc = UNREACHABLE ($($_.Exception.Message))" -ForegroundColor Red } } } } # Usage examples Test-ServiceHealth -ComputerName "SRV-DC01","SRV-APP01" Test-ServiceHealth -ComputerName "WORKSTATION-05" -Services @("TermService", "Schedule") # Advanced function with advanced features (param attributes) function Enable-MultiFactorAuth { [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [string]$Username, [switch]$VerboseLogging ) process { Write-Verbose "Processing $Username" # MFA enrollment logic here return @{ Username = $Username; Status = "Enrolled"; Timestamp = Get-Date } } }

Scheduling Scripts with Task Scheduler

# Create a scheduled task via PowerShell $action = New-ScheduledTaskAction -Execute "PowerShell.exe" ` -Argument "-ExecutionPolicy Bypass -File C:\Scripts\DailyCleanup.ps1" $trigger = New-ScheduledTaskTrigger -Daily -At "2:00AM" $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd Register-ScheduledTask -TaskName "Daily-System-Cleanup" -Action $action ` -Trigger $trigger -Principal $principal -Settings $settings ` -Description "Cleans temp files and rotates logs daily at 2 AM" # Create a weekly Monday morning task for employee count reports $weeklyTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "8:00AM" Register-ScheduledTask -TaskName "Weekly-Employee-Report" ` -Action (New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\Scripts\EmployeeReport.ps1") ` -Trigger $weeklyTrigger -Principal (New-ScheduledTaskPrincipal -UserId "DOMAIN\admin" -LogonType Password -RunLevel Highest) ` -Description "Generates weekly employee headcount report"

Security Best Practices

DO NOT Do This

  • Hardcoding passwords in plain text
  • Using -AsPlainText -Force for sensitive data
  • Running scripts with unrestricted execution policy
  • Storing credentials in config files
  • Sharing admin scripts without access controls

Do This Instead

  • Use Get-Credential prompts or Azure Key Vault
  • Store encrypted credentials: Read-Host -AsSecureString | ConvertFrom-SecureString
  • Set ExecutionPolicy to RemoteSigned at minimum
  • Place scripts in protected network shares with ACLs
  • Audit script execution via Windows Event Logs
# Secure credential storage pattern $credentialPath = "$env:APPDATA\scripts\credentials.enc" # One-time setup: save encrypted credential # Read-Host "Enter password" -AsSecureString | ConvertFrom-SecureString | Set-Content $credentialPath # Daily use: retrieve and use #$cred = New-Object PSCredential("admin", (Get-Content $credentialPath | ConvertTo-SecureString)) # Use $cred in your script...

Knowledge Check

1. What does the $_.Property syntax represent in a PowerShell pipeline?
Answer: B. $_ is the automatic variable representing the current object in the pipeline during each iteration of Where-Object, ForEach-Object, etc.
2. Which command creates a new Active Directory user account?
Answer: B) New-ADUser. Part of the ActiveDirectory module. Requires proper permissions and an OU path.
3. How do you prevent a PowerShell script from stopping entirely when a non-terminating error occurs?
Answer: D. -ErrorAction SilentlyContinue suppresses non-terminating errors without stopping. Using -ErrorAction Stop converts them to terminating errors so try/catch can handle them gracefully.
4. What is the recommended way to store credentials securely in a scheduled PowerShell script?
Answer: C. DPAPI (Data Protection API) encryption via ConvertFrom-SecureString binds the encrypted credential to the user account and machine, making it readable only by that specific user on that specific machine.
5. Which execution policy allows local scripts to run but requires downloaded scripts to be signed?
Answer: C) RemoteSigned. This is the default on most systems and is the recommended minimum for production. Restricted blocks all scripts. AllSigned requires every script to be signed. Unrestricted runs everything including unsigned downloaded scripts (dangerous).

Recommended Resources

Progress Checklist

Next Steps

Where to Go From Here

You now have strong PowerShell skills for Windows automation. Next, complete Bash Scripting & IT Service Management to gain the complementary Linux scripting skills every modern IT support professional needs. After finishing Phase 5, you'll be ready for Phase 6: Land Your First Role.

← Previous: Azure & AWS Basics Next: Bash & ITSM →