IntermediateLesson 8 of 16

Working with Objects and JSON

Create custom objects with [PSCustomObject], shape pipeline output with Select-Object computed properties, convert data to and from JSON, and call REST APIs with Invoke-RestMethod to integrate PowerShell with Azure and third-party services.

🧒 Simple Explanation (ELI5)

A PowerShell object is like a form with labeled fields: Name, Status, CPU. Some forms come pre-filled (Get-Process), and some you create from scratch and fill yourself. JSON is how computers share those forms online—it is a structured text format that websites and APIs use to send and receive data. Once you know how to create objects and parse JSON in PowerShell, you can talk to any API and shape the results any way you need.

🔧 Why Do We Need It?

⚙️ Technical Explanation

The [PSCustomObject] accelerator creates lightweight custom objects from a hashtable. Properties are immediately accessible and the object flows through the pipeline like any built-in type. Use Add-Member to add properties after creation, though inline creation is preferred.

Select-Object with calculated properties (hashtable with n=name and e=expression) lets you transform, rename, or compute new properties inline in the pipeline without creating a separate step.

ConvertTo-Json serializes any .NET object or hashtable to a JSON string. ConvertFrom-Json deserializes a JSON string to a PowerShell object with dot-notation access. Invoke-RestMethod calls a REST API and automatically deserializes JSON responses.

📐
ConvertTo-Json Depth Warning

ConvertTo-Json defaults to a depth of 2. Deeply nested objects (common in Azure API responses) are truncated at depth 2 with "@{...}" text. Always specify depth explicitly: ConvertTo-Json -Depth 10 for complex objects. Truncated JSON is one of the most common silent data-loss bugs when serializing Azure resource objects for storage or transmission.

🔗
Invoke-RestMethod vs Invoke-WebRequest

Use Invoke-RestMethod when the API returns JSON—it automatically deserializes the response so you get an object with dot-notation access. Use Invoke-WebRequest when you need the raw HTTP response (headers, status code, binary content). For 99% of API integration work, Invoke-RestMethod is the right choice.

📊 Visual Representation

JSON ↔ PowerShell Object Flow
API JSON response
→ ConvertFrom-Json
PS Object
→ pipeline
Filter / Transform
→ ConvertTo-Json
Output JSON

⌨️ Commands / Syntax

powershell
# Create a custom object
$server = [PSCustomObject]@{
    Name        = "web01"
    Environment = "prod"
    Status      = "Healthy"
    CPU         = 12.5
    MemoryGB    = 3.8
}
$server.Name    # "web01"

# Build a list of custom objects
$report = foreach ($svc in Get-Service) {
    [PSCustomObject]@{
        Name      = $svc.Name
        Status    = $svc.Status
        IsRunning = $svc.Status -eq 'Running'
    }
}
$report | Where-Object IsRunning -eq $false

# Calculated property in Select-Object
Get-Process |
  Select-Object Name, Id,
    @{n="MemoryMB"; e={[math]::Round($_.WorkingSet64/1MB, 1)}},
    @{n="CPURounded"; e={[math]::Round($_.CPU, 2)}} |
  Sort-Object MemoryMB -Descending |
  Select-Object -First 10

# Convert to JSON
$config = @{
    environment = "prod"
    replicas    = 3
    tags        = @{ team = "platform"; cost = "infra" }
}
$json = $config | ConvertTo-Json -Depth 5
Write-Output $json

# Parse JSON from a file
$appConfig = Get-Content "appsettings.json" -Raw | ConvertFrom-Json
Write-Host "DB connection: $($appConfig.ConnectionStrings.Default)"

# Call a REST API
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/microsoft/powershell" `
    -Headers @{ "User-Agent" = "PowerShell" }
Write-Host "Stars: $($response.stargazers_count)"

# Call Azure REST API with authentication header
$token = (Get-AzAccessToken).Token
$headers = @{
    Authorization = "Bearer $token"
    "Content-Type" = "application/json"
}
$apps = Invoke-RestMethod `
    -Uri "https://management.azure.com/subscriptions/$subId/providers/Microsoft.Web/sites?api-version=2022-03-01" `
    -Headers $headers
$apps.value | Select-Object name, location, @{n="state";e={$_.properties.state}}

💼 Example (Real-world Use Case)

An on-call engineer needs a dashboard of all Azure App Services across subscriptions showing name, resource group, state, and last modified. Using Invoke-RestMethod against the Azure management API and ConvertFrom-Json, they build a custom object per service, add an IsHealthy boolean, and pipe to Export-Csv to share with the team. The whole script is 30 lines and runs in one minute against 200 services.

🧪 Hands-on

  1. Create a [PSCustomObject] for a server with Name, IP, and Status properties.
  2. Use Select-Object with a calculated property to convert a process's WorkingSet64 from bytes to megabytes.
  3. Convert a hashtable to JSON with ConvertTo-Json -Depth 5 and inspect the output.
  4. Fetch a public API response: Invoke-RestMethod "https://httpbin.org/json" and access a nested property.
  5. Read a local JSON config file with Get-Content -Raw | ConvertFrom-Json and access one property.
🎮
Try It Yourself

Call the GitHub API to get the latest release of PowerShell: Invoke-RestMethod "https://api.github.com/repos/PowerShell/PowerShell/releases/latest" -Headers @{"User-Agent"="PS"}. From the response, extract: tag_name, published_at, and the download URL of the .msi asset. Build a [PSCustomObject] with those three properties and output it as JSON with ConvertTo-Json.

🐛 Debugging Scenario

Problem: you convert an Azure resource object to JSON using ConvertTo-Json, save it to a file, then ConvertFrom-Json the file and find nested properties missing or showing as "@{...}" strings.

🎯 Interview Questions

Beginner

How do you create a custom object in PowerShell?

Use [PSCustomObject]@{ ... } with a hashtable of name-value pairs. Example: [PSCustomObject]@{ Name = "web01"; Status = "Running" }. The result is a full PowerShell object with properties accessible via dot notation and pipeable to Export-Csv, ConvertTo-Json, and other cmdlets.

What is a calculated property in Select-Object?

A hashtable with n (name) and e (expression) keys: @{n="MemMB"; e={$_.WorkingSet64/1MB}}. It creates a new property on the output object computed from an expression, letting you reshape data inline without a separate step.

What is the difference between ConvertTo-Json and Export-Clixml?

ConvertTo-Json produces JSON—a standard, cross-platform format readable by any language. Export-Clixml produces a PowerShell-specific XML format that preserves .NET types exactly and can be reimported with Import-Clixml. Use ConvertTo-Json for interoperability with APIs and other tools; use Clixml only for PowerShell-to-PowerShell data persistence.

Scenario-based

You call a REST API and the response has nested objects, but after ConvertTo-Json the nested parts show as @{...} strings. How do you fix it?

Add -Depth to ConvertTo-Json: $response | ConvertTo-Json -Depth 10. The default depth of 2 truncates deeper objects. Always check the output for "@{" fragments, which indicate truncation. For Azure management API responses, depth 10 is typically sufficient.

A monitoring script calls a third-party API to check application health, but the API sometimes times out. How do you make the call resilient?

Wrap Invoke-RestMethod in a try/catch, catch [System.Net.WebException] and [System.TimeoutException] specifically, implement a retry loop with exponential backoff (Start-Sleep increases per attempt), set a -TimeoutSec parameter on the call, and after a maximum number of retries, throw a clear error with the endpoint URL and the number of attempts made.

🌐 Real-world Usage

Platform teams use Invoke-RestMethod to call Azure Monitor REST APIs to pull metric data, push alerts to Slack webhooks, and trigger Azure DevOps pipeline runs from maintenance scripts. [PSCustomObject] is the standard way to produce structured, pipeable output from reporting functions. JSON configuration files are read at script startup to avoid hard-coded environment variables in pipeline definitions.

📝 Summary

[PSCustomObject] creates structured objects ideal for pipeline output and export. Calculated properties in Select-Object transform data inline. ConvertTo-Json and ConvertFrom-Json serialize/deserialize data—always specify -Depth when the object is complex. Invoke-RestMethod calls REST APIs and auto-deserializes JSON. Invoke-WebRequest gives you raw HTTP access. These tools together make PowerShell capable of integrating with any API-based service.