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.
$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.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")
}
}
# 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 Adaxes service account 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.
$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