Script repository

Synchronize Google group memberships with members of an AD group

Updated on: Jan 18, 2026, Views: 8902

Google apps, Group membership

The scripts synchronize memberships of a Google Apps group based on members of an Active Directory group. To execute the script, create a business rule, custom command or scheduled task configured for the Group object type.

Script 1: Using GAM

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

Parameters

  • $gamPath - the 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 - 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.
  • $memberIdentityAttribute - the name of the property that will be used to identify users in Google Apps.
$gamPath = "C:ScriptsGamgam.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.CreateNoWindow = $true
    $processInfo.Arguments = $arguments
    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $processInfo
    [void]$process.Start()
    $processCompleted = $process.WaitForExit($waitTimeMilliseconds)
    if (!$processCompleted)
    {
        $process.Kill()
        Write-Error "The process timeout."
        return $null
    
    }
    $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")
        }
    }
    
    $groupMail = ($googleGroupInfo.Output | Select-String -Pattern "email:.+").Matches[0].Value.Replace("email:", "").Trim()
    foreach ($memberIdentity in $membersToAdd)
    {
        # Add member to 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")
}

Script 2: Using gShell

Before using the script, perform the steps listed in gShell 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 Adaxes service account (specified during the service installation).

Parameters

  • $groupIdentity - 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.
  • $memberIdentityAttribute - the name of the property that will be used to identify users in Google Apps.
$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
}

$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

You must be signed in to comment.

    Got questions?

    Support Questions & Answers

    We use cookies to improve your experience.
    By your continued use of this site you accept such use.
    For more details please see our privacy policy and cookies policy.