Table of Contents

PowerShell: Complete Development Guide

"PowerShell is the Swiss Army knife of system administration and automation. Once you master its fundamentals, you'll wonder how you ever managed without it." - Jeffrey Snover, Architect of PowerShell¹

PowerShell is a powerful cross-platform task automation solution consisting of a command-line shell, scripting language, and configuration management framework. Built on the .NET platform, PowerShell provides IT professionals and developers with comprehensive tools for managing systems, automating tasks, and building robust applications.

Table of Contents


PowerShell Fundamentals

What is PowerShell?

PowerShell is a cross-platform task automation and configuration management framework, consisting of:

  • Command-line shell: Interactive interface for executing commands
  • Scripting language: Rich programming language with advanced features
  • Configuration management: Infrastructure as Code (IaC) capabilities
  • Object-oriented pipeline: Processes .NET objects rather than plain text

Core Architecture

PowerShell is built on the .NET runtime, providing:

  • Object-based operations: Commands work with .NET objects
  • Extensible type system: Custom types and formatting
  • Rich cmdlet ecosystem: Thousands of built-in and community cmdlets
  • Module system: Organized, reusable command collections

PowerShell Editions

Edition Description Platform Support
PowerShell Core Open-source, cross-platform Windows, Linux, macOS
Windows PowerShell Built-in Windows version Windows only

Key Advantages

  1. Object-Oriented Pipeline: Unlike traditional shells that pass text, PowerShell passes rich .NET objects
  2. Consistent Syntax: Verb-Noun naming convention (Get-Process, Set-Location)
  3. Extensive Help System: Built-in documentation with examples
  4. Rich Type System: Native support for .NET data types and methods
  5. Powerful Remoting: Execute commands on remote systems seamlessly

Getting Started

Installation and Setup

PowerShell Core Installation

Windows:

# Install via Windows Package Manager
winget install Microsoft.PowerShell

# Install via Chocolatey
choco install powershell-core

# Install via Microsoft Store
# Search for "PowerShell" in Microsoft Store

Linux (Ubuntu/Debian):

# Update package list
sudo apt update

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

# Download and install PowerShell
wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt update
sudo apt install -y powershell

macOS:

# Install via Homebrew
brew install powershell/tap/powershell

First Steps

Starting PowerShell

  • Windows: Type pwsh in Command Prompt or search "PowerShell"
  • Linux/macOS: Type pwsh in terminal

Basic Navigation Commands

# Get current location
Get-Location

# Change directory  
Set-Location "C:\Users\Username\Documents"

# List directory contents
Get-ChildItem

# Get command help
Get-Help Get-Process

Common Cmdlets

PowerShell cmdlets follow a consistent Verb-Noun naming convention. Here are the most commonly used cmdlets:

# File and directory operations
Get-ChildItem                         # List files/directories (alias: ls, dir)
Set-Location                          # Change directory (alias: cd)
New-Item                              # Create files/directories
Copy-Item                             # Copy files/directories
Move-Item                             # Move/rename files/directories
Remove-Item                           # Delete files/directories

# System information
Get-ComputerInfo                      # System details
Get-Process                           # Running processes (alias: ps)
Get-Service                           # System services
Get-EventLog                          # Event logs
Get-WmiObject                         # WMI information

# Network operations
Get-NetAdapter                        # Network adapters
Test-NetConnection                    # Network connectivity
Invoke-WebRequest                     # HTTP requests (alias: wget, curl)
Invoke-RestMethod                     # REST API calls

# Text and data processing
Select-String                         # Search text patterns
Sort-Object                           # Sort objects
Group-Object                          # Group objects
Measure-Object                        # Count/measure objects
Where-Object                          # Filter objects (alias: ?)
ForEach-Object                        # Process each object (alias: %)

# Variable and object manipulation
Get-Variable                          # List variables
Set-Variable                          # Set variable values
Get-Member                            # Object properties/methods
Select-Object                         # Select specific properties
Format-Table                          # Format as table
Format-List                           # Format as list

# Module management
Get-Module                            # List loaded modules
Import-Module                         # Load modules
Install-Module                        # Install from PowerShell Gallery
Update-Module                         # Update modules

# Help system
Get-Help                              # Command help
Get-Command                           # Find commands
Update-Help                           # Update help files

PowerShell ISE vs Visual Studio Code

PowerShell ISE (Integrated Scripting Environment)

  • Pros: Built into Windows, integrated debugging
  • Cons: Windows-only, limited features compared to modern editors

Visual Studio Code with PowerShell Extension

  • Pros: Cross-platform, rich features, active development
  • Cons: Requires separate installation
  • Recommended: Use for serious PowerShell development

VS Code PowerShell Extension Features:

  • Syntax highlighting and IntelliSense
  • Integrated debugging
  • Code formatting and linting
  • Interactive console
  • Script analysis (PSScriptAnalyzer integration)

Execution Policy

PowerShell execution policies control script execution permissions:

# View current execution policy
Get-ExecutionPolicy

# Set execution policy (run as Administrator)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# Bypass execution policy for single script
PowerShell -ExecutionPolicy Bypass -File script.ps1

Execution Policy Levels

Policy Description
Restricted No scripts allowed (default on Windows clients)
AllSigned Only scripts signed by trusted publishers
RemoteSigned Local scripts run freely, remote scripts must be signed
Unrestricted All scripts run (not recommended)
Bypass No restrictions or warnings

Core Language Features

PowerShell Syntax Fundamentals

Command Structure

PowerShell follows a consistent Verb-Noun pattern:

# Basic syntax: Verb-Noun -Parameter Value
Get-Process -Name "notepad"
Set-Content -Path "file.txt" -Value "Hello World"
New-Item -ItemType File -Name "test.txt"

Parameters and Aliases

# Full parameter names
Get-ChildItem -Path "C:\" -Recurse -Force

# Parameter aliases
Get-ChildItem -Path "C:\" -r -f

# Positional parameters
Get-Content "file.txt"  # -Path is implied for first parameter

Pipeline Operations

PowerShell's pipeline passes objects between commands:

# Basic pipeline
Get-Process | Where-Object CPU -gt 100 | Sort-Object CPU -Descending

# Complex pipeline with formatting
Get-Service | 
    Where-Object Status -eq "Running" | 
    Select-Object Name, Status, StartType |
    Sort-Object Name |
    Format-Table -AutoSize

Operators

Comparison Operators

# Equality comparisons
$value -eq "test"       # Equals
$value -ne "test"       # Not equals
$value -gt 5            # Greater than
$value -ge 5            # Greater than or equal
$value -lt 10           # Less than
$value -le 10           # Less than or equal

# Case sensitivity
$value -ceq "Test"      # Case-sensitive equals
$value -ieq "test"      # Case-insensitive equals (default)

Pattern Matching

# Wildcard matching
$name -like "J*"        # Starts with J
$name -notlike "*test*" # Doesn't contain "test"

# Regular expressions
$email -match "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
$text -replace "old", "new"  # Replace using regex

Logical Operators

# Logical operations
($value -gt 5) -and ($value -lt 10)   # Both conditions true
($status -eq "Running") -or ($status -eq "Stopped")  # Either condition true
-not ($process -eq $null)             # Logical NOT

Variables and Scoping

Variable Declaration and Assignment

# Simple assignment (dynamic typing)
$message = "Hello PowerShell"
$number = 42
$array = @("item1", "item2", "item3")

# Strongly typed variables
[string]$name = "John Doe"
[int]$age = 30
[datetime]$birthDate = "1990-01-15"

Variable Scopes

# Global scope - available throughout session
$global:SharedVariable = "Available everywhere"

# Script scope - available throughout script
$script:ScriptVariable = "Available in script"

# Local scope - current scope only (default)
$local:LocalVariable = "Available locally"

# Private scope - current scope only, not inherited
$private:PrivateVariable = "Not inherited by child scopes"

Automatic Variables

PowerShell provides numerous automatic variables:

$_              # Current pipeline object
$args           # Command line arguments
$Error          # Error objects from previous commands  
$Host           # Current host application information
$PSVersionTable # PowerShell version information
$pwd            # Current directory (Present Working Directory)
$true/$false    # Boolean values

Data Types and Variables

Understanding PowerShell Data Types

PowerShell uses the .NET type system, providing rich data type support with automatic type conversion when possible. Understanding data types is crucial for effective PowerShell programming.

Variable Declaration and Typing

# Dynamic typing (PowerShell infers type)
$dynamicVar = "Hello World"        # System.String
$dynamicVar = 42                   # System.Int32 
$dynamicVar = Get-Date            # System.DateTime
$dynamicVar = @("a", "b", "c")    # System.Object[]

# Strongly typed variables
[string]$name = "John Doe"
[int]$age = 30
[bool]$isActive = $true
[datetime]$created = Get-Date

Type Checking and Conversion

# Check variable type
$variable = "123"
$variable.GetType().Name          # Returns: String

# Type conversion
[int]$number = "123"              # Converts string to integer
[string]$text = 123               # Converts integer to string
[bool]$boolean = 1                # Converts to True

# Explicit conversion methods
$stringValue = "123.45"
$floatValue = [float]::Parse($stringValue)
$intValue = [int]::Parse($stringValue)  # Will truncate decimal

Core Data Types

String Manipulation

Strings are fundamental for text processing and output formatting:

# String creation
$singleQuoted = 'Literal string - variables not expanded'
$doubleQuoted = "Interpolated string - $env:USERNAME"
$hereString = @"
Multi-line string
with preserved formatting
and $variable expansion
"@

# String methods
$text = "PowerShell Scripting"
$text.Length                      # 19
$text.ToUpper()                   # POWERSHELL SCRIPTING
$text.ToLower()                   # powershell scripting
$text.Substring(0, 10)            # PowerShel
$text.Replace("Shell", "Core")    # PowerCore Scripting
$text.Split(" ")                  # ["PowerShell", "Scripting"]
$text.Contains("Shell")           # True
$text.StartsWith("Power")         # True
$text.EndsWith("ing")             # True

# String formatting
$name = "Alice"
$age = 30
"Hello {0}, you are {1} years old" -f $name, $age
"Hello $name, you are $age years old"

Numeric Types

PowerShell supports various numeric types for different precision requirements:

# Integer types
[byte]$byteValue = 255                    # 0 to 255
[int16]$shortValue = -32768               # -32,768 to 32,767  
[int32]$intValue = 2147483647             # Standard integer
[int64]$longValue = 9223372036854775807   # Large integers

# Floating point types
[single]$floatValue = 3.14159             # 32-bit precision
[double]$doubleValue = 3.141592653589793  # 64-bit precision (default)
[decimal]$decimalValue = 123.456789       # High precision for financial calculations

# Numeric operations
$a = 10
$b = 3
$a + $b          # Addition: 13
$a - $b          # Subtraction: 7
$a * $b          # Multiplication: 30
$a / $b          # Division: 3.33333333333333
$a % $b          # Modulus: 1
[Math]::Pow($a, $b)  # Power: 1000
[Math]::Sqrt($a)     # Square root: 3.16227766016838

# Number formatting
$price = 1234.56
$price.ToString("C")              # Currency: $1,234.56
$price.ToString("F2")             # Fixed point: 1234.56
$price.ToString("N2")             # Number: 1,234.56
$price.ToString("P2")             # Percentage: 123,456.00%

Boolean and Logical Operations

# Boolean values
[bool]$isTrue = $true
[bool]$isFalse = $false

# Boolean conversion
[bool]0           # False
[bool]1           # True
[bool]""          # False (empty string)
[bool]"false"     # True (non-empty string)
[bool]@()         # False (empty array)
[bool]@(1,2,3)    # True (non-empty array)

# Logical operations
$condition1 = $true
$condition2 = $false
$condition1 -and $condition2      # False
$condition1 -or $condition2       # True
-not $condition1                  # False
!$condition1                      # False (alternative syntax)

DateTime Operations

DateTime handling is essential for logging, scheduling, and data processing:

# Creating DateTime objects
$now = Get-Date
$specific = Get-Date "2024-12-25 09:30:00"
$utc = (Get-Date).ToUniversalTime()

# DateTime arithmetic
$tomorrow = $now.AddDays(1)
$lastWeek = $now.AddDays(-7)
$nextMonth = $now.AddMonths(1)
$inOneYear = $now.AddYears(1)

# Time span calculations
$timeSpan = $specific - $now
$timeSpan.Days                    # Days between dates
$timeSpan.Hours                   # Hours component
$timeSpan.TotalHours              # Total hours as decimal

# DateTime formatting
$now.ToString("yyyy-MM-dd")              # 2024-11-23
$now.ToString("yyyy-MM-dd HH:mm:ss")     # 2024-11-23 14:30:25
$now.ToString("dddd, MMMM dd, yyyy")     # Saturday, November 23, 2024

# Parsing dates
$dateString = "2024-01-15"
$parsedDate = [DateTime]::Parse($dateString)
$parsedDate = [DateTime]::ParseExact("20240115", "yyyyMMdd", $null)

# Working with time zones
$easternTime = [TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($now, "Eastern Standard Time")
$availableTimeZones = [TimeZoneInfo]::GetSystemTimeZones()

Custom Objects

Custom objects allow you to create structured data by defining properties. In PowerShell, you can create custom objects using the New-Object cmdlet or by using a more modern and concise syntax with [PSCustomObject].

# PSCustomObject (recommended)
$user = [PSCustomObject]@{
    Name = "John Doe"
    Department = "IT"
    HireDate = Get-Date "2020-01-15"
    IsActive = $true
    Skills = @("PowerShell", "Azure", "Windows Server")
}

# Access properties
$user.Name                        # John Doe
$user.Skills[0]                   # PowerShell

# Add properties dynamically
$user | Add-Member -MemberType NoteProperty -Name "Manager" -Value "Jane Smith"

# New-Object method (legacy)
$server = New-Object -TypeName PSCustomObject -Property @{
    Name = "Server01"
    OS = "Windows Server 2022"
    RAM = 32
    CPU = 16
}

Type Accelerators Reference

PowerShell provides convenient type accelerators for common .NET types:

Accelerator Full Type Name Description
[string] System.String Text data
[char] System.Char Single Unicode character
[byte] System.Byte 8-bit unsigned integer
[int] System.Int32 32-bit signed integer
[long] System.Int64 64-bit signed integer
[bool] System.Boolean True/False values
[decimal] System.Decimal High-precision decimal
[single] System.Single Single-precision float
[double] System.Double Double-precision float
[datetime] System.DateTime Date and time
[xml] System.Xml.XmlDocument XML document
[array] System.Array Array of objects
[hashtable] System.Collections.Hashtable Key-value pairs

Collections and Arrays

Understanding PowerShell Collections

Collections are fundamental for managing groups of data efficiently. PowerShell supports various collection types, each optimized for different scenarios and performance requirements.

Array Fundamentals

# Array creation
$simpleArray = @("apple", "banana", "cherry")
$numericArray = @(1, 2, 3, 4, 5)
$mixedArray = @("text", 123, $true, (Get-Date))

# Array access and manipulation
$simpleArray[0]                   # apple
$simpleArray[-1]                  # cherry (last element)
$simpleArray[1..2]               # banana, cherry (range)
$simpleArray.Length               # 3
$simpleArray.Count               # 3

Performance-Optimized Collections

ArrayList for Dynamic Sizing:

# Create ArrayList
$arrayList = New-Object System.Collections.ArrayList
# or
$arrayList = [System.Collections.ArrayList]@()

# Add items efficiently
[void]$arrayList.Add("Item1")     # [void] suppresses return value
[void]$arrayList.Add("Item2")
[void]$arrayList.Add("Item3")

# ArrayList methods
$arrayList.Remove("Item2")        # Remove specific item
$arrayList.RemoveAt(0)           # Remove by index
$arrayList.Insert(1, "NewItem")  # Insert at index
$arrayList.Contains("Item1")     # Check if contains
$arrayList.Clear()               # Remove all items

Generic Lists (Recommended):

# Strongly typed generic list
$stringList = [System.Collections.Generic.List[string]]@()
$objectList = [System.Collections.Generic.List[object]]@()

# Add items
$stringList.Add("PowerShell")
$stringList.Add("Azure")
$stringList.Add("Windows")

# Generic list methods
$stringList.AddRange(@("Linux", "macOS"))
$filteredList = $stringList.FindAll({param($item) $item.Length -gt 5})
$stringList.Sort()
$stringList.Reverse()

Hashtables (Dictionaries)

Hashtables provide fast key-based lookups:

# Hashtable creation
$serverInfo = @{
    Name = "Server01"
    OS = "Windows Server 2022"
    IP = "192.168.1.100"
    Services = @("IIS", "SQL Server", "DNS")
}

# Access and modify
$serverInfo["Name"]               # Server01
$serverInfo.Name                  # Alternative syntax
$serverInfo["Location"] = "DataCenter1"  # Add new key
$serverInfo.Remove("Services")    # Remove key

# Hashtable operations
$serverInfo.Keys                  # Get all keys
$serverInfo.Values               # Get all values
$serverInfo.ContainsKey("OS")    # Check if key exists

# Iterate through hashtable
foreach ($key in $serverInfo.Keys) 
{
    Write-Output "$key : $($serverInfo[$key])"
}

Ordered Collections

# Ordered hashtable (maintains insertion order)
$orderedHash = [ordered]@{
    First = "Value1"
    Second = "Value2"
    Third = "Value3"
}

# Queue (First In, First Out)
$queue = New-Object System.Collections.Queue
$queue.Enqueue("First")
$queue.Enqueue("Second")
$firstOut = $queue.Dequeue()     # Returns "First"

# Stack (Last In, First Out)
$stack = New-Object System.Collections.Stack
$stack.Push("First")
$stack.Push("Second")
$lastOut = $stack.Pop()          # Returns "Second"

Performance Considerations

Avoid Array Concatenation in Loops:

Adding to an standard array using "+=" is expensive and slow and the performance decrease is greater the larger the collection is. The reason for this is that arrays are fixed size and items cannot be added to it after it is created. To get around this a new array is created each time "+=" is executed with the new item added.

There is a considerable performance improvement using an array list or generic list.

$Array = @()
$ArrayList = [System.Collections.ArrayList]@()
$GenericList = [System.Collections.Generic.List[string]]@()

$ArrayTime = Measure-Command `
    -Expression {@(0..10000).foreach({$Array += ("Number: {0}" -f $_)})}
$ArrayListTime = Measure-Command `
    -Expression {@(0..10000).foreach({$ArrayList.Add("Number: {0}" -f $_)})}
$GenaricListTime = Measure-Command `
    -Expression {@(0..10000).foreach({$GenericList.Add("Number: {0}" -f $_)})}

Write-Output ("Array: {0}ms ArrayList: {1}ms Generic List: {2}ms" -f $ArrayTime.Milliseconds, $ArrayListTime.Milliseconds, $GenaricListTime.Milliseconds)
# ❌ Slow - Creates new array each iteration
$results = @()
foreach ($i in 1..1000) 
{
    $results += "Item $i"        # Poor performance
}

# ✅ Fast - Use ArrayList or Generic List
$results = [System.Collections.Generic.List[string]]@()
foreach ($i in 1..1000) 
{
    $results.Add("Item $i")      # Excellent performance
}

Performance Comparison:

# Measure performance difference
$iterations = 10000

$arrayTime = Measure-Command {
    $array = @()
    1..$iterations | ForEach-Object { $array += $_ }
}

$listTime = Measure-Command {
    $list = [System.Collections.Generic.List[int]]@()
    1..$iterations | ForEach-Object { $list.Add($_) }
}

Write-Output "Array: $($arrayTime.TotalMilliseconds)ms"
Write-Output "Generic List: $($listTime.TotalMilliseconds)ms"

Control Structures

Conditional Statements

If Statements

# Basic if statement
$number = 10
if ($number -gt 5) 
{
    Write-Output "Number is greater than 5"
}

# If-else
if ($number -eq 10) 
{
    Write-Output "Number is exactly 10"
} 
else 
{
    Write-Output "Number is not 10"
}

# If-elseif-else
$grade = 85
if ($grade -ge 90) 
{
    Write-Output "Grade A"
} 
elseif ($grade -ge 80) 
{
    Write-Output "Grade B"
} 
elseif ($grade -ge 70) 
{
    Write-Output "Grade C"
} 
else 
{
    Write-Output "Grade F"
}

Switch Statements

Switch statements provide efficient multi-condition handling:

# Basic switch
$dayOfWeek = (Get-Date).DayOfWeek
switch ($dayOfWeek) 
{
    "Monday"    { Write-Output "Start of work week" }
    "Friday"    { Write-Output "TGIF!" }
    "Saturday"  { Write-Output "Weekend!" }
    "Sunday"    { Write-Output "Weekend!" }
    default     { Write-Output "Regular work day" }
}

# Switch with pattern matching
$filename = "report.pdf"
switch -Regex ($filename) 
{
    "\.txt$"  { Write-Output "Text file" }
    "\.pdf$"  { Write-Output "PDF document" }
    "\.doc$"  { Write-Output "Word document" }
    default   { Write-Output "Unknown file type" }
}

# Switch with arrays
$numbers = @(1, 2, 3, 4, 5)
switch ($numbers) 
{
    {$_ -lt 3}  { Write-Output "$_ is less than 3" }
    {$_ -eq 3}  { Write-Output "$_ is exactly 3" }
    {$_ -gt 3}  { Write-Output "$_ is greater than 3" }
}

Loops

For Loops

# Traditional for loop
for ($i = 0; $i -lt 5; $i++) 
{
    Write-Output "Iteration: $i"
}

# For loop with step
for ($i = 0; $i -le 20; $i += 5) 
{
    Write-Output "Count: $i"
}

# Nested for loops
for ($row = 1; $row -le 3; $row++) 
{
    for ($col = 1; $col -le 3; $col++) 
    {
        Write-Output "Position: $row,$col"
    }
}

ForEach Loops

# ForEach statement (fastest for arrays)
$services = Get-Service | Select-Object -First 5
foreach ($service in $services) 
{
    Write-Output "Service: $($service.Name) - Status: $($service.Status)"
}

# ForEach-Object (pipeline cmdlet)
Get-Process | ForEach-Object 
{
    if ($_.WorkingSet -gt 100MB) 
    {
        Write-Output "$($_.Name): $([Math]::Round($_.WorkingSet / 1MB))MB"
    }
}

# ForEach method (collection method)
$numbers = @(1, 2, 3, 4, 5)
$squared = $numbers.ForEach({$_ * $_})

While Loops

# While loop
$counter = 0
while ($counter -lt 5) 
{
    Write-Output "Counter: $counter"
    $counter++
}

# Do-while loop (executes at least once)
$input = ""
do 
{
    $input = Read-Host "Enter 'exit' to quit"
    Write-Output "You entered: $input"
} while ($input -ne "exit")

# Do-until loop
$attempts = 0
do 
{
    $attempts++
    Write-Output "Attempt: $attempts"
    $success = Get-Random -Maximum 10 -lt 2  # 20% success rate
} until ($success -or $attempts -ge 5)

Loop Control

# Break - exit loop
foreach ($number in 1..10) 
{
    if ($number -eq 5) 
    {
        break  # Exit the loop
    }
    Write-Output $number
}

# Continue - skip current iteration
foreach ($number in 1..10) 
{
    if ($number % 2 -eq 0) 
    {
        continue  # Skip even numbers
    }
    Write-Output "Odd number: $number"
}

# Labeled breaks for nested loops
:outerLoop foreach ($i in 1..3) 
{
    foreach ($j in 1..3) 
    {
        if ($i -eq 2 -and $j -eq 2) 
        {
            break outerLoop  # Break out of both loops
        }
        Write-Output "i=$i, j=$j"
    }
}

Comments are used to explain code and make scripts easier to understand and maintain. PowerShell supports both single-line and multi-line comments.

Single-line comment:

# This is a single-line comment
Write-Output "Hello, World!" # This is an inline comment

Multi-line comment:

<#
This is a
multi-line comment.
It can span several lines.
#>

Comment-based help for functions:

You can add special comments before a function to provide help information.

<#
.SYNOPSIS
    Gets a friendly greeting.
.DESCRIPTION
    This function returns a greeting for the specified name.
.PARAMETER Name
    The name of the person to greet.
.EXAMPLE
    Get-Greeting -Name "Alice"
#>
function Get-Greeting 
{
    param($Name)
    "Hello, $Name!"
}

Data Types

What are data types?

String

Strings are sequences of characters used to represent text. In PowerShell, strings are of the type [string]. Strings support a wide range of operations, including concatenation, substring extraction, and pattern matching.

Methods:

Character

A character data type in PowerShell represents a single character, such as a letter, digit, or symbol. In PowerShell, characters are of the type [char], which corresponds to a Unicode character. This type is useful for handling individual characters in strings, performing character-level operations, and working with text data.

If you simply set the variable to a single character the variable will be created as a string.

$Char = "A"
$char.GetType()

Output:

IsPublic IsSerial Name          BaseType
-------- -------- ----          --------
True     True     String        System.Object

Use the [char] accelerator to cast the variable as char if that is the data type needed.

[char]$Char = "A"
$char.GetType()

Output:

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Char                                     System.ValueType

An error is thrown if the variable is being cast as char and the value being assigned is more than a single character.

[char]$char = "AB" 

Output

MetadataError: Cannot convert value "AB" to type "System.Char". Error: "String must be exactly one character long."

Byte

A byte is an 8-bit unsigned integer with a range of 0 to 255. Useful for binary data.

[byte]$b = 255
$b.GetType()

Output:

IsPublic IsSerial Name          BaseType
-------- -------- ----          --------
True     True     Byte          System.ValueType

Numeric Type

A numeric type is one that allows representation of integer or fractional values, and that supports arithmetic operations on those values. The set of numerical types includes the integer and real number types, but does not include bool or char.

Integer

Integers are whole numbers without any fractional component. They can be positive or negative. In PowerShell, integers are typically of the type [int], which corresponds to a 32-bit signed integer in the .NET framework.

Other integer types are SByte, Int16, UInt16, UInt32, and UInt64, all in the namespace System.

[int]$positiveInt = 26
[int]$negativeInt = -17

$sum = $positiveInt + $negativeInt

Write-Output $sum

Output:

9

Long

The type of [long] uses 64 bits as opposed to [int], which only uses 32 bits. Using 64 bits gives it a range of -9223372036854775808 to +9223372036854775807 opposed to -2147483648 to +2147483647.

Decimal

Decimals are used for high-precision arithmetic, especially useful in financial calculations where rounding errors can be unacceptable. The [decimal] type in PowerShell provides a higher precision and a smaller range of values compared to floats and doubles, making it ideal for currency and other precise calculations.

Single

A single is a 32-bit floating point number, less precise than a double.

[single]$single = 3.14
$single.GetType()
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Single                                   System.ValueType

Float and Double

Floats and doubles represent numbers with fractional components. Floats (single precision) and doubles (double precision) differ in the amount of precision and range they offer. Floats are generally used when less precision is acceptable, while doubles are used for more accurate calculations.

[float]$float = 3.14
[double]$double = 3.141592653589793

$floatSum = $float + 2.86
$doubleSum = $double + 2.86

Write-Output $floatSum
Write-Output $doubleSum

Output:

6.00000010490417
6.00159265358979

Bool

Booleans represent true or false values for logical operations and control flow. In PowerShell, Booleans are of the type [bool]. They are essential for making decisions in scripts, such as conditional statements and loops, which allow scripts to execute different code paths based on certain conditions.

DateTime

Dates represent date and time values. In PowerShell, dates are of the type [datetime], which provides various methods and properties for manipulating date and time data. DateTime is a .NET System.DateTime object. The DateTime object can be formated many different ways to meet your needs. You can return the specific parts of the DateTime object, such as the year, month, hour, day, or seconds, and format the output any way that you want.

Get-Date

Output

Saturday, November 23, 2024 5:31:32 AM

Calculate a date 13 days from now (November 23, 2024)

(get-date).AddDays(13)

Output

Friday, December 6, 2024 5:32:43 AM

Format the date. yy = 2 digit year yyyy = 4 digit year MM = Month dd = Day mm = Minutes ss = Seconds

(get-date).ToString("yyyy-MM-dd")

Output

2024-11-23

Specify a date

(get-date 2024-10-13)
(get-date 2024-10-13).ToString("yyyy-MM-dd")

Output

Sunday, October 13, 2024 12:00:00 AM
2024-10-13

Measure the difference between dates and times by subtracting one DateTime from another.

(get-date 2024-12-25) - (get-Date)

Output

Days              : 31
Hours             : 18
Minutes           : 19
Seconds           : 1
Milliseconds      : 333
Ticks             : 27443413334918
TotalDays         : 31.7632098783773
TotalHours        : 762.317037081056
TotalMinutes      : 45739.0222248633
TotalSeconds      : 2744341.3334918
TotalMilliseconds : 2744341333.4918

TimeSpans can also be used todefine a measurement of time between two DateTime objects.

$TimeSpan = New-TimeSpan -Start (Get-Date) -End (Get-Date "2024-12-25")

Output

Days              : 31
Hours             : 17
Minutes           : 46
Seconds           : 7
Milliseconds      : 868
Ticks             : 27423678684757
TotalDays         : 31.7403688480984
TotalHours        : 761.768852354361
TotalMinutes      : 45706.1311412617
TotalSeconds      : 2742367.8684757
TotalMilliseconds : 2742367868.4757
$diff = (get-date 2024-12-25) - (get-Date)
Write-Output ("{0} days until Christmas!" -f $diff.Days)

Output

31 days until Christmas!

Convert DateTime to Universal Time.

(Get-Date).ToUniversalTime().ToString('yyyy-MM-dd:hh-mm-ss')

Output

2024-11-23:11-53-23

The DateTime accelerator can be used instead of the Get-Date CmdLet.

$date = [DateTime]"2023-01-16 12:00:00 AM"
$date.ToString("MM/dd/yyyy")

Output

01/16/2023

Calculate the number of days in a month

[DateTime]::DaysInMonth(2025,2)
[DateTime]::DaysInMonth(2024,2)

Output

28
29

Output Stream Standards

Understanding PowerShell Streams

PowerShell provides multiple output streams that allow developers to send different types of information to appropriate destinations:

Stream Stream Number Description Default Destination
Success (Output) 1 Standard output for pipeline objects Console/Pipeline
Error 2 Error messages and exceptions Console (red text)
Warning 3 Warning messages Console (yellow text)
Verbose 4 Detailed operational information Suppressed by default
Debug 5 Development and troubleshooting info Suppressed by default
Information 6 Informational messages Console
Progress N/A Progress indicators Console (progress bars)

Stream Cmdlets and Usage

Write-Output - Standard Pipeline Output

Use for data that should flow through the PowerShell pipeline:

function Get-ServerInfo 
{
    param([string]$ComputerName)
    
    # This data flows to the pipeline
    Write-Output [PSCustomObject]@{
        Name = $ComputerName
        Status = "Online"
        LastChecked = Get-Date
    }
}

# Can be captured and processed
$servers = Get-ServerInfo "Server01"

Write-Host - Console Display Only

Use for console messages that should not be captured:

function Install-Application 
{
    param([string]$AppName)
    
    Write-Host "Starting installation of $AppName..." -ForegroundColor Green
    # Installation logic here
    Write-Host "Installation completed successfully!" -ForegroundColor Green
}

Write-Error - Error Messages

Use for error conditions and exceptions:

function Get-UserAccount 
{
    param([string]$UserName)
    
    try 
    {
        $user = Get-ADUser $UserName -ErrorAction Stop
        return $user
    }
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] 
    {
        Write-Error "User '$UserName' not found in Active Directory"
        return $null
    }
}

Write-Warning - Warning Messages

Use for conditions that require attention but aren't errors:

function Set-ServerMaintenance 
{
    param([string]$ComputerName)
    
    $uptime = (Get-WmiObject Win32_OperatingSystem -ComputerName $ComputerName).LastBootUpTime
    if ((Get-Date) - $uptime -lt (New-TimeSpan -Days 30)) 
    {
        Write-Warning "Server $ComputerName has been running for less than 30 days"
    }
}

Write-Verbose - Detailed Information

Use for detailed operational information:

function Backup-Database 
{
    [CmdletBinding()]
    param([string]$DatabaseName)
    
    Write-Verbose "Starting backup process for database: $DatabaseName"
    Write-Verbose "Checking database connectivity..."
    # Backup logic
    Write-Verbose "Backup completed successfully"
}

# Enable with -Verbose parameter or $VerbosePreference = "Continue"
Backup-Database -DatabaseName "MyDB" -Verbose

Write-Debug - Development Information

Use for debugging and development information:

function Process-DataFile 
{
    [CmdletBinding()]
    param([string]$FilePath)
    
    Write-Debug "Processing file: $FilePath"
    Write-Debug "File size: $((Get-Item $FilePath).Length) bytes"
    # Processing logic
}

# Enable with -Debug parameter or $DebugPreference = "Continue"
Process-DataFile -FilePath "C:\data.txt" -Debug

Write-Information - Informational Messages

Use for structured informational output:

function Deploy-Application 
{
    [CmdletBinding()]
    param([string]$ApplicationName)
    
    Write-Information "Deployment started for $ApplicationName" -InformationAction Continue
    
    # Deployment steps with structured information
    Write-Information @{
        Stage = "Validation"
        Status = "Complete"
        Timestamp = Get-Date
    } -InformationAction Continue
}

Stream Redirection and Capture

Redirecting Streams

# Redirect error stream to file
Get-Process "NonExistentProcess" 2> errors.txt

# Redirect verbose stream to file
Get-Process -Verbose 4> verbose.txt

# Redirect all streams to file
Get-Process * *> all_output.txt

# Redirect errors to success stream (combine streams)
Get-Process "NonExistentProcess" 2>&1

Capturing Streams in Variables

# Capture different streams
$result = Get-Process 2>&1  # Captures both output and errors
$errors = Get-Process 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }
$output = Get-Process 2>&1 | Where-Object { $_ -isnot [System.Management.Automation.ErrorRecord] }

Best Practices for Output Streams

1. Choose the Right Stream

function Get-SystemStatus 
{
    [CmdletBinding()]
    param()
    
    Write-Verbose "Checking system status..." # Development info
    
    $cpu = Get-WmiObject Win32_Processor | Measure-Object -Property LoadPercentage -Average
    
    if ($cpu.Average -gt 80) 
    {
        Write-Warning "CPU usage is high: $($cpu.Average)%" # Warning condition
    }
    
    Write-Information "System check completed at $(Get-Date)" -InformationAction Continue # Info
    
    # Return structured data
    Write-Output [PSCustomObject]@{
        CPUUsage = $cpu.Average
        Status = if ($cpu.Average -gt 80) { "Warning" } else { "Normal" }
        CheckTime = Get-Date
    }
}

2. Support Stream Preferences

function Advanced-Function 
{
    [CmdletBinding()]
    param()
    
    # Respect user preferences
    Write-Verbose "Verbose message" # Shown only if $VerbosePreference allows
    Write-Debug "Debug information" # Shown only if $DebugPreference allows
    Write-Information "Info message" -InformationAction $InformationPreference
}

3. Consistent Error Handling

function Robust-Function 
{
    [CmdletBinding()]
    param([string]$Path)
    
    try 
    {
        if (-not (Test-Path $Path)) 
        {
            Write-Error "Path not found: $Path" -ErrorAction Stop
        }
        
        Write-Verbose "Processing path: $Path"
        # Main logic here
        
    } 
    catch 
    {
        Write-Error "Failed to process $Path`: $($_.Exception.Message)"
        throw
    }
}

Stream Performance Considerations

  • Write-Host is slower than other cmdlets as it bypasses the pipeline
  • Write-Output is fastest for pipeline data
  • Stream redirection has minimal performance impact
  • Verbose/Debug streams should be used judiciously in performance-critical code

Error Handling Fundamentals

Understanding PowerShell Errors

PowerShell has two primary types of errors that developers must handle effectively:

Terminating Errors: Stop cmdlet or function processing immediately and cannot be ignored without proper handling.

Non-Terminating Errors: Generate error messages but allow processing to continue unless configured otherwise.

# Non-terminating error example
Get-ChildItem "C:\NonExistentPath" -ErrorAction Continue

# Converting to terminating error
Get-ChildItem "C:\NonExistentPath" -ErrorAction Stop

Error Action Preferences

Control how PowerShell responds to non-terminating errors using $ErrorActionPreference or the -ErrorAction parameter:

# Global setting
$ErrorActionPreference = "Stop"  # Stop, Continue, SilentlyContinue, Inquire

# Per-cmdlet setting
Get-Process "NonExistentProcess" -ErrorAction SilentlyContinue

Structured Error Handling

Try-Catch-Finally Blocks

try 
{
    # Risky operation
    $user = Get-ADUser $username -ErrorAction Stop
    Write-Verbose "Successfully retrieved user: $($user.Name)"
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{
    Write-Warning "User '$username' not found in Active Directory"
    return $null
}
catch [System.UnauthorizedAccessException]
{
    Write-Error "Insufficient permissions to access Active Directory"
    throw
}
catch 
{
    Write-Error "Unexpected error: $($_.Exception.Message)"
    Write-Debug "Full exception: $($_.Exception | Out-String)"
    throw
}
finally 
{
    # Cleanup operations (always executes)
    Write-Verbose "Cleaning up resources"
}

Automatic Error Variables

PowerShell provides built-in variables for error information:

# $Error array contains recent errors
Write-Output "Last error: $($Error[0])"
Write-Output "Error count: $($Error.Count)"

# $? indicates success/failure of last command
Get-Process "ValidProcess"
Write-Output "Command succeeded: $?"

# $_ in catch blocks contains current error
try { throw "Test error" }
catch { Write-Output "Error message: $($_.Exception.Message)" }

Best Practices for Error Handling

  1. Use specific exception types when possible
  2. Set -ErrorAction Stop for operations in try blocks
  3. Provide meaningful error messages without exposing sensitive data
  4. Log errors appropriately for debugging and monitoring
  5. Clean up resources in finally blocks

When to Use Write-Error vs Throw

Understanding when to use Write-Error versus throw is crucial for proper error handling in PowerShell:

Use Write-Error When

  • Non-terminating errors: The function should continue processing other items
  • Validation failures: Input validation that shouldn't stop the entire script
  • Recoverable conditions: Errors that the calling code can handle gracefully
  • Logging errors: You want to record the error but continue execution
function Get-UserInfo 
{
    param([string[]]$UserNames)
    
    foreach ($user in $UserNames) 
    {
        try 
        {
            $userInfo = Get-ADUser $user -ErrorAction Stop
            Write-Output $userInfo
        }
        catch 
        {
            # Use Write-Error to report the problem but continue with next user
            Write-Error "Failed to get information for user '$user': $($_.Exception.Message)"
            # Continue processing other users
        }
    }
}

# This will process all users, even if some fail
Get-UserInfo -UserNames @("validuser", "invaliduser", "anotheruser")

Use Throw When

  • Critical failures: Conditions that make it impossible to continue
  • Re-throwing exceptions: After logging or partial handling in catch blocks
  • Invalid states: When the function cannot fulfill its primary purpose
  • Resource failures: Database connections, file access, etc.
function Initialize-DatabaseConnection 
{
    param([string]$ConnectionString)
    
    try 
    {
        $connection = New-Object System.Data.SqlClient.SqlConnection($ConnectionString)
        $connection.Open()
        return $connection
    }
    catch 
    {
        Write-Error "Database connection failed: $($_.Exception.Message)"
        # Use throw because the function cannot fulfill its primary purpose
        throw "Unable to establish database connection. Operation cannot continue."
    }
}

# This will stop execution if database connection fails
$db = Initialize-DatabaseConnection -ConnectionString $connString

Decision Matrix

Scenario Use Write-Error Use Throw Reason
Processing multiple items Allow processing of remaining items
Critical resource failure Cannot continue without the resource
Input validation failure Caller may want to retry with different input
System configuration error Fundamental issue that prevents operation
Network timeout Temporary condition, may succeed on retry
Security violation Critical failure requiring immediate attention

Best Practice Examples

function Process-Files 
{
    param([string[]]$FilePaths)
    
    $results = @()
    
    foreach ($file in $FilePaths) 
    {
        try 
        {
            if (-not (Test-Path $file)) 
            {
                # Non-terminating - continue with other files
                Write-Error "File not found: $file"
                continue
            }
            
            $content = Get-Content $file -ErrorAction Stop
            $results += [PSCustomObject]@{
                File = $file
                LineCount = $content.Count
                Status = "Success"
            }
        }
        catch 
        {
            # Log the error but continue processing
            Write-Error "Failed to process file '$file': $($_.Exception.Message)"
            $results += [PSCustomObject]@{
                File = $file
                LineCount = 0
                Status = "Error"
            }
        }
    }
    
    return $results
}

function Connect-ToService 
{
    param([string]$ServiceUrl, [PSCredential]$Credential)
    
    if (-not $Credential) 
    {
        # Critical failure - cannot proceed without credentials
        throw "Credentials are required for service connection"
    }
    
    try 
    {
        $service = New-WebServiceProxy -Uri $ServiceUrl -Credential $Credential
        Test-Connection $service  # Verify connection works
        return $service
    }
    catch 
    {
        Write-Error "Service connection failed: $($_.Exception.Message)"
        # Re-throw because the function cannot fulfill its purpose
        throw "Unable to establish service connection to $ServiceUrl"
    }
}

Best Practices

Code Organization and Structure

Script Structure Standards

Following the Allman formatting style defined in our PowerShell scripting guidelines:

<#
.SYNOPSIS
    Comprehensive user management script following best practices.
.DESCRIPTION
    Demonstrates proper PowerShell script structure, error handling,
    and documentation standards.
.PARAMETER UserName
    The username to process.
.PARAMETER Action
    The action to perform (Create, Modify, Validate).
.EXAMPLE
    .\UserManagement.ps1 -UserName "jdoe" -Action "Create"
.NOTES
    Author: Your Name
    Version: 1.0
    Last Modified: 2025-08-20
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string]$UserName,
    
    [Parameter(Mandatory = $true)]
    [ValidateSet("Create", "Modify", "Validate")]
    [string]$Action
)

# Import required modules
if (-not (Get-Module -Name ActiveDirectory -ListAvailable))
{
    throw "ActiveDirectory module is required but not available"
}
Import-Module ActiveDirectory -ErrorAction Stop

# Main execution
try
{
    Write-Verbose "Processing user: $UserName with action: $Action"
    
    switch ($Action)
    {
        "Create" 
        {
            # Creation logic
        }
        "Modify" 
        {
            # Modification logic  
        }
        "Validate" 
        {
            # Validation logic
        }
    }
}
catch
{
    Write-Error "Script execution failed: $($_.Exception.Message)"
    exit 1
}

Parameter Validation and Security

Comprehensive Parameter Validation

function New-UserAccount
{
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(3, 20)]
        [ValidatePattern('^[a-zA-Z][a-zA-Z0-9_-]*$')]
        [string]$UserName,
        
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$FirstName,
        
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$LastName,
        
        [Parameter()]
        [ValidateScript({
            if (Test-Path (Split-Path $_ -Parent))
            {
                $true
            }
            else
            {
                throw "Directory does not exist: $(Split-Path $_ -Parent)"
            }
        })]
        [string]$LogPath = "$PSScriptRoot\UserManagement.log",
        
        [Parameter()]
        [ValidateRange(1, 365)]
        [int]$PasswordExpirationDays = 90
    )
    
    # Function implementation
}

Secure Credential Handling

# ❌ Never do this - hardcoded credentials
$username = "admin"
$password = "P@ssw0rd"

# ✅ Proper credential handling
$credential = Get-Credential -Message "Enter service account credentials"

# ✅ For automation scenarios
$securePassword = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential("admin", $securePassword)

# ✅ Using Windows Credential Manager
$credential = Get-StoredCredential -Target "MyApplication"

Performance Optimization

Collection Performance

# ❌ Inefficient - array concatenation
$results = @()
foreach ($item in $largeCollection)
{
    $results += $item  # Creates new array each time
}

# ✅ Efficient - Generic List
$results = [System.Collections.Generic.List[object]]@()
foreach ($item in $largeCollection)
{
    $results.Add($item)  # Fast insertion
}

# ✅ Most efficient - pipeline when possible
$results = $largeCollection | Where-Object { $_.Property -eq "Value" }

Memory Management

# Clear variables when done with large datasets
$largeDataset = Get-VeryLargeDataset
# Process the data
$results = $largeDataset | Process-Data
# Clear the large dataset from memory
$largeDataset = $null
[System.GC]::Collect()  # Force garbage collection if needed

Error Handling Standards

Advanced Error Handling Patterns

function Get-UserInformation
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$UserName
    )
    
    try
    {
        # Attempt to get user
        $user = Get-ADUser $UserName -ErrorAction Stop
        Write-Verbose "Successfully retrieved user: $($user.Name)"
        return $user
    }
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
    {
        Write-Warning "User '$UserName' not found in Active Directory"
        return $null
    }
    catch [System.Security.Authentication.AuthenticationException]
    {
        Write-Error "Authentication failed. Check your credentials and permissions."
        throw
    }
    catch
    {
        Write-Error "Unexpected error retrieving user '$UserName': $($_.Exception.Message)"
        Write-Debug "Full exception details: $($_ | Out-String)"
        throw
    }
}

Development Resources

Specialized PowerShell Documentation

This repository contains comprehensive documentation for advanced PowerShell development topics:

Core Development Guides

  • Functions - Advanced function development, parameter validation, pipeline input
  • Scripts - Script structure, formatting standards, quality assurance
  • Modules - Module creation, distribution, and management
  • Cmdlets - Custom cmdlet development and compilation

Advanced Topics

Platform-Specific Guides

Official Microsoft Resources

PowerShell Documentation

Learning Resources

Development Tools

Editors and IDEs

Quality Assurance Tools

Community Resources

Forums and Discussion

Blogs and Content


Footnotes and References

¹ Jeffrey Snover Quote: Snover, J. (2016). The Monad Manifesto - The Origin of PowerShell. Microsoft TechEd. Available at: Monad Manifesto

² PowerShell Architecture: Microsoft Corporation. (2024). PowerShell Architecture and Components. Available at: PowerShell Architecture

³ Cross-Platform PowerShell: Microsoft Corporation. (2024). Installing PowerShell on Linux. Available at: Installing PowerShell

PowerShell Performance: Microsoft Corporation. (2024). PowerShell Performance Best Practices. Available at: Performance Best Practices

Error Handling: Microsoft Corporation. (2024). Everything you wanted to know about exceptions. Available at: Exception Handling

PowerShell Security: Microsoft Corporation. (2024). PowerShell Security Best Practices. Available at: Security Best Practices

Additional References


This comprehensive PowerShell guide serves as the foundation for all PowerShell development in this repository. For specialized topics, refer to the individual documentation files linked in the Development Resources section above.