Skip to content

Formatting Output

Note

Learn how to format, display, and export PowerShell output using Format-Table, Format-List, Export-Csv, and other output cmdlets.

Overview

PowerShell gives you powerful tools to control how data is displayed and exported. Whether you're displaying results on screen, saving to files, or sending data to other systems, choosing the right output format is crucial. Remember: format commands should always be last in the pipeline because they convert objects to formatted text.

Critical Rule: Format Last!

Format cmdlets (Format-Table, Format-List, etc.) should always be the last command in a pipeline. They convert objects to formatted display text, which breaks the pipeline for further processing.

Format-Table (Tabular Display)

Display data in columns:

Basic Usage

# Auto-format with default properties
Get-Process | Format-Table

# Specify properties to display
Get-Process | Format-Table Name, CPU, WS

# Auto-size columns (recommended)
Get-Process | Format-Table Name, CPU, WS -AutoSize

# Wrap text that's too long
Get-Service | Format-Table Name, DisplayName -Wrap

Customizing Display

# Group by property
Get-Service | Sort-Object Status | Format-Table -GroupBy Status

# Custom column headers with expressions
Get-Process | Format-Table Name,
    @{Label="Memory(MB)";Expression={[math]::Round($_.WS/1MB,2)};Align="Right"},
    @{Label="CPU(s)";Expression={[math]::Round($_.CPU,2)};Align="Right"}

# Hide table headers
Get-Process | Format-Table -HideTableHeaders

Real-World Example

# Custom process table
Get-Process | Where-Object {$_.CPU -gt 0} |
    Sort-Object CPU -Descending |
    Select-Object -First 10 |
    Format-Table -AutoSize `
        Name,
        @{L="PID";E={$_.Id};Align="Right"},
        @{L="CPU(s)";E={"{0:N2}" -f $_.CPU};Align="Right"},
        @{L="Mem(MB)";E={"{0:N0}" -f ($_.WS/1MB)};Align="Right"},
        @{L="Handles";E={$_.Handles};Align="Right"}

Format-List (Detailed Display)

Display properties in a list format (one property per line):

Basic Usage

# Display all properties
Get-Process powershell | Format-List

# Display specific properties
Get-Process powershell | Format-List Name, CPU, WS, StartTime

# Display all properties including hidden ones
Get-Process powershell | Format-List *

When to Use Format-List

# Good for: Single item with many properties
Get-Service Spooler | Format-List *

# Good for: Detailed view of one or few items
Get-ChildItem "C:\Temp\file.txt" | Format-List Name, Length, CreationTime, LastWriteTime

# Bad for: Many items (too verbose)
Get-Process | Format-List  # Creates pages of output

Real-World Example

# Detailed service information
Get-Service | Where-Object {$_.Status -eq "Running"} |
    Select-Object -First 5 |
    Format-List Name,
        DisplayName,
        Status,
        StartType,
        @{L="Dependencies";E={$_.ServicesDependedOn.Name -join ", "}}

Format-Wide (Multiple Columns)

Display items in multiple columns:

# Default: single property across multiple columns
Get-Process | Format-Wide Name

# Specify number of columns
Get-ChildItem | Format-Wide Name -Column 4

# Use a property other than Name
Get-Process | Format-Wide -Property Id -Column 5

Format-Custom (Advanced)

Custom formatting using views (advanced):

# Use predefined custom views
Get-Process | Format-Custom -View Priority

# Usually not needed for day-to-day work

Out-GridView (Interactive Table)

Display in a searchable, filterable GUI window:

# Basic grid view
Get-Process | Out-GridView

# With title
Get-Service | Out-GridView -Title "Services on $env:COMPUTERNAME"

# Allow selecting items (returns selected items)
$selected = Get-Process | Out-GridView -PassThru
# User can select items and click OK
# Selected items are returned to $selected

# Multiple selection
$services = Get-Service | Out-GridView -PassThru -Title "Select services to restart"
$services | Restart-Service

Out-GridView Features

  • Search: Type in search box to filter
  • Column sorting: Click column headers
  • Column filtering: Click filter icon, add criteria
  • Select items: Use -PassThru to return selections

Export-Csv (Comma-Separated Values)

Export data to CSV files:

# Basic export
Get-Process | Export-Csv -Path "processes.csv"

# Without type information header
Get-Process | Export-Csv -Path "processes.csv" -NoTypeInformation

# Append to existing file
Get-Process | Export-Csv -Path "processes.csv" -Append -NoTypeInformation

# Custom delimiter
Get-Process | Export-Csv -Path "processes.txt" -Delimiter "`t" -NoTypeInformation  # Tab-delimited

# Select specific properties first
Get-Process | Select-Object Name, CPU, WS |
    Export-Csv -Path "processes.csv" -NoTypeInformation

Import-Csv (Read CSV Files)

# Import CSV
$data = Import-Csv -Path "processes.csv"

# Now you can work with the data
$data | Where-Object {$_.CPU -gt 50}
$data | Sort-Object Name

ConvertTo-Csv / ConvertFrom-Csv

Convert to/from CSV format (in memory, not files):

# Convert to CSV string
$csv = Get-Process | ConvertTo-Csv -NoTypeInformation
# $csv is a string array of CSV data

# Convert from CSV string
$objects = $csv | ConvertFrom-Csv
# Back to objects

Export-Clixml / Import-Clixml

Export objects preserving all properties and types:

# Export (preserves object structure completely)
Get-Process | Export-Clixml -Path "processes.xml"

# Import (recreates original objects)
$processes = Import-Clixml -Path "processes.xml"
$processes | Get-Member  # All properties and methods preserved

Use Clixml for PowerShell-to-PowerShell

When saving PowerShell data for later use in PowerShell, use Export-Clixml instead of CSV. It preserves data types, complex objects, and all properties.

ConvertTo-Json / ConvertFrom-Json

Work with JSON format:

# Convert to JSON
$json = Get-Process | Select-Object Name, CPU, WS | ConvertTo-Json

# Pretty-print JSON
$json = Get-Process | Select-Object Name, CPU, WS | ConvertTo-Json -Depth 3

# Convert from JSON
$data = $json | ConvertFrom-Json
$data | Where-Object {$_.CPU -gt 50}

# Export to JSON file
Get-Service | Select-Object Name, Status, StartType |
    ConvertTo-Json |
    Out-File "services.json"

ConvertTo-Html

Create HTML reports:

# Basic HTML
Get-Process | ConvertTo-Html | Out-File "report.html"

# With custom properties and title
Get-Process | Where-Object {$_.CPU -gt 0} |
    Sort-Object CPU -Descending |
    Select-Object -First 10 Name, CPU, WS |
    ConvertTo-Html -Title "Top 10 Processes" -PreContent "<h1>System Report</h1>" |
    Out-File "report.html"

# Open in browser
Invoke-Item "report.html"

Custom HTML with CSS

$css = @"
<style>
    body { font-family: Arial; }
    table { border-collapse: collapse; width: 100%; }
    th { background-color: #4CAF50; color: white; padding: 10px; }
    td { border: 1px solid #ddd; padding: 8px; }
    tr:nth-child(even) { background-color: #f2f2f2; }
</style>
"@

Get-Service | Where-Object {$_.Status -eq "Running"} |
    Select-Object Name, DisplayName, Status |
    ConvertTo-Html -Head $css -Title "Running Services" |
    Out-File "services.html"

Invoke-Item "services.html"

Out-File

Write output to a text file:

# Basic output
Get-Process | Out-File "processes.txt"

# Append to file
Get-Process | Out-File "processes.txt" -Append

# Specify encoding
Get-Content file.txt | Out-File "output.txt" -Encoding UTF8

# Set width (default is 80 characters)
Get-Process | Format-Table -AutoSize | Out-File "processes.txt" -Width 200

Out-Host (Display on Screen)

Force output to console:

# Paginate output (like more/less)
Get-Process | Out-Host -Paging

# Useful in functions to display progress while returning data
function Get-DataWithProgress {
    "Processing..." | Out-Host
    Get-Process
}

Out-Null (Discard Output)

Suppress output completely:

# Suppress output (don't display anything)
New-Item "test.txt" -ItemType File | Out-Null

# Useful for commands that return unwanted output
$list = [System.Collections.ArrayList]@()
$list.Add("item") | Out-Null  # Suppresses index return value

# Alternative: assign to $null
$null = New-Item "test.txt" -ItemType File

Real-World Examples

Example: Daily Process Report

Scenario: Generate a daily HTML report of top processes

$date = Get-Date -Format "yyyy-MM-dd"
$title = "Process Report - $date"

$css = @"
<style>
    body { font-family: 'Segoe UI', Arial; margin: 20px; }
    h1 { color: #333; }
    table { border-collapse: collapse; width: 100%; margin-top: 20px; }
    th { background-color: #0078d4; color: white; padding: 12px; text-align: left; }
    td { border: 1px solid #ddd; padding: 10px; }
    tr:nth-child(even) { background-color: #f9f9f9; }
    tr:hover { background-color: #f5f5f5; }
    .warning { color: #d13438; font-weight: bold; }
</style>
"@

# Get top CPU processes
$topCPU = Get-Process | Where-Object {$_.CPU -gt 0} |
    Sort-Object CPU -Descending |
    Select-Object -First 10 Name,
        @{L="CPU(s)";E={"{0:N2}" -f $_.CPU}},
        @{L="Memory(MB)";E={"{0:N0}" -f ($_.WS/1MB)}},
        @{L="Handles";E={$_.Handles}}

# Get top memory processes
$topMemory = Get-Process |
    Sort-Object WS -Descending |
    Select-Object -First 10 Name,
        @{L="Memory(MB)";E={"{0:N0}" -f ($_.WS/1MB)}},
        @{L="CPU(s)";E={"{0:N2}" -f $_.CPU}}

# Build HTML
$html = @"
$css
<h1>$title</h1>
<h2>Top 10 Processes by CPU</h2>
$($topCPU | ConvertTo-Html -Fragment)
<h2>Top 10 Processes by Memory</h2>
$($topMemory | ConvertTo-Html -Fragment)
<p><em>Generated: $(Get-Date)</em></p>
"@

$html | Out-File "ProcessReport_$date.html"
Invoke-Item "ProcessReport_$date.html"

Example: Service Status to Multiple Formats

Scenario: Export service status to CSV, JSON, and HTML

$date = Get-Date -Format "yyyyMMdd_HHmmss"
$outputDir = "C:\Reports"

# Get service data
$services = Get-Service | Select-Object Name, DisplayName, Status, StartType

# Export to CSV
$services | Export-Csv -Path "$outputDir\Services_$date.csv" -NoTypeInformation

# Export to JSON
$services | ConvertTo-Json | Out-File "$outputDir\Services_$date.json"

# Export to HTML
$services | ConvertTo-Html -Title "Service Status Report" |
    Out-File "$outputDir\Services_$date.html"

# Display summary
$summary = $services | Group-Object Status | Select-Object Name, Count
Write-Output "`nService Summary:"
$summary | Format-Table -AutoSize

Write-Output "`nReports saved to: $outputDir"

Example: Interactive File Selection

Scenario: Let user select files to process using Out-GridView

# Get all log files
$logFiles = Get-ChildItem -Path C:\Logs -Filter "*.log" -Recurse |
    Select-Object Name, FullName,
        @{L="SizeMB";E={[math]::Round($_.Length/1MB,2)}},
        @{L="Age(Days)";E={((Get-Date) - $_.LastWriteTime).Days}},
        LastWriteTime

# Let user select files
$selectedFiles = $logFiles | Out-GridView -Title "Select log files to archive" -PassThru

if ($selectedFiles) {
    Write-Output "`nYou selected $($selectedFiles.Count) files"
    Write-Output "Total size: $(($selectedFiles | Measure-Object SizeMB -Sum).Sum) MB"

    # Process selected files
    foreach ($file in $selectedFiles) {
        Write-Output "Processing: $($file.Name)"
        # Compress or move files here
    }
} else {
    Write-Output "No files selected"
}

Common Output Patterns

Pattern 1: Data Analysis Report

# Analyze and format
$data = Get-Process
$summary = $data | Measure-Object WS -Sum -Average
$top10 = $data | Sort-Object WS -Descending | Select-Object -First 10

Write-Output "`n===== Process Memory Analysis ====="
Write-Output "Total processes: $($data.Count)"
Write-Output "Total memory: $([math]::Round($summary.Sum/1GB, 2)) GB"
Write-Output "Average memory: $([math]::Round($summary.Average/1MB, 2)) MB"
Write-Output "`nTop 10 by memory:"
$top10 | Format-Table Name, @{L="Memory(MB)";E={[math]::Round($_.WS/1MB,0)}} -AutoSize

Pattern 2: Multi-Format Export

# Get data once
$data = Get-Service | Where-Object {$_.Status -eq "Running"}

# Export to multiple formats
$data | Export-Csv "running_services.csv" -NoTypeInformation
$data | Export-Clixml "running_services.xml"
$data | ConvertTo-Json | Out-File "running_services.json"
$data | ConvertTo-Html | Out-File "running_services.html"

Write-Output "Exported $($data.Count) services to 4 formats"

Pattern 3: Conditional Formatting

# Add visual indicators based on values
Get-Process | Where-Object {$_.CPU -gt 0} |
    Select-Object -First 20 Name, CPU,
        @{L="MemoryMB";E={[math]::Round($_.WS/1MB,0)}},
        @{L="Status";E={
            if ($_.CPU -gt 100) { "HIGH CPU" }
            elseif ($_.WS -gt 500MB) { "HIGH MEM" }
            else { "OK" }
        }} |
    Sort-Object CPU -Descending |
    Format-Table -AutoSize

Tips & Tricks

Always Format Last

# WRONG: Can't filter after formatting
Get-Process | Format-Table | Where-Object {$_.CPU -gt 50}  # ERROR!

# RIGHT: Filter first, format last
Get-Process | Where-Object {$_.CPU -gt 50} | Format-Table

Use -AutoSize with Format-Table

# Without AutoSize: Columns may be too wide or truncated
Get-Process | Format-Table Name, CPU, WS

# With AutoSize: Columns sized to content
Get-Process | Format-Table Name, CPU, WS -AutoSize

Select Properties Before Export

# Exports ALL properties (messy CSV)
Get-Process | Export-Csv "processes.csv"

# Export only what you need (clean CSV)
Get-Process | Select-Object Name, CPU, WS | Export-Csv "processes.csv" -NoTypeInformation

Format Cmdlets Destroy Objects

# This doesn't work:
$formatted = Get-Process | Format-Table
$formatted.Name  # ERROR: It's formatted text, not objects

# Keep objects, format only when displaying:
$processes = Get-Process
$processes.Name  # Works: Still objects
$processes | Format-Table  # Format only for display

CSV vs Clixml for PowerShell Data

# CSV loses type information
Get-Process | Export-Csv "proc.csv"
$data = Import-Csv "proc.csv"
$data[0].CPU  # It's a string! "12.34"

# Clixml preserves types
Get-Process | Export-Clixml "proc.xml"
$data = Import-Clixml "proc.xml"
$data[0].CPU  # It's a number! 12.34

Out-GridView Requires GUI

# Won't work in:
# - Remote sessions (without -EnableNetworkAccess)
# - Windows Server Core
# - Non-Windows systems

# Use Format-Table or Export-Csv as alternatives

Additional Resources