Example: Membership Runbook
A Step-by-Step Guide for Azure Automation with Hybrid Workers and PowerShell 7.2
This tutorial provides guidance for implementing an Azure Automation runbook that synchronizes membership changes from ServiceChanger to on-premises Active Directory and Entra ID using Azure Arc-enabled Hybrid Worker(s).
System Requirements
Infrastructure Prerequisites
Arc-enabled Hybrid Worker(s)
PowerShell 7: Runtime environment configured in Azure Automation
Active Directory Access: VMs must have network access to domain controllers
Azure AD Connect: PowerShell remoting enabled for delta sync operations
Service Account Permissions
Hybrid Worker Service Account: Group membership management permissions in AD
Azure AD Connect Service Account: Sync operation permissions and remote execution rights
SMTP Service Account: Email server authentication (if alerts enabled)
Step 1: Configure Azure Automation Variables
Navigate to your Azure Automation Account and create the following variables under Shared Resources > Variables:
Core Configuration Variables
Configuration Details:
Name:
SC_ApiKeyType: String
Encrypted: Yes
Value: [Your ServiceChanger API Key]
Description: ServiceChanger API authentication key
Configuration Details:
Name:
SC_LastSyncDateType: String
Encrypted: No
Value:
2024-01-01T00:00:00Z(or empty string for full synchronization)Description: ISO 8601 UTC timestamp of last successful synchronization
Azure AD Connect Variables
Configuration Details:
Name: SC_AADConnectServer
Type: String
Encrypted: No
Value: ADCONNECT-CLUSTER.domain.local (server name or cluster VIP)
Description: Azure AD Connect server where delta sync will be executed
Configuration Details:
Name: SC_AADConnectUsername
Type: String
Encrypted: Yes
Value: DOMAIN\svc-adconnect
Description: Service account username for Azure AD Connect operations
Configuration Details:
Name: SC_AADConnectPassword
Type: String
Encrypted: Yes
Value: [Service account password]
Description: Password for Azure AD Connect service account
Email Alert Variables (Optional)
Note: These variables are only required if email notifications for critical failures are desired
Name:
SC_SmtpUsernameType: String
Encrypted: Yes
Value: [SMTP service account email address]
Name:
SC_SmtpPasswordType: String
Encrypted: Yes
Value: [SMTP service account password]
Name:
SC_SmtpToType: String
Encrypted: No
Value:
admin1@company.com,admin2@company.comDescription: Comma-separated email addresses for alert recipients
Step 2: Create and Configure the Runbook
Navigation and Creation
Select "+ Create Runbook"
Runbook Configuration
Name
ServiceChanger-AD-MembershipSync
Runbook Type
PowerShell
Runtime Version
7.2
Description
Enterprise AD membership synchronization with ServiceChanger API using Arc Workers
Code Deployment
Open the newly created runbook
Insert the runbook code below
Save your changes
Publish the runbook
Note: If you encounter any issues during setup, verify that your Hybrid Worker is correctly configured and that PowerShell 7.2 is installed on your server.
Use the script below at your own risk. We strongly recommend thoroughly reading and testing it before execution.
<#
.SYNOPSIS
    Enterprise Azure Automation Runbook: ServiceChanger AD Membership Synchronization
.DESCRIPTION
    This runbook synchronizes membership changes from ServiceChanger API to on-premises Active Directory.
    It polls the ServiceChanger API for membership changes where onPremisesSyncEnabled is true,
    then applies those changes to local AD groups using a Hybrid Runbook Worker.
.FEATURES
    • Delta synchronization using persistent LastSyncDate tracking
    • Batch processing for optimal performance
    • AD object caching to minimize directory queries
    • Automatic Azure AD Connect delta sync to Entra ID via PowerShell remoting
    • Comprehensive error handling with retry logic
    • Optional email alerting on failures
    • Idempotent operations to prevent duplicate changes
    • Detailed logging with UTC timestamps
.PREREQUISITES
    • Azure Automation Account with Hybrid Runbook Worker (ARC-enabled recommended)
    • PowerShell 7 runtime
    • Active Directory PowerShell module on Hybrid Worker
    • Azure AD Connect server with PowerShell remoting enabled
    • Valid ServiceChanger API key
    • SMTP service account (if email alerts enabled)
    • Hybrid Worker service account with AD group management permissions
    • Dedicated service account for Azure AD Connect operations
.ARC HYBRID WORKERS
    This runbook is fully compatible with Azure Arc-enabled Hybrid Workers, which provide:
    • Enhanced security through Azure managed identity integration
    • Improved connectivity and reliability via Azure Arc agent
    • Centralized management through Azure portal
    • Better monitoring and diagnostics capabilities
    
    For ARC-enabled deployments:
    • Ensure ActiveDirectory PowerShell module is installed on all ARC machines
    • Verify outbound HTTPS connectivity to ServiceChanger API (api.servicechanger.com)
    • Confirm PowerShell remoting access to Azure AD Connect server from all VMs
    • Consider using managed identity for future authentication enhancements
    • Multiple ARC-enabled VMs provide automatic redundancy and load balancing
    
    Network Requirements for ARC Workers:
    • HTTPS 443 outbound to Azure (handled by ARC agent)
    • Domain connectivity for Active Directory operations
    • Network access to Azure AD Connect server for PowerShell remoting
    • SMTP connectivity if email alerts are enabled
.AZURE AUTOMATION VARIABLES
    Required (Encrypted):
    • SC_ApiKey: ServiceChanger API authentication key
    • SC_AADConnectUsername: Azure AD Connect service account username
    • SC_AADConnectPassword: Azure AD Connect service account password
    
    Required (Not Encrypted):
    • SC_LastSyncDate: ISO 8601 UTC timestamp of last successful sync
    • SC_AADConnectServer: Server name where Azure AD Connect is installed
    
    Optional (Encrypted - for email alerts):
    • SC_SmtpUsername: SMTP service account username
    • SC_SmtpPassword: SMTP service account password
    • SC_SmtpTo: Comma-separated email addresses for alerts
.NOTES
    Version: 1.2
    Author: ServiceChanger BV - Ruben van der Graaf
    Last Modified: 2025-07-31
    
    This runbook automatically triggers Azure AD Connect delta sync after making AD changes
    using PowerShell remoting to execute commands on the designated Azure AD Connect server.
    The Hybrid Worker must have network connectivity to the AD Connect server and appropriate
    service account permissions for remote execution.
    
.PARAMETER EnableEmailAlerts
    Enable email notifications on errors. Default: $false
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory = $false)]
    [bool]$EnableEmailAlerts = $false
)
#region Configuration
# ServiceChanger API Configuration
$ApiBaseUrl = "https://api.servicechanger.com/public/directory-changes"
# SMTP Configuration (customize for your environment)
$SmtpServer = "smtp.office365.com"
$SmtpPort = 587
$SmtpFrom = "serviceaccount@yourdomain.com"
$EnableSsl = $true
# Processing Configuration
$RetryCount = 3
$BatchSize = 100
$LogFilePath = "$env:TEMP\MembershipSyncLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
#endregion
#region Azure Automation Variables
try {
    # Core ServiceChanger variables
    $ApiKey = Get-AutomationVariable -Name 'SC_ApiKey'
    $lastSyncDate = Get-AutomationVariable -Name 'SC_LastSyncDate'
    
    # Azure AD Connect variables
    $AADConnectServer = Get-AutomationVariable -Name 'SC_AADConnectServer'
    $AADConnectUsername = Get-AutomationVariable -Name 'SC_AADConnectUsername'
    $AADConnectPassword = Get-AutomationVariable -Name 'SC_AADConnectPassword'
    
    # SMTP variables (only load if email alerts enabled)
    if ($EnableEmailAlerts) {
        $SmtpUsername = Get-AutomationVariable -Name 'SC_SmtpUsername'
        $SmtpPassword = Get-AutomationVariable -Name 'SC_SmtpPassword'
        $SmtpToVar = Get-AutomationVariable -Name 'SC_SmtpTo'
        $SmtpTo = if ($SmtpToVar) { $SmtpToVar.Split(',') | ForEach-Object { $_.Trim() } } else { @() }
    } else {
        # Initialize SMTP variables as null for safety
        $SmtpUsername = $null
        $SmtpPassword = $null
        $SmtpTo = @()
    }
    
} catch {
    throw "Failed to load Azure Automation Variables. Ensure all required variables are configured: $_"
}
#endregion
#region Helper Functions
function Write-Log {
    <#
    .SYNOPSIS
        Writes timestamped log messages with color coding for Azure Automation console
    #>
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message,
        
        [Parameter(Mandatory = $false)]
        [ValidateSet("INFO", "WARNING", "ERROR", "SUCCESS")]
        [string]$Level = "INFO"
    )
    
    $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
    $logMessage = "[$timestamp] [$Level] $Message"
    
    # Azure Automation console output with ANSI color codes
    switch ($Level) {
        "INFO"    { Write-Output "`e[32m$logMessage`e[0m" }
        "WARNING" { Write-Warning "`e[33m$logMessage`e[0m" }
        "ERROR"   { Write-Error "`e[31m$logMessage`e[0m" }
        "SUCCESS" { Write-Output "`e[36m$logMessage`e[0m" }
    }
    
    # Append to log file for email attachments
    Add-Content -Path $LogFilePath -Value $logMessage -ErrorAction SilentlyContinue
}
function Send-ErrorAlert {
    <#
    .SYNOPSIS
        Sends email alert with log attachment when runbook fails after all retries
    #>
    param (
        [Parameter(Mandatory = $true)]
        [string]$ErrorMessage
    )
    
    if (-not $EnableEmailAlerts) { return }
    
    # Validate SMTP variables are available
    if (-not $SmtpUsername -or -not $SmtpPassword -or $SmtpTo.Count -eq 0) {
        Write-Log "Email alerts enabled but SMTP variables not properly configured" "WARNING"
        return
    }
    
    Write-Log "Sending email alert to $($SmtpTo -join ', ')..." "INFO"
    
    try {
        $securePassword = ConvertTo-SecureString $SmtpPassword -AsPlainText -Force
        $credential = New-Object System.Management.Automation.PSCredential ($SmtpUsername, $securePassword)
        
        $subject = "ServiceChanger AD Sync - Error Alert"
        $body = @"
ServiceChanger AD Synchronization Runbook failed after $RetryCount retries.
Error Details:
$ErrorMessage
Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC')
Please review the attached log file for detailed information and take appropriate action.
This is an automated alert from Azure Automation.
"@
        
        Send-MailMessage -SmtpServer $SmtpServer -Port $SmtpPort -UseSsl:$EnableSsl `
                         -Credential $credential -From $SmtpFrom -To $SmtpTo `
                         -Subject $subject -Body $body -Attachments $LogFilePath
        
        Write-Log "Email alert sent successfully" "SUCCESS"
    } catch {
        Write-Log "Failed to send email alert: $_" "ERROR"
    }
}
function Invoke-AADConnectSync {
    <#
    .SYNOPSIS
        Triggers Azure AD Connect delta sync on remote server using PowerShell remoting
    #>
    param (
        [Parameter(Mandatory = $true)]
        [int]$ChangesProcessed
    )
    
    if ($ChangesProcessed -eq 0) {
        Write-Log "No changes processed - skipping Azure AD Connect sync" "INFO"
        return
    }
    
    Write-Log "Triggering Azure AD Connect delta sync to push $ChangesProcessed changes to Entra ID..." "INFO"
    
    try {
        # Validate required variables
        if (-not $AADConnectServer) {
            throw "SC_AADConnectServer variable not configured"
        }
        if (-not $AADConnectUsername) {
            throw "SC_AADConnectUsername variable not configured"
        }
        if (-not $AADConnectPassword) {
            throw "SC_AADConnectPassword variable not configured"
        }
        
        # Create credential object for remote connection
        $securePassword = ConvertTo-SecureString $AADConnectPassword -AsPlainText -Force
        $credential = New-Object System.Management.Automation.PSCredential ($AADConnectUsername, $securePassword)
        
        Write-Log "Executing sync command on remote server: $AADConnectServer" "INFO"
        
        # Execute Azure AD Connect sync on remote server
        $syncResult = Invoke-Command -ComputerName $AADConnectServer -Credential $credential -ScriptBlock {
            try {
                Import-Module ADSync -ErrorAction Stop
                $result = Start-ADSyncSyncCycle -PolicyType Delta -ErrorAction Stop
                return $result
            } catch {
                throw "Remote sync execution failed: $($_.Exception.Message)"
            }
        } -ErrorAction Stop
        
        if ($syncResult) {
            Write-Log "Azure AD Connect delta sync initiated successfully on $AADConnectServer - Result: $($syncResult.Result)" "SUCCESS"
        } else {
            Write-Log "Azure AD Connect sync command executed but no result returned from $AADConnectServer" "WARNING"
        }
        
    } catch {
        Write-Log "Failed to trigger Azure AD Connect sync: $_" "WARNING"
        Write-Log "Changes were applied to local AD but may not sync to Entra ID immediately" "WARNING"
        Write-Log "Verify network connectivity to $AADConnectServer and service account permissions" "WARNING"
    }
}
#endregion
#region Main Execution
try {
    # Initialize execution
    "" | Out-File $LogFilePath
    Write-Log "Starting ServiceChanger AD membership synchronization" "INFO"
    Write-Log "Runbook version: 1.2 | Batch size: $BatchSize | Retry count: $RetryCount" "INFO"
    Write-Log "Azure AD Connect server: $AADConnectServer" "INFO"
    
    # Load required PowerShell module
    Import-Module ActiveDirectory -ErrorAction Stop
    Write-Log "Active Directory PowerShell module loaded successfully" "SUCCESS"
    
    # Prepare API request parameters
    $utcNow = (Get-Date).ToUniversalTime()
    $queryParams = @{
        resourceType = "membership"
        onPremisesSyncEnabled = "true"
    }
    if ($lastSyncDate) { 
        $queryParams['lastSyncDate'] = $lastSyncDate
        Write-Log "Querying changes since last sync: $lastSyncDate" "INFO"
    } else {
        Write-Log "No previous sync date found - performing full synchronization" "INFO"
    }
    
    # Build API request URL with proper encoding
    $queryString = ($queryParams.GetEnumerator() | ForEach-Object { "$($_.Key)=" + [uri]::EscapeDataString($_.Value) }) -join "&"
    $fullUrl = $ApiBaseUrl + "?" + $queryString
    Write-Log "API endpoint: $fullUrl" "INFO"
    
    # Execute API call with retry logic
    $apiResponse = $null
    for ($attempt = 1; $attempt -le $RetryCount; $attempt++) {
        try {
            Write-Log "API call attempt $attempt of $RetryCount" "INFO"
            
            $headers = @{
                "x-api-key" = $ApiKey
                "Content-Type" = "application/json"
            }
            
            $response = Invoke-RestMethod -Uri $fullUrl -Method Get -Headers $headers -ErrorAction Stop
            
            # Validate and extract ServiceChanger API response structure
            if (-not $response.data) {
                throw "Invalid API response structure. Expected 'data' property."
            }
            $apiResponse = $response.data
            
            Write-Log "API call successful - Retrieved $($apiResponse.Count) membership changes" "SUCCESS"
            break
        } catch {
            Write-Log "API call attempt $attempt failed: $_" "WARNING"
            if ($attempt -eq $RetryCount) { 
                throw "API call failed after $RetryCount attempts: $_"
            }
            Start-Sleep -Seconds (5 * $attempt)
        }
    }
    
    # Early exit if no changes to process
    if ($apiResponse.Count -eq 0) {
        $newSyncDate = $utcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
        Write-Log "No membership changes found - Updating LastSyncDate to $newSyncDate" "SUCCESS"
        Set-AutomationVariable -Name 'SC_LastSyncDate' -Value $newSyncDate
        Write-Log "Synchronization completed successfully" "SUCCESS"
        return
    }
    
    Write-Log "Processing $($apiResponse.Count) membership changes from ServiceChanger API" "INFO"
    
    # Pre-validate AD objects and build cache for performance optimization
    Write-Log "Pre-validating Active Directory objects for performance optimization..." "INFO"
    $groupCache = @{}
    $userCache = @{}
    $validChanges = @()
    
    foreach ($change in $apiResponse) {
        $groupId = $change.resource.groupId
        $userId = $change.resource.userId
        
        # Cache group lookups to minimize AD queries
        if (-not $groupCache.ContainsKey($groupId)) {
            $localGroup = Get-ADGroup -Identity $groupId -ErrorAction SilentlyContinue
            $groupCache[$groupId] = $localGroup
        }
        
        # Cache user lookups to minimize AD queries
        if (-not $userCache.ContainsKey($userId)) {
            $localUser = Get-ADUser -Identity $userId -ErrorAction SilentlyContinue
            $userCache[$userId] = $localUser
        }
        
        # Only process changes where both AD objects exist
        if ($groupCache[$groupId] -and $userCache[$userId]) {
            $validChanges += $change
        } else {
            $missingGroup = if (-not $groupCache[$groupId]) { "group '$groupId'" } else { "" }
            $missingUser = if (-not $userCache[$userId]) { "user '$userId'" } else { "" }
            $missing = ($missingGroup, $missingUser | Where-Object { $_ }) -join " and "
            Write-Log "Skipping change: Local $missing not found in Active Directory" "WARNING"
        }
    }
    
    Write-Log "Validation complete: $($validChanges.Count) valid changes, $($apiResponse.Count - $validChanges.Count) skipped" "INFO"
    
    # Early exit if no valid changes after AD validation
    if ($validChanges.Count -eq 0) {
        $newSyncDate = $utcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
        Write-Log "No valid changes to process (all changes reference missing AD objects)" "WARNING"
        Set-AutomationVariable -Name 'SC_LastSyncDate' -Value $newSyncDate
        Write-Log "LastSyncDate updated to prevent reprocessing these changes" "INFO"
        return
    }
    
    # Process changes in batches for optimal performance
    Write-Log "Processing $($validChanges.Count) valid changes in batches of $BatchSize" "INFO"
    $processedCount = 0
    $errorCount = 0
    
    for ($i = 0; $i -lt $validChanges.Count; $i += $BatchSize) {
        $batchEnd = [Math]::Min($i + $BatchSize, $validChanges.Count)
        $batch = $validChanges[$i..($batchEnd - 1)]
        Write-Log "Processing batch: items $($i + 1) to $batchEnd" "INFO"
        
        foreach ($change in $batch) {
            try {
                # Extract change details from ServiceChanger API response
                $groupId = $change.resource.groupId
                $userId = $change.resource.userId
                $action = $change.changeType
                $timestamp = $change.timestamp
                
                # Retrieve cached AD objects for performance
                $localGroup = $groupCache[$groupId]
                $localUser = $userCache[$userId]
                
                # Get human-readable names for logging
                $groupName = $localGroup.Name
                $userName = $localUser.DisplayName ?? $localUser.SamAccountName
                
                Write-Log "Processing $action - '$userName' in group '$groupName'" "INFO"
                
                # Apply membership changes with idempotent operations
                $actualChangePerformed = $false
                switch ($action) {
                    "created" {
                        $currentMembers = Get-ADGroupMember -Identity $localGroup -ErrorAction SilentlyContinue
                        $memberNames = if ($currentMembers) { $currentMembers.SamAccountName } else { @() }
                        if ($memberNames -notcontains $localUser.SamAccountName) {
                            Add-ADGroupMember -Identity $localGroup -Members $localUser -ErrorAction Stop
                            Write-Log "Added '$userName' to group '$groupName' (timestamp: $timestamp)" "SUCCESS"
                            $actualChangePerformed = $true
                        } else {
                            Write-Log "User '$userName' already member of group '$groupName' - No action required" "INFO"
                        }
                    }
                    
                    "deleted" {
                        $currentMembers = Get-ADGroupMember -Identity $localGroup -ErrorAction SilentlyContinue
                        $memberNames = if ($currentMembers) { $currentMembers.SamAccountName } else { @() }
                        if ($memberNames -contains $localUser.SamAccountName) {
                            Remove-ADGroupMember -Identity $localGroup -Members $localUser -Confirm:$false -ErrorAction Stop
                            Write-Log "Removed '$userName' from group '$groupName' (timestamp: $timestamp)" "SUCCESS"
                            $actualChangePerformed = $true
                        } else {
                            Write-Log "User '$userName' not member of group '$groupName' - No action required" "INFO"
                        }
                    }
                    
                    "updated" {
                        Write-Log "Membership update detected for '$userName' in group '$groupName' - Manual review recommended" "WARNING"
                    }
                    
                    default {
                        Write-Log "Unknown change type '$action' for user '$userName' in group '$groupName' - Skipping" "WARNING"
                    }
                }
                
                if ($actualChangePerformed) {
                    $processedCount++
                }
                
            } catch {
                $errorCount++
                $userDisplayName = if ($userName) { "'$userName'" } else { $userId }
                $groupDisplayName = if ($groupName) { "'$groupName'" } else { $groupId }
                Write-Log "Failed to process change for user $userDisplayName in group $groupDisplayName - Error: $_" "ERROR"
            }
        }
    }
    
    # Trigger Azure AD Connect delta sync via PowerShell remoting
    Invoke-AADConnectSync -ChangesProcessed $processedCount
    
    # Update LastSyncDate to current UTC time
    $newSyncDate = $utcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
    
    # Provide comprehensive completion summary
    $skippedChanges = $apiResponse.Count - $validChanges.Count
    $reviewedChanges = $validChanges.Count - $errorCount
    if ($skippedChanges -gt 0) {
        Write-Log "Synchronization completed: $processedCount actual changes, $reviewedChanges reviewed, $errorCount errors, $skippedChanges skipped (missing AD objects)" "INFO"
    } else {
        Write-Log "Synchronization completed successfully: $processedCount actual changes, $reviewedChanges reviewed, $errorCount errors" "SUCCESS"
    }
    
    Write-Log "LastSyncDate updated to $newSyncDate for next synchronization cycle" "SUCCESS"
    Set-AutomationVariable -Name 'SC_LastSyncDate' -Value $newSyncDate
    
} catch {
    Write-Log "Critical error in runbook execution: $_" "ERROR"
    Send-ErrorAlert $_
    throw $_
} finally {
    # Cleanup temporary log file if email alerts are disabled
    if (-not $EnableEmailAlerts) { 
        Remove-Item $LogFilePath -Force -ErrorAction SilentlyContinue
    }
    Write-Log "ServiceChanger AD membership synchronization completed" "INFO"
}
#endregion Step 3: Email Alert Configuration (Optional)
To enable email notifications:
Edit the runbook
Customize environment-specific settings:
# Line 57: Configure SMTP server for your environment
$SmtpServer = "smtp.office365.com"
# Line 59: Set sender email address
$SmtpFrom = "serviceaccount@yourdomain.com"Modify the parameter at the top of the script:
# Change from:
[bool]$EnableEmailAlerts = $false
# Change to:
[bool]$EnableEmailAlerts = $trueSave and republish the runbook
Step 4: Schedule Configuration
Schedule Creation
Recommended Frequency: 1 hour (minimum interval supported by Azure Automation)
Select "+ Link to schedule"
Choose "Link a schedule to your runbook"
Configure the new schedule:
Name
ServiceChanger-Sync-Schedule
Frequency
Recurring
Recur every
1 hour
Time zone
Your organizational timezone
Execution Settings
Run on: Hybrid Worker
Hybrid Worker Group: [Your configured group name]
Important: Ensure the runbook is configured to run on the Hybrid Worker, not in Azure. The runbook requires access to on-premises Active Directory.
Step 5: Testing and Validation
Initial Test Execution
Execute "Start" within the runbook interface
Monitor the Output tab for execution logs and status
Verify successful completion and review log entries
Validation Checklist
System Verification Points:
API connection established successfully
Active Directory objects located and processed
SC_LastSyncDate variable updated correctly
Azure AD Connect sync initiated (if applicable)
Email alert functionality operational (if enabled)
Troubleshooting Guide
Common Error Scenarios
Error Message: "Failed to load Azure Automation Variables"
Resolution Steps:
Verify all required variables exist with correct naming (case-sensitive)
Confirm encrypted variables are properly marked as encrypted
Validate variable names for typographical errors
Ensure the Automation Account has proper permissions
Check specifically for new Azure AD Connect variables (SC_AADConnectServer, SC_AADConnectUsername, SC_AADConnectPassword)
Error Message: "API call failed after 3 attempts"
Resolution Steps:
Validate ServiceChanger API key authenticity and permissions
Verify x-api-key header format (not Authorization Bearer)
Confirm query parameters: resourceType=membership, onPremisesSyncEnabled=true
Test API endpoint connectivity from Arc VM: Test-NetConnection api.servicechanger.com -Port 443
Check firewall rules and proxy settings on Arc VMs
Error Message: "Local user/group not found"
Resolution Steps:
Confirm users/groups exist in local Active Directory
Verify ObjectGUID matching between ServiceChanger and AD (direct lookup approach)
Validate service account has read permissions on AD
Test AD connectivity: Get-ADDomain from Arc VM
Check domain trust relationships if multiple domains involved
Error Message: "Failed to trigger Azure AD Connect sync: Access denied"
Resolution Steps:
Verify SC_AADConnectServer variable contains correct server/cluster name
Confirm PowerShell remoting is enabled on Azure AD Connect server
Validate service account permissions for remote execution
Test WinRM connectivity: Test-WsMan ADCONNECT-SERVER.domain.local
Check ADSync module availability on Azure AD Connect server
Verify service account is member of ADSyncAdmins group
Error Message: "Job failed to start on Hybrid Worker"
Resolution Steps:
Check Azure Arc agent status: azcmagent show
Verify Arc VM connectivity in Azure portal
Confirm Hybrid Worker group configuration
Test Azure Automation connectivity from Arc VM
Check Arc agent logs in Event Viewer
Restart Azure Arc agent service if needed
Support Resources
Last updated