
In today’s digital age, maintaining accurate and up-to-date information about users is crucial for personalized user experiences and efficient management. One common requirement is mapping user birthday data to custom attributes in Azure Active Directory (Entra ID).This mapping allows for quicker access to the birthday field, as it is
directly available when making a call to the users endpoint of the Microsoft Graph API.
In this blog post, we will guide you through the process of mapping the birthday field to an extension attribute in Entra ID using a PowerShell script.
Prerequisites
Before running the script, ensure you have the following prerequisites:
- Azure AD Tenant: You need access to an Azure Active Directory tenant.
- Application Registration: Register an application in Azure AD to get the Client ID and Client Secret. You can do this by following the official Microsoft guide.
- Graph API Permissions: Assign the necessary permissions to your app to access user data. The User.Read permission is required. For more details, visit the Microsoft Graph API permissions reference.
Script Breakdown
The PowerShell script is divided into several functions, each handling a specific task. Here’s a detailed explanation of each function:
Getting the Access Token
function Get-AccessToken { Write-Host "Getting access token." $body = @{ client_id = $CLIENT_ID scope = "https://graph.microsoft.com/.default" client_secret = $CLIENT_SECRET grant_type = "client_credentials" } $response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" -ContentType "application/x-www-form-urlencoded" -Body $body if ($response.access_token) { Write-Host "Access token acquired." LogMessage "Access token acquired." $LOG_FILE_PATH return $response.access_token } else { $error_msg = "Could not acquire access token" Write-Error $error_msg LogMessage $error_msg $LOG_FILE_PATH throw $error_msg } }
Requests an access token from Azure AD to authenticate with the Microsoft Graph API.
Retrieving Users from Microsoft Graph
function Get-Users { param ( [string]$accessToken ) Write-Host "Fetching users from Microsoft Graph." LogMessage "Fetching users from Microsoft Graph." $LOG_FILE_PATH $users = @() $url = "https://graph.microsoft.com/beta/users" $headers = @{ "Authorization" = "Bearer $accessToken" } do { $response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get $users += $response.value $url = $response."@odata.nextLink" $usersFetchedCount = $response.value.Count Write-Host "Fetched $usersFetchedCount users." LogMessage "Fetched $usersFetchedCount users." $LOG_FILE_PATH } while ($url) Write-Host "Total users fetched: $($users.Count)" LogMessage "Total users fetched: $($users.Count)" $LOG_FILE_PATH return $users }
Retrieves a list of users from Microsoft Graph, handling pagination to ensure all users are fetched.
Getting User’s Birthday
function Get-UserBirthday { param ( [string]$userId, [string]$accessToken ) Write-Host "Fetching birthday for user $userId." LogMessage "Fetching birthday for user $userId." $LOG_FILE_PATH $url = "https://graph.microsoft.com/beta/users/$userId" + '?$select=birthday' $headers = @{ "Authorization" = "Bearer $accessToken" } $response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get $birthday = $response.birthday Write-Host "User $userId birthday: $birthday" LogMessage "User $userId birthday: $birthday" $LOG_FILE_PATH return $birthday }
Fetches the birthday of a specific user using their ID.
Updating User’s Extension Attribute
function Update-UserExtensionAttribute { param ( [string]$userId, [string]$birthday, [string]$accessToken ) Write-Host "Updating user $userId with birthday $birthday." LogMessage "Updating user $userId with birthday $birthday." $LOG_FILE_PATH $url = "https://graph.microsoft.com/beta/users/$userId" $headers = @{ "Authorization" = "Bearer $accessToken" "Content-Type" = "application/json" } $data = @{ "onPremisesExtensionAttributes" = @{ $EXTENSION_ATTRIBUTE = $birthday } } $json_data = $data | ConvertTo-Json -Depth 3 Invoke-RestMethod -Uri $url -Headers $headers -Method Patch -Body $json_data Write-Host "User $userId updated successfully." LogMessage "User $userId updated successfully." $LOG_FILE_PATH }
Updates a user’s extension attribute with their birthday.
Main Function
function Main { Write-Host "Processing a request to update user birthdays." LogMessage "Processing a request to update user birthdays." $LOG_FILE_PATH try { $requiredFields = @($TENANT_ID, $CLIENT_ID, $CLIENT_SECRET, $EXTENSION_ATTRIBUTE, $LOG_FILE_PATH) foreach ($field in $requiredFields) { if (-not $field) { $errorMsg = "Parameter '$field' is missing or empty." Write-Error $errorMsg LogMessage $errorMsg $LOG_FILE_PATH return $errorMsg } } if (-not (Test-Path $LOG_FILE_PATH)) { Write-Host "Creating log file at $LOG_FILE_PATH" New-Item -Path $LOG_FILE_PATH -ItemType File -Force | Out-Null } Write-Host "Logging results to $LOG_FILE_PATH" LogMessage "Starting script execution at $(Get-Date)" $LOG_FILE_PATH $accessToken = Get-AccessToken Write-Host "Access token acquired." $users = Get-Users -accessToken $accessToken foreach ($user in $users) { if ($user.userType -eq "Member") { $userId = $user.id Write-Host "Processing user $userId." LogMessage "Processing user $userId." $LOG_FILE_PATH $birthday = Get-UserBirthday -userId $userId -accessToken $accessToken if ($birthday) { Update-UserExtensionAttribute -userId $userId -birthday $birthday -accessToken $accessToken } else { Write-Host "Birthday not found for user $userId." LogMessage "Birthday not found for user $userId." $LOG_FILE_PATH } } } Write-Host "Birthday update process completed successfully." LogMessage "Birthday update process completed at $(Get-Date)" $LOG_FILE_PATH return "Birthday update completed successfully." } catch { Write-Error "Error processing the request: $_" LogMessage "Error processing the request: $_" $LOG_FILE_PATH return "Error processing the request: $_" } } # Run the main function directly for testing purposes $result = Main Write-Host $result
Orchestrates the entire process of updating user birthdays with error handling and logging.
Logging Messages
function LogMessage { param ( [string]$message, [string]$logFilePath ) if (-not (Test-Path $logFilePath)) { New-Item -Path $logFilePath -ItemType File -Force | Out-Null } # Format message with timestamp and write to log file $formattedMessage = "$(Get-Date) - $message" Add-Content -Path $logFilePath -Value $formattedMessage }
Logs messages to a specified file, creating the file if it doesn’t exist. It includes timestamps for better tracking.
Running the Script
To execute the script, save it as Update-Birthdays.ps1 and run it from PowerShell with the appropriate parameters:
.Update-Birthdays.ps1 -TENANT_ID "your_tenant_id" -CLIENT_ID "your_client_id" -CLIENT_SECRET "your_client_secret" -EXTENSION_ATTRIBUTE "your_extension_attribute" -LOG_FILE_PATH "C:PathToLogFile.txt"
Replace the placeholders with your actual tenant ID, client ID, client secret, extension attribute name, and desired log file path.
Mapping the birthday field to a custom extension attribute in Entra ID allows for more efficient user management by providing quicker access to birthday information through the Microsoft Graph API. This setup simplifies the process of retrieving and managing user data, facilitating better personalization and user experience within your organization.
By following the steps outlined in this guide, you can ensure that your Azure AD setup is optimized for managing user birthday data effectively. Whether you’re an IT administrator looking to enhance your user directory or a developer aiming to integrate birthday data into your applications, this approach provides a robust solution.
Feel free to customize the provided script further based on your specific needs, such as adding additional logging or modifying data retrieval processes. If you encounter any issues or have specific requirements, refer to the Microsoft Graph API documentation or reach out to the community for support.
Download the full PowerShell script here.