article thumbnail
Powershell
Microsoft's Cross-Platform Answer to Unix Shells
17 min read
#scripting, #programming, #windows

For decades, Windows administrators faced a choice: click through endless GUI wizards or wrestle with the limitations of CMD.EXE - a shell designed in the 1980s that treats everything as text. Meanwhile, Unix administrators had powerful shells that could pipe, filter, and transform data with ease.

Enter PowerShell: Microsoft's answer to the automation gap. Built on .NET, treating everything as an object rather than text, and now running cross-platform on Windows, Linux, and macOS, PowerShell has evolved from a Windows-only tool into a modern automation platform used by millions of system administrators, DevOps engineers, and developers worldwide.

This guide will take you from PowerShell fundamentals to practical automation, covering its history, architecture, command syntax, and real-world examples that will make you productive immediately.


A Brief History

PowerShell was born from frustration. In the early 2000s, Microsoft faced a critical problem: Windows administrators lacked the automation tools that Unix administrators took for granted. GUI-based management didn't scale, and CMD.EXE was woefully inadequate for modern system administration.

The Monad Manifesto

In August 2002, Jeffrey Snover, a Microsoft Distinguished Engineer, published the "Monad Manifesto" - a white paper outlining his vision for a new Windows shell. Snover's key insight: Linux treats everything as a text file, but Windows treats everything as an API that returns structured data. Why force administrators to parse text when you could work with objects directly?

Development began in 2003 under the codename "Monad" (also known as Microsoft Shell or MSH). Snover implemented the first prototype in C#, introducing the revolutionary concept of an object pipeline - not text flowing between commands, but rich .NET objects.

Key Milestones

2003 - Project Monad begins; first public demonstration at Professional Development Conference 2006 - PowerShell 1.0 released (November); downloaded nearly one million times within six months 2009 - PowerShell 2.0 ships with Windows 7; adds remoting and background jobs 2012 - PowerShell 3.0 with Windows 8; introduces Workflow and improved cmdlet discovery 2016 - PowerShell open-sourced; PowerShell Core announced as cross-platform edition 2018 - PowerShell Core 6.0 released; runs on Windows, Linux, and macOS 2020 - PowerShell 7.0 released; replaces PowerShell Core as the primary cross-platform version 2025 - PowerShell 7.5 (currently 7.5.4) continues active development with performance improvements and new features

The Challenges

Initially met with skepticism, Snover's idea faced resistance from a company culture that favored graphical interfaces. Creating a command-line tool at Microsoft required persistence and vision. Today, PowerShell is one of Microsoft's most successful open-source projects with over 51,000 stars on GitHub.


Why PowerShell?

The Core Value Proposition

PowerShell differentiates itself through four fundamental pillars:

  1. Object-Based Pipeline: Unlike traditional shells that pass text between commands, PowerShell passes .NET objects. No more parsing text with grep, awk, or sed - you work with structured data directly.

  2. Consistent Syntax: Every command (cmdlet) follows the Verb-Noun naming convention: Get-Process, Set-Service, New-Item. Once you learn the pattern, you can guess command names.

  3. Discoverability: With Get-Command, Get-Help, and Get-Member, you can explore the entire system without leaving the shell or consulting documentation.

  4. Cross-Platform: PowerShell 7+ runs identically on Windows, Linux, and macOS, enabling true cross-platform automation scripts.

Real-World Impact

PowerShell's adoption speaks for itself:


Use Cases

PowerShell excels in scenarios requiring Windows automation, but its reach extends far beyond:

System Administration

Administrators use PowerShell to manage Active Directory, configure servers, install software, and monitor system health - all through repeatable, auditable scripts instead of manual GUI clicks.

DevOps and CI/CD

DevOps teams integrate PowerShell into build pipelines, deploy applications, provision infrastructure, and orchestrate complex deployment workflows across hybrid environments.

Cloud Management

Azure, AWS, and other cloud providers offer PowerShell modules for managing cloud resources. Provision virtual machines, configure networking, and deploy applications entirely from scripts.

Security and Compliance

Security teams use PowerShell to audit systems, enforce compliance policies, investigate incidents, and automate threat detection and response.

Active Directory Management

PowerShell is the de facto tool for managing Active Directory at scale - creating users, managing groups, resetting passwords, and auditing permissions across thousands of objects.

Automation and Orchestration

From scheduled tasks to complex workflows, PowerShell automates repetitive tasks, reducing human error and freeing administrators to focus on strategic work.

Database Management and Scripting

PowerShell's versatility extends to database management and administration. The WaSQL platform now officially supports PowerShell as a scripting language, joining its multi-language ecosystem that includes PHP, Python, Node.js, and others. This support enables administrators to write database procedures, triggers, and scheduled tasks in PowerShell, leveraging familiar syntax and cmdlets while working directly with database records.


Installation

PowerShell comes in two primary editions: Windows PowerShell 5.1 (pre-installed on Windows) and PowerShell 7+ (cross-platform, modern).

Windows PowerShell 5.1 (Pre-Installed)

Windows 10 and Windows Server 2016 or later include Windows PowerShell 5.1 by default:

# Check your version
$PSVersionTable.PSVersion

# Launch Windows PowerShell
# Start menu: "Windows PowerShell"
# Or run: powershell.exe

Windows PowerShell 5.1 is the last version of Windows PowerShell. Microsoft recommends migrating to PowerShell 7+ for new projects.

PowerShell 7+ (Recommended)

PowerShell 7 is the modern, cross-platform version. It installs side-by-side with Windows PowerShell without replacing it.

Windows Installation

# Method 1: WinGet (Windows Package Manager)
winget install --id Microsoft.PowerShell --source winget

# Method 2: MSI Installer
# Download from: https://github.com/PowerShell/PowerShell/releases

# Method 3: Microsoft Store
# Search for "PowerShell" in Microsoft Store

# Launch PowerShell 7
# Run: pwsh.exe (not powershell.exe)

Linux Installation (Ubuntu/Debian)

# Update package list
sudo apt update

# Install prerequisite packages
sudo apt install -y wget apt-transport-https software-properties-common

# Download Microsoft repository GPG key
wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb"

# Register the repository
sudo dpkg -i packages-microsoft-prod.deb

# Install PowerShell
sudo apt update
sudo apt install -y powershell

# Launch PowerShell
pwsh

macOS Installation

# Using Homebrew
brew install --cask powershell

# Launch PowerShell
pwsh

Docker

# Run PowerShell in a container
docker run -it mcr.microsoft.com/powershell:latest

# This drops you into a PowerShell prompt

Getting Started

Launching PowerShell

On Windows:

On Linux/macOS:

The Prompt

When you launch PowerShell, you'll see a prompt like this:

PS C:\Users\YourName>

Or on Linux/macOS:

PS /home/username>

The PS indicates you're in PowerShell. The path shows your current directory.

Your First Commands

# Get help about any command
Get-Help Get-Process

# List all available commands
Get-Command

# Find commands containing "service"
Get-Command *service*

# See what properties an object has
Get-Process | Get-Member

Core Concepts

Understanding PowerShell requires grasping three fundamental concepts:

1. Cmdlets (Command-lets)

Cmdlets are PowerShell's native commands. They follow a strict Verb-Noun naming convention:

Approved verbs include: Get, Set, New, Remove, Start, Stop, Add, Clear, Read, Write, Test, and more.

2. Objects, Not Text

This is PowerShell's most important difference from traditional shells:

# In Bash, you get text
$ ps aux | grep firefox

# In PowerShell, you get objects
Get-Process -Name firefox

The PowerShell command returns a Process object with properties like Id, CPU, Memory, StartTime - not a text string you must parse.

3. The Pipeline

The pipeline (|) passes objects from one command to another:

# Get all services, filter to running ones, select first 5
Get-Service | Where-Object {$_.Status -eq "Running"} | Select-Object -First 5

Each cmdlet receives full objects, not text, allowing rich filtering and transformation.


Essential Commands

Here are the most used PowerShell commands every user should know:

Discovery and Help

# Get help for any command
Get-Help Get-Process
Get-Help Get-Process -Examples
Get-Help Get-Process -Full

# Update help files (run as Administrator)
Update-Help

# List all commands
Get-Command

# Find commands by verb
Get-Command -Verb Get

# Find commands by noun
Get-Command -Noun Service

# See object properties and methods
Get-Service | Get-Member

File System Navigation

# List files and directories (like 'ls' or 'dir')
Get-ChildItem
Get-ChildItem C:\Windows\System32\*.dll

# Aliases work too
ls
dir

# Change directory
Set-Location C:\Temp
cd C:\Temp

# Get current directory
Get-Location
pwd

# Create new file or directory
New-Item -Path "C:\Temp\test.txt" -ItemType File
New-Item -Path "C:\Temp\TestFolder" -ItemType Directory
mkdir C:\Temp\NewFolder

# Copy items
Copy-Item -Path "C:\Temp\test.txt" -Destination "C:\Temp\test_backup.txt"

# Move items
Move-Item -Path "C:\Temp\test.txt" -Destination "C:\Temp\Archive\test.txt"

# Delete items
Remove-Item -Path "C:\Temp\test.txt"
Remove-Item -Path "C:\Temp\OldFolder" -Recurse

Process Management

# List all running processes
Get-Process

# Get specific process
Get-Process -Name chrome

# Sort by CPU usage
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10

# Stop a process
Stop-Process -Name notepad
Stop-Process -Id 1234

# Start a process
Start-Process notepad.exe
Start-Process "C:\Program Files\App\app.exe" -ArgumentList "/silent"

Service Management

# List all services
Get-Service

# Get specific service
Get-Service -Name wuauserv

# Filter running services
Get-Service | Where-Object {$_.Status -eq "Running"}

# Start a service
Start-Service -Name wuauserv

# Stop a service
Stop-Service -Name wuauserv

# Restart a service
Restart-Service -Name wuauserv

# Set service startup type
Set-Service -Name wuauserv -StartupType Automatic

Working with Text and Files

# Read file content
Get-Content -Path "C:\Temp\log.txt"
cat C:\Temp\log.txt

# Read last 10 lines
Get-Content -Path "C:\Temp\log.txt" -Tail 10

# Write to file (overwrite)
"Hello World" | Out-File -FilePath "C:\Temp\output.txt"
Set-Content -Path "C:\Temp\output.txt" -Value "Hello World"

# Append to file
"New Line" | Add-Content -Path "C:\Temp\output.txt"

# Search file content
Select-String -Path "C:\Temp\*.log" -Pattern "ERROR"

Network Commands

# Test network connection (like ping)
Test-Connection -ComputerName google.com

# Test specific port
Test-NetConnection -ComputerName google.com -Port 443

# Get IP configuration
Get-NetIPAddress
Get-NetIPConfiguration

# Download a file
Invoke-WebRequest -Uri "https://example.com/file.zip" -OutFile "C:\Temp\file.zip"

Variables

# Create a variable
$name = "John"
$age = 30
$numbers = 1,2,3,4,5

# Use a variable
Write-Host "My name is $name and I am $age years old"

# Automatic variables
$PSVersionTable     # PowerShell version info
$HOME               # User's home directory
$PWD                # Current directory
$PID                # Current process ID

# Environment variables
$env:PATH           # System PATH
$env:USERNAME       # Current username
$env:COMPUTERNAME   # Computer name

PowerShell Syntax

Basic Command Structure

Verb-Noun -ParameterName ParameterValue

Example:

Get-Process -Name chrome

Parameters

# Named parameters
Get-ChildItem -Path C:\Temp -Filter *.txt

# Positional parameters (order matters)
Get-ChildItem C:\Temp *.txt

# Switch parameters (boolean flags)
Get-ChildItem -Recurse

# Multiple parameters
Get-ChildItem -Path C:\Temp -Filter *.txt -Recurse

Operators

# Comparison operators
-eq     # Equal to
-ne     # Not equal to
-gt     # Greater than
-ge     # Greater than or equal to
-lt     # Less than
-le     # Less than or equal to
-like   # Wildcard match
-match  # Regex match

# Examples
5 -eq 5                    # True
"hello" -like "hel*"       # True
"abc123" -match "\d+"      # True

# Logical operators
-and    # Logical AND
-or     # Logical OR
-not    # Logical NOT
!       # Logical NOT (alternative)

# Examples
(5 -gt 3) -and (10 -lt 20)    # True

Filtering with Where-Object

# Filter objects in the pipeline
Get-Service | Where-Object {$_.Status -eq "Running"}

# Shorter syntax (PowerShell 3.0+)
Get-Service | Where Status -eq "Running"

# Multiple conditions
Get-Process | Where-Object {$_.CPU -gt 10 -and $_.WorkingSet -gt 100MB}

Selecting with Select-Object

# Select specific properties
Get-Process | Select-Object Name, Id, CPU

# Select first N items
Get-Process | Select-Object -First 10

# Select last N items
Get-Process | Select-Object -Last 5

# Create custom properties
Get-Process | Select-Object Name, @{Name="MemoryMB";Expression={$_.WorkingSet / 1MB}}

Loops

# ForEach-Object (pipeline)
1..5 | ForEach-Object { "Number: $_" }

# foreach statement
foreach ($i in 1..5) {
    Write-Host "Number: $i"
}

# for loop
for ($i = 0; $i -lt 5; $i++) {
    Write-Host "Count: $i"
}

# while loop
$i = 0
while ($i -lt 5) {
    Write-Host "Count: $i"
    $i++
}

Conditionals

# if statement
$number = 10
if ($number -gt 5) {
    Write-Host "Number is greater than 5"
} elseif ($number -eq 5) {
    Write-Host "Number is exactly 5"
} else {
    Write-Host "Number is less than 5"
}

# switch statement
$day = "Monday"
switch ($day) {
    "Monday"    { "Start of work week" }
    "Friday"    { "End of work week" }
    "Saturday"  { "Weekend!" }
    "Sunday"    { "Weekend!" }
    default     { "Midweek day" }
}

Real-World Example: Automated User Onboarding

Let's build a practical script that automates new employee onboarding - a common IT administration task.

The Scenario

When a new employee joins, IT must:

  1. Create an Active Directory user account
  2. Add the user to appropriate security groups
  3. Create a home directory with proper permissions
  4. Send a welcome email with credentials
  5. Log all actions for compliance

The Script

# New-EmployeeOnboarding.ps1
# Automates employee onboarding process

param(
    [Parameter(Mandatory=$true)]
    [string]$FirstName,

    [Parameter(Mandatory=$true)]
    [string]$LastName,

    [Parameter(Mandatory=$true)]
    [string]$Department,

    [Parameter(Mandatory=$true)]
    [string]$ManagerEmail
)

# Configuration
$domain = "contoso.com"
$ouPath = "OU=Users,DC=contoso,DC=com"
$homeDirectoryBase = "\\fileserver\home"
$logFile = "C:\IT\Logs\Onboarding.log"

# Generate username (first initial + last name)
$username = "$($FirstName.Substring(0,1))$LastName".ToLower()
$email = "$username@$domain"

# Function to log actions
function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$timestamp - $Message" | Add-Content -Path $logFile
    Write-Host $Message
}

try {
    Write-Log "Starting onboarding for $FirstName $LastName"

    # Step 1: Create AD User
    Write-Log "Creating Active Directory account..."
    $password = ConvertTo-SecureString "TempPass123!" -AsPlainText -Force

    New-ADUser -Name "$FirstName $LastName" `
               -GivenName $FirstName `
               -Surname $LastName `
               -SamAccountName $username `
               -UserPrincipalName $email `
               -EmailAddress $email `
               -Department $Department `
               -Path $ouPath `
               -AccountPassword $password `
               -Enabled $true `
               -ChangePasswordAtLogon $true

    Write-Log "User account created: $username"

    # Step 2: Add to security groups
    Write-Log "Adding user to security groups..."
    Add-ADGroupMember -Identity "Domain Users" -Members $username
    Add-ADGroupMember -Identity $Department -Members $username

    Write-Log "User added to groups: Domain Users, $Department"

    # Step 3: Create home directory
    Write-Log "Creating home directory..."
    $homeDirectory = Join-Path $homeDirectoryBase $username
    New-Item -Path $homeDirectory -ItemType Directory -Force

    # Set permissions (user has full control)
    $acl = Get-Acl $homeDirectory
    $permission = "$domain\$username", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
    $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
    $acl.SetAccessRule($accessRule)
    Set-Acl -Path $homeDirectory -AclObject $acl

    Write-Log "Home directory created: $homeDirectory"

    # Step 4: Send welcome email
    Write-Log "Sending welcome email..."
    $emailBody = @"
Welcome to the team, $FirstName!

Your account has been created:
Username: $username
Email: $email
Temporary Password: TempPass123!

You will be prompted to change your password on first login.

If you have any questions, please contact IT support.

Best regards,
IT Department
"@

    Send-MailMessage -To $email `
                     -From "it@$domain" `
                     -Subject "Welcome to Contoso!" `
                     -Body $emailBody `
                     -SmtpServer "smtp.$domain" `
                     -Cc $ManagerEmail

    Write-Log "Welcome email sent to $email"

    # Step 5: Create summary report
    $report = [PSCustomObject]@{
        Username = $username
        FullName = "$FirstName $LastName"
        Email = $email
        Department = $Department
        HomeDirectory = $homeDirectory
        CreatedDate = Get-Date
    }

    $report | Export-Csv -Path "C:\IT\Reports\NewUsers.csv" -Append -NoTypeInformation

    Write-Log "Onboarding completed successfully for $username"
    Write-Host "`nOnboarding Summary:" -ForegroundColor Green
    Write-Host "Username: $username"
    Write-Host "Email: $email"
    Write-Host "Home Directory: $homeDirectory"

} catch {
    Write-Log "ERROR: $($_.Exception.Message)"
    Write-Host "Onboarding failed. Check log file: $logFile" -ForegroundColor Red
}

Running the Script

# Run the onboarding script
.\New-EmployeeOnboarding.ps1 -FirstName "Jane" `
                              -LastName "Smith" `
                              -Department "Marketing" `
                              -ManagerEmail "manager@contoso.com"

What This Demonstrates


Advanced Features

Remoting

Execute commands on remote computers:

# Enable remoting (run once on target machine)
Enable-PSRemoting -Force

# Execute command on remote computer
Invoke-Command -ComputerName Server01 -ScriptBlock {
    Get-Service | Where Status -eq "Running"
}

# Interactive session
Enter-PSSession -ComputerName Server01
# ... run commands ...
Exit-PSSession

# Execute on multiple computers
$servers = "Server01", "Server02", "Server03"
Invoke-Command -ComputerName $servers -ScriptBlock {
    Get-EventLog -LogName System -Newest 10
}

Modules

Extend PowerShell with modules:

# List available modules
Get-Module -ListAvailable

# Import a module
Import-Module ActiveDirectory

# Find modules in PowerShell Gallery
Find-Module -Name Az

# Install a module
Install-Module -Name Az -Scope CurrentUser

# Update a module
Update-Module -Name Az

Functions and Scripts

# Define a function
function Get-DiskUsage {
    param([string]$Path = "C:\")

    Get-ChildItem -Path $Path -Recurse -ErrorAction SilentlyContinue |
        Measure-Object -Property Length -Sum |
        Select-Object @{Name="Path";Expression={$Path}},
                      @{Name="SizeGB";Expression={[math]::Round($_.Sum / 1GB, 2)}}
}

# Call the function
Get-DiskUsage -Path "C:\Users"

# Save as script: Save-DiskUsage.ps1
# Run script: .\Save-DiskUsage.ps1

Working with APIs

# Call a REST API
$response = Invoke-RestMethod -Uri "https://api.github.com/users/powershell" -Method Get

# Access response properties
$response.name
$response.public_repos

# POST data to API
$body = @{
    name = "test"
    description = "Test repository"
} | ConvertTo-Json

$response = Invoke-RestMethod -Uri "https://api.github.com/user/repos" `
                               -Method Post `
                               -Headers @{Authorization = "token YOUR_TOKEN"} `
                               -Body $body `
                               -ContentType "application/json"

Scheduled Tasks

# Create a scheduled task
$action = New-ScheduledTaskAction -Execute "pwsh.exe" `
                                   -Argument "-File C:\Scripts\Backup.ps1"

$trigger = New-ScheduledTaskTrigger -Daily -At 2am

Register-ScheduledTask -TaskName "Daily Backup" `
                       -Action $action `
                       -Trigger $trigger `
                       -Description "Runs backup script daily at 2am"

# List scheduled tasks
Get-ScheduledTask | Where-Object {$_.State -eq "Ready"}

Best Practices

1. Use Approved Verbs

# Good - uses approved verb
Get-UserList

# Bad - uses non-standard verb
Fetch-UserList

# Check approved verbs
Get-Verb

2. Use Full Cmdlet Names in Scripts

# Good - explicit and readable
Get-ChildItem -Path C:\Temp -Recurse

# Acceptable in interactive shell, avoid in scripts
ls C:\Temp -r

3. Use Comment-Based Help

<#
.SYNOPSIS
    Gets disk usage for a specified path
.DESCRIPTION
    Calculates total disk usage by recursively scanning directories
.PARAMETER Path
    The path to scan
.EXAMPLE
    Get-DiskUsage -Path "C:\Users"
#>
function Get-DiskUsage {
    param([string]$Path)
    # Function body...
}

4. Use Error Handling

# Use try/catch for terminating errors
try {
    Get-Content -Path "C:\NonExistent.txt" -ErrorAction Stop
} catch {
    Write-Error "File not found: $_"
}

# Use -ErrorAction for non-terminating errors
Get-ChildItem -Path C:\Windows\System32 -ErrorAction SilentlyContinue

5. Use WhatIf and Confirm

# Preview what would happen
Remove-Item -Path C:\Temp\*.log -WhatIf

# Prompt for confirmation on dangerous operations
Stop-Service -Name ImportantService -Confirm

6. Follow Naming Conventions

# Variables: camelCase or PascalCase
$userName = "jsmith"
$ComputerName = "Server01"

# Functions: Verb-Noun format
function Get-UserInfo { }

# Script files: Verb-Noun.ps1
# Good: Get-SystemInfo.ps1
# Bad: script1.ps1

Performance Tips

1. Filter Left, Format Right

# Slow - gets all processes, then filters
Get-Process | Where-Object {$_.Name -eq "chrome"}

# Fast - filters at the source
Get-Process -Name chrome

2. Use .NET Methods When Appropriate

# Slower - PowerShell cmdlet overhead
Get-Date | Select-Object -ExpandProperty Year

# Faster - direct .NET method
(Get-Date).Year

3. Avoid Unnecessary Pipeline Operations

# Slower - unnecessary pipeline
Get-Process | ForEach-Object { $_.Name }

# Faster - direct property access
(Get-Process).Name

4. Use -Filter Instead of Where-Object

# Slower - gets all items, then filters
Get-ChildItem -Path C:\Temp | Where-Object {$_.Extension -eq ".log"}

# Faster - filters at source
Get-ChildItem -Path C:\Temp -Filter *.log

5. Parallel Processing (PowerShell 7+)

# Sequential processing
1..10 | ForEach-Object {
    Start-Sleep -Seconds 1
    $_
}  # Takes 10 seconds

# Parallel processing
1..10 | ForEach-Object -Parallel {
    Start-Sleep -Seconds 1
    $_
} -ThrottleLimit 10  # Takes ~1 second

When to Choose PowerShell

Choose PowerShell When:

Consider Alternatives When:


Resources

Books and Learning


Conclusion

PowerShell represents Microsoft's most significant contribution to system administration and automation. From its origins as "Monad" - Jeffrey Snover's vision of an object-based shell - to its current form as a mature, cross-platform automation platform, PowerShell has transformed how administrators manage Windows infrastructure and beyond.

Its object-oriented pipeline, consistent Verb-Noun syntax, and unparalleled discoverability make it accessible to beginners while remaining powerful enough for enterprise automation at scale. With PowerShell 7 bringing cross-platform support and continuous performance improvements, it has evolved from a Windows-only tool to a genuine competitor in the broader automation ecosystem.

Whether you are provisioning cloud infrastructure, managing Active Directory, orchestrating DevOps workflows, or simply automating repetitive tasks, PowerShell provides the tools, consistency, and community support to succeed.

Start simple. Install PowerShell 7, run Get-Command, explore with Get-Help, and build from there. The investment you make in learning PowerShell will pay dividends throughout your career.

# Your journey starts here
Install-Module -Name PSReadLine
Get-Help about_*
Get-Command -Module Microsoft.PowerShell.Management

Happy scripting.


Sources