How to Map User Birthdays To an Extension Attribute in Entra ID Using PowerShell

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:

  1. Azure AD Tenant: You need access to an Azure Active Directory tenant.
  2. 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.
  3. 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.

Interested?

ContactsGet in touch with us