Back to Home
Published: Sun Aug 25 2024EN

PowerShell Scripting

Introduction

PowerShell is a powerful task automation and configuration management framework from Microsoft, consisting of a command-line shell and associated scripting language. Initially built on .NET Framework, and now cross-platform with PowerShell Core (built on .NET Core), it provides robust capabilities for system administrators and developers to automate tasks across Windows, macOS, and Linux environments.

This guide covers PowerShell fundamentals, advanced techniques, best practices, and real-world applications to help you leverage its full potential in your DevOps workflows.

Table of Contents

  1. PowerShell Basics
  2. Script Structure and Syntax
  3. Variables and Data Types
  4. Flow Control
  5. Functions and Modules
  6. Error Handling
  7. Working with Files and Folders
  8. Network Operations
  9. Working with APIs
  10. PowerShell and Azure
  11. PowerShell in DevOps
  12. Security Best Practices
  13. Performance Optimization
  14. Common Use Cases
  15. Resources

PowerShell Basics

PowerShell Versions

PowerShell has evolved significantly over time:

  • Windows PowerShell 1.0-5.1: Built on .NET Framework, Windows-only
  • PowerShell Core 6.x+: Cross-platform, built on .NET Core
  • PowerShell 7+: Modern, cross-platform version (current recommendation)

Check your PowerShell version with:

POWERSHELL
$PSVersionTable

Example output:

TXT
Name                           Value
----                           -----
PSVersion                      7.3.0
PSEdition                      Core
GitCommitId                    7.3.0
OS                             Microsoft Windows 10.0.19045
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

To install the latest PowerShell version:

POWERSHELL
# On Windows using winget
winget install Microsoft.PowerShell

# On Windows using chocolatey
choco install powershell-core

# On macOS using Homebrew
brew install --cask powershell

# On Ubuntu Linux
sudo apt-get update
sudo apt-get install -y wget apt-transport-https software-properties-common
wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb"
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y powershell

Command Types

PowerShell has several command types:

  1. Cmdlets: Native PowerShell commands following verb-noun format (e.g., Get-Process)
  2. Functions: Custom reusable code blocks
  3. Scripts: Collections of commands saved as .ps1 files
  4. Aliases: Shortcuts for commands (e.g., dir is an alias for Get-ChildItem)

Discovering commands:

POWERSHELL
# List all commands
Get-Command

# Find commands with specific noun
Get-Command -Noun Process

# Find commands with specific verb
Get-Command -Verb Get

# Get all aliases
Get-Alias

# Find help on how to use a command
Get-Help Get-Process -Detailed
Get-Help Get-Process -Examples
Get-Help Get-Process -Online  # Opens browser documentation

Basic Command Structure

PowerShell cmdlets follow a verb-noun naming convention:

POWERSHELL
Verb-Noun -Parameter Value

Common verbs include Get, Set, New, Remove, Start, Stop, etc.

Examples of common cmdlets:

POWERSHELL
# List running processes
Get-Process

# List specific processes
Get-Process -Name chrome, firefox

# Get process by ID
Get-Process -Id 1234

# Get services
Get-Service

# Start/stop a service
Start-Service -Name Spooler
Stop-Service -Name Spooler

# Get event logs
Get-EventLog -LogName System -Newest 10

# Get system information
Get-ComputerInfo

# List environment variables
Get-ChildItem Env:
$env:USERNAME  # Access specific environment variable

Execution Policy

PowerShell's execution policy determines which scripts can run:

POWERSHELL
# View the current execution policy
Get-ExecutionPolicy

# View execution policy for all scopes
Get-ExecutionPolicy -List

# Set execution policy (run as administrator)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

# Set execution policy for current user only
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# Bypass execution policy for a single script execution
PowerShell -ExecutionPolicy Bypass -File "C:\Scripts\MyScript.ps1"

Common policies:

  • Restricted: No scripts can run (default)
  • AllSigned: Only signed scripts can run
  • RemoteSigned: Local scripts can run; downloaded scripts need signing
  • Unrestricted: All scripts can run (not recommended for production)
  • Bypass: No restrictions; nothing is blocked and no warnings (use with caution)

PowerShell Profiles

PowerShell profiles allow you to customize your PowerShell environment by loading settings, functions, and aliases whenever you start PowerShell:

POWERSHELL
# Check if you have a profile
Test-Path $PROFILE

# Create a profile if it doesn't exist
if (!(Test-Path $PROFILE)) {
    New-Item -Type File -Path $PROFILE -Force
}

# Edit your profile
notepad $PROFILE
# or
code $PROFILE  # With VS Code

# Example profile content
# Add this to your profile file:
function prompt {
    $currentDir = $executionContext.SessionState.Path.CurrentLocation.Path
    "PS [$env:COMPUTERNAME] $currentDir> "
}

# Create custom aliases
Set-Alias -Name np -Value notepad

Profile locations:

  • Current user, current host: $PROFILE
  • Current user, all hosts: $PROFILE.CurrentUserAllHosts
  • All users, current host: $PROFILE.AllUsersCurrentHost
  • All users, all hosts: $PROFILE.AllUsersAllHosts

Script Structure and Syntax

Basic Script Structure

PowerShell scripts use the .ps1 extension. Here's a more comprehensive example of a well-structured script:

POWERSHELL
<#
.SYNOPSIS
    Brief description of what the script does.
.DESCRIPTION
    Detailed description of the script's functionality.
.PARAMETER ComputerName
    The name of the computer to query.
.PARAMETER OutputPath
    The path where the report will be saved.
.EXAMPLE
    .\Get-SystemReport.ps1 -ComputerName "Server01" -OutputPath "C:\Reports"
.NOTES
    Author: Your Name
    Date: April 13, 2025
    Version: 1.0
#>

#Requires -Version 7.0
#Requires -Modules ActiveDirectory, SqlServer
#Requires -RunAsAdministrator

param (
    [Parameter(Mandatory=$true, Position=0)]
    [string]$ComputerName,

    [Parameter(Mandatory=$false)]
    [string]$OutputPath = ".\Reports",

    [switch]$IncludeServices
)

# Script initialization
$ErrorActionPreference = "Stop"
$VerbosePreference = "Continue"

# Import required modules
Import-Module ActiveDirectory -ErrorAction Stop

# Define functions
function Get-ComputerInfo {
    [CmdletBinding()]
    param (
        [string]$Name
    )

    Write-Verbose "Querying system information for $Name"
    return Get-CimInstance -ComputerName $Name -ClassName Win32_ComputerSystem
}

function Write-Report {
    [CmdletBinding()]
    param (
        [object]$Data,
        [string]$Path
    )

    if (!(Test-Path -Path $Path)) {
        New-Item -Path $Path -ItemType Directory -Force | Out-Null
    }

    $reportPath = Join-Path -Path $Path -ChildPath "$($Data.Name)_Report.json"
    $Data | ConvertTo-Json -Depth 5 | Out-File -FilePath $reportPath
    return $reportPath
}

# Main script execution
try {
    Write-Verbose "Script started at $(Get-Date)"

    # Verify computer is reachable
    if (!(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)) {
        throw "Computer $ComputerName is not reachable."
    }

    # Get computer information
    $systemInfo = Get-ComputerInfo -Name $ComputerName

    # Add services if requested
    if ($IncludeServices) {
        Write-Verbose "Including services information"
        $services = Get-Service -ComputerName $ComputerName
        $systemInfo | Add-Member -MemberType NoteProperty -Name Services -Value $services
    }

    # Generate report
    $reportFile = Write-Report -Data $systemInfo -Path $OutputPath
    Write-Output "Report generated successfully at $reportFile"
}
catch {
    Write-Error "An error occurred: $_"
    exit 1
}
finally {
    Write-Verbose "Script completed at $(Get-Date)"
}

Advanced Script Header Comments

PowerShell supports special comment-based help that VS Code and PowerShell ISE can recognize:

POWERSHELL
<#
.SYNOPSIS
    Short description of the script's purpose.
.DESCRIPTION
    Detailed explanation of what the script does and how it works.
.PARAMETER ParameterName
    Description of a parameter.
.EXAMPLE
    PS> .\MyScript.ps1 -Parameter1 "Value"
    Example description of what happens.
.EXAMPLE
    PS> .\MyScript.ps1 -Parameter1 "Value" -Switch
    Another example with different parameters.
.INPUTS
    Description of input objects if your script accepts pipeline input.
.OUTPUTS
    Description of objects that the script returns.
.NOTES
    Additional information about the script.
.LINK
    https://related-documentation-url.com
#>

Parameter Declarations with Validation

PowerShell allows for sophisticated parameter validation:

POWERSHELL
param (
    [Parameter(Mandatory=$true,
               Position=0,
               HelpMessage="Enter the server name:")]
    [ValidateNotNullOrEmpty()]
    [string]$ServerName,

    [Parameter(Mandatory=$false)]
    [ValidateSet("Development", "Testing", "Production")]
    [string]$Environment = "Development",

    [Parameter(Mandatory=$false)]
    [ValidateRange(1, 100)]
    [int]$MaxItems = 25,

    [Parameter(Mandatory=$false)]
    [ValidatePattern("[a-zA-Z][a-zA-Z0-9]{5,10}")]
    [string]$UserName,

    [Parameter(Mandatory=$false)]
    [ValidateScript({Test-Path $_ -PathType Container})]
    [string]$OutputFolder = ".\Output",

    [switch]$Force
)

Here-Strings for Multi-line Text

PowerShell provides "here-strings" for multi-line text content:

POWERSHELL
# Basic here-string with variable substitution
$name = "John"
$message = @"
Hello, $name!
This is a multi-line message
that preserves all whitespace and line breaks.
Today is $(Get-Date -Format "yyyy-MM-dd").
"@

# Single-quoted here-string without variable substitution
$sql = @'
SELECT *
FROM Customers
WHERE Region = 'North'
  AND Status = 'Active';
'@

Pipeline Techniques and Examples

The pipeline is one of PowerShell's most powerful features:

POWERSHELL
# Basic pipeline example
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 | Format-Table Name, CPU, WorkingSet

# Pipeline with calculated properties
Get-ChildItem -Path C:\Windows -Filter *.exe -Recurse -ErrorAction SilentlyContinue |
    Select-Object -Property Name,
                           @{Name="SizeKB"; Expression={[math]::Round($_.Length/1KB, 2)}},
                           LastWriteTime |
    Sort-Object -Property SizeKB -Descending |
    Select-Object -First 10

# Pipeline with filtering and grouping
Get-Service |
    Where-Object {$_.Status -eq "Running"} |
    Group-Object -Property StartType |
    Select-Object Name, Count

# Processing each item in the pipeline
Get-ChildItem -Path C:\Logs -Filter *.log |
    ForEach-Object {
        $content = Get-Content -Path $_.FullName
        $errorCount = ($content | Select-String -Pattern "ERROR" -SimpleMatch).Count
        $warningCount = ($content | Select-String -Pattern "WARNING" -SimpleMatch).Count

        [PSCustomObject]@{
            LogFile = $_.Name
            ErrorCount = $errorCount
            WarningCount = $warningCount
            TotalLines = $content.Count
        }
    } |
    Sort-Object -Property ErrorCount -Descending

Using the Splatting Technique

Splatting is a technique for passing parameters to a command using a hashtable:

POWERSHELL
# Traditional approach - long command line
Send-MailMessage -From "sender@example.com" -To "recipient@example.com" -Subject "Report" -Body "See attached report." -SmtpServer "smtp.example.com" -Port 587 -UseSsl -Credential $credential -Attachments "C:\Reports\Report.pdf"

# Using splatting - cleaner and more maintainable
$emailParams = @{
    From = "sender@example.com"
    To = "recipient@example.com"
    Subject = "Report"
    Body = "See attached report."
    SmtpServer = "smtp.example.com"
    Port = 587
    UseSsl = $true
    Credential = $credential
    Attachments = "C:\Reports\Report.pdf"
}
Send-MailMessage @emailParams  # Note the @ instead of $

Script Flow Control with Break, Continue, and Return

POWERSHELL
# Break and Continue example
foreach ($server in $servers) {
    if ($server.Status -eq "Maintenance") {
        Write-Warning "Server $($server.Name) is in maintenance mode. Skipping..."
        continue  # Skip to the next iteration
    }

    if ($server.Status -eq "Offline") {
        Write-Error "Server $($server.Name) is offline. Stopping script."
        break  # Exit the loop completely
    }

    # Process server
    Write-Output "Processing server $($server.Name)"
}

# Return example in a function
function Test-ServerConnection {
    param (
        [string]$ServerName
    )

    if (!(Test-Connection -ComputerName $ServerName -Count 1 -Quiet)) {
        return $false  # Exit the function with a false value
    }

    # Continue with other tests
    $portTest = Test-NetConnection -ComputerName $ServerName -Port 3389 -WarningAction SilentlyContinue

    return $portTest.TcpTestSucceeded  # Return the result of the port test
}

Using Requires Statements

Require statements help ensure script prerequisites are met:

POWERSHELL
#Requires -Version 7.0                  # Minimum PowerShell version
#Requires -Modules ActiveDirectory, Az  # Required modules
#Requires -RunAsAdministrator           # Must run as admin
#Requires -PSEdition Core               # Must be PowerShell Core

Variables and Data Types

Variable Declaration

Variables in PowerShell start with $:

POWERSHELL
$name = "PowerShell"
$age = 15
$isAwesome = $true

Common Data Types

PowerShell variables can hold different data types:

POWERSHELL
$string = "Hello"                   # String
$int = 42                          # Integer
$double = 3.14                     # Double
$bool = $true                      # Boolean
$array = 1, 2, 3, "four"           # Array
$hash = @{Key1 = "Value1"; Key2 = 2} # Hashtable
$null = $null                      # Null value

Arrays

POWERSHELL
# Array creation
$array = @(1, 2, 3, 4, 5)
$array = 1..5  # Range operator

# Accessing elements
$firstElement = $array[0]
$lastElement = $array[-1]

# Adding elements
$array += 6

# Filtering arrays
$filtered = $array | Where-Object { $_ -gt 3 }

Hashtables (Dictionaries)

POWERSHELL
# Creating a hashtable
$user = @{
    Name = "John Doe"
    Age = 30
    Role = "Developer"
}

# Accessing elements
$userName = $user["Name"]
$userAge = $user.Age

# Adding or updating elements
$user["Department"] = "IT"
$user.Location = "New York"

# Removing an element
$user.Remove("Age")

Flow Control

Conditional Statements

POWERSHELL
# If-ElseIf-Else
if ($condition1) {
    # Code block
}
elseif ($condition2) {
    # Code block
}
else {
    # Code block
}

# Switch statement
$value = "apple"
switch ($value) {
    "apple" { "It's an apple" }
    "orange" { "It's an orange" }
    default { "Unknown fruit" }
}

# Switch with wildcards
switch -Wildcard ($filename) {
    "*.txt" { "Text file" }
    "*.jpg" { "Image file" }
    default { "Other file type" }
}

Loops

POWERSHELL
# For loop
for ($i = 0; $i -lt 10; $i++) {
    # Code block
}

# ForEach loop
foreach ($item in $collection) {
    # Code block
}

# ForEach-Object in pipeline
$collection | ForEach-Object {
    # Process $_ (current item)
}

# While loop
while ($condition) {
    # Code block
}

# Do-While loop (executes at least once)
do {
    # Code block
} while ($condition)

# Do-Until loop
do {
    # Code block
} until ($condition)

Functions and Modules

Basic Functions

POWERSHELL
function Get-FullName {
    param (
        [string]$FirstName,
        [string]$LastName
    )

    return "$FirstName $LastName"
}

$fullName = Get-FullName -FirstName "John" -LastName "Doe"

Advanced Functions

POWERSHELL
function Get-SystemInfo {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$ComputerName,

        [Parameter(Mandatory=$false)]
        [switch]$IncludeServices,

        [ValidateSet("Basic", "Detailed", "Full")]
        [string]$Level = "Basic"
    )

    begin {
        # Initialization code
    }

    process {
        # Main processing
        $systemInfo = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_OperatingSystem

        if ($IncludeServices) {
            $services = Get-Service -ComputerName $ComputerName
        }

        # Return based on level
        switch ($Level) {
            "Basic" { return $systemInfo | Select-Object Caption, Version }
            "Detailed" { return $systemInfo }
            "Full" { return @{ OS = $systemInfo; Services = $services } }
        }
    }

    end {
        # Cleanup code
    }
}

Modules

Modules are collections of related functions, cmdlets, variables, etc.:

POWERSHELL
# Module structure (MyModule.psm1)
function Get-Something {
    # Function code
}

function Set-Something {
    # Function code
}

# Export only specific functions
Export-ModuleMember -Function Get-Something, Set-Something

Using modules:

POWERSHELL
# Import a module
Import-Module -Name MyModule

# List available modules
Get-Module -ListAvailable

# Find commands in a module
Get-Command -Module MyModule

Error Handling

Try-Catch-Finally

POWERSHELL
try {
    # Code that might cause an error
    $result = 10 / 0
}
catch [System.DivideByZeroException] {
    # Handle specific exception
    Write-Error "Division by zero error"
}
catch {
    # Handle any other exception
    Write-Error "An error occurred: $_"
}
finally {
    # Code that always runs
    Write-Output "Cleanup operations"
}

Error Preference Variables

POWERSHELL
# Set behavior for non-terminating errors
$ErrorActionPreference = "Stop"  # Options: Continue, SilentlyContinue, Stop, Inquire

# Use -ErrorAction parameter for individual commands
Get-Content -Path "NonExistentFile.txt" -ErrorAction SilentlyContinue

Working with Files and Folders

File Operations

POWERSHELL
# Read file content
$content = Get-Content -Path "C:\path\to\file.txt"

# Write to a file
"Content" | Out-File -FilePath "C:\path\to\output.txt"
"Appended content" | Add-Content -FilePath "C:\path\to\output.txt"

# Test if file exists
if (Test-Path -Path "C:\path\to\file.txt") {
    # File exists
}

# Copy files
Copy-Item -Path "C:\source\file.txt" -Destination "C:\destination\"

# Move files
Move-Item -Path "C:\source\file.txt" -Destination "C:\destination\"

# Delete files
Remove-Item -Path "C:\path\to\file.txt"

Folder Operations

POWERSHELL
# Create a new directory
New-Item -Path "C:\path\to\new\folder" -ItemType Directory

# List directory contents
Get-ChildItem -Path "C:\path" -Recurse

# Filter files by extension
Get-ChildItem -Path "C:\path" -Filter "*.txt"

Working with CSV/JSON/XML

POWERSHELL
# CSV
$csvData = Import-Csv -Path "data.csv"
$objects | Export-Csv -Path "output.csv" -NoTypeInformation

# JSON
$jsonData = Get-Content -Path "data.json" | ConvertFrom-Json
$objects | ConvertTo-Json | Out-File -FilePath "output.json"

# XML
[xml]$xmlData = Get-Content -Path "data.xml"
$objects | Export-Clixml -Path "output.xml"

Network Operations

Basic Network Commands

POWERSHELL
# Test network connectivity
Test-NetConnection -ComputerName "www.example.com" -Port 443

# Get IP configuration
Get-NetIPConfiguration

# DNS resolution
Resolve-DnsName -Name "www.example.com"

# TCP port test
Test-NetConnection -ComputerName "server" -Port 80 -InformationLevel Detailed

Web Requests

POWERSHELL
# GET request
$response = Invoke-WebRequest -Uri "https://api.example.com/data"
$responseContent = $response.Content

# POST request with JSON body
$body = @{
    name = "John Doe"
    email = "john@example.com"
} | ConvertTo-Json

$response = Invoke-WebRequest -Uri "https://api.example.com/users" -Method Post -Body $body -ContentType "application/json"

# REST API calls
$params = @{
    Uri         = "https://api.example.com/users"
    Method      = "POST"
    Headers     = @{ Authorization = "Bearer $token" }
    ContentType = "application/json"
    Body        = $body
}
$response = Invoke-RestMethod @params

Working with APIs

REST API Example

POWERSHELL
# Function to interact with REST API
function Invoke-ApiRequest {
    [CmdletBinding()]
    param (
        [string]$Endpoint,
        [string]$Method = "GET",
        [hashtable]$Headers = @{},
        [object]$Body = $null
    )

    $baseUrl = "https://api.example.com/v1"
    $uri = "$baseUrl/$Endpoint"

    $params = @{
        Uri = $uri
        Method = $Method
        Headers = $Headers
        ContentType = "application/json"
    }

    if ($Body -and $Method -ne "GET") {
        $params.Body = ($Body | ConvertTo-Json -Depth 10)
    }

    try {
        $response = Invoke-RestMethod @params
        return $response
    }
    catch {
        Write-Error "API error: $_"
        throw
    }
}

# Usage
$token = "YOUR_API_TOKEN"
$headers = @{
    "Authorization" = "Bearer $token"
}

# Get users
$users = Invoke-ApiRequest -Endpoint "users" -Headers $headers

# Create user
$newUser = @{
    name = "Jane Smith"
    email = "jane@example.com"
    role = "admin"
}
$createdUser = Invoke-ApiRequest -Endpoint "users" -Method "POST" -Headers $headers -Body $newUser

PowerShell and Azure

Azure PowerShell Module

POWERSHELL
# Install Azure PowerShell module
Install-Module -Name Az -AllowClobber -Force

# Connect to Azure
Connect-AzAccount

# Select subscription
Set-AzContext -SubscriptionId "subscription-id"

# Common Azure operations
$resourceGroup = "MyResourceGroup"
$location = "eastus"

# Create a resource group
New-AzResourceGroup -Name $resourceGroup -Location $location

# Deploy a virtual machine
New-AzVM -ResourceGroupName $resourceGroup -Name "myVM" -Location $location -Image "UbuntuLTS"

# List resources
Get-AzResource -ResourceGroupName $resourceGroup

Azure Automation

POWERSHELL
# Azure Automation runbook example
param (
    [Parameter(Mandatory=$true)]
    [string]$ResourceGroupName
)

# Connect to Azure with managed identity
Connect-AzAccount -Identity

# Start all stopped VMs in a resource group
$vms = Get-AzVM -ResourceGroupName $ResourceGroupName -Status
foreach ($vm in $vms) {
    if ($vm.PowerState -eq "VM deallocated") {
        Write-Output "Starting VM: $($vm.Name)"
        Start-AzVM -ResourceGroupName $ResourceGroupName -Name $vm.Name
    }
}

PowerShell in DevOps

CI/CD Integration

POWERSHELL
# Example: Deployment script for a web application
param (
    [string]$Environment = "dev",
    [string]$Version
)

# Configuration for different environments
$config = @{
    dev = @{
        ServerPath = "\\devserver\sites\"
        AppPoolName = "DevAppPool"
    }
    staging = @{
        ServerPath = "\\stagingserver\sites\"
        AppPoolName = "StagingAppPool"
    }
    prod = @{
        ServerPath = "\\prodserver\sites\"
        AppPoolName = "ProdAppPool"
    }
}

# Environment-specific settings
$envConfig = $config[$Environment]
$deployPath = Join-Path -Path $envConfig.ServerPath -ChildPath "MyApp"

# Stop the application pool
Write-Output "Stopping application pool: $($envConfig.AppPoolName)"
Invoke-Command -ComputerName "webserver" -ScriptBlock {
    param($appPoolName)
    Import-Module WebAdministration
    Stop-WebAppPool -Name $appPoolName
} -ArgumentList $envConfig.AppPoolName

# Deploy the application
Write-Output "Deploying version $Version to $Environment environment"
$sourcePath = ".\build\$Version\*"
Copy-Item -Path $sourcePath -Destination $deployPath -Recurse -Force

# Start the application pool
Write-Output "Starting application pool: $($envConfig.AppPoolName)"
Invoke-Command -ComputerName "webserver" -ScriptBlock {
    param($appPoolName)
    Import-Module WebAdministration
    Start-WebAppPool -Name $appPoolName
} -ArgumentList $envConfig.AppPoolName

Write-Output "Deployment complete"

Infrastructure as Code

POWERSHELL
# Example: Creating a testing environment with PowerShell
function New-TestEnvironment {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$ProjectName,

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

    # Create resource group
    $resourceGroupName = "$ProjectName-Test-$BuildNumber"
    $deploymentName = "Deployment-$BuildNumber"
    $location = "eastus"

    New-AzResourceGroup -Name $resourceGroupName -Location $location -Force

    # Deploy ARM template
    $templateFile = ".\infrastructure\template.json"
    $templateParameters = @{
        projectName = $ProjectName
        environment = "test"
        buildNumber = $BuildNumber
    }

    $deployment = New-AzResourceGroupDeployment -Name $deploymentName `
                                              -ResourceGroupName $resourceGroupName `
                                              -TemplateFile $templateFile `
                                              -TemplateParameterObject $templateParameters

    # Return environment information
    return @{
        ResourceGroup = $resourceGroupName
        Deployment = $deployment
        Endpoints = @{
            WebApp = $deployment.Outputs.webAppUrl.Value
            API = $deployment.Outputs.apiUrl.Value
        }
    }
}

# Usage
$env = New-TestEnvironment -ProjectName "MyProject" -BuildNumber "20250413.1"

Security Best Practices

Secure Credential Handling

POWERSHELL
# Never store credentials in plain text in scripts

# Use encrypted credentials
$securePassword = ConvertTo-SecureString "PlainTextPassword" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential("username", $securePassword)

# Better: Store encrypted credentials in a file (Windows only)
$credential = Get-Credential
$credential | Export-CliXml -Path "C:\secure\credentials.xml"

# Later, retrieve the credentials
$credential = Import-CliXml -Path "C:\secure\credentials.xml"

# For automation, use managed identities or key vaults

Script Signing

POWERSHELL
# Create a self-signed certificate for testing
$cert = New-SelfSignedCertificate -Subject "CN=PowerShell Code Signing" -Type CodeSigningCert -CertStoreLocation "Cert:\CurrentUser\My"

# Sign a script
Set-AuthenticodeSignature -FilePath ".\MyScript.ps1" -Certificate $cert

# In production, use certificates from a trusted certification authority

Permission Management

POWERSHELL
# Use Just Enough Administration (JEA)
# Example: Create a JEA configuration file

# PSSC file (session configuration)
New-PSSessionConfigurationFile -Path ".\JEAConfig.pssc" `
                             -SessionType RestrictedRemoteServer `
                             -VisibleCmdlets "Get-Service", "Restart-Service" `
                             -VisibleFunctions "Get-SystemInfo" `
                             -LanguageMode NoLanguage

# Register the configuration
Register-PSSessionConfiguration -Path ".\JEAConfig.pssc" `
                              -Name "MaintenanceSession" `
                              -Force

Performance Optimization

Efficient Coding Practices

POWERSHELL
# Bad: Slow string concatenation in a loop
$result = ""
foreach ($item in 1..10000) {
    $result += $item.ToString() + ","
}

# Good: Use StringBuilder for string operations
$sb = New-Object System.Text.StringBuilder
foreach ($item in 1..10000) {
    [void]$sb.Append("$item,")
}
$result = $sb.ToString()

# Bad: Filtering objects in the pipeline multiple times
Get-Process | Where-Object { $_.CPU -gt 100 } | Where-Object { $_.Name -like "S*" }

# Good: Use a single Where-Object with compound conditions
Get-Process | Where-Object { $_.CPU -gt 100 -and $_.Name -like "S*" }

# Use Jobs for parallel processing
$jobs = 1..10 | ForEach-Object {
    $server = "Server$_"
    Start-Job -ScriptBlock {
        param($serverName)
        Get-WmiObject Win32_OperatingSystem -ComputerName $serverName
    } -ArgumentList $server
}
Wait-Job $jobs
$results = Receive-Job $jobs

# PowerShell 7+: Use parallel foreach
$results = 1..10 | ForEach-Object -Parallel {
    $server = "Server$_"
    Get-WmiObject Win32_OperatingSystem -ComputerName $server
} -ThrottleLimit 5

Common Use Cases

System Administration

POWERSHELL
# Get system information
function Get-DetailedSystemInfo {
    [CmdletBinding()]
    param (
        [string]$ComputerName = $env:COMPUTERNAME
    )

    $os = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_OperatingSystem
    $cs = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_ComputerSystem
    $proc = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_Processor
    $disk = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_LogicalDisk -Filter "DriveType=3"

    [PSCustomObject]@{
        ComputerName = $ComputerName
        OSName = $os.Caption
        OSVersion = $os.Version
        Manufacturer = $cs.Manufacturer
        Model = $cs.Model
        Processor = $proc.Name
        PhysicalMemoryGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
        Disks = $disk | ForEach-Object {
            [PSCustomObject]@{
                Drive = $_.DeviceID
                SizeGB = [math]::Round($_.Size / 1GB, 2)
                FreeSpaceGB = [math]::Round($_.FreeSpace / 1GB, 2)
                PercentFree = [math]::Round(($_.FreeSpace / $_.Size) * 100, 2)
            }
        }
    }
}

Automation Examples

User Account Management

POWERSHELL
# Bulk user creation
Import-Csv ".\users.csv" | ForEach-Object {
    $securePassword = ConvertTo-SecureString $_.InitialPassword -AsPlainText -Force

    $params = @{
        Name = $_.Username
        GivenName = $_.FirstName
        Surname = $_.LastName
        SamAccountName = $_.Username
        UserPrincipalName = "$($_.Username)@domain.com"
        AccountPassword = $securePassword
        Enabled = $true
        Path = "OU=$($_.Department),DC=domain,DC=com"
        ChangePasswordAtLogon = $true
    }

    New-ADUser @params
}

System Monitoring

POWERSHELL
# Monitor services and restart if necessary
$servicesToMonitor = @("MSSQLSERVER", "W3SVC", "BITS")

foreach ($service in $servicesToMonitor) {
    $serviceStatus = Get-Service -Name $service -ErrorAction SilentlyContinue

    if ($serviceStatus -and $serviceStatus.Status -ne "Running") {
        Write-Output "$(Get-Date) - Service $service is not running. Attempting to start..."

        try {
            Start-Service -Name $service
            Write-Output "$(Get-Date) - Service $service started successfully."
        }
        catch {
            Write-Error "$(Get-Date) - Failed to start service $service. Error: $_"
            Send-MailMessage -To "admin@example.com" -From "monitor@example.com" -Subject "Service Failure: $service" -Body "The service $service failed to start. Error: $_" -SmtpServer "smtp.example.com"
        }
    }
}

Resources

Official Documentation and Learning Resources

Community Resources

Books and Courses

  • "PowerShell in a Month of Lunches" by Don Jones and Jeffrey Hicks
  • "PowerShell for Sysadmins" by Adam Bertram
  • "Learn PowerShell in Y Minutes" - Quick reference guide

Conclusion

PowerShell is a versatile and powerful tool for automation, system administration, and DevOps workflows. By mastering PowerShell, you can significantly improve your productivity, create consistent and reliable processes, and effectively manage complex IT environments.

This guide covers the fundamentals, but PowerShell's capabilities extend far beyond what's documented here. As you continue to work with PowerShell, you'll discover innovative ways to solve problems and automate tasks across your infrastructure.

Next Running Scripts in Linux
An unhandled error has occurred. Reload