Skip to content

PowerShell Writing to Files

How to write plain text, formatted tables, CSV, and JSON files in PowerShell using Set-Content, Out-File, Export-Csv, and ConvertTo-Json for automation and data export.

Writing data to files is a central component of task automation, report generation, and system configuration management. In PowerShell, writing to disk goes beyond outputting unformatted strings; the runtime provides cmdlets to stream plain text directly, render console-formatted tables to disk, or serialize structured .NET objects directly into standard data formats like CSV and JSON.

Plain Text Writing: Set-Content vs. Out-File

PowerShell provides two primary cmdlets for writing plain text to disk: Set-Content and Out-File. While both overwrite existing file content by default, they interact with the PowerShell formatting system differently.

Incoming Data Objects
         │
         ├─── Set-Content ────────► Calls .ToString() ────────► Raw String to Disk
         │
         └─── Out-File ───────────► Formatting Engine ────────► Formatted Visual Layout

Architectural Differences

Dimension Set-Content (alias: sc) Out-File (alias: > operator)
Pipeline Interaction Converts incoming objects to strings by calling their native .ToString() method. Passes objects through the PowerShell formatting subsystem before writing.
Formatting Applied None; writes raw, unformatted text strings. Renders data exactly as it appears in the terminal console.
Line Truncation Never truncates text. Subject to line wrapping based on -Width (default: console width or 120 chars).
Primary Use Case Writing raw configuration lines, scripts, or arrays of clean text. Writing human-readable visual reports and formatted tables.

Writing Raw Text with Set-Content

Set-Content writes string content to a file, creating the file if it doesn't exist and overwriting it entirely if it does.

Consolepowershell
Set-Content -Path "C:\Logs\status.txt" -Value "System operational"
Consolepowershell
Get-Content "C:\Logs\status.txt"

adn then:

Outputplaintext
System operational

Multiple lines can be written by passing an array of strings:

write-raw-text.ps1powershell
$ConfigLines = @(
    "# Application Gateway Configuration",
    "MaxConnections=2500",
    "EnableSSL=true",
    "TimeoutSeconds=30"
)

Set-Content -Path "$PSScriptRoot\gateway.conf" -Value $ConfigLines -Encoding utf8

Get-Content -Path "$PSScriptRoot\gateway.conf"

and run it:

Consolepowershell
pwsh ./write-raw-text.ps1
Outputplaintext
MaxConnections=2500
EnableSSL=true
TimeoutSeconds=30

Where:

  • -Path specifies the destination file path.
  • -Value accepts a string or array of strings to write; each array element becomes a separate line.
  • -Encoding utf8 enforces standard UTF-8 encoding.

Writing Formatted Layouts with Out-File

Out-File writes pipeline output to a file, applying the same formatting logic used when displaying objects in the console.

Consolepowershell
Get-Process | Select-Object Name, Id | Out-File -FilePath "C:\Logs\processes.txt"
Consolepowershell
Get-Content "C:\Logs\processes.txt" | Select-Object -First 4
Outputplaintext

ProcessName                                       Id
-----------                                       --
chrome                                           8192
pwsh                                            12948

Where Out-File renders the object collection using the default table format, identical to how it would appear if printed directly to the console.

Controlling Output Width

By default, Out-File may truncate wide tables to fit standard console width. Use -Width to prevent unwanted truncation.

write-table-report.ps1powershell
$ReportFile = "$PSScriptRoot\service-report.txt"

Get-Service -Name "wuauserv", "Spooler" |
    Format-Table -Property Name, DisplayName, Status -AutoSize |
    Out-File -FilePath $ReportFile -Width 150 -Encoding utf8

Get-Content -Path $ReportFile
Consolepowershell
pwsh ./write-table-report.ps1
Outputplaintext
Name     DisplayName     Status
----     -----------     ------
Spooler  Print Spooler  Running
wuauserv Windows Update Running

Where:

  • Format-Table constructs a columnar visual layout.
  • Out-File -Width 150 prevents column truncation by extending the character width boundary.

Writing Objects with Export-Csv

The Export-Csv cmdlet converts a collection of objects into Comma-Separated Values (CSV) format, saving them directly to disk. This is the standard method for preserving structured, tabular data. The cmdlet automatically inspects the object's properties to build the CSV header row and writes each object as an individual data record.

Syntax and Core Parameters

<objects> | Export-Csv -Path <String> [-NoTypeInformation] [-Delimiter <Char>] [-Encoding <String>] [-Force] [-Append]
Consolepowershell
Get-Process | Select-Object Name, Id, CPU | Export-Csv -Path "C:\Reports\processes.csv" -NoTypeInformation
Consolepowershell
Get-Content "C:\Reports\processes.csv" -TotalCount 3
Outputplaintext
"Name","Id","CPU"
"AggregatorHost","1860",
"ApplicationFrameHost","7764","0.296875"

Where:

  • -NoTypeInformation omits the #TYPE metadata line that older PowerShell versions included by default at the top of the file (required in Windows PowerShell 5.1; default in PowerShell 7+).
  • Each object property becomes a CSV column, and each object becomes a row.

Exporting Custom Objects

An example:

export-report.ps1powershell
$results = @(
    [PSCustomObject]@{ Server = "WEB01"; Status = "Online"; Uptime = "15d" }
    [PSCustomObject]@{ Server = "WEB02"; Status = "Offline"; Uptime = "0d" }
)

$results | Export-Csv -Path "C:\Reports\server-status.csv" -NoTypeInformation
Consolepowershell
./export-report.ps1
Get-Content "C:\Reports\server-status.csv"
Outputplaintext
"Server","Status","Uptime"
"WEB01","Online","15d"
"WEB02","Offline","0d"

Another example:

export-services-csv.ps1powershell
Get-Service |
    Where-Object { $_.Status -eq "Running" } |
    Select-Object -First 3 -Property ServiceName, DisplayName, StartType |
    Export-Csv -Path "$PSScriptRoot\running-services.csv" -NoTypeInformation -Encoding utf8

Get-Content -Path "$PSScriptRoot\running-services.csv"
Outputplaintext
"ServiceName","DisplayName","StartType"
"AppHostSvc","Application Host Helper Service","Automatic"
"Appinfo","Application Information","Manual"
"AppXSvc","AppX Deployment Service (AppXSVC)","Manual"

Appending Rows to an Existing CSV

Consolepowershell
$newEntry = [PSCustomObject]@{ Server = "DB01"; Status = "Online"; Uptime = "30d" }
$newEntry | Export-Csv -Path "C:\Reports\server-status.csv" -NoTypeInformation -Append

Where -Append adds the new row to the end of the existing file instead of overwriting it, provided the column structure matches.

Writing JSON: ConvertTo-Json and File Output

Unlike Export-Csv, PowerShell does not have a dedicated Export-Json cmdlet. Just as reading JSON requires ConvertFrom-Json, writing JSON requires converting an object with ConvertTo-Json before saving it with Set-Content or Out-File.

The ConvertTo-Json Pipeline

Consolepowershell
$config = [PSCustomObject]@{
    server  = "WEB01"
    port    = 8080
    enabled = $true
}

$config | ConvertTo-Json | Out-File -FilePath "C:\Config\config.json"
Consolepowershell
Get-Content "C:\Config\config.json"
Outputplaintext
{
    "server":  "WEB01",
    "port":  8080,
    "enabled":  true
}

Where:

  • ConvertTo-Json serializes the [PSCustomObject] into a JSON-formatted string.
  • Out-File writes that string to disk exactly as produced.

Handling Nested Objects and Depth

By default, ConvertTo-Json only serializes 2 levels of nested objects. Deeper structures require the -Depth parameter.

Consolepowershell
$settings = [PSCustomObject]@{
    app = [PSCustomObject]@{
        name    = "InventoryManager"
        version = "2.1.0"
    }
    features = @("reporting", "export", "sync")
}

$settings | ConvertTo-Json -Depth 5 | Set-Content -Path "C:\Config\settings.json"
Consolepowershell
Get-Content "C:\Config\settings.json"
Outputplaintext
{
    "app":  {
                "name":  "InventoryManager",
                "version":  "2.1.0"
            },
    "features":  [
                     "reporting",
                     "export",
                     "sync"
                 ]
}

A more complete example with nested hash tables:

export-json-demo.ps1powershell
$ServerConfiguration = [PSCustomObject]@{
    ClusterName = "Cluster-EU-West"
    Port        = 8443
    IsActive    = $true
    NodePool    = @("node-01", "node-02", "node-03")
    Metadata    = @{
        Environment = "Production"
        Owner       = "Systems Engineering"
    }
}

$JsonPayload = $ServerConfiguration | ConvertTo-Json -Depth 5
Set-Content -Path "$PSScriptRoot\cluster-settings.json" -Value $JsonPayload -Encoding utf8

Get-Content -Path "$PSScriptRoot\cluster-settings.json"
Consolepowershell
pwsh ./export-json-demo.ps1
Outputplaintext
{
    "ClusterName":  "Cluster-EU-West",
    "Port":  8443,
    "IsActive":  true,
    "NodePool":  [
                     "node-01",
                     "node-02",
                     "node-03"
                 ],
    "Metadata":  {
                     "Environment":  "Production",
                     "Owner":  "Systems Engineering"
                 }
}

Practical Example: Automated Multi-Format Telemetry Exporter

The following production script gathers host storage metrics, converts the collected objects into both an analytical CSV export and a serialized JSON configuration artifact, and verifies file generation.

export-storage-telemetry.ps1powershell
param (
    [string]$ExportDirectory = "$PSScriptRoot\Telemetry",
    [int]$LowSpaceThresholdGB = 25
)

if (-not (Test-Path -Path $ExportDirectory -PathType Container)) {
    $null = New-Item -ItemType Directory -Path $ExportDirectory -Force
}

$CsvTarget  = Join-Path -Path $ExportDirectory -ChildPath "storage-audit.csv"
$JsonTarget = Join-Path -Path $ExportDirectory -ChildPath "storage-audit.json"

$StorageReport = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
    $freeGB  = [math]::Round($_.FreeSpace / 1GB, 2)
    $totalGB = [math]::Round($_.Size / 1GB, 2)
    $usedGB  = [math]::Round(($_.Size - $_.FreeSpace) / 1GB, 2)

    [PSCustomObject]@{
        HostName      = $env:COMPUTERNAME
        DriveLetter   = $_.DeviceID
        TotalSizeGB   = $totalGB
        UsedSpaceGB   = $usedGB
        FreeSpaceGB   = $freeGB
        PercentFree   = [math]::Round(($freeGB / $totalGB) * 100, 1)
        LowSpaceAlert = ($freeGB -lt $LowSpaceThresholdGB)
        CapturedAt    = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
    }
}

$StorageReport | Export-Csv -Path $CsvTarget -NoTypeInformation -Encoding utf8

$StorageReport | ConvertTo-Json -Depth 3 | Set-Content -Path $JsonTarget -Encoding utf8

[PSCustomObject]@{
    RecordsExported = $StorageReport.Count
    CsvFileBytes    = (Get-Item $CsvTarget).Length
    JsonFileBytes   = (Get-Item $JsonTarget).Length
    TargetDirectory = $ExportDirectory
}

Where:

  • Join-Path resolves file paths safely across operating systems.
  • Get-CimInstance queries physical storage volumes.
  • Export-Csv -NoTypeInformation writes flat, spreadsheet-ready analytical records.
  • ConvertTo-Json -Depth 3 | Set-Content writes structured JSON for APIs and modern tooling.
  • Get-Item ... Length checks the size in bytes of each generated file.

Execute the script from the console:

Consolepowershell
pwsh ./export-storage-telemetry.ps1
Outputplaintext
RecordsExported CsvFileBytes JsonFileBytes TargetDirectory
--------------- ------------ ------------- ---------------
              2          312           486 C:\Scripts\Telemetry
Navigation