PowerShell for IIS - Tips & Best Practices

Table of Contents

1. [Module Loading Explained](#module-loading-explained)

2. [PowerShell Session Management](#powershell-session-management)

3. [Essential Aliases & Shortcuts](#essential-aliases--shortcuts)

4. [Error Handling Tips](#error-handling-tips)

5. [Output Formatting Tricks](#output-formatting-tricks)

6. [Performance & Memory Tips](#performance--memory-tips)

7. [Remote Management](#remote-management)

8. [Scripting Best Practices](#scripting-best-practices)

9. [Common Gotchas](#common-gotchas)

10. [Productivity Hacks](#productivity-hacks)

---

Module Loading Explained

When Do You Need to Import Modules?

✅ NEED TO IMPORT:

❌ DON'T NEED TO IMPORT:

Quick Module Check


# Check if IIS modules are loaded
gmo Web*,IIS*

# Expected output if loaded:
# ModuleType Name                ExportedCommands
# ---------- ----                ----------------
# Manifest   IISAdministration   {Get-IISAppPool, Start-IISCommitDelay, Stop-IISCommitDelay, Get-IISConfigCollection...}
# Manifest   WebAdministration   {Add-WebConfiguration, Add-WebConfigurationLock, Add-WebConfigurationProperty, Backup-WebConfiguration...}

# If empty, modules aren't loaded - run import
ipmo WebAdministration,IISAdministration

Auto-Loading Solutions

#### Option 1: PowerShell Profile (Recommended)


# Check if profile exists
Test-Path $PROFILE
# Output: True/False

# If False, create profile
if(!(Test-Path $PROFILE)) { 
    New-Item $PROFILE -ItemType File -Force
    Write-Host "Profile created at: $PROFILE" -ForegroundColor Green
}

# Add auto-import to profile
Add-Content $PROFILE @"
# Auto-load IIS modules
Import-Module WebAdministration,IISAdministration -ErrorAction SilentlyContinue
if(Get-Module WebAdministration) { 
    Write-Host "IIS modules loaded" -ForegroundColor Green 
}
"@

# Reload current session
. $PROFILE

#### Option 2: Custom Batch Launcher

Create iis-ps.bat:


@echo off
title IIS PowerShell
powershell -NoExit -Command "Import-Module WebAdministration,IISAdministration; Clear-Host; Write-Host 'IIS PowerShell Ready' -ForegroundColor Cyan; Write-Host 'Modules loaded: WebAdministration, IISAdministration' -ForegroundColor Green"

#### Option 3: Quick Load Function


# Add to profile or run manually
function iis { 
    ipmo WebAdministration,IISAdministration -ea 0
    Write-Host "IIS modules loaded" -ForegroundColor Green
    Get-IISSite | select Name,State | ft -Auto
}

# Usage: just type 'iis' and hit enter

#### Option 4: Module Auto-Import (Windows 10/Server 2016+)


# Enable auto-import (one-time setup)
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
# This allows PowerShell to auto-load modules when you use their commands

---

PowerShell Session Management

Session Types & When to Use

Regular PowerShell Console:

PowerShell ISE:

VS Code with PowerShell Extension:

Windows Terminal + PowerShell:

Session Persistence Tips


# Keep session alive during long operations
$host.UI.RawUI.WindowTitle = "IIS Management - $(Get-Date)"

# Prevent accidental closure
function exit { 
    $response = Read-Host "Are you sure you want to exit? (y/N)"
    if($response -eq 'y') { Exit }
}

# Save command history for later
Get-History | Export-Csv "C:\temp\ps-history-$(Get-Date -f 'yyyyMMdd').csv"

---

Essential Aliases & Shortcuts

Built-in Aliases You Should Know


# File/Directory operations
ls = Get-ChildItem (gci)
dir = Get-ChildItem (gci)  
cd = Set-Location (sl)
pwd = Get-Location (gl)
cat = Get-Content (gc)
cp = Copy-Item (copy)
mv = Move-Item (move)
rm = Remove-Item (del)

# Object manipulation
select = Select-Object
where = Where-Object (?)
foreach = ForEach-Object (%)
sort = Sort-Object
group = Group-Object
measure = Measure-Object

# Common commands
gsv = Get-Service
gps = Get-Process
gwmi = Get-WmiObject
gcim = Get-CimInstance

# Formatting
ft = Format-Table
fl = Format-List
fw = Format-Wide

Custom IIS Aliases


# Add these to your profile
Set-Alias gwc Get-WebConfiguration
Set-Alias swc Set-WebConfiguration
Set-Alias sites Get-IISSite
Set-Alias pools Get-IISAppPool
Set-Alias bindings Get-IISSiteBinding

# Usage examples:
# sites | select Name,State
# pools | where State -eq Stopped
# gwc "system.webserver/defaultDocument" "IIS:\Sites\MySite"

Quick Navigation Functions


# Add to profile for quick directory changes
function logs { cd "C:\inetpub\logs\LogFiles" }
function wwwroot { cd "C:\inetpub\wwwroot" }
function iisconfig { cd "$env:windir\System32\inetsrv\config" }
function temp { cd $env:TEMP }

# Usage: just type 'logs' to jump to log directory

---

Error Handling Tips

Suppress Non-Critical Errors


# Use -ErrorAction parameter (or -ea shorthand)
Get-IISSite "NonExistentSite" -ea SilentlyContinue  # Returns nothing if not found
Get-IISSite "NonExistentSite" -ea Stop              # Throws terminating error
Get-IISSite "NonExistentSite" -ea Continue          # Shows error but continues

# Try-catch for better error handling
try {
    $site = Get-IISSite "MySite"
    Write-Host "Site found: $($site.Name)" -ForegroundColor Green
} catch {
    Write-Host "Site not found: $($_.Exception.Message)" -ForegroundColor Red
}

Common Error Scenarios


# Check if site exists before operations
function Test-IISSiteExists {
    param([string]$SiteName)
    try {
        Get-IISSite $SiteName -ea Stop | Out-Null
        return $true
    } catch {
        return $false
    }
}

# Safe site operations
if(Test-IISSiteExists "MySite") {
    Stop-IISSite "MySite"
    Write-Host "Site stopped" -ForegroundColor Green
} else {
    Write-Host "Site 'MySite' not found" -ForegroundColor Yellow
}

# Handle multiple sites safely
$sites = @("Site1", "Site2", "Site3")
foreach($site in $sites) {
    try {
        Get-IISSite $site | Stop-IISSite
        Write-Host "✓ Stopped: $site" -ForegroundColor Green
    } catch {
        Write-Host "✗ Failed: $site - $($_.Exception.Message)" -ForegroundColor Red
    }
}

---

Output Formatting Tricks

Table Formatting


# Auto-size columns
Get-IISSite | ft -AutoSize

# Select specific columns
Get-IISSite | select Name,State,ID | ft

# Custom column names and expressions
Get-IISSite | select Name,State,@{n="App Pool";e={$_.Applications[0].ApplicationPoolName}} | ft

# Wrap long content
Get-IISSite | ft -Wrap

# Group output
Get-IISSite | sort State | ft -GroupBy State

List Formatting for Details


# Full details in list format
Get-IISSite "MySite" | fl

# Specific properties
Get-IISSite "MySite" | fl Name,State,ID,Applications

# Custom formatting
Get-IISAppPool | fl Name,State,@{n="Identity";e={$_.ProcessModel.IdentityType}},@{n=".NET Version";e={$_.ManagedRuntimeVersion}}

Grid View for Interactive Filtering


# Interactive table (GUI)
Get-IISSite | Out-GridView

# Select multiple items and pass to pipeline
Get-IISSite | Out-GridView -PassThru | Stop-IISSite

# Filter and select app pools
Get-IISAppPool | Out-GridView -Title "Select App Pools to Restart" -PassThru | Restart-WebAppPool

Export Options


# Export to CSV
Get-IISSite | Export-Csv "C:\temp\iis-sites.csv" -NoTypeInformation

# Export to HTML report
Get-IISSite | ConvertTo-Html -Property Name,State,ID | Out-File "C:\temp\iis-report.html"

# Export to JSON
Get-IISSite | ConvertTo-Json | Out-File "C:\temp\iis-sites.json"

# Copy to clipboard
Get-IISSite | ft | clip

---

Performance & Memory Tips

Efficient Commands


# BAD: Slow for many sites
Get-IISSite | ForEach-Object { Get-WebConfiguration "system.webserver/defaultDocument" "IIS:\Sites\$($_.Name)" }

# GOOD: Faster approach
$sites = Get-IISSite
foreach($site in $sites) {
    try {
        gwc "system.webserver/defaultDocument" "IIS:\Sites\$($site.Name)" -ea Stop
    } catch {
        Write-Warning "Cannot get config for $($site.Name)"
    }
}

# BETTER: Parallel processing for many sites (PowerShell 7+)
$sites | ForEach-Object -Parallel {
    Import-Module WebAdministration
    gwc "system.webserver/defaultDocument" "IIS:\Sites\$($_.Name)" -ea SilentlyContinue
} -ThrottleLimit 10

Memory Management


# Clear variables when done with large datasets
$largeSiteData = Get-IISSite | foreach { /* complex processing */ }
# Use $largeSiteData...
Remove-Variable largeSiteData

# Use -OutVariable to avoid storing large objects
Get-Content "C:\inetpub\logs\LogFiles\W3SVC1\huge.log" | Where-Object {$_ -like "*error*"} -OutVariable errors
# $errors now contains just the filtered results

# Garbage collection for long-running scripts
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

Progress Indicators


# Show progress for long operations
$sites = Get-IISSite
$total = $sites.Count
$current = 0

foreach($site in $sites) {
    $current++
    Write-Progress -Activity "Processing Sites" -Status "Site: $($site.Name)" -PercentComplete (($current / $total) * 100)
    
    # Your processing here
    Start-Sleep 1  # Simulate work
}
Write-Progress -Activity "Processing Sites" -Completed

---

Remote Management

Remote Session Setup


# Enable WinRM on target server (run on server)
Enable-PSRemoting -Force
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "ServerIP" -Force

# Connect from management machine
$cred = Get-Credential
$session = New-PSSession -ComputerName "ServerIP" -Credential $cred

# Import IIS modules in remote session
Invoke-Command -Session $session -ScriptBlock { 
    Import-Module WebAdministration,IISAdministration 
}

# Run IIS commands remotely
Invoke-Command -Session $session -ScriptBlock { Get-IISSite }

# Interactive remote session
Enter-PSSession $session
# Now you're "inside" the remote server
# Run IIS commands normally
# Type 'exit' to return to local machine

# Clean up
Remove-PSSession $session

Remote IIS Functions


# Function to get remote IIS info
function Get-RemoteIISInfo {
    param(
        [string]$ComputerName,
        [pscredential]$Credential
    )
    
    $scriptBlock = {
        Import-Module WebAdministration,IISAdministration -ea 0
        [PSCustomObject]@{
            ComputerName = $env:COMPUTERNAME
            IISVersion = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\InetStp").MajorVersion
            Sites = (Get-IISSite | measure).Count
            AppPools = (Get-IISAppPool | measure).Count
            RunningPools = (Get-IISAppPool | where State -eq Started | measure).Count
            Services = (Get-Service W3SVC,WAS | select Name,Status)
        }
    }
    
    if($Credential) {
        Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock
    } else {
        Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptBlock
    }
}

# Usage:
# Get-RemoteIISInfo -ComputerName "Server01"
# Get-RemoteIISInfo -ComputerName "Server01" -Credential (Get-Credential)

---

Scripting Best Practices

Parameter Validation


function Restart-IISSiteComplete {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$SiteName,
        
        [int]$WaitSeconds = 3,
        
        [switch]$Force
    )
    
    # Validate site exists
    try {
        $site = Get-IISSite $SiteName -ErrorAction Stop
    } catch {
        throw "Site '$SiteName' not found"
    }
    
    # Rest of function...
}

Logging and Verbose Output


function Stop-IISSiteWithLogging {
    [CmdletBinding()]
    param([string]$SiteName)
    
    $logFile = "C:\temp\iis-operations.log"
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    
    try {
        Write-Verbose "Stopping site: $SiteName"
        Stop-IISSite $SiteName
        
        $logEntry = "$timestamp - SUCCESS - Stopped site: $SiteName"
        Add-Content $logFile $logEntry
        Write-Host "✓ $SiteName stopped" -ForegroundColor Green
        
    } catch {
        $logEntry = "$timestamp - ERROR - Failed to stop site: $SiteName - $($_.Exception.Message)"
        Add-Content $logFile $logEntry
        Write-Error "Failed to stop $SiteName`: $($_.Exception.Message)"
    }
}

# Usage with verbose output:
# Stop-IISSiteWithLogging "MySite" -Verbose

Configuration as Code


# Store IIS configuration in hash tables
$iisConfig = @{
    Sites = @(
        @{
            Name = "Website1"
            Path = "C:\inetpub\wwwroot\site1"
            Port = 80
            AppPool = "Site1Pool"
        },
        @{
            Name = "Website2" 
            Path = "C:\inetpub\wwwroot\site2"
            Port = 8080
            AppPool = "Site2Pool"
        }
    )
    AppPools = @(
        @{
            Name = "Site1Pool"
            RuntimeVersion = "v4.0"
            Identity = "ApplicationPoolIdentity"
        },
        @{
            Name = "Site2Pool"
            RuntimeVersion = "v4.0" 
            Identity = "ApplicationPoolIdentity"
        }
    )
}

# Function to apply configuration
function Deploy-IISConfiguration {
    param($Config)
    
    # Create app pools
    foreach($pool in $Config.AppPools) {
        if(!(Get-IISAppPool $pool.Name -ea 0)) {
            New-WebAppPool $pool.Name
            Set-ItemProperty "IIS:\AppPools\$($pool.Name)" managedRuntimeVersion $pool.RuntimeVersion
            Write-Host "Created app pool: $($pool.Name)" -ForegroundColor Green
        }
    }
    
    # Create sites
    foreach($site in $Config.Sites) {
        if(!(Get-IISSite $site.Name -ea 0)) {
            New-IISSite -Name $site.Name -BindingInformation "*:$($site.Port):" -PhysicalPath $site.Path
            Set-ItemProperty "IIS:\Sites\$($site.Name)" applicationPool $site.AppPool
            Write-Host "Created site: $($site.Name)" -ForegroundColor Green
        }
    }
}

# Deploy the configuration
Deploy-IISConfiguration $iisConfig

---

Common Gotchas

Permission Issues


# Always check if running as administrator
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Error "This script requires Administrator privileges. Please run PowerShell as Administrator."
    exit 1
}

# Check IIS features are installed
$iisFeatures = Get-WindowsFeature | where {$_.Name -like "*IIS*" -and $_.InstallState -eq "Installed"}
if($iisFeatures.Count -eq 0) {
    Write-Error "IIS is not installed on this server"
    exit 1
}

Path Issues


# Always use full paths, not relative paths
$sitePath = "C:\inetpub\wwwroot\mysite"  # Good
$sitePath = ".\mysite"                   # Bad - depends on current directory

# Check paths exist before using
if(!(Test-Path $sitePath)) {
    Write-Error "Site path does not exist: $sitePath"
    return
}

# Handle UNC paths correctly
$uncPath = "\\server\share\website"
if($uncPath -like "\\*") {
    # Special handling for UNC paths
    Test-Path $uncPath -Credential $cred
}

Pipeline Gotchas


# BAD: This doesn't work as expected
Get-IISSite | Stop-IISSite  # Stop-IISSite doesn't accept pipeline input

# GOOD: Use ForEach-Object
Get-IISSite | foreach { Stop-IISSite $_.Name }

# BETTER: Filter first, then process
Get-IISSite | where State -eq "Started" | foreach { Stop-IISSite $_.Name }

String Comparisons


# Case-sensitive vs case-insensitive
$siteName = "MyWebsite"

# BAD: Case sensitive (might fail)
if($siteName -eq "mywebsite") { }

# GOOD: Case insensitive
if($siteName -ieq "mywebsite") { }
if($siteName.ToLower() -eq "mywebsite") { }

# Pattern matching
if($siteName -like "*web*") { }      # Case insensitive wildcard
if($siteName -match "^My.*") { }     # Regex match

---

Productivity Hacks

Quick Site Health Check


# One-liner to check all sites
Get-IISSite | select Name,State,@{n="AppPool";e={(Get-IISAppPool $_.Applications[0].ApplicationPoolName).State}},@{n="Path";e={$_.Applications[0].VirtualDirectories[0].PhysicalPath}},@{n="PathExists";e={Test-Path $_.Applications[0].VirtualDirectories[0].PhysicalPath}} | ft -Auto

Mass Operations


# Start all stopped sites
Get-IISSite | where State -eq Stopped | foreach { Start-IISSite $_.Name; Write-Host "Started: $($_.Name)" -ForegroundColor Green }

# Restart all app pools containing "test"
Get-IISAppPool | where Name -like "*test*" | foreach { Restart-WebAppPool $_.Name; Write-Host "Restarted: $($_.Name)" -ForegroundColor Yellow }

# Stop sites on specific ports
Get-IISSiteBinding | where BindingInformation -like "*:8080:*" | foreach { Stop-IISSite $_.SiteName; Write-Host "Stopped site on port 8080: $($_.SiteName)" }

Bookmark Commands


# Save commonly used commands as functions in your profile

function iis-status {
    Write-Host "=== IIS Quick Status ===" -ForegroundColor Cyan
    Write-Host "Services:" -ForegroundColor Yellow
    gsv W3SVC,WAS | ft Name,Status -Auto
    Write-Host "Sites:" -ForegroundColor Yellow  
    Get-IISSite | group State | ft Name,Count -Auto
    Write-Host "App Pools:" -ForegroundColor Yellow
    Get-IISAppPool | group State | ft Name,Count -Auto
}

function iis-errors {
    Write-Host "Recent IIS Errors:" -ForegroundColor Red
    gel Application -EntryType Error -Newest 10 | where Source -like "*IIS*" | select TimeGenerated,Source,Message | ft -Wrap
}

function iis-recycle-all {
    Get-IISAppPool | foreach { 
        Restart-WebAppPool $_.Name
        Write-Host "Recycled: $($_.Name)" -ForegroundColor Green
    }
}

# Usage: just type function name
# iis-status
# iis-errors  
# iis-recycle-all

History and Shortcuts


# Search command history
h | where CommandLine -like "*Get-IIS*"

# Repeat last command
r

# Edit and re-run command (opens in notepad)
# h | select -Last 1 | foreach { $_.CommandLine | Out-File temp.ps1; notepad temp.ps1 }

# Create shortcuts for complex commands
$sites = Get-IISSite  # Store in variable for reuse
$pools = Get-IISAppPool

# Now use $sites and $pools instead of running Get-IIS* repeatedly
$sites | where State -eq Stopped
$pools | where State -eq Started

Copy-Paste Helpers


# Copy site names to clipboard for use elsewhere
Get-IISSite | select -ExpandProperty Name | clip

# Copy app pool names
Get-IISAppPool | select -ExpandProperty Name | clip

# Copy site info as formatted text
Get-IISSite | ft Name,State,ID | Out-String | clip