Scripting — PowerShell Cheatsheet
PowerShell automation and project structure — 2024–2025
Table of Contents
- PowerShell Model
- Objects and Pipeline
- Variables and Collections
- Selection and Loops
- Functions and Scripts
- Error Handling and Logging
- Files and Data Formats
- Windows and AD Automation
- Robust Project Structure
PowerShell Model
Cmdlets follow Verb-Noun; use approved verbs (Get-Verb). PowerShell passes objects, not formatted text. Aliases are handy interactively but unclear in scripts.
| Operator | Meaning |
|---|---|
-eq, -ne |
equal, not equal |
-lt, -le, -gt, -ge |
comparisons |
-like, -match |
wildcard, regex |
-contains, -in |
collection contains value |
-and, -or, -not |
logic |
These settings surface hidden errors faster, but test existing scripts since non-terminating errors can become terminating.
Objects and Pipeline
Get-Service |
Where-Object Status -eq 'Running' |
Sort-Object DisplayName |
Select-Object Name, DisplayName, Status
Get-Process |
Select-Object Name, Id, @{Name='MemoryMB'; Expression={ [math]::Round($_.WorkingSet64 / 1MB, 2) }}
| Cmdlet | Task |
|---|---|
Where-Object |
filter |
Select-Object |
pick/compute properties |
Sort-Object |
sort |
Group-Object |
group |
Measure-Object |
count/sum/average |
ForEach-Object |
process each pipeline object |
Format at the end
Format-Table and Format-List create formatting objects. Use them only as the final step for screen output, never before export or further processing.
$services = Get-Service # data
$services | Export-Csv services.csv -NoTypeInformation
$services | Format-Table # presentation
Variables and Collections
$array = @('a', 'b', 'c')
$array[0]
$array[-1]
$list = [System.Collections.Generic.List[string]]::new()
$list.Add('item')
$settings = @{
Domain = 'lab.local'
Prefix = 'LAB'
}
$settings.Domain
Use [pscustomobject] for records:
Variables inside double quotes are expanded; single quotes are literal. Use $($object.Property) for expressions in strings.
Selection and Loops
foreach ($user in $users) { $user.Name }
for ($i = 0; $i -lt $items.Count; $i++) { $items[$i] }
while ($service.Status -ne 'Running') { $service.Refresh() }
do { $answer = Read-Host 'yes/no' } until ($answer -in 'yes','no')
Avoid infinite loops: provide a timeout, a maximum number of attempts, or an explicit stop condition.
Functions and Scripts
function New-ProjectFolder {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$Path,
[switch]$Force
)
process {
if ($PSCmdlet.ShouldProcess($Path, 'Create directory')) {
New-Item -ItemType Directory -Path $Path -Force:$Force
}
}
}
Advanced functions support common parameters such as -Verbose, -ErrorAction, and with SupportsShouldProcess also -WhatIf/-Confirm.
Script parameters
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path $_ -PathType Leaf })]
[string]$ConfigPath
)
Use return sparingly: any uncaptured output goes onto the pipeline. Status messages belong on Write-Verbose; warnings on Write-Warning; object results on the success stream.
Scope and modules
- Avoid global variables.
- Pass data via parameters and return objects.
- Put reusable functions in a
.psm1and export deliberately. - Use a
.psd1manifest for metadata and exports.
Error Handling and Logging
try {
$content = Get-Content -LiteralPath $Path -ErrorAction Stop
} catch [System.IO.FileNotFoundException] {
Write-Error "File not found: $Path"
} catch {
Write-Error "Failed: $($_.Exception.Message)"
throw
} finally {
# cleanup that must always run
}
try/catch catches terminating errors. Add -ErrorAction Stop when a cmdlet would otherwise only write a non-terminating error.
function Write-Log {
param([string]$Message, [ValidateSet('INFO','WARN','ERROR')]$Level = 'INFO')
$entry = '{0:o} [{1}] {2}' -f (Get-Date), $Level, $Message
Add-Content -LiteralPath $script:LogPath -Value $entry
}
Log the action, target object, result and useful error context; never log passwords or secrets.
Files and Data Formats
Get-Content -LiteralPath file.txt
Set-Content -LiteralPath file.txt -Value $data -Encoding utf8
Add-Content -LiteralPath file.txt -Value $line
Get-ChildItem -LiteralPath $root -Recurse -File
Test-Path -LiteralPath $path
Use -LiteralPath for paths that may contain [ ] or wildcards.
CSV
CSV yields strings: cast numeric, date and boolean fields explicitly.
JSON
$config = Get-Content config.json -Raw | ConvertFrom-Json
$object | ConvertTo-Json -Depth 10 | Set-Content result.json -Encoding utf8
XML
Combining hashtable/config data
Validate required fields before applying changes. Normalize data into one internal object model, even when input comes from CSV, JSON and XML.
Windows and AD Automation
Get-LocalUser
New-LocalUser -Name 'operator' -Password $securePassword
Add-LocalGroupMember -Group 'Remote Desktop Users' -Member 'operator'
Get-SmbShare
New-SmbShare -Name 'Data' -Path 'D:\Data' -ChangeAccess 'LAB\Data_RW'
Import-Module ActiveDirectory
Get-ADUser -Filter * -SearchBase 'OU=Users,DC=lab,DC=local'
New-ADOrganizationalUnit -Name 'Sales' -Path 'DC=lab,DC=local'
New-ADGroup -Name 'GG_Sales' -GroupScope Global -GroupCategory Security -Path $ou
Add-ADGroupMember -Identity 'GG_Sales' -Members $user
Idempotency
A script must be safely re-runnable:
if (-not (Get-ADOrganizationalUnit -LDAPFilter '(ou=Sales)' -SearchBase $base -ErrorAction SilentlyContinue)) {
New-ADOrganizationalUnit -Name 'Sales' -Path $base
}
Check the existing state, change only the difference, and report what happened. Use -WhatIf for risky administrative actions.
Robust Project Structure
project/
├── Invoke-Deployment.ps1
├── Config/
│ ├── Domain.Settings.xml
│ └── users.json
├── Data/
│ ├── ous.csv
│ ├── securitygroups.csv
│ └── shares.csv
├── Modules/
│ └── ProjectTools.psm1
└── Logs/
Recommended phases:
- determine parameters and paths;
- read configuration/data;
- validate schema, required values and references;
- show the plan (
-WhatIf); - execute dependencies in order: OUs → groups → users → memberships → folders → ACLs → shares;
- handle and log errors per step;
- verify and summarize the final state.
No plaintext passwords
Use SecureString, a secret store/vault or safe interactive input. Don't put credentials in source code, CSV, JSON or logs.