Introduction: Unlocking the Power of Workspace ONE APIs
The Workspace ONE UEM API represents one of the most powerful yet underutilized capabilities in the platform. After spending years implementing API-driven solutions across hundreds of organizations, I can tell you that mastering the Workspace ONE API is what separates basic administrators from true automation architects who can solve complex business challenges that are impossible through the console alone.
If you’re a Workspace ONE administrator looking to go beyond manual console operations and implement sophisticated automation, custom integrations, and intelligent workflows, this comprehensive guide will show you how to effectively leverage the Workspace ONE UEM API. We’ll cover everything from basic authentication to advanced automation scenarios that will revolutionize how you manage your mobile and desktop environment.
Understanding the Workspace ONE UEM API
What is the Workspace ONE UEM API?
The Workspace ONE UEM API is a comprehensive RESTful API that provides programmatic access to virtually all Workspace ONE UEM functionality. This API enables you to automate device management tasks, integrate with external systems, and create custom solutions that extend the platform’s capabilities far beyond what’s possible through the web console.
Key Capabilities of the UEM API:
- Device Management: Programmatically manage devices, applications, and configurations
- User Management: Automate user provisioning, group assignments, and access control
- Content Management: Manage applications, profiles, and content distribution
- Reporting and Analytics: Extract detailed reports and analytics data
- System Administration: Automate administrative tasks and system configuration
Why API Integration Matters in Modern Device Management
In my experience, API integration addresses several critical limitations of manual console-based management:
Scale and Efficiency:
- Bulk Operations: Perform operations on thousands of devices simultaneously
- Automated Workflows: Create intelligent workflows that respond to events and conditions
- Consistent Execution: Eliminate human error through automated processes
- Time Savings: Reduce manual effort from hours to minutes for complex operations
Integration Capabilities:
- External System Integration: Connect Workspace ONE with ITSM, CMDB, and other enterprise systems
- Custom Applications: Build custom applications and portals for specific business needs
- Data Synchronization: Keep data synchronized between Workspace ONE and other systems
- Business Process Automation: Integrate device management into broader business processes
API Architecture and Structure
The Workspace ONE UEM API follows RESTful principles and is organized into logical functional areas.
API Structure Overview:
- Base URL Structure:
- SaaS:
https://[your-server].awmdm.com/API
- On-Premises:
https://[your-server]/API
- Version-specific endpoints:
/v1/
,/v2/
, etc.
- SaaS:
- Authentication Methods:
- Basic Authentication: Username and password for interactive scenarios
- API Key Authentication: API key and tenant code for service accounts
- OAuth 2.0: Modern authentication for third-party integrations
- Certificate-Based: Certificate authentication for high-security scenarios
Core API Categories:
- Device Management APIs:
/devices
– Device enrollment, management, and queries/deviceprofiles
– Device profile management and assignment/devicecommands
– Remote device commands and actions/devicecompliance
– Compliance monitoring and enforcement
- Application Management APIs:
/apps
– Application catalog and deployment management/appgroups
– Application group management/appinstalls
– Application installation tracking and management/appwrapping
– Application wrapping and MAM policy management
- User and Group Management APIs:
/users
– User account management and provisioning/usergroups
– User group management and assignments/roles
– Role-based access control management/admins
– Administrator account management
- Content and Configuration APIs:
/profiles
– Configuration profile management/resources
– Resource and content management/policies
– Policy configuration and assignment/certificates
– Certificate management and distribution
Setting Up API Access
Configuring API Authentication
Before you can use the Workspace ONE UEM API, you need to configure appropriate authentication credentials.
Creating API Credentials in the Console:
- Access the API Configuration:
- Navigate to Groups & Settings → All Settings
- Select System → Advanced → API
- Click on REST API to configure API settings
- Configure API Access:
- Enable API Access for your organization group
- Configure API Timeout settings (recommended: 300 seconds)
- Set API Rate Limiting if required for your environment
- Configure API Logging for audit and troubleshooting purposes
Creating Service Account Credentials:
- Create a Dedicated Service Account:
- Navigate to Accounts → Administrators → List View
- Click Add → Add Admin
- Create a dedicated service account (e.g., “API_Service_Account”)
- Assign appropriate roles based on required API operations
- Generate API Key:
- Navigate to Groups & Settings → All Settings
- Select System → Advanced → API → REST API
- Click Add to create a new API key
- Configure the API key with appropriate permissions and expiration
Testing API Connectivity
Before implementing complex automation, it’s essential to test basic API connectivity and authentication.
Basic API Test Using PowerShell:
# Basic Workspace ONE UEM API Test Script
# API Configuration
$apiServer = "your-server.awmdm.com" # Replace with your server
$apiKey = "your-api-key" # Replace with your API key
$tenantCode = "your-tenant-code" # Replace with your tenant code
$username = "api-service-account" # Replace with your service account
$password = "service-account-password" # Replace with service account password
# Function to create authentication headers
function Get-AuthHeaders {
param(
[string]$Username,
[string]$Password,
[string]$ApiKey,
[string]$TenantCode
)
# Create basic authentication string
$authString = "$Username`:$Password"
$authBytes = [System.Text.Encoding]::UTF8.GetBytes($authString)
$authBase64 = [System.Convert]::ToBase64String($authBytes)
# Create headers
$headers = @{
"Authorization" = "Basic $authBase64"
"aw-tenant-code" = $TenantCode
"Accept" = "application/json"
"Content-Type" = "application/json"
}
return $headers
}
# Function to test API connectivity
function Test-APIConnectivity {
param(
[string]$Server,
[hashtable]$Headers
)
try {
Write-Host "Testing API connectivity to $Server..."
# Test basic connectivity with system info endpoint
$uri = "https://$Server/API/v1/system/info"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $Headers -TimeoutSec 30
Write-Host "✓ API connectivity successful" -ForegroundColor Green
Write-Host " Server Version: $($response.ProductVersion)" -ForegroundColor Cyan
Write-Host " Build Number: $($response.BuildNumber)" -ForegroundColor Cyan
Write-Host " Server Time: $($response.CurrentServerTime)" -ForegroundColor Cyan
return $true
} catch {
Write-Host "✗ API connectivity failed: $($_.Exception.Message)" -ForegroundColor Red
return $false
}
}
# Function to test device query
function Test-DeviceQuery {
param(
[string]$Server,
[hashtable]$Headers
)
try {
Write-Host "Testing device query..."
# Query first 10 devices
$uri = "https://$Server/API/v1/devices/search?pagesize=10"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $Headers -TimeoutSec 30
Write-Host "✓ Device query successful" -ForegroundColor Green
Write-Host " Total Devices: $($response.Total)" -ForegroundColor Cyan
Write-Host " Devices Returned: $($response.Devices.Count)" -ForegroundColor Cyan
if ($response.Devices.Count -gt 0) {
Write-Host " Sample Device: $($response.Devices[0].DeviceFriendlyName)" -ForegroundColor Cyan
}
return $true
} catch {
Write-Host "✗ Device query failed: $($_.Exception.Message)" -ForegroundColor Red
return $false
}
}
# Main testing process
Write-Host "=== Workspace ONE UEM API Connectivity Test ===" -ForegroundColor Yellow
# Create authentication headers
$headers = Get-AuthHeaders -Username $username -Password $password -ApiKey $apiKey -TenantCode $tenantCode
# Test basic connectivity
$connectivityTest = Test-APIConnectivity -Server $apiServer -Headers $headers
if ($connectivityTest) {
# Test device query
Test-DeviceQuery -Server $apiServer -Headers $headers
} else {
Write-Host "Skipping additional tests due to connectivity failure" -ForegroundColor Yellow
}
Write-Host "=== Test Complete ===" -ForegroundColor Yellow
Testing with Postman or Similar Tools:
- Configure Postman Collection:
- Import the official Workspace ONE UEM API Postman collection
- Configure environment variables for your server and credentials
- Test basic endpoints like
/system/info
and/devices/search
- Verify response formats and status codes
- Common Test Endpoints:
GET /API/v1/system/info
– System information and versionGET /API/v1/devices/search
– Device search and listingGET /API/v1/users/search
– User search and listingGET /API/v1/apps/search
– Application search and listing
Common API Use Cases and Examples
Device Management Automation
Device management is one of the most common use cases for the Workspace ONE UEM API.
Example 1: Bulk Device Operations
This PowerShell script demonstrates how to perform bulk operations on devices:
# Bulk Device Operations Script
# Performs various bulk operations on Workspace ONE managed devices
# API Configuration
$apiConfig = @{
Server = "your-server.awmdm.com"
ApiKey = "your-api-key"
TenantCode = "your-tenant-code"
Username = "api-service-account"
Password = "service-account-password"
}
# Function to create authentication headers
function Get-AuthHeaders {
param([hashtable]$Config)
$authString = "$($Config.Username):$($Config.Password)"
$authBytes = [System.Text.Encoding]::UTF8.GetBytes($authString)
$authBase64 = [System.Convert]::ToBase64String($authBytes)
return @{
"Authorization" = "Basic $authBase64"
"aw-tenant-code" = $Config.TenantCode
"Accept" = "application/json"
"Content-Type" = "application/json"
}
}
# Function to get devices by criteria
function Get-DevicesByCriteria {
param(
[hashtable]$Config,
[hashtable]$Headers,
[string]$Platform = "",
[string]$Model = "",
[string]$Ownership = "",
[int]$PageSize = 500
)
try {
$uri = "https://$($Config.Server)/API/v1/devices/search?pagesize=$PageSize"
# Add search criteria
if ($Platform) { $uri += "&platform=$Platform" }
if ($Model) { $uri += "&model=$Model" }
if ($Ownership) { $uri += "&ownership=$Ownership" }
Write-Host "Querying devices with criteria: Platform=$Platform, Model=$Model, Ownership=$Ownership"
$allDevices = @()
$page = 0
do {
$pageUri = "$uri&page=$page"
$response = Invoke-RestMethod -Uri $pageUri -Method GET -Headers $Headers -TimeoutSec 60
if ($response.Devices) {
$allDevices += $response.Devices
Write-Host "Retrieved $($response.Devices.Count) devices from page $page"
}
$page++
} while ($response.Devices.Count -eq $PageSize)
Write-Host "Total devices retrieved: $($allDevices.Count)"
return $allDevices
} catch {
Write-Error "Failed to retrieve devices: $($_.Exception.Message)"
return @()
}
}
# Function to send command to multiple devices
function Send-BulkDeviceCommand {
param(
[hashtable]$Config,
[hashtable]$Headers,
[array]$DeviceIds,
[string]$Command,
[hashtable]$CommandParameters = @{}
)
try {
Write-Host "Sending command '$Command' to $($DeviceIds.Count) devices..."
$successCount = 0
$failureCount = 0
$results = @()
foreach ($deviceId in $DeviceIds) {
try {
$uri = "https://$($Config.Server)/API/v1/devices/$deviceId/commands"
$commandBody = @{
Command = $Command
} + $CommandParameters
$jsonBody = $commandBody | ConvertTo-Json -Depth 10
$response = Invoke-RestMethod -Uri $uri -Method POST -Headers $Headers -Body $jsonBody -TimeoutSec 30
$results += [PSCustomObject]@{
DeviceId = $deviceId
Status = "Success"
CommandId = $response.CommandUuid
Message = "Command sent successfully"
}
$successCount++
} catch {
$results += [PSCustomObject]@{
DeviceId = $deviceId
Status = "Failed"
CommandId = $null
Message = $_.Exception.Message
}
$failureCount++
}
# Add small delay to avoid rate limiting
Start-Sleep -Milliseconds 100
}
Write-Host "Command execution completed: $successCount successful, $failureCount failed"
return $results
} catch {
Write-Error "Failed to send bulk device command: $($_.Exception.Message)"
return @()
}
}
# Function to update device tags
function Update-DeviceTags {
param(
[hashtable]$Config,
[hashtable]$Headers,
[array]$DeviceIds,
[array]$TagsToAdd = @(),
[array]$TagsToRemove = @()
)
try {
Write-Host "Updating tags for $($DeviceIds.Count) devices..."
$successCount = 0
$failureCount = 0
$results = @()
foreach ($deviceId in $DeviceIds) {
try {
# Get current device details
$deviceUri = "https://$($Config.Server)/API/v1/devices/$deviceId"
$device = Invoke-RestMethod -Uri $deviceUri -Method GET -Headers $Headers -TimeoutSec 30
# Prepare tag update
$currentTags = if ($device.Tags) { $device.Tags } else { @() }
$updatedTags = $currentTags | Where-Object { $_ -notin $TagsToRemove }
$updatedTags += $TagsToAdd | Where-Object { $_ -notin $updatedTags }
# Update device tags
$updateBody = @{
Tags = $updatedTags
}
$jsonBody = $updateBody | ConvertTo-Json -Depth 10
$updateUri = "https://$($Config.Server)/API/v1/devices/$deviceId"
Invoke-RestMethod -Uri $updateUri -Method PUT -Headers $Headers -Body $jsonBody -TimeoutSec 30
$results += [PSCustomObject]@{
DeviceId = $deviceId
Status = "Success"
PreviousTags = $currentTags -join ", "
UpdatedTags = $updatedTags -join ", "
Message = "Tags updated successfully"
}
$successCount++
} catch {
$results += [PSCustomObject]@{
DeviceId = $deviceId
Status = "Failed"
PreviousTags = ""
UpdatedTags = ""
Message = $_.Exception.Message
}
$failureCount++
}
# Add small delay to avoid rate limiting
Start-Sleep -Milliseconds 200
}
Write-Host "Tag update completed: $successCount successful, $failureCount failed"
return $results
} catch {
Write-Error "Failed to update device tags: $($_.Exception.Message)"
return @()
}
}
# Main execution
Write-Host "=== Workspace ONE UEM Bulk Device Operations ===" -ForegroundColor Yellow
# Create authentication headers
$headers = Get-AuthHeaders -Config $apiConfig
# Example 1: Get all Windows devices
Write-Host "`n--- Example 1: Retrieving Windows Devices ---" -ForegroundColor Cyan
$windowsDevices = Get-DevicesByCriteria -Config $apiConfig -Headers $headers -Platform "WinRT"
if ($windowsDevices.Count -gt 0) {
Write-Host "Found $($windowsDevices.Count) Windows devices"
# Example 2: Send sync command to first 5 Windows devices
Write-Host "`n--- Example 2: Sending Sync Command ---" -ForegroundColor Cyan
$deviceIds = $windowsDevices[0..4] | ForEach-Object { $_.Id.Value }
$syncResults = Send-BulkDeviceCommand -Config $apiConfig -Headers $headers -DeviceIds $deviceIds -Command "SyncDevice"
# Display results
$syncResults | Format-Table -AutoSize
# Example 3: Add tags to devices
Write-Host "`n--- Example 3: Adding Tags to Devices ---" -ForegroundColor Cyan
$tagsToAdd = @("BulkManaged", "APIControlled")
$tagResults = Update-DeviceTags -Config $apiConfig -Headers $headers -DeviceIds $deviceIds -TagsToAdd $tagsToAdd
# Display results
$tagResults | Format-Table -AutoSize
} else {
Write-Host "No Windows devices found for bulk operations"
}
Write-Host "`n=== Bulk Operations Complete ===" -ForegroundColor Yellow
Example 2: Automated Device Compliance Monitoring
This script monitors device compliance and takes automated remediation actions:
# Automated Device Compliance Monitoring Script
# Monitors device compliance and performs automated remediation
# API Configuration
$apiConfig = @{
Server = "your-server.awmdm.com"
ApiKey = "your-api-key"
TenantCode = "your-tenant-code"
Username = "api-service-account"
Password = "service-account-password"
}
# Function to create authentication headers
function Get-AuthHeaders {
param([hashtable]$Config)
$authString = "$($Config.Username):$($Config.Password)"
$authBytes = [System.Text.Encoding]::UTF8.GetBytes($authString)
$authBase64 = [System.Convert]::ToBase64String($authBytes)
return @{
"Authorization" = "Basic $authBase64"
"aw-tenant-code" = $Config.TenantCode
"Accept" = "application/json"
"Content-Type" = "application/json"
}
}
# Function to get non-compliant devices
function Get-NonCompliantDevices {
param(
[hashtable]$Config,
[hashtable]$Headers
)
try {
Write-Host "Retrieving non-compliant devices..."
$uri = "https://$($Config.Server)/API/v1/devices/compliance/noncompliant"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $Headers -TimeoutSec 60
if ($response.Devices) {
Write-Host "Found $($response.Devices.Count) non-compliant devices"
return $response.Devices
} else {
Write-Host "No non-compliant devices found"
return @()
}
} catch {
Write-Error "Failed to retrieve non-compliant devices: $($_.Exception.Message)"
return @()
}
}
# Function to get device compliance details
function Get-DeviceComplianceDetails {
param(
[hashtable]$Config,
[hashtable]$Headers,
[int]$DeviceId
)
try {
$uri = "https://$($Config.Server)/API/v1/devices/$DeviceId/compliance"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $Headers -TimeoutSec 30
return $response
} catch {
Write-Warning "Failed to get compliance details for device $DeviceId`: $($_.Exception.Message)"
return $null
}
}
# Function to perform automated remediation
function Invoke-ComplianceRemediation {
param(
[hashtable]$Config,
[hashtable]$Headers,
[object]$Device,
[object]$ComplianceDetails
)
$remediationActions = @()
try {
Write-Host "Analyzing compliance issues for device: $($Device.DeviceFriendlyName)"
# Check for common compliance issues and determine remediation actions
if ($ComplianceDetails.ComplianceStatus) {
foreach ($rule in $ComplianceDetails.ComplianceStatus.ComplianceRules) {
if (-not $rule.IsCompliant) {
Write-Host " Non-compliant rule: $($rule.RuleName)"
switch ($rule.RuleName) {
"Device Encryption" {
$remediationActions += @{
Action = "SendCommand"
Command = "EnableDeviceEncryption"
Description = "Enable device encryption"
}
}
"Passcode Policy" {
$remediationActions += @{
Action = "SendCommand"
Command = "ClearPasscode"
Description = "Clear passcode to force policy compliance"
}
}
"OS Version" {
$remediationActions += @{
Action = "SendCommand"
Command = "InstallOSUpdate"
Description = "Install OS updates"
}
}
"Jailbreak/Root Detection" {
$remediationActions += @{
Action = "Quarantine"
Command = "EnterpriseWipe"
Description = "Quarantine compromised device"
}
}
default {
$remediationActions += @{
Action = "Sync"
Command = "SyncDevice"
Description = "Sync device to refresh compliance status"
}
}
}
}
}
}
# Execute remediation actions
foreach ($action in $remediationActions) {
try {
Write-Host " Executing remediation: $($action.Description)"
$uri = "https://$($Config.Server)/API/v1/devices/$($Device.Id.Value)/commands"
$commandBody = @{
Command = $action.Command
}
$jsonBody = $commandBody | ConvertTo-Json -Depth 10
$response = Invoke-RestMethod -Uri $uri -Method POST -Headers $Headers -Body $jsonBody -TimeoutSec 30
Write-Host " ✓ Remediation command sent successfully: $($response.CommandUuid)"
} catch {
Write-Warning " ✗ Failed to send remediation command: $($_.Exception.Message)"
}
}
return $remediationActions.Count
} catch {
Write-Error "Failed to perform compliance remediation for device $($Device.Id.Value): $($_.Exception.Message)"
return 0
}
}
# Function to generate compliance report
function Generate-ComplianceReport {
param(
[array]$NonCompliantDevices,
[hashtable]$RemediationResults
)
try {
$reportPath = Join-Path $env:TEMP "ComplianceReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$reportData = @()
foreach ($device in $NonCompliantDevices) {
$remediationCount = if ($RemediationResults.ContainsKey($device.Id.Value)) {
$RemediationResults[$device.Id.Value]
} else {
0
}
$reportData += [PSCustomObject]@{
DeviceId = $device.Id.Value
DeviceName = $device.DeviceFriendlyName
Platform = $device.Platform
Model = $device.Model
OSVersion = $device.OperatingSystem
LastSeen = $device.LastSeen
ComplianceStatus = $device.ComplianceStatus
RemediationActions = $remediationCount
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
}
$reportData | Export-Csv -Path $reportPath -NoTypeInformation
Write-Host "Compliance report generated: $reportPath"
return $reportPath
} catch {
Write-Error "Failed to generate compliance report: $($_.Exception.Message)"
return $null
}
}
# Main compliance monitoring process
Write-Host "=== Workspace ONE UEM Compliance Monitoring ===" -ForegroundColor Yellow
# Create authentication headers
$headers = Get-AuthHeaders -Config $apiConfig
# Get non-compliant devices
$nonCompliantDevices = Get-NonCompliantDevices -Config $apiConfig -Headers $headers
if ($nonCompliantDevices.Count -gt 0) {
Write-Host "`nProcessing $($nonCompliantDevices.Count) non-compliant devices..." -ForegroundColor Cyan
$remediationResults = @{}
$totalRemediationActions = 0
foreach ($device in $nonCompliantDevices) {
# Get detailed compliance information
$complianceDetails = Get-DeviceComplianceDetails -Config $apiConfig -Headers $headers -DeviceId $device.Id.Value
if ($complianceDetails) {
# Perform automated remediation
$actionCount = Invoke-ComplianceRemediation -Config $apiConfig -Headers $headers -Device $device -ComplianceDetails $complianceDetails
$remediationResults[$device.Id.Value] = $actionCount
$totalRemediationActions += $actionCount
}
# Add delay to avoid rate limiting
Start-Sleep -Milliseconds 500
}
# Generate compliance report
Write-Host "`nGenerating compliance report..." -ForegroundColor Cyan
$reportPath = Generate-ComplianceReport -NonCompliantDevices $nonCompliantDevices -RemediationResults $remediationResults
# Summary
Write-Host "`n=== Compliance Monitoring Summary ===" -ForegroundColor Yellow
Write-Host "Non-compliant devices processed: $($nonCompliantDevices.Count)"
Write-Host "Total remediation actions taken: $totalRemediationActions"
Write-Host "Report generated: $reportPath"
} else {
Write-Host "All devices are compliant - no remediation required" -ForegroundColor Green
}
Write-Host "`n=== Compliance Monitoring Complete ===" -ForegroundColor Yellow
User and Group Management
The API provides powerful capabilities for automating user and group management tasks.
Example 3: Automated User Provisioning
This script demonstrates automated user provisioning and group assignment:
# Automated User Provisioning Script
# Provisions users and manages group assignments through the API
# API Configuration
$apiConfig = @{
Server = "your-server.awmdm.com"
ApiKey = "your-api-key"
TenantCode = "your-tenant-code"
Username = "api-service-account"
Password = "service-account-password"
}
# Function to create authentication headers
function Get-AuthHeaders {
param([hashtable]$Config)
$authString = "$($Config.Username):$($Config.Password)"
$authBytes = [System.Text.Encoding]::UTF8.GetBytes($authString)
$authBase64 = [System.Convert]::ToBase64String($authBytes)
return @{
"Authorization" = "Basic $authBase64"
"aw-tenant-code" = $Config.TenantCode
"Accept" = "application/json"
"Content-Type" = "application/json"
}
}
# Function to create or update user
function Set-WorkspaceONEUser {
param(
[hashtable]$Config,
[hashtable]$Headers,
[hashtable]$UserData
)
try {
# Check if user already exists
$searchUri = "https://$($Config.Server)/API/v1/users/search?username=$($UserData.Username)"
try {
$existingUser = Invoke-RestMethod -Uri $searchUri -Method GET -Headers $Headers -TimeoutSec 30
if ($existingUser.Users -and $existingUser.Users.Count -gt 0) {
# User exists, update
$userId = $existingUser.Users[0].Id.Value
$updateUri = "https://$($Config.Server)/API/v1/users/$userId"
Write-Host "Updating existing user: $($UserData.Username)"
$response = Invoke-RestMethod -Uri $updateUri -Method PUT -Headers $Headers -Body ($UserData | ConvertTo-Json -Depth 10) -TimeoutSec 30
return @{
Action = "Updated"
UserId = $userId
Username = $UserData.Username
Success = $true
}
}
} catch {
# User doesn't exist or search failed, proceed with creation
}
# Create new user
$createUri = "https://$($Config.Server)/API/v1/users"
Write-Host "Creating new user: $($UserData.Username)"
$response = Invoke-RestMethod -Uri $createUri -Method POST -Headers $Headers -Body ($UserData | ConvertTo-Json -Depth 10) -TimeoutSec 30
return @{
Action = "Created"
UserId = $response.Value
Username = $UserData.Username
Success = $true
}
} catch {
Write-Error "Failed to create/update user $($UserData.Username): $($_.Exception.Message)"
return @{
Action = "Failed"
UserId = $null
Username = $UserData.Username
Success = $false
Error = $_.Exception.Message
}
}
}
# Function to assign user to groups
function Add-UserToGroups {
param(
[hashtable]$Config,
[hashtable]$Headers,
[int]$UserId,
[array]$GroupNames
)
$results = @()
foreach ($groupName in $GroupNames) {
try {
# Find group by name
$groupSearchUri = "https://$($Config.Server)/API/v1/usergroups/search?name=$groupName"
$groupResponse = Invoke-RestMethod -Uri $groupSearchUri -Method GET -Headers $Headers -TimeoutSec 30
if ($groupResponse.UserGroups -and $groupResponse.UserGroups.Count -gt 0) {
$groupId = $groupResponse.UserGroups[0].Id.Value
# Add user to group
$addToGroupUri = "https://$($Config.Server)/API/v1/usergroups/$groupId/users"
$addUserBody = @{
Users = @(@{ Id = @{ Value = $UserId } })
}
Invoke-RestMethod -Uri $addToGroupUri -Method POST -Headers $Headers -Body ($addUserBody | ConvertTo-Json -Depth 10) -TimeoutSec 30
Write-Host " ✓ Added user to group: $groupName"
$results += @{
GroupName = $groupName
GroupId = $groupId
Success = $true
}
} else {
Write-Warning " ✗ Group not found: $groupName"
$results += @{
GroupName = $groupName
GroupId = $null
Success = $false
Error = "Group not found"
}
}
} catch {
Write-Warning " ✗ Failed to add user to group $groupName`: $($_.Exception.Message)"
$results += @{
GroupName = $groupName
GroupId = $null
Success = $false
Error = $_.Exception.Message
}
}
}
return $results
}
# Function to process user data from CSV
function Import-UsersFromCSV {
param(
[string]$CSVPath,
[hashtable]$Config,
[hashtable]$Headers
)
try {
if (-not (Test-Path $CSVPath)) {
Write-Error "CSV file not found: $CSVPath"
return
}
$userData = Import-Csv -Path $CSVPath
Write-Host "Processing $($userData.Count) users from CSV file..."
$results = @()
foreach ($user in $userData) {
Write-Host "`nProcessing user: $($user.Username)"
# Prepare user data for API
$userApiData = @{
Username = $user.Username
FirstName = $user.FirstName
LastName = $user.LastName
Email = $user.Email
Password = if ($user.Password) { $user.Password } else { "TempPassword123!" }
SecurityType = if ($user.SecurityType) { $user.SecurityType } else { "Directory" }
Status = if ($user.Status) { $user.Status } else { "Active" }
}
# Add optional fields if present
if ($user.Department) { $userApiData.Department = $user.Department }
if ($user.Title) { $userApiData.Title = $user.Title }
if ($user.PhoneNumber) { $userApiData.PhoneNumber = $user.PhoneNumber }
if ($user.MobileNumber) { $userApiData.MobileNumber = $user.MobileNumber }
# Create or update user
$userResult = Set-WorkspaceONEUser -Config $Config -Headers $Headers -UserData $userApiData
if ($userResult.Success) {
# Assign to groups if specified
if ($user.Groups) {
$groupNames = $user.Groups -split ";"
$groupResults = Add-UserToGroups -Config $Config -Headers $Headers -UserId $userResult.UserId -GroupNames $groupNames
$userResult.GroupAssignments = $groupResults
}
}
$results += $userResult
# Add delay to avoid rate limiting
Start-Sleep -Milliseconds 200
}
return $results
} catch {
Write-Error "Failed to import users from CSV: $($_.Exception.Message)"
return @()
}
}
# Function to generate provisioning report
function Generate-ProvisioningReport {
param(
[array]$Results,
[string]$OutputPath
)
try {
$reportData = @()
foreach ($result in $Results) {
$groupStatus = if ($result.GroupAssignments) {
($result.GroupAssignments | Where-Object { $_.Success }).Count
} else {
0
}
$reportData += [PSCustomObject]@{
Username = $result.Username
Action = $result.Action
Success = $result.Success
UserId = $result.UserId
GroupsAssigned = $groupStatus
Error = if ($result.Error) { $result.Error } else { "" }
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
}
$reportData | Export-Csv -Path $OutputPath -NoTypeInformation
Write-Host "Provisioning report generated: $OutputPath"
return $OutputPath
} catch {
Write-Error "Failed to generate provisioning report: $($_.Exception.Message)"
return $null
}
}
# Main user provisioning process
Write-Host "=== Workspace ONE UEM User Provisioning ===" -ForegroundColor Yellow
# Create authentication headers
$headers = Get-AuthHeaders -Config $apiConfig
# Example CSV file path (create this file with user data)
$csvPath = Join-Path $env:TEMP "users_to_provision.csv"
# Create sample CSV file if it doesn't exist
if (-not (Test-Path $csvPath)) {
Write-Host "Creating sample CSV file: $csvPath"
$sampleData = @"
Username,FirstName,LastName,Email,Department,Title,Groups
john.doe,John,Doe,john.doe@company.com,IT,System Administrator,IT Admins;All Users
jane.smith,Jane,Smith,jane.smith@company.com,HR,HR Manager,HR Team;All Users
bob.johnson,Bob,Johnson,bob.johnson@company.com,Sales,Sales Rep,Sales Team;All Users
"@
$sampleData | Out-File -FilePath $csvPath -Encoding UTF8
Write-Host "Sample CSV file created. Please update with actual user data and run the script again."
return
}
# Process users from CSV
Write-Host "`nImporting users from CSV file..." -ForegroundColor Cyan
$provisioningResults = Import-UsersFromCSV -CSVPath $csvPath -Config $apiConfig -Headers $headers
# Generate report
if ($provisioningResults.Count -gt 0) {
$reportPath = Join-Path $env:TEMP "UserProvisioningReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
Generate-ProvisioningReport -Results $provisioningResults -OutputPath $reportPath
# Summary
$successCount = ($provisioningResults | Where-Object { $_.Success }).Count
$failureCount = ($provisioningResults | Where-Object { -not $_.Success }).Count
Write-Host "`n=== User Provisioning Summary ===" -ForegroundColor Yellow
Write-Host "Total users processed: $($provisioningResults.Count)"
Write-Host "Successful operations: $successCount"
Write-Host "Failed operations: $failureCount"
Write-Host "Report generated: $reportPath"
}
Write-Host "`n=== User Provisioning Complete ===" -ForegroundColor Yellow
Application Management
The API provides comprehensive capabilities for managing applications and their deployment.
Example 4: Automated Application Deployment
This script demonstrates automated application deployment and management:
# Automated Application Deployment Script
# Manages application deployment and assignment through the API
# API Configuration
$apiConfig = @{
Server = "your-server.awmdm.com"
ApiKey = "your-api-key"
TenantCode = "your-tenant-code"
Username = "api-service-account"
Password = "service-account-password"
}
# Function to create authentication headers
function Get-AuthHeaders {
param([hashtable]$Config)
$authString = "$($Config.Username):$($Config.Password)"
$authBytes = [System.Text.Encoding]::UTF8.GetBytes($authString)
$authBase64 = [System.Convert]::ToBase64String($authBytes)
return @{
"Authorization" = "Basic $authBase64"
"aw-tenant-code" = $Config.TenantCode
"Accept" = "application/json"
"Content-Type" = "application/json"
}
}
# Function to get application by name
function Get-ApplicationByName {
param(
[hashtable]$Config,
[hashtable]$Headers,
[string]$ApplicationName
)
try {
$uri = "https://$($Config.Server)/API/v1/apps/search?applicationname=$ApplicationName"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $Headers -TimeoutSec 30
if ($response.Applications -and $response.Applications.Count -gt 0) {
return $response.Applications[0]
} else {
return $null
}
} catch {
Write-Error "Failed to search for application '$ApplicationName': $($_.Exception.Message)"
return $null
}
}
# Function to assign application to smart group
function Set-ApplicationAssignment {
param(
[hashtable]$Config,
[hashtable]$Headers,
[int]$ApplicationId,
[string]$SmartGroupName,
[string]$AssignmentType = "Auto"
)
try {
# Find smart group by name
$groupSearchUri = "https://$($Config.Server)/API/v1/smartgroups/search?name=$SmartGroupName"
$groupResponse = Invoke-RestMethod -Uri $groupSearchUri -Method GET -Headers $Headers -TimeoutSec 30
if (-not ($groupResponse.SmartGroups -and $groupResponse.SmartGroups.Count -gt 0)) {
Write-Error "Smart group not found: $SmartGroupName"
return $false
}
$smartGroupId = $groupResponse.SmartGroups[0].SmartGroupId
# Create assignment
$assignmentUri = "https://$($Config.Server)/API/v1/apps/$ApplicationId/assignments"
$assignmentBody = @{
SmartGroupIds = @($smartGroupId)
DeploymentParameters = @{
AssignmentType = $AssignmentType
PushMode = "Auto"
WhenToInstall = @{
DiskSpaceRequiredInKb = 0
DeviceConnectivity = "OnlineAndOffline"
RamRequiredInMb = 0
}
WhenToCallInstallComplete = @{
UseAdditionalCriteria = $false
}
InstallContext = "Device"
EncodedFileSize = 0
}
}
$jsonBody = $assignmentBody | ConvertTo-Json -Depth 10
$response = Invoke-RestMethod -Uri $assignmentUri -Method POST -Headers $Headers -Body $jsonBody -TimeoutSec 30
Write-Host "✓ Application assigned to smart group: $SmartGroupName"
return $true
} catch {
Write-Error "Failed to assign application to smart group '$SmartGroupName': $($_.Exception.Message)"
return $false
}
}
# Function to monitor application installation status
function Get-ApplicationInstallationStatus {
param(
[hashtable]$Config,
[hashtable]$Headers,
[int]$ApplicationId,
[string]$ApplicationName
)
try {
$uri = "https://$($Config.Server)/API/v1/apps/$ApplicationId/devices"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $Headers -TimeoutSec 30
if ($response.DeviceApps) {
$totalDevices = $response.DeviceApps.Count
$installedCount = ($response.DeviceApps | Where-Object { $_.Status -eq "Installed" }).Count
$failedCount = ($response.DeviceApps | Where-Object { $_.Status -eq "Failed" }).Count
$pendingCount = ($response.DeviceApps | Where-Object { $_.Status -eq "Pending" }).Count
return @{
ApplicationName = $ApplicationName
TotalDevices = $totalDevices
Installed = $installedCount
Failed = $failedCount
Pending = $pendingCount
InstallationRate = if ($totalDevices -gt 0) { [math]::Round(($installedCount / $totalDevices) * 100, 2) } else { 0 }
}
} else {
return @{
ApplicationName = $ApplicationName
TotalDevices = 0
Installed = 0
Failed = 0
Pending = 0
InstallationRate = 0
}
}
} catch {
Write-Error "Failed to get installation status for application '$ApplicationName': $($_.Exception.Message)"
return $null
}
}
# Function to retry failed installations
function Retry-FailedInstallations {
param(
[hashtable]$Config,
[hashtable]$Headers,
[int]$ApplicationId,
[string]$ApplicationName
)
try {
Write-Host "Retrying failed installations for: $ApplicationName"
# Get devices with failed installations
$uri = "https://$($Config.Server)/API/v1/apps/$ApplicationId/devices"
$response = Invoke-RestMethod -Uri $uri -Method GET -Headers $Headers -TimeoutSec 30
$failedDevices = $response.DeviceApps | Where-Object { $_.Status -eq "Failed" }
if ($failedDevices.Count -eq 0) {
Write-Host "No failed installations found for: $ApplicationName"
return 0
}
Write-Host "Found $($failedDevices.Count) devices with failed installations"
$retryCount = 0
foreach ($deviceApp in $failedDevices) {
try {
# Send install command to device
$commandUri = "https://$($Config.Server)/API/v1/devices/$($deviceApp.DeviceId)/commands"
$commandBody = @{
Command = "InstallApplication"
ApplicationId = $ApplicationId
}
$jsonBody = $commandBody | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri $commandUri -Method POST -Headers $Headers -Body $jsonBody -TimeoutSec 30
$retryCount++
} catch {
Write-Warning "Failed to retry installation on device $($deviceApp.DeviceId): $($_.Exception.Message)"
}
# Add delay to avoid rate limiting
Start-Sleep -Milliseconds 100
}
Write-Host "Retry commands sent to $retryCount devices"
return $retryCount
} catch {
Write-Error "Failed to retry failed installations for '$ApplicationName': $($_.Exception.Message)"
return 0
}
}
# Function to generate application deployment report
function Generate-DeploymentReport {
param(
[array]$DeploymentResults,
[string]$OutputPath
)
try {
$reportData = @()
foreach ($result in $DeploymentResults) {
$reportData += [PSCustomObject]@{
ApplicationName = $result.ApplicationName
ApplicationId = $result.ApplicationId
SmartGroup = $result.SmartGroup
AssignmentSuccess = $result.AssignmentSuccess
TotalDevices = $result.InstallationStatus.TotalDevices
InstalledDevices = $result.InstallationStatus.Installed
FailedDevices = $result.InstallationStatus.Failed
PendingDevices = $result.InstallationStatus.Pending
InstallationRate = $result.InstallationStatus.InstallationRate
RetriedDevices = $result.RetriedDevices
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
}
$reportData | Export-Csv -Path $OutputPath -NoTypeInformation
Write-Host "Deployment report generated: $OutputPath"
return $OutputPath
} catch {
Write-Error "Failed to generate deployment report: $($_.Exception.Message)"
return $null
}
}
# Main application deployment process
Write-Host "=== Workspace ONE UEM Application Deployment ===" -ForegroundColor Yellow
# Create authentication headers
$headers = Get-AuthHeaders -Config $apiConfig
# Define applications to deploy
$applicationsToDeploy = @(
@{
Name = "Microsoft Office 365"
SmartGroup = "All Windows Devices"
AssignmentType = "Auto"
},
@{
Name = "Adobe Acrobat Reader DC"
SmartGroup = "All Windows Devices"
AssignmentType = "Auto"
},
@{
Name = "Google Chrome"
SmartGroup = "All Devices"
AssignmentType = "Auto"
}
)
$deploymentResults = @()
foreach ($appConfig in $applicationsToDeploy) {
Write-Host "`n--- Processing Application: $($appConfig.Name) ---" -ForegroundColor Cyan
# Find application
$application = Get-ApplicationByName -Config $apiConfig -Headers $headers -ApplicationName $appConfig.Name
if ($application) {
Write-Host "Found application: $($application.ApplicationName) (ID: $($application.Id.Value))"
# Assign to smart group
$assignmentSuccess = Set-ApplicationAssignment -Config $apiConfig -Headers $headers -ApplicationId $application.Id.Value -SmartGroupName $appConfig.SmartGroup -AssignmentType $appConfig.AssignmentType
# Wait for assignment to propagate
Start-Sleep -Seconds 10
# Get installation status
$installationStatus = Get-ApplicationInstallationStatus -Config $apiConfig -Headers $headers -ApplicationId $application.Id.Value -ApplicationName $application.ApplicationName
# Retry failed installations if any
$retriedDevices = 0
if ($installationStatus -and $installationStatus.Failed -gt 0) {
$retriedDevices = Retry-FailedInstallations -Config $apiConfig -Headers $headers -ApplicationId $application.Id.Value -ApplicationName $application.ApplicationName
}
$deploymentResults += @{
ApplicationName = $application.ApplicationName
ApplicationId = $application.Id.Value
SmartGroup = $appConfig.SmartGroup
AssignmentSuccess = $assignmentSuccess
InstallationStatus = $installationStatus
RetriedDevices = $retriedDevices
}
} else {
Write-Warning "Application not found: $($appConfig.Name)"
$deploymentResults += @{
ApplicationName = $appConfig.Name
ApplicationId = $null
SmartGroup = $appConfig.SmartGroup
AssignmentSuccess = $false
InstallationStatus = $null
RetriedDevices = 0
}
}
}
# Generate deployment report
if ($deploymentResults.Count -gt 0) {
Write-Host "`n--- Generating Deployment Report ---" -ForegroundColor Cyan
$reportPath = Join-Path $env:TEMP "ApplicationDeploymentReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
Generate-DeploymentReport -DeploymentResults $deploymentResults -OutputPath $reportPath
# Summary
$successfulAssignments = ($deploymentResults | Where-Object { $_.AssignmentSuccess }).Count
$totalApplications = $deploymentResults.Count
Write-Host "`n=== Application Deployment Summary ===" -ForegroundColor Yellow
Write-Host "Total applications processed: $totalApplications"
Write-Host "Successful assignments: $successfulAssignments"
Write-Host "Report generated: $reportPath"
# Display installation status summary
foreach ($result in $deploymentResults | Where-Object { $_.InstallationStatus }) {
Write-Host "`n$($result.ApplicationName):"
Write-Host " Total devices: $($result.InstallationStatus.TotalDevices)"
Write-Host " Installed: $($result.InstallationStatus.Installed) ($($result.InstallationStatus.InstallationRate)%)"
Write-Host " Failed: $($result.InstallationStatus.Failed)"
Write-Host " Pending: $($result.InstallationStatus.Pending)"
if ($result.RetriedDevices -gt 0) {
Write-Host " Retried: $($result.RetriedDevices)"
}
}
}
Write-Host "`n=== Application Deployment Complete ===" -ForegroundColor Yellow
Advanced API Integration Patterns
Error Handling and Resilience
Robust error handling is essential for production API integrations.
Comprehensive Error Handling Pattern:
# Advanced Error Handling and Resilience Pattern for Workspace ONE API
# Function for resilient API calls with retry logic
function Invoke-ResilientAPICall {
param(
[string]$Uri,
[string]$Method = "GET",
[hashtable]$Headers,
[object]$Body = $null,
[int]$MaxRetries = 3,
[int]$BaseDelaySeconds = 2,
[array]$RetryableStatusCodes = @(429, 500, 502, 503, 504),
[int]$TimeoutSeconds = 30
)
for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) {
try {
$params = @{
Uri = $Uri
Method = $Method
Headers = $Headers
TimeoutSec = $TimeoutSeconds
UseBasicParsing = $true
}
if ($Body) {
if ($Body -is [string]) {
$params.Body = $Body
} else {
$params.Body = $Body | ConvertTo-Json -Depth 10
}
$params.ContentType = "application/json"
}
$response = Invoke-RestMethod @params
# Success - return response
return @{
Success = $true
Data = $response
Attempt = $attempt
Error = $null
}
} catch {
$statusCode = $null
$errorMessage = $_.Exception.Message
# Extract status code if available
if ($_.Exception.Response) {
$statusCode = [int]$_.Exception.Response.StatusCode
}
Write-Warning "API call attempt $attempt failed: $errorMessage (Status: $statusCode)"
# Check if we should retry
$shouldRetry = $false
if ($attempt -lt $MaxRetries) {
if ($statusCode -and $statusCode -in $RetryableStatusCodes) {
$shouldRetry = $true
} elseif ($_.Exception -is [System.Net.WebException] -and $_.Exception.Status -eq "Timeout") {
$shouldRetry = $true
} elseif ($_.Exception -is [System.Net.WebException] -and $_.Exception.Status -eq "ConnectFailure") {
$shouldRetry = $true
}
}
if ($shouldRetry) {
# Calculate delay with exponential backoff
$delay = $BaseDelaySeconds * [Math]::Pow(2, $attempt - 1)
Write-Host "Retrying in $delay seconds..."
Start-Sleep -Seconds $delay
} else {
# No more retries or non-retryable error
return @{
Success = $false
Data = $null
Attempt = $attempt
Error = @{
Message = $errorMessage
StatusCode = $statusCode
Exception = $_.Exception
}
}
}
}
}
# All retries exhausted
return @{
Success = $false
Data = $null
Attempt = $MaxRetries
Error = @{
Message = "All retry attempts exhausted"
StatusCode = $statusCode
Exception = $_.Exception
}
}
}
# Function for batch processing with error handling
function Invoke-BatchAPIOperation {
param(
[array]$Items,
[scriptblock]$Operation,
[int]$BatchSize = 10,
[int]$DelayBetweenBatches = 1000,
[switch]$ContinueOnError
)
$results = @()
$totalItems = $Items.Count
$processedItems = 0
Write-Host "Processing $totalItems items in batches of $BatchSize..."
for ($i = 0; $i -lt $totalItems; $i += $BatchSize) {
$batchEnd = [Math]::Min($i + $BatchSize - 1, $totalItems - 1)
$batch = $Items[$i..$batchEnd]
Write-Host "Processing batch $([Math]::Floor($i / $BatchSize) + 1): items $($i + 1) to $($batchEnd + 1)"
foreach ($item in $batch) {
try {
$result = & $Operation $item
$results += @{
Item = $item
Success = $true
Result = $result
Error = $null
}
$processedItems++
} catch {
$errorResult = @{
Item = $item
Success = $false
Result = $null
Error = $_.Exception.Message
}
$results += $errorResult
if (-not $ContinueOnError) {
Write-Error "Batch processing failed on item: $item"
throw $_
}
Write-Warning "Error processing item $item`: $($_.Exception.Message)"
}
}
# Progress update
$percentComplete = [Math]::Round(($processedItems / $totalItems) * 100, 2)
Write-Host "Progress: $processedItems/$totalItems ($percentComplete%)"
# Delay between batches to avoid rate limiting
if ($i + $BatchSize -lt $totalItems) {
Start-Sleep -Milliseconds $DelayBetweenBatches
}
}
return $results
}
# Function for monitoring long-running operations
function Wait-ForOperationCompletion {
param(
[hashtable]$Config,
[hashtable]$Headers,
[string]$OperationId,
[string]$OperationType,
[int]$MaxWaitMinutes = 30,
[int]$CheckIntervalSeconds = 30
)
$startTime = Get-Date
$maxWaitTime = $startTime.AddMinutes($MaxWaitMinutes)
Write-Host "Monitoring $OperationType operation: $OperationId"
Write-Host "Maximum wait time: $MaxWaitMinutes minutes"
do {
try {
# Check operation status (this would be specific to the operation type)
$statusUri = "https://$($Config.Server)/API/v1/operations/$OperationId/status"
$statusResponse = Invoke-ResilientAPICall -Uri $statusUri -Method GET -Headers $Headers
if ($statusResponse.Success) {
$status = $statusResponse.Data.Status
$progress = $statusResponse.Data.Progress
Write-Host "Operation status: $status ($progress% complete)"
if ($status -eq "Completed") {
Write-Host "✓ Operation completed successfully"
return @{
Success = $true
Status = $status
Result = $statusResponse.Data
}
} elseif ($status -eq "Failed") {
Write-Error "✗ Operation failed"
return @{
Success = $false
Status = $status
Error = $statusResponse.Data.ErrorMessage
}
}
} else {
Write-Warning "Failed to check operation status: $($statusResponse.Error.Message)"
}
} catch {
Write-Warning "Error checking operation status: $($_.Exception.Message)"
}
# Wait before next check
Start-Sleep -Seconds $CheckIntervalSeconds
} while ((Get-Date) -lt $maxWaitTime)
# Timeout reached
Write-Warning "Operation monitoring timed out after $MaxWaitMinutes minutes"
return @{
Success = $false
Status = "Timeout"
Error = "Operation monitoring timed out"
}
}
Performance Optimization
Optimizing API performance is crucial for large-scale operations.
Performance Optimization Techniques:
# Performance Optimization Techniques for Workspace ONE API
# Function for parallel API processing
function Invoke-ParallelAPIOperations {
param(
[array]$Operations,
[int]$MaxConcurrency = 5,
[int]$TimeoutMinutes = 10
)
$jobs = @()
$results = @()
Write-Host "Starting $($Operations.Count) operations with max concurrency: $MaxConcurrency"
# Process operations in parallel batches
for ($i = 0; $i -lt $Operations.Count; $i += $MaxConcurrency) {
$batchEnd = [Math]::Min($i + $MaxConcurrency - 1, $Operations.Count - 1)
$batch = $Operations[$i..$batchEnd]
Write-Host "Processing parallel batch: operations $($i + 1) to $($batchEnd + 1)"
# Start jobs for this batch
foreach ($operation in $batch) {
$job = Start-Job -ScriptBlock $operation.ScriptBlock -ArgumentList $operation.Arguments
$jobs += @{
Job = $job
Operation = $operation
}
}
# Wait for batch to complete
$batchJobs = $jobs | Where-Object { $_.Job.State -eq "Running" }
$timeout = (Get-Date).AddMinutes($TimeoutMinutes)
do {
Start-Sleep -Seconds 1
$runningJobs = $batchJobs | Where-Object { $_.Job.State -eq "Running" }
} while ($runningJobs.Count -gt 0 -and (Get-Date) -lt $timeout)
# Collect results from completed jobs
foreach ($jobInfo in $jobs) {
try {
if ($jobInfo.Job.State -eq "Completed") {
$result = Receive-Job -Job $jobInfo.Job
$results += @{
Operation = $jobInfo.Operation.Name
Success = $true
Result = $result
Error = $null
}
} else {
$results += @{
Operation = $jobInfo.Operation.Name
Success = $false
Result = $null
Error = "Job failed or timed out (State: $($jobInfo.Job.State))"
}
}
} catch {
$results += @{
Operation = $jobInfo.Operation.Name
Success = $false
Result = $null
Error = $_.Exception.Message
}
} finally {
Remove-Job -Job $jobInfo.Job -Force
}
}
# Clear jobs for next batch
$jobs = @()
}
return $results
}
# Function for efficient data pagination
function Get-AllPaginatedData {
param(
[hashtable]$Config,
[hashtable]$Headers,
[string]$BaseUri,
[int]$PageSize = 500,
[string]$PageParameter = "page",
[string]$PageSizeParameter = "pagesize"
)
$allData = @()
$page = 0
$hasMoreData = $true
Write-Host "Retrieving paginated data from: $BaseUri"
while ($hasMoreData) {
try {
# Construct URI with pagination parameters
$separator = if ($BaseUri.Contains("?")) { "&" } else { "?" }
$uri = "$BaseUri$separator$PageSizeParameter=$PageSize&$PageParameter=$page"
$response = Invoke-ResilientAPICall -Uri $uri -Method GET -Headers $Headers
if ($response.Success) {
$data = $response.Data
# Extract data based on common response patterns
$pageData = $null
if ($data.Devices) { $pageData = $data.Devices }
elseif ($data.Users) { $pageData = $data.Users }
elseif ($data.Applications) { $pageData = $data.Applications }
elseif ($data.Items) { $pageData = $data.Items }
elseif ($data -is [array]) { $pageData = $data }
else { $pageData = @($data) }
if ($pageData -and $pageData.Count -gt 0) {
$allData += $pageData
Write-Host "Retrieved page $page`: $($pageData.Count) items (Total: $($allData.Count))"
# Check if we have more data
$hasMoreData = $pageData.Count -eq $PageSize
$page++
} else {
$hasMoreData = $false
}
} else {
Write-Error "Failed to retrieve page $page`: $($response.Error.Message)"
$hasMoreData = $false
}
} catch {
Write-Error "Error retrieving page $page`: $($_.Exception.Message)"
$hasMoreData = $false
}
}
Write-Host "Pagination complete. Total items retrieved: $($allData.Count)"
return $allData
}
# Function for efficient bulk operations with rate limiting
function Invoke-BulkOperationWithRateLimit {
param(
[array]$Items,
[scriptblock]$Operation,
[int]$RequestsPerSecond = 10,
[int]$BurstSize = 50,
[switch]$ShowProgress
)
$results = @()
$totalItems = $Items.Count
$processedItems = 0
$startTime = Get-Date
# Calculate timing for rate limiting
$intervalMs = 1000 / $RequestsPerSecond
$burstInterval = $BurstSize * $intervalMs
Write-Host "Processing $totalItems items with rate limit: $RequestsPerSecond requests/second"
for ($i = 0; $i -lt $totalItems; $i++) {
$item = $Items[$i]
try {
$operationStart = Get-Date
$result = & $Operation $item
$operationEnd = Get-Date
$results += @{
Item = $item
Success = $true
Result = $result
Duration = ($operationEnd - $operationStart).TotalMilliseconds
Error = $null
}
$processedItems++
} catch {
$results += @{
Item = $item
Success = $false
Result = $null
Duration = 0
Error = $_.Exception.Message
}
}
# Rate limiting
if (($i + 1) % $BurstSize -eq 0) {
# Burst complete, wait for burst interval
Start-Sleep -Milliseconds $burstInterval
} else {
# Regular interval
Start-Sleep -Milliseconds $intervalMs
}
# Progress reporting
if ($ShowProgress -and ($processedItems % 100 -eq 0 -or $processedItems -eq $totalItems)) {
$elapsed = (Get-Date) - $startTime
$rate = $processedItems / $elapsed.TotalSeconds
$eta = if ($rate -gt 0) { [TimeSpan]::FromSeconds(($totalItems - $processedItems) / $rate) } else { [TimeSpan]::Zero }
Write-Host "Progress: $processedItems/$totalItems ($([Math]::Round(($processedItems / $totalItems) * 100, 1))%) - Rate: $([Math]::Round($rate, 1))/sec - ETA: $($eta.ToString('hh:mm:ss'))"
}
}
$totalTime = (Get-Date) - $startTime
$averageRate = $processedItems / $totalTime.TotalSeconds
Write-Host "Bulk operation completed in $($totalTime.ToString('hh:mm:ss')) - Average rate: $([Math]::Round($averageRate, 1))/sec"
return $results
}
Best Practices for API Integration
Security and Authentication
Security is paramount when working with the Workspace ONE UEM API.
Security Best Practices:
- Credential Management:
- Store API credentials securely using Windows Credential Manager or Azure Key Vault
- Use service accounts with minimal required permissions
- Implement credential rotation policies
- Never hardcode credentials in scripts or configuration files
- API Key Security:
- Generate unique API keys for each integration or service
- Set appropriate expiration dates for API keys
- Monitor API key usage and detect anomalies
- Revoke unused or compromised API keys immediately
- Network Security:
- Use HTTPS for all API communications
- Implement certificate pinning where possible
- Restrict API access to specific IP addresses or networks
- Use VPN or private network connections for sensitive operations
Performance and Scalability
Designing for performance ensures your API integrations can scale with your organization.
Performance Best Practices:
- Efficient API Usage:
- Use appropriate page sizes for data retrieval (typically 500-1000 items)
- Implement caching for frequently accessed data
- Use bulk operations where available
- Minimize unnecessary API calls through intelligent caching
- Rate Limiting Compliance:
- Respect API rate limits and implement backoff strategies
- Monitor API usage and adjust request patterns accordingly
- Implement queue-based processing for high-volume operations
- Use parallel processing judiciously to avoid overwhelming the API
Monitoring and Maintenance
Ongoing monitoring ensures your API integrations remain reliable and performant.
Monitoring Best Practices:
- Logging and Auditing:
- Implement comprehensive logging for all API operations
- Log both successful operations and errors with sufficient detail
- Use structured logging formats for easier analysis
- Implement log retention policies and archival strategies
- Health Monitoring:
- Implement health checks for API connectivity and authentication
- Monitor API response times and error rates
- Set up alerts for API failures or performance degradation
- Create dashboards for visualizing API usage and performance metrics
Conclusion: Mastering the Workspace ONE UEM API
The Workspace ONE UEM API represents the ultimate tool for achieving enterprise-scale device management automation. After implementing API-driven solutions across hundreds of organizations, I can confidently say that mastering this capability is what transforms good administrators into automation architects who can solve complex business challenges that are impossible through manual console operations.
Key Success Factors
Organizations that excel with the Workspace ONE UEM API share several characteristics:
- Strategic Vision: They view the API as a strategic enabler, not just a technical tool
- Security-First Approach: They implement robust security practices from the beginning
- Iterative Development: They start with simple use cases and gradually build complexity
- Comprehensive Testing: They thoroughly test API integrations before production deployment
Transformative Impact
The impact of mastering the Workspace ONE UEM API extends far beyond simple automation:
- Operational Excellence: Achieve unprecedented levels of automation and consistency
- Business Integration: Integrate device management into broader business processes
- Innovation Enablement: Enable new use cases and business capabilities
- Competitive Advantage: Deliver capabilities that differentiate your organization
Looking Forward
As enterprise mobility continues to evolve, the API will remain the critical bridge between Workspace ONE and the broader enterprise ecosystem. The key is to approach API integration strategically—building a foundation of secure, reliable, and maintainable integrations that can evolve with your organization’s needs.
Remember that the Workspace ONE UEM API is not just about automating existing processes—it’s about reimagining what’s possible in device management. The combination of comprehensive API coverage, robust authentication, and powerful automation capabilities creates opportunities for innovation that were previously impossible.
The investment in mastering API integration pays dividends through improved operational efficiency, enhanced security posture, and the ability to address unique organizational requirements that would otherwise be impossible to meet. As you continue to develop your expertise in this area, you’ll find that the API becomes an indispensable tool for delivering world-class device management capabilities that truly serve your business objectives.