PowerShell Scripting

Learn how to integrate Entra Auth Cli with PowerShell for Windows automation and Azure management.


Basic Token Retrieval

  # Get token
$token = entra-auth-cli get-token -p my-profile --silent

# Use with Invoke-RestMethod
$headers = @{
    "Authorization" = "Bearer $token"
    "Content-Type" = "application/json"
}

$response = Invoke-RestMethod `
    -Uri "https://graph.microsoft.com/v1.0/me" `
    -Headers $headers `
    -Method Get

$response | ConvertTo-Json
  

Token Caching

Implement token caching for better performance.

  $tokenCache = "$env:TEMP\entra-auth-cli-token.txt"
$tokenMaxAge = 3000  # 50 minutes

function Get-CachedToken {
    param([string]$Profile)
    
    # Check if cached token is valid
    if (Test-Path $tokenCache) {
        entra-auth-cli discover -f $tokenCache 2>$null
        if ($LASTEXITCODE -eq 0) {
            return Get-Content $tokenCache -Raw
        }
    }
    
    # Get fresh token
    $token = entra-auth-cli get-token -p $Profile --silent
    $token | Out-File -FilePath $tokenCache -NoNewline
    return $token
}

# Use it
$token = Get-CachedToken -Profile "my-profile"
  

Error Handling

Implement robust error handling with retries.

  function Get-TokenWithRetry {
    param(
        [string]$Profile,
        [int]$MaxRetries = 3
    )
    
    for ($i = 0; $i -lt $MaxRetries; $i++) {
        try {
            $token = entra-auth-cli get-token -p $Profile --silent 2>&1
            if ($LASTEXITCODE -eq 0) {
                return $token
            }
        } catch {
            Write-Warning "Retry $($i + 1)/$MaxRetries..."
            Start-Sleep -Seconds 5
        }
    }
    
    throw "Failed to get token after $MaxRetries attempts"
}

# Use it
try {
    $token = Get-TokenWithRetry -Profile "my-profile"
    # Use token...
} catch {
    Write-Error "Fatal: Could not acquire token"
    exit 1
}
  

Multi-API Script

Work with multiple APIs using different tokens.

  # Get tokens for different APIs
$graphToken = entra-auth-cli get-token -p graph-profile --silent
$azureToken = entra-auth-cli get-token -p azure-profile --silent

# Use Graph API
Write-Host "Fetching user profile..."
$graphHeaders = @{ "Authorization" = "Bearer $graphToken" }
$user = Invoke-RestMethod `
    -Uri "https://graph.microsoft.com/v1.0/me" `
    -Headers $graphHeaders

Write-Host "User: $($user.displayName)"

# Use Azure Management API
Write-Host "Fetching subscriptions..."
$azureHeaders = @{ "Authorization" = "Bearer $azureToken" }
$subs = Invoke-RestMethod `
    -Uri "https://management.azure.com/subscriptions?api-version=2020-01-01" `
    -Headers $azureHeaders

$subs.value | ForEach-Object { Write-Host $_.displayName }
  

Conditional Token Refresh

Only refresh tokens when necessary.

  function Get-ValidToken {
    param([string]$Profile)
    
    $tokenFile = "$env:TEMP\token-$Profile.txt"
    $minValidity = 300  # 5 minutes
    
    # Check if token exists
    if (Test-Path $tokenFile) {
        # Check expiration
        $tokenJson = entra-auth-cli inspect -f $tokenFile 2>$null | ConvertFrom-Json
        if ($tokenJson) {
            $exp = $tokenJson.payload.exp
            $now = [DateTimeOffset]::Now.ToUnixTimeSeconds()
            $remaining = $exp - $now
            
            if ($remaining -gt $minValidity) {
                return Get-Content $tokenFile -Raw
            }
        }
    }
    
    # Get fresh token
    $token = entra-auth-cli get-token -p $Profile --silent
    $token | Out-File -FilePath $tokenFile -NoNewline
    return $token
}

$token = Get-ValidToken -Profile "my-profile"
  

Parallel API Calls

Make multiple API calls concurrently using PowerShell jobs.

  # Get token once
$token = entra-auth-cli get-token -p my-profile --silent
$headers = @{ "Authorization" = "Bearer $token" }

# Start parallel jobs
$jobs = @()
$jobs += Start-Job -ScriptBlock {
    param($headers)
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $headers
} -ArgumentList $headers

$jobs += Start-Job -ScriptBlock {
    param($headers)
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/messages" -Headers $headers
} -ArgumentList $headers

$jobs += Start-Job -ScriptBlock {
    param($headers)
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/calendars" -Headers $headers
} -ArgumentList $headers

# Wait for all jobs and get results
$results = $jobs | Wait-Job | Receive-Job
$jobs | Remove-Job

Write-Host "All API calls completed"
$results | ForEach-Object { $_ | ConvertTo-Json }
  

API Pagination

Handle paginated API responses.

  $token = entra-auth-cli get-token -p graph-admin --silent
$headers = @{ "Authorization" = "Bearer $token" }
$url = "https://graph.microsoft.com/v1.0/users"

while ($url) {
    $response = Invoke-RestMethod -Uri $url -Headers $headers
    
    # Process users
    $response.value | ForEach-Object {
        Write-Host $_.displayName
    }
    
    # Get next page URL
    $url = $response.'@odata.nextLink'
}
  

Rate Limiting

Handle API rate limiting with exponential backoff.

  function Invoke-ApiWithRateLimit {
    param(
        [string]$Uri,
        [hashtable]$Headers,
        [int]$MaxRetries = 5
    )
    
    for ($retry = 0; $retry -lt $MaxRetries; $retry++) {
        try {
            $response = Invoke-WebRequest -Uri $Uri -Headers $Headers -Method Get
            return $response.Content | ConvertFrom-Json
        } catch {
            if ($_.Exception.Response.StatusCode -eq 429) {
                $waitTime = [Math]::Pow(2, $retry)
                Write-Warning "Rate limited, waiting $waitTime seconds..."
                Start-Sleep -Seconds $waitTime
            } else {
                throw
            }
        }
    }
    
    throw "Max retries exceeded"
}

$token = entra-auth-cli get-token -p my-profile --silent
$headers = @{ "Authorization" = "Bearer $token" }
$result = Invoke-ApiWithRateLimit -Uri "https://graph.microsoft.com/v1.0/users" -Headers $headers
  

Complete Example Script

Production-ready script with logging and error handling.

  #Requires -Version 5.1

[CmdletBinding()]
param(
    [Parameter(Mandatory=$true)]
    [string]$Profile,
    
    [Parameter(Mandatory=$false)]
    [string]$LogFile = "$env:TEMP\my-script.log"
)

# Logging function
function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$timestamp - $Message" | Tee-Object -FilePath $LogFile -Append
}

# Get cached or fresh token
function Get-Token {
    param([string]$Profile)
    
    $tokenCache = "$env:TEMP\entra-auth-cli-$Profile.token"
    
    if (Test-Path $tokenCache) {
        entra-auth-cli discover -f $tokenCache 2>$null
        if ($LASTEXITCODE -eq 0) {
            return Get-Content $tokenCache -Raw
        }
    }
    
    $token = entra-auth-cli get-token -p $Profile --silent
    $token | Out-File -FilePath $tokenCache -NoNewline
    return $token
}

# Call API with error handling
function Invoke-Api {
    param(
        [string]$Uri,
        [string]$Token
    )
    
    $headers = @{ "Authorization" = "Bearer $Token" }
    
    try {
        $response = Invoke-RestMethod -Uri $Uri -Headers $headers -Method Get
        return $response
    } catch {
        Write-Log "ERROR: API call failed - $_"
        throw
    }
}

# Main
try {
    Write-Log "Starting script..."
    
    # Get token
    $token = Get-Token -Profile $Profile
    if (-not $token) {
        Write-Log "FATAL: Could not get token"
        exit 1
    }
    
    # Call API
    $result = Invoke-Api -Uri "https://graph.microsoft.com/v1.0/me" -Token $token
    Write-Log "SUCCESS: API call completed"
    $result | ConvertTo-Json | Write-Host
    
    Write-Log "Script completed successfully"
} catch {
    Write-Log "FATAL: $_"
    exit 1
}
  

Best Practices

Use Strict Mode

  Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
  

Use Silent Mode

  # Good
$token = entra-auth-cli get-token -p my-profile --silent

# Bad (may include extra output)
$token = entra-auth-cli get-token -p my-profile
  

Secure Token Storage

  # Restrict file permissions
$acl = Get-Acl $tokenFile
$acl.SetAccessRuleProtection($true, $false)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    $env:USERNAME, "FullControl", "Allow"
)
$acl.AddAccessRule($rule)
Set-Acl $tokenFile $acl
  

Check Exit Codes

  entra-auth-cli get-token -p my-profile --silent
if ($LASTEXITCODE -ne 0) {
    Write-Error "Token generation failed"
    exit 1
}
  

Next Steps