SYNERGY
Chapters

15.9Examples

Last updated

The examples use Windows PowerShell 5.1, which is present on every supported Windows server. They work unchanged in PowerShell 7.

Setting up a session

powershell
$base    = 'https://gateway.example.com/api'
$headers = @{
    'X-API-Key'   = $env:GATEWAY_API_KEY   # keep the key out of the script
    'X-Client-Id' = 'hr-sync'
}
Tip: Store the API key in a secure location such as the Windows Credential Manager or an environment variable set for the service account, never in the script itself.

Checking connectivity

powershell
Invoke-RestMethod -Uri "$base/health"
Invoke-RestMethod -Uri "$base/metadata" -Headers $headers

/health needs no credential. /metadata succeeds with any valid key, so it proves the URL and the key are right.

Searching all users in an organizational unit

powershell
$users = @()
$token = $null
do {
    $query = @{ baseDn = 'OU=Staff,DC=example,DC=com'; pageSize = 500 }
    if ($token) { $query.continuationToken = $token }
    $page  = Invoke-RestMethod -Uri "$base/ad/users" -Headers $headers -Body $query
    $users += $page.data
    $token = $page.meta.nextContinuationToken
} while ($page.meta.hasNextPage)

"Found $($users.Count) users"

Passing a hashtable to -Body on a GET request sends it as the query string, encoded for you.

Creating a user and handling approval

powershell
# expiresAt is UTC, with or without a trailing Z. PowerShell 7 may already have
# turned it into a date; Windows PowerShell 5.1 leaves it as text.
function ConvertTo-UtcDate($value) {
    if ($value -is [datetime]) {
        if ($value.Kind -eq 'Unspecified') { return [datetime]::SpecifyKind($value, 'Utc') }
        return $value.ToUniversalTime()
    }
    $styles = [Globalization.DateTimeStyles]'AssumeUniversal, AdjustToUniversal'
    return [datetime]::Parse($value, [Globalization.CultureInfo]::InvariantCulture, $styles)
}

$body = @{
    name           = 'Jane Doe'
    path           = 'OU=Staff,DC=example,DC=com'
    samAccountName = 'jdoe'
    accountPolicy  = @{ enabled = $true; changePasswordAtLogon = $true }
    attributes     = @{ givenName = 'Jane'; sn = 'Doe'; mail = 'jane.doe@example.com' }
} | ConvertTo-Json -Depth 5

$response = Invoke-WebRequest -Uri "$base/ad/users" -Method Post -Headers $headers `
    -ContentType 'application/json' -Body $body -UseBasicParsing
$content  = $response.Content | ConvertFrom-Json

if ($response.StatusCode -eq 202) {
    Write-Host "Waiting for approval ($($content.reason)): $($content.reasonDetail)"
    $operationId = $content.operationId
    do {
        Start-Sleep -Seconds 30
        $op = Invoke-RestMethod -Uri "$base/operations/$operationId" -Headers $headers
        $expired = $op.expiresAt -and (ConvertTo-UtcDate $op.expiresAt) -lt [datetime]::UtcNow
    } while ($op.status -eq 'WaitingToApprove' -and -not $expired)

    if ($op.status -eq 'Success') {
        $newUserId = $op.result.entityId
    } else {
        throw "Create was not carried out: $($op.status) $($op.errorMessage)"
    }
} else {
    $newUserId = $content.objectGuid
}

"New user: $newUserId"

Invoke-WebRequest is used for the write so the script can see the status code and tell 201 from 202. Both are successes as far as PowerShell is concerned; only the status code tells them apart.

Reading an error

In Windows PowerShell 5.1, a 4xx or 5xx response throws. The problem details are in the error record:

powershell
try {
    Invoke-RestMethod -Uri "$base/ad/users/$id" -Method Delete -Headers $headers
} catch {
    $problem = $_.ErrorDetails.Message | ConvertFrom-Json
    # Problem details carry errorCode and detail; the licence and X-Client-Id refusals use code/error and message
    $code    = if ($problem.errorCode) { $problem.errorCode } elseif ($problem.code) { $problem.code } else { $problem.error }
    $message = if ($problem.detail) { $problem.detail } else { $problem.message }
    Write-Warning "$([int]$_.Exception.Response.StatusCode) ${code}: $message"
}

Updating several users in one batch

powershell
$batch = @{
    items = @(
        @{ id = '3f2504e0-4f89-11d3-9a0c-0305e82c3301'; data = @{ replace = @{ department = 'Finance' } } }
        @{ id = '9b7c1d0a-2e4f-4c55-8a1b-7f3e6d2c1a09'; data = @{ replace = @{ department = 'Finance' } } }
    )
} | ConvertTo-Json -Depth 6

$response = Invoke-WebRequest -Uri "$base/ad/users/batch" -Method Patch -Headers $headers `
    -ContentType 'application/json' -Body $batch -UseBasicParsing
$result = $response.Content | ConvertFrom-Json

switch ($response.StatusCode) {
    200 { "All $($result.total) updated" }
    207 { $result.items | Where-Object { -not $_.succeeded } |
            ForEach-Object { Write-Warning "Item $($_.index): $($_.errorCode) $($_.errorMessage)" } }
    202 { "Batch waiting for approval: $($result.operationId)" }
}

Plain HTTP examples

Adding a user to a group:

http
POST /api/ad/users/3f2504e0-4f89-11d3-9a0c-0305e82c3301/groups HTTP/1.1
Host: gateway.example.com
X-API-Key: <your API key>
X-Client-Id: hr-sync
Content-Type: application/json

{ "groupId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e" }

Adding a member to a group (the member is a user, group, computer or other object, by GUID):

http
POST /api/ad/groups/b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e/members HTTP/1.1
Host: gateway.example.com
X-API-Key: <your API key>
X-Client-Id: hr-sync
Content-Type: application/json

{ "memberId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301" }

Disabling a user:

http
POST /api/ad/users/3f2504e0-4f89-11d3-9a0c-0305e82c3301/disable HTTP/1.1
Host: gateway.example.com
X-API-Key: <your API key>
X-Client-Id: hr-sync

Resetting a password:

http
POST /api/ad/users/3f2504e0-4f89-11d3-9a0c-0305e82c3301/reset-password HTTP/1.1
Host: gateway.example.com
X-API-Key: <your API key>
X-Client-Id: hr-sync
Content-Type: application/json

{ "newPassword": "<new password>" }

A successful reset answers 204 No Content.

Good practice

  • Use one API key per integration, with the narrowest role that works, so that the operations log shows which integration did what and one key can be revoked on its own.
  • Always handle 202. Any write can be queued, depending on the key's supervision level and what it touches.
  • Branch on errorCode, never on message text, which may change.
  • Treat 207 as a partial failure and check each item.
  • Respect Retry-After on both 202 and 429.
  • Keep keys secret and rotate them. A revoked key stops working on its next request. Keys can be given an expiry date (Chapter 12).
  • Never put credentials in URLs. Send the key only in the X-API-Key header, only over HTTPS.

Last updated

Did this page help you?