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 LayoutArchitectural 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.
Set-Content -Path "C:\Logs\status.txt" -Value "System operational"Get-Content "C:\Logs\status.txt"adn then:
System operationalMultiple lines can be written by passing an array of strings:
$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:
pwsh ./write-raw-text.ps1MaxConnections=2500
EnableSSL=true
TimeoutSeconds=30Where:
-Pathspecifies the destination file path.-Valueaccepts a string or array of strings to write; each array element becomes a separate line.-Encoding utf8enforces 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.
Get-Process | Select-Object Name, Id | Out-File -FilePath "C:\Logs\processes.txt"Get-Content "C:\Logs\processes.txt" | Select-Object -First 4
ProcessName Id
----------- --
chrome 8192
pwsh 12948Where 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.
$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 $ReportFilepwsh ./write-table-report.ps1Name DisplayName Status
---- ----------- ------
Spooler Print Spooler Running
wuauserv Windows Update RunningWhere:
Format-Tableconstructs a columnar visual layout.Out-File -Width 150prevents 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]Get-Process | Select-Object Name, Id, CPU | Export-Csv -Path "C:\Reports\processes.csv" -NoTypeInformationGet-Content "C:\Reports\processes.csv" -TotalCount 3"Name","Id","CPU"
"AggregatorHost","1860",
"ApplicationFrameHost","7764","0.296875"Where:
-NoTypeInformationomits the#TYPEmetadata 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:
$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./export-report.ps1
Get-Content "C:\Reports\server-status.csv""Server","Status","Uptime"
"WEB01","Online","15d"
"WEB02","Offline","0d"Another example:
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""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
$newEntry = [PSCustomObject]@{ Server = "DB01"; Status = "Online"; Uptime = "30d" }
$newEntry | Export-Csv -Path "C:\Reports\server-status.csv" -NoTypeInformation -AppendWhere -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
$config = [PSCustomObject]@{
server = "WEB01"
port = 8080
enabled = $true
}
$config | ConvertTo-Json | Out-File -FilePath "C:\Config\config.json"Get-Content "C:\Config\config.json"{
"server": "WEB01",
"port": 8080,
"enabled": true
}Where:
ConvertTo-Jsonserializes the[PSCustomObject]into a JSON-formatted string.Out-Filewrites 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.
$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"Get-Content "C:\Config\settings.json"{
"app": {
"name": "InventoryManager",
"version": "2.1.0"
},
"features": [
"reporting",
"export",
"sync"
]
}A more complete example with nested hash tables:
$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"pwsh ./export-json-demo.ps1{
"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.
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-Pathresolves file paths safely across operating systems.Get-CimInstancequeries physical storage volumes.Export-Csv -NoTypeInformationwrites flat, spreadsheet-ready analytical records.ConvertTo-Json -Depth 3 | Set-Contentwrites structured JSON for APIs and modern tooling.Get-Item ... Lengthchecks the size in bytes of each generated file.
Execute the script from the console:
pwsh ./export-storage-telemetry.ps1RecordsExported CsvFileBytes JsonFileBytes TargetDirectory
--------------- ------------ ------------- ---------------
2 312 486 C:\Scripts\Telemetry