Script Repository


Synchronize Google group memberships with members of an AD group

December 21, 2016
1709

The scripts synchronize memberships of a Google Apps group with members of an Active Directory group. The 1st script performs the task using the GAM tool, and the 2nd one updates Google groups memberships using a set of PowerShell cmdlets called gShell.

To synchronize the group members, you need to create a Business Rule triggered After adding or removing a member from a group that runs one of the below scripts. Additionally, you can create a Scheduled Task for Group objects that runs one of the scripts. Using a task allows you to track AD group membership changes performed outside of Adaxes, for example, using ADUC or Exchange.

GAM Script

Note: Before using the script, install and configure the GAM Tool on the computer where Adaxes service runs. For details, see GAM Wiki.

Parameters:

  • $gamPath - specifies a path to the GAM executable file;
  • $waitTimeMilliseconds - specifies the time to wait for GAM response. It is recommended not to set a time exceeding the 10 minutes' limit applied by Adaxes to scripts executed by Business Rules, Custom Commands and Scheduled Tasks. If a script runs for more time than you specify, it will be completed, but the errors, warnings and other messages will not be added to the Execution Log;
  • $groupId - specifies a value reference for the AD property that serves as the group identifier in Google Apps. The script will search Google Apps groups by the specified property. For example, if you specify %sAMAccountName%, group names in Google Apps must correspond to the Group Name property of the corresponding AD groups;
  • $memberIdentityAttribute - specifies the LDAP display name of the attribute that will be used to identify users in Google Apps. For example, if you specify userPrincipalName, users' email addresses in Google Apps correspond to the User Logon Name property of their accounts.
Edit Remove
PowerShell
$gamPath = "C:\Scripts\Gam\gam.exe" # TODO: modify me
$waitTimeMilliseconds = 8 * 60 * 1000 # TODO: modify me
$groupId = "%sAMAccountName%" # TODO: modify me
$memberIdentityAttribute = "userprincipalName" # TODO: modify me

function StartProcess ($arguments)
{
    # Start GAM process
    $processInfo = New-Object System.Diagnostics.ProcessStartInfo
    $processInfo.FileName = $gamPath
    $processInfo.RedirectStandardOutput = $true 
    $processInfo.RedirectStandardError = $true
    $processInfo.UseShellExecute = $false
    $processInfo.Arguments = $arguments
    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $processInfo
    [void]$process.Start()
    [void]$process.WaitForExit($waitTimeMilliseconds)
    $resultErrors = $process.StandardError.ReadToEnd()
    $resultOutput = $process.StandardOutput.ReadToEnd()
    
    return @{
        "Output" = $resultOutput.Trim()
        "Error" = $resultErrors.Trim()
    }
}

function SyncGroup ($googleGroupInfo, $membersToAdd)
{
    # Get group properties
    $googleGroupName = ($googleGroupInfo.Output | Select-String -Pattern "name:.+").Matches[0].Value.Replace("name:", "").Trim()
    
    # Sync group members
    $matchInfo = $googleGroupInfo.Output | Select-String -Pattern "member:.+" -AllMatches
    if ($matchInfo -ne $NULL)
    {
        foreach ($record in $matchInfo.Matches)
        {
            $recordValue = $record.Value.Trim()
            $memberEmail = ($recordValue | Select-String -Pattern "\s.+\s").Matches[0].Value.Trim()
            if ($membersToAdd.Remove($memberEmail))
            {
                continue
            }
            
            # Remove member from group
            $operationRemoveResult = StartProcess "update group $googleGroupName remove user $memberEmail"
            if (([System.String]::IsNullOrEmpty($operationRemoveResult.Error)) -or ($operationRemoveResult.Error.Trim() -eq "removing $memberEmail"))
            {
                continue
            }
            
            $Context.LogMessage("Operation output: " + $operationRemoveResult.Output, "Warning")
            $Context.LogMessage("An error occurred while removing a user from the Google group. Error: " + $operationRemoveResult.Error, "Error")
        }
    }
    
    # Add users to group
    $groupMail = ($googleGroupInfo.Output | Select-String -Pattern "email:.+").Matches[0].Value.Replace("email:", "").Trim()
    foreach ($memberIdentity in $membersToAdd)
    {
        # Add member from group
        $operationAddResult = StartProcess "update group $groupMail add user $memberIdentity"
        if ((([System.String]::IsNullOrEmpty($operationAddResult.Error)) -and ($operationAddResult.Output -notlike "*Error*")) -or ($operationAddResult.Error.Trim() -eq "adding member $memberIdentity`..."))
        {
            continue
        }
        
        $Context.LogMessage("Operation output: " + $operationAddResult.Output, "Warning")
        $Context.LogMessage("An error occurred while adding a user to the Google group. Error: " + $operationAddResult.Error, "Error")
    }
    
    # Sync group description and name
    $propertiesToUpdate = ""
    $googleGroupDescription = ($googleGroupInfo.Output | Select-String -Pattern "description:.+").Matches[0].Value.Replace("description:", "").Trim()
    if ($googleGroupDescription -ne "%description%")
    {
        $processArguments += 'description "%description%"'
    }
    if ($googleGroupName -ne "%name%")
    {
        $processArguments += 'name "%name%"'
    }
    
    if ([System.String]::IsNullOrEmpty($processArguments))
    {
        return
    }
    
    $operationUpdateResult = StartProcess "update group $googleGroupName $processArguments"
    if (([System.String]::IsNullOrEmpty($operationUpdateResult.Error)) -or ($operationUpdateResult.Output -notlike "*error*"))
    {
        continue
    }
    
    $Context.LogMessage("Operation output: " + $operationUpdateResult.Output, "Warning")
    $Context.LogMessage("An error occurred when updating the Google group. Error: " + $operationUpdateResult.Error, "Error")
}

function GetUsersMailAddress($guidsBytes, $userIdentity)
{
    $filter = New-Object "System.Text.StringBuilder"
    [void]$filter.Append("(&(sAMAccountType=805306368)($userIdentity=*)(|")
    foreach ($guidBytes in $guidsBytes)
    {
        $filterPart = [Softerra.Adaxes.Ldap.FilterBuilder]::Create("ObjectGuid", $guidBytes)
        [void]$filter.Append($filterPart)
    }
    [void]$filter.Append("))")
    
    $searcher = $Context.BindToObject("Adaxes://rootDSE")
    $searcher.SearchFilter = $filter.ToString()
    $searcher.SearchScope = "ADS_SCOPE_SUBTREE"
    $searcher.PageSize = 500
    $searcher.ReferralChasing = "ADS_CHASE_REFERRALS_NEVER"
    $searcher.SetPropertiesToLoad(@($userIdentity))
    $searcher.VirtualRoot = $True
    
    try
    {
        $searchResultIterator = $searcher.ExecuteSearch()
        $searchResults = $searchResultIterator.FetchAll()
        
        foreach ($searchResult in $searchResults)
        {
            [void]$memberIdentities.Add($searchResult.Properties[$userIdentity].Value)
        }
    }
    finally
    {
        if ($searchResultIterator){ $searchResultIterator.Dispose() }
    }
}

# Check group id
if ([System.String]::IsNullOrEmpty($groupId))
{
    return # Don't perform the sync
}

# Search group by group id
$gamResult = StartProcess "print groups id"
if (-not([System.String]::IsNullOrEmpty($gamResult.Output)))
{
    # Parse info
    $records = $gamResult.Output.Split("`n")
    $googleGroupInfo = $NULL
    for ($i = 1; $i -lt $records.Length; $i++)
    {
        $googleGroupValues = $records[$i].Split(",")
        $googleGroupId = $googleGroupValues[1].Trim()
        if ($googleGroupId -ne $groupId)
        {
            continue
        }
        
        # Get AD group members
        try
        {
            $membersGuidsBytes = $Context.TargetObject.GetEx("adm-DirectMembersGuid")
        }
        catch
        {
            $membersGuidsBytes = $NULL
        }

        $memberIdentities = New-Object "System.Collections.Generic.HashSet[System.String]"
        if ($membersGuidsBytes -ne $NULL)
        {
            GetUsersMailAddress $membersGuidsBytes $memberIdentityAttribute
        }
        
        # Get Google group info
        $googleGroupMail = $googleGroupValues[0].Trim()
        $googleGroupInfo = StartProcess "info group $googleGroupMail"
        SyncGroup $googleGroupInfo $memberIdentities 
        return
    }
    
    if ($googleGroupInfo -eq $NULL)
    {
        $Context.LogMessage("Google group not found. Id: $groupId", "Warning")
    }
}
else
{
    $Context.LogMessage("An error occurred while getting a Google groups list. Error: " + $gamResult.Error, "Error")
}

gShell Script

Note: Before using the script, you need to perform the steps listed in gShell's Getting Started document. Step Enter the Client ID and Secret must be performed on the computer where Adaxes Service is installed using the credentials of the default service administrator you specified when installing the service.

Parameters:

  • $groupIdentity - specifies a value reference for the AD property that serves as the group identifier in Google Apps. The script will search Google Apps groups by the specified property. For example, if you specify %sAMAccountName%, group names in Google Apps must correspond to the Group Name property of the corresponding AD groups;
  • $memberIdentityAttribute - specifies the LDAP display name of the attribute that will be used to identify users in Google Apps. For example, if you specify userPrincipalName, users' email addresses in Google Apps correspond to the User Logon Name property of their accounts.
Edit Remove
PowerShell
$groupIdentity = "%sAMAccountName%" # TODO: modify me
$memberEmailAttribute = "mail" # TODO: modify me

function GetUsersMailAddress($guidsBytes, $userIdentity)
{
    $filter = New-Object "System.Text.StringBuilder"
    [void]$filter.Append("(&(sAMAccountType=805306368)($userIdentity=*)(|")
    foreach ($guidBytes in $guidsBytes)
    {
        $filterPart = [Softerra.Adaxes.Ldap.FilterBuilder]::Create("ObjectGuid", $guidBytes)
        [void]$filter.Append($filterPart)
    }
    [void]$filter.Append("))")
    
    $searcher = $Context.BindToObject("Adaxes://rootDSE")
    $searcher.SearchFilter = $filter.ToString()
    $searcher.SearchScope = "ADS_SCOPE_SUBTREE"
    $searcher.PageSize = 500
    $searcher.ReferralChasing = "ADS_CHASE_REFERRALS_NEVER"
    $searcher.SetPropertiesToLoad(@($userIdentity))
    $searcher.VirtualRoot = $True
    
    try
    {
        $searchResultIterator = $searcher.ExecuteSearch()
        $searchResults = $searchResultIterator.FetchAll()
        
        foreach ($searchResult in $searchResults)
        {
            [void]$memberFromAd.Add($searchResult.Properties[$userIdentity].Value)
        }
    }
    finally
    {
        if ($searchResultIterator){ $searchResultIterator.Dispose() }
    }
}

# Get AD group members
try
{
    $membersGuidsBytes = $Context.TargetObject.GetEx("adm-DirectMembersGuid")
}
catch
{
    $membersGuidsBytes = $NULL
}

# Get email addresses of all members
$memberFromAd = New-Object "System.Collections.Generic.HashSet[System.String]"
if ($membersGuidsBytes -ne $NULL)
{
    GetUsersMailAddress $membersGuidsBytes $memberEmailAttribute
}

# Sync group members
$scriptBlock = {
    param($groupIdentity, [System.Collections.Generic.HashSet[System.String]]$memberFromAd)
    Import-Module gshell

    # Find the Google group
    try
    {
        $googleGroup = Get-GAGroup -GroupName $groupIdentity
    }
    catch
    {
        $errorMessage = "An error occurred when searching for Google group '$groupIdentity'. Error: " + $_.Exception.Message
        Write-Error $errorMessage
        return
    }

    # Get current members of the Google group
    try
    {
        $googleGroupMembers = Get-GAGroupMember -GroupName $googleGroup.Email -ErrorAction Stop
    }
    catch
    {
        $googleGroupMembers = @()
    }
    
    foreach ($member in $googleGroupMembers)
    {
        if ($memberFromAd.Remove($member.Email))
        {
            continue
        }
        
        try
        {
            # Remove member from the Google group
            Remove-GAGroupMember -GroupName $groupIdentity -UserName $member.Email -Force -ErrorAction Stop
        }
        catch
        {
            $errorMessage = "An error occurred when removing member '" + $member.Email + "' from group '$groupIdentity'. Error: " + $_.Exception.Message
            Write-Error $errorMessage
        }
    }

    # Add new members
    foreach ($memberIdentity in $memberFromAd)
    {
        try
        {
            Add-GAGroupMember -GroupName $groupIdentity -UserName $memberIdentity -Role MEMBER -ErrorAction Stop
        }
        catch
        {
            $errorMessage = "An error occurred when adding member '$memberIdentity' to group '$groupIdentity'. Error: " + $_.Exception.Message
            Write-Error $errorMessage
        }
    }
}

Invoke-Command -ComputerName localhost -ScriptBlock $scriptBlock -ArgumentList $groupIdentity, $memberFromAd


Comments ( 0 )
No results found.
Leave a comment