Post

AAD Connect / Cloud Sync with Existing Cloud Users

Tackling Duplicate Users in AAD Connect / Cloud Sync

Integrating your existing Active Directory with Azure AD using AD Connect or Cloud Sync can sometimes result in duplicate user entries. This video demonstrates how to link existing ImmutableIDs from Active Directory to Azure Directory users and ensure that all proxy addresses in Office 365 are present in Active Directory.

Disclaimer for Script Usage

Please note that these scripts are provided for informational purposes and should be used with caution. Always test scripts in a non-production environment first, and ensure you have backups of your data. I am not responsible for any issues that may arise from the use of these scripts. Users should have an understanding of PowerShell and Active Directory operations before proceeding.

Prerequisites

The following PowerShell command will install the Powershell Azure AD Module.

1
Install-Module AzureAD

Step 1: Exporting User Data from Office 365

The following PowerShell script will export all user data from Office 365, which serves as both an information source and a backup. The script will exclude Guest and Contact users.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# Define the Temp folder path
$FolderName = "C:\Temp"

# Check if the folder exists and create it if it doesn't
if (-not (Test-Path -Path $FolderName)) {
    Write-Host "Folder Doesn't Exist. Creating folder..."
    New-Item -Path $FolderName -ItemType Directory
} else {
    Write-Host "Folder Exists"
    Get-ChildItem -Path $FolderName | Where-Object {$_.CreationTime -gt (Get-Date).Date}
}

# Connect to Azure AD
Connect-AzureAD

# Initialize an array to store user reports
$reportoutput = @()

# Get all Azure AD users
$users = Get-AzureADUser -All $true

# Process each user
$users | ForEach-Object {
    # Filter out external users or contacts
    if ($_.UserType -ne 'Guest' -and $_.UserType -ne 'Contact') {
        $SMTP_Addresses = $_.ProxyAddresses -join ";"
        
        # Get license details for the user
        $licenses = Get-AzureADUserLicenseDetail -ObjectId $_.ObjectId
        $licenseNames = $licenses.SkuPartNumber -join ";"
        
        $reportoutput += [PSCustomObject]@{
            UserPrincipalName = $_.UserPrincipalName
            SamAccountName = $_.SamAccountName
            ImmutableID = $_.ImmutableID
            DisplayName = $_.DisplayName
            ProxyAddresses = $SMTP_Addresses
            Licenses = $licenseNames
        }
    }
}

# Export the user reports to a CSV file
$reportoutput | Export-Csv -Path "$FolderName\Users_AAD.csv" -NoTypeInformation -Encoding UTF8

Write-Host "Export completed. The report is saved at $FolderName\Users_AAD.csv"


Step 2: Exporting Data from Active Directory

This script performs a similar export from Active Directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#Check for Existing c:\Temp folder and if needed create the c:\Temp folder
$FolderName = "C:\Temp"

if([System.IO.Directory]::Exists($FolderName))
{
    Write-Host "Folder Exists"
    Get-ChildItem -Path $FolderName | Where-Object {$_.CreationTime -gt (Get-Date).Date}   
}
else
{
    Write-Host "Folder Doesn't Exists"
    
    #PowerShell Create directory if not exists
    New-Item $FolderName -ItemType Directory
}


$reportoutput=@()
$users = Get-ADUser -Filter * -Properties *
$users | Foreach-Object {
 
    $user = $_
    $objectid = $user.ObjectGUID
    $immutableid = [Convert]::ToBase64String([guid]::New($objectid).ToByteArray())

    $SMTP_Addresses = $user.ProxyAddresses
    $SMTP_List = $SMTP_Addresses -join ";" 

    $report = New-Object -TypeName PSObject
    $report | Add-Member -MemberType NoteProperty -Name 'UserPrincipalName' -Value $user.UserPrincipalName
    $report | Add-Member -MemberType NoteProperty -Name 'SamAccountName' -Value $user.samaccountname
    $report | Add-Member -MemberType NoteProperty -Name 'ImmutableID' -Value $immutableid
    $report | Add-Member -MemberType NoteProperty -Name 'ProxyAddresses' -Value $SMTP_List
    $reportoutput += $report
}
 # Report
$reportoutput | Export-Csv -Path c:\temp\Users_AD.csv -NoTypeInformation -Encoding UTF8

Step 3: Comparing Data Between AD and AAD

To compare and identify differences, we will be using 2 scripts:

Prepare AD Update List:

Compare Azure AD users (AADUsers) against AD users (ADUserHash):

  • If a match is found, add the user details to the update list with Skip_Import set to “No”.
  • If no match is found, add the user details to the update list with Skip_Import set to “Yes”.

This script will generate two files:

  • AD_Update_List.csv: Contains users that need to be updated in AD, with a Skip_Import column indicating if they should be skipped because they don’t match between AD and AAD. Verify the Skip_Import “Yes” users and manually correct the UPN if needed in AD and run the script again.
  • Unmatched_AD_Users.csv: Contains users from AD that do not have a match in Azure AD, marked with Skip_Import as “Yes”. These users will be newly created if synced to AAD, adjust the UPN if needed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# Load CSV files
$AADUsers = Import-Csv -Path C:\Temp\Users_AAD.csv
$ADUsers = Import-Csv -Path C:\Temp\Users_AD.csv

# Create hash tables for quick lookup
$AADUserHash = @{}
$ADUserHash = @{}

$AADUsers | ForEach-Object {
    $AADUserHash[$_.UserPrincipalName] = $_
}

$ADUsers | ForEach-Object {
    $ADUserHash[$_.UserPrincipalName] = $_
}

# Prepare list for AD updates and unmatched AD users
$ADUpdateList = @()
$UnmatchedADUsers = @()

# Compare AAD users against AD users
$AADUsers | ForEach-Object {
    $AADUser = $_
    if ($ADUserHash.ContainsKey($AADUser.UserPrincipalName)) {
        $ADUser = $ADUserHash[$AADUser.UserPrincipalName]
        $ADUpdateList += [PSCustomObject]@{
            UserPrincipalName = $AADUser.UserPrincipalName
            SamAccountName = $ADUser.SamAccountName
            ImmutableID = $ADUser.ImmutableID  # Ensure ImmutableID from AD is captured here
            DisplayName = $AADUser.DisplayName
            ProxyAddresses = $AADUser.ProxyAddresses
            Licenses = $AADUser.Licenses
            Skip_Import = "No"
        }
    } else {
        $ADUpdateList += [PSCustomObject]@{
            UserPrincipalName = $AADUser.UserPrincipalName
            SamAccountName = $AADUser.SamAccountName
            ImmutableID = $AADUser.ImmutableID
            DisplayName = $AADUser.DisplayName
            ProxyAddresses = $AADUser.ProxyAddresses
            Licenses = $AADUser.Licenses
            Skip_Import = "Yes"
        }
    }
}

# Identify unmatched AD users
$ADUsers | ForEach-Object {
    if (-not $AADUserHash.ContainsKey($_.UserPrincipalName)) {
        $UnmatchedADUsers += $_
    }
}

# Export the AD update list to a CSV file
$ADUpdateList | Export-Csv -Path C:\Temp\AD_Update_List.csv -NoTypeInformation -Encoding UTF8

# Export the unmatched AD users to a CSV file
$UnmatchedADUsers | ForEach-Object {
    $_ | Add-Member -MemberType NoteProperty -Name Skip_Import -Value "Yes" -Force
}
$UnmatchedADUsers | Export-Csv -Path C:\Temp\Unmatched_AD_Users.csv -NoTypeInformation -Encoding UTF8

Write-Host "Export completed. The AD update list is saved at C:\Temp\AD_Update_List.csv"
Write-Host "The unmatched AD users are saved at C:\Temp\Unmatched_AD_Users.csv"

Create the AAD Update List

Now, create a script to filter out users with Skip_Import set to “No” and prepare the AAD_Update_List.csv including the SamAccountName: This approach ensures that only the users who need to be updated are included in the AAD_Update_List.csv and subsequently updated in Azure AD.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Load AD Update List
$ADUpdateList = Import-Csv -Path C:\Temp\AD_Update_List.csv

# Filter users who should be imported (Skip_Import is "No")
$AADUpdateList = $ADUpdateList | Where-Object { $_.Skip_Import -eq "No" } | ForEach-Object {
    [PSCustomObject]@{
        UserPrincipalName = $_.UserPrincipalName
        ImmutableID = $_.ImmutableID
        SamAccountName = $_.SamAccountName
    }
}

# Export the AAD update list to a CSV file
$AADUpdateList | Export-Csv -Path C:\Temp\AAD_Update_List.csv -NoTypeInformation -Encoding UTF8

Write-Host "Export completed. The AAD update list is saved at C:\Temp\AAD_Update_List.csv"

Step 4: Importing Proxy Addresses into Active Directory

It’s crucial that Active Directory’s data reflects all proxyAddresses in AAD. Use the following script to import these addresses:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Load the AD Update List with Skip Information
$ADUpdateList = Import-Csv -Path C:\Temp\AD_Update_List.csv

# Import the Active Directory module
Import-Module ActiveDirectory

# Iterate through the list and update AD users
$ADUpdateList | ForEach-Object {
    if ($_.Skip_Import -eq "No") {
        # Get the AD user
        $ADUser = Get-ADUser $_.SamAccountName -Properties ProxyAddresses
        
        if ($ADUser) {
            # Update the AD user's ProxyAddresses
            Set-ADUser -Identity $ADUser -Replace @{
                ProxyAddresses = $_.ProxyAddresses -split ";"
            }

            Write-Host "Updated ProxyAddresses for AD user: $($_.UserPrincipalName)"
        } else {
            Write-Host "AD user not found: $($_.UserPrincipalName)"
        }
    } else {
        Write-Host "Skipping import for user: $($_.UserPrincipalName)"
    }
}

Write-Host "AD update process for ProxyAddresses completed."

Finally, this script will write the ObjectID from Active Directory to the corresponding user in Azure Directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Connect to Azure AD
Connect-AzureAD

# Load AAD Update List
$AADUpdateList = Import-Csv -Path C:\Temp\AAD_Update_List.csv

# Update ImmutableID for each user in Azure AD
$AADUpdateList | ForEach-Object {
    $user = Get-AzureADUser -Filter "UserPrincipalName eq '$($_.UserPrincipalName)'"
    if ($user) {
        Set-AzureADUser -ObjectId $user.ObjectId -ImmutableId $_.ImmutableID
        Write-Host "Updated ImmutableID for user: $($_.UserPrincipalName)"
    } else {
        Write-Host "User not found in Azure AD: $($_.UserPrincipalName)"
    }
}

Write-Host "Azure AD update completed."

Rename C:\Temp\Users_AAD.csv C:\Temp\Users_AAD.old and re run the script from Step 1: Exporting User Data from Office 365

Next you can run the following script to compare the Hard Link between AAD and AD.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# Load the CSV files
$AADUsers = Import-Csv -Path C:\Temp\Users_AAD.csv
$ADUsers = Import-Csv -Path C:\Temp\Users_AD.csv

# Create hash tables for quick lookup
$AADUserHash = @{}
$ADUserHash = @{}

$AADUsers | ForEach-Object {
    $AADUserHash[$_.UserPrincipalName] = $_
}

$ADUsers | ForEach-Object {
    $ADUserHash[$_.UserPrincipalName] = $_
}

# Prepare list for comparison results
$ComparisonResults = @()

# Compare AD users against Azure AD users
$ADUsers | ForEach-Object {
    $ADUser = $_
    if ($AADUserHash.ContainsKey($ADUser.UserPrincipalName)) {
        $AADUser = $AADUserHash[$ADUser.UserPrincipalName]
        $ComparisonResults += [PSCustomObject]@{
            UserPrincipalName = $ADUser.UserPrincipalName
            AD_ImmutableID = $ADUser.ImmutableID
            AAD_ImmutableID = $AADUser.ImmutableID
            Match = if ($ADUser.ImmutableID -eq $AADUser.ImmutableID) { "Yes" } else { "No" }
        }
    } else {
        $ComparisonResults += [PSCustomObject]@{
            UserPrincipalName = $ADUser.UserPrincipalName
            AD_ImmutableID = $ADUser.ImmutableID
            AAD_ImmutableID = "Not Found"
            Match = "No"
        }
    }
}

# Export the comparison results to a CSV file
$ComparisonResults | Export-Csv -Path C:\Temp\ImmutableID_Comparison.csv -NoTypeInformation -Encoding UTF8

Write-Host "Comparison completed. The results are saved at C:\Temp\ImmutableID_Comparison.csv"

Closing Thoughts

Successfully merging existing Azure/Office 365 tenant users with your Active Directory environment requires careful planning and execution. This tutorial is designed to guide you through this process, helping you avoid the common pitfall of duplicate user entries.

Your experiences, insights, or any questions about this process are invaluable. Please share them in the comments below. Your input helps us all learn and grow in our understanding of Azure AD and Active Directory integrations.

Stay tuned for more informative guides and tutorials!

This post is licensed under CC BY 4.0 by the author.