Skip to content

Script Configuration Files

Note

Learn how to externalize script settings using various configuration file formats for maintainable, reusable PowerShell scripts.

Overview

Configuration files separate your script's settings from its logic, making scripts more maintainable and portable. Instead of hardcoding values like server names, file paths, or environment-specific settings, you load them from external files.

Benefits:

  • Change settings without modifying code
  • Manage different environments (dev/test/prod)
  • Share scripts without exposing sensitive values
  • Version control configs separately from code

Common Configuration File Formats

PSD1 (PowerShell Data Files)

Native PowerShell format using hashtable syntax. Best for PowerShell-specific configs.

# config.psd1
@{
    ServerName = "prod-server-01"
    Port = 8080
    Timeout = 30
    Features = @("Logging", "Monitoring", "Alerts")
}

Loading PSD1:

$config = Import-PowerShellDataFile -Path ".\config.psd1"
Write-Host "Connecting to $($config.ServerName):$($config.Port)"

JSON

Cross-platform, widely supported. Great for interop with other tools.

# config.json
{
    "ServerName": "prod-server-01",
    "Port": 8080,
    "Timeout": 30,
    "Features": ["Logging", "Monitoring", "Alerts"]
}

Loading JSON:

$config = Get-Content ".\config.json" | ConvertFrom-Json
Write-Host "Connecting to $($config.ServerName):$($config.Port)"

XML

Common in enterprise environments and legacy systems.

# config.xml
<Configuration>
    <ServerName>prod-server-01</ServerName>
    <Port>8080</Port>
    <Timeout>30</Timeout>
    <Features>
        <Feature>Logging</Feature>
        <Feature>Monitoring</Feature>
    </Features>
</Configuration>

Loading XML:

[xml]$config = Get-Content ".\config.xml"
Write-Host "Connecting to $($config.Configuration.ServerName)"

INI Files

Simple key-value pairs, common with older applications.

# config.ini
[Server]
Name=prod-server-01
Port=8080

[Settings]
Timeout=30
LogLevel=Info

Loading INI:

function Read-IniFile {
    param([string]$Path)
    $ini = @{}
    $section = $null

    Get-Content $Path | ForEach-Object {
        $line = $_.Trim()
        if ($line -match '^\[(.+)\]$') {
            $section = $matches[1]
            $ini[$section] = @{}
        }
        elseif ($line -match '^(.+?)=(.+)$' -and $section) {
            $ini[$section][$matches[1].Trim()] = $matches[2].Trim()
        }
    }
    return $ini
}

$config = Read-IniFile -Path ".\config.ini"
Write-Host "Server: $($config.Server.Name)"

Key Points

  • PSD1 - Best for PowerShell-only projects, supports complex data types
  • JSON - Best for cross-platform compatibility and modern apps
  • XML - Best for enterprise systems and structured data
  • INI - Best for simple configs and legacy system integration

Common Use Cases

Use Case 1: Environment-Specific Configurations

What it does: Manage separate configs for dev, test, and production environments

# Load config based on environment
$env = $env:ENVIRONMENT ?? "dev"
$configPath = ".\configs\config.$env.json"
$config = Get-Content $configPath | ConvertFrom-Json

Write-Host "Running in $env environment"
Write-Host "Database: $($config.DatabaseServer)"

Config files:

# config.dev.json
{"DatabaseServer": "localhost", "LogLevel": "Debug"}

# config.prod.json
{"DatabaseServer": "prod-sql-01.company.com", "LogLevel": "Error"}

Use Case 2: Credential Management

What it does: Store credential references (never plaintext passwords)

# config.psd1
@{
    CredentialPath = "C:\Secrets\service-account.xml"
    CredentialName = "MyApp_ServiceAccount"  # For SecretManagement
    UseWindowsAuth = $true
}
# In your script
$config = Import-PowerShellDataFile ".\config.psd1"

if ($config.UseWindowsAuth) {
    $credential = Get-Credential  # Or use current context
} else {
    # Load from SecretManagement vault
    $credential = Get-Secret -Name $config.CredentialName
}

Use Case 3: Feature Flags

What it does: Enable/disable features without code changes

# config.json
{
    "Features": {
        "EmailNotifications": true,
        "DetailedLogging": false,
        "BackupBeforeModify": true
    }
}
$config = Get-Content ".\config.json" | ConvertFrom-Json

if ($config.Features.EmailNotifications) {
    Send-EmailReport -To $config.AdminEmail
}

if ($config.Features.BackupBeforeModify) {
    Backup-Data -Path $config.DataPath
}

Real-World Examples

Example: Multi-Environment Deployment Script

Scenario: Deploy application with different settings per environment

# config.prod.psd1
@{
AppServers = @("web-01", "web-02", "web-03")
DatabaseServer = "prod-sql-cluster.company.com"
BackupPath = "\\backup-server\prod\myapp"
Features = @{
    EnableMonitoring = $true
    DebugMode = $false
    EmailAlerts = $true
}
ContactEmail = "ops-team@company.com"
}

# deploy.ps1
param(
[ValidateSet("dev","test","prod")]
[string]$Environment = "dev"
)

$configFile = ".\config.$Environment.psd1"
if (-not (Test-Path $configFile)) {
throw "Config file not found: $configFile"
}

$config = Import-PowerShellDataFile $configFile

Write-Host "Deploying to $Environment environment" -ForegroundColor Cyan
Write-Host "Target servers: $($config.AppServers -join ', ')"

foreach ($server in $config.AppServers) {
Write-Host "  Deploying to $server..."
# Deployment logic here
Copy-Item ".\app\*" "\\$server\c$\app\" -Recurse -Force
}

if ($config.Features.EmailAlerts) {
Send-MailMessage -To $config.ContactEmail `
    -Subject "Deployment to $Environment completed" `
    -Body "Deployment successful at $(Get-Date)"
}

Explanation: The script loads environment-specific configs, validates their existence, and uses feature flags to control optional behaviors like email notifications.

Example: Application Settings with Validation

Scenario: Load and validate required configuration values

function Get-ValidatedConfig {
param([string]$Path)

if (-not (Test-Path $Path)) {
    throw "Configuration file not found: $Path"
}

$config = Get-Content $Path | ConvertFrom-Json

# Validate required fields
$required = @("ServerName", "Port", "DatabaseName")
foreach ($field in $required) {
    if (-not $config.$field) {
        throw "Required configuration field missing: $field"
    }
}

# Apply defaults for optional fields
if (-not $config.Timeout) { $config | Add-Member -NotePropertyName Timeout -NotePropertyValue 30 }
if (-not $config.RetryCount) { $config | Add-Member -NotePropertyName RetryCount -NotePropertyValue 3 }

return $config
}

# Usage
try {
$config = Get-ValidatedConfig -Path ".\config.json"
Write-Host "Configuration loaded successfully"
}
catch {
Write-Error "Configuration error: $_"
exit 1
}

Explanation: This function validates required fields exist and applies sensible defaults for optional settings, preventing runtime errors from missing configs.

Configuration File Location Best Practices

# Option 1: Same directory as script
$configPath = Join-Path $PSScriptRoot "config.json"

# Option 2: User profile directory
$configPath = Join-Path $env:USERPROFILE ".myapp\config.json"

# Option 3: Common application data (system-wide)
$configPath = Join-Path $env:ProgramData "MyApp\config.json"

# Option 4: Environment variable
$configPath = $env:MYAPP_CONFIG_PATH ?? ".\config.json"

# Always validate the path exists
if (-not (Test-Path $configPath)) {
    throw "Configuration file not found: $configPath"
}

Merging Default and User Configurations

# Load default configuration
$defaultConfig = Import-PowerShellDataFile ".\config.default.psd1"

# Load user overrides (if they exist)
$userConfigPath = Join-Path $env:USERPROFILE ".myapp\config.psd1"
if (Test-Path $userConfigPath) {
    $userConfig = Import-PowerShellDataFile $userConfigPath

    # Merge configs (user settings override defaults)
    foreach ($key in $userConfig.Keys) {
        $defaultConfig[$key] = $userConfig[$key]
    }
}

# Use the merged configuration
$config = $defaultConfig

Tips & Tricks

Store Config Path in Script Header

Define config location at the top of your script for easy maintenance

# At the top of your script
[CmdletBinding()]
param(
[string]$ConfigPath = "$PSScriptRoot\config.json"
)

# Now users can override: .\myscript.ps1 -ConfigPath "C:\custom\config.json"
$config = Get-Content $ConfigPath | ConvertFrom-Json

Use Environment Variables for Sensitive Values

Never store passwords in config files, even if they're "protected"

# BAD - Password in config file
{
"Username": "admin",
"Password": "MyP@ssw0rd"  // NEVER DO THIS
}

# GOOD - Reference to where credential is stored
{
"Username": "admin",
"CredentialVault": "MyApp_Credentials",
"UseWindowsAuth": false
}

# In script: retrieve from secure store
$credential = Get-Secret -Name $config.CredentialVault

Version Your Configuration Schema

Include a version number to handle config format changes

# config.json
{
"ConfigVersion": "2.0",
"ServerName": "prod-01",
"Features": { ... }
}

# In script
$config = Get-Content ".\config.json" | ConvertFrom-Json

if ($config.ConfigVersion -ne "2.0") {
Write-Warning "Config file is version $($config.ConfigVersion), expected 2.0"
Write-Warning "Some features may not work correctly"
}

Don't Commit Secrets to Version Control

Use .gitignore to exclude configs with sensitive data

# Project structure
/configs
config.template.json    # ✅ Commit this (template with placeholders)
config.dev.json         # ⚠️  Add to .gitignore
config.prod.json        # ⚠️  Add to .gitignore

# .gitignore
config.*.json
!config.template.json

# config.template.json - Safe to commit
{
"ServerName": "YOUR_SERVER_HERE",
"DatabaseName": "YOUR_DB_HERE",
"CredentialVault": "YOUR_VAULT_NAME"
}

Validate Configuration on Load

Always validate configs to fail fast with clear error messages

# BAD - Script fails later with cryptic error
$config = Get-Content ".\config.json" | ConvertFrom-Json
Connect-Database -Server $config.ServerName  # Fails if ServerName is null

# GOOD - Validate immediately with helpful error
$config = Get-Content ".\config.json" | ConvertFrom-Json

if ([string]::IsNullOrEmpty($config.ServerName)) {
throw "Configuration error: ServerName is required but not set in config.json"
}

if ($config.Port -lt 1 -or $config.Port -gt 65535) {
throw "Configuration error: Port must be between 1 and 65535, got $($config.Port)"
}

Connect-Database -Server $config.ServerName -Port $config.Port

Watch for Type Conversion Issues

JSON doesn't preserve all PowerShell types

# JSON limitation example
# config.json
{
"Timeout": 30,           // Becomes [int]
"Price": 29.99,          // Becomes [double]
"IsEnabled": true,       // Becomes [bool]
"Path": "C:\\Temp"       // Becomes [string] - note escaped backslash
}

# PSD1 preserves types better
# config.psd1
@{
Timeout = [TimeSpan]::FromSeconds(30)  # Actual TimeSpan object
Path = "C:\Temp"                       # No escaping needed
ScriptBlock = { Get-Process }          # Can store code!
}

Secure Credential Storage

Never Store Plaintext Passwords

Use PowerShell's built-in credential storage or secret management modules

# Export encrypted credential (only works for same user on same machine)
$credential = Get-Credential
$credential | Export-Clixml -Path "C:\Secrets\service-account.xml"

# Import encrypted credential
$credential = Import-Clixml -Path "C:\Secrets\service-account.xml"

# Use with your script
Connect-SomeService -Credential $credential

Better: Use SecretManagement module (cross-platform)

# One-time setup
Install-Module Microsoft.PowerShell.SecretManagement
Install-Module SecretStore  # Or another vault provider

# Register a vault
Register-SecretVault -Name "MyAppVault" -ModuleName SecretStore

# Store a secret
$credential = Get-Credential
Set-Secret -Name "MyApp_ServiceAccount" -Secret $credential -Vault "MyAppVault"

# In your script: retrieve the secret
$config = Import-PowerShellDataFile ".\config.psd1"
$credential = Get-Secret -Name $config.CredentialName -Vault $config.VaultName

Additional Resources