Using PowerShell to manage Google Apps

With the limitations of Google’s console to manage Google Apps for system admins, scripting and command line tools have naturally become a topic for the system admin community. Previous posts here have discussed the options for use of various tools and so far I have used both Dito GAM and PowerShell with extensions as a Google Apps management tool. Dito GAM is a command line tool specifically and is well suited for batch operations managing many aspects of Google Apps. It is well thought out, completely free and proved quite invaluable for managing our first Google Apps deployment in a New Zealand school.
However it does not specifically integrate to PowerShell in the way that a PowerShell extension would be capable of achieving as it is a separate executable application and so I have more recently looked at the gShell extension module to accomplish scripted tasks within PowerShell in a different school. Having mentioned various PowerShell extensions that were available in an earlier post, gShell has been the one I have focused on to date and it has a supportive user community and open development model which has made it easy to implement to date.
I now turn specifically to some of the tasks I have achieved in Google Apps experimentally to manage specifically the synchronisation of user accounts between Active Directory and Google Apps so far. The scripts examined in this post are:
  • Provision-Students: Provision accounts in Google Apps from Active Directory
  • Classify-Students and Reclassify-Students: Organise student accounts in Google Apps to match the class structure of the school
  • Deprovision-Students: Move accounts of non-current students to a different suborganisation in Google Apps.
Provision-Students
Provision-Users is a simple script that generates accounts in Google Apps based on the list of accounts it can find by searching a specific tree in Active Directory.

import-module ActiveDirectory
import-module gShell

#Retrieve all students from Active Directory
$ADStudents = Get-ADUser -Filter * -SearchBase “ou=Z,ou=HCS Students,ou=HCS,DC=hcs,DC=local” -SearchScope Subtree -Properties EmailAddress
foreach ($S in $ADStudents)
{
    # Check account is enabled
    if ($S.Enabled -eq $false)
    {
        continue
    }

    $Email = $S.EmailAddress  
    if ($Email -eq $null)
    {
        Write-Host ($S.SamAccountName + ” does not have an email address set. Skipping.”)
        continue
    }

In the first section of the script we are searching AD using a subtree filter and it has used one specific OU (named for for students whose surnames start with Z) but it can use a higher level OU to search multiple lower levels. We have to specify in the query that the EmailAddress is being returned from the query as it isn’t one of the default user object properties that are returned from this cmdlet. After checking the account is enabled and has an email address set we can fall through to the next section of the script, otherwise that user account is skipped.

   #Look up in Google Apps
    Write-Host (“Looking up Google Apps for ” + $Email + “…”) -NoNewline
    $User = $null
    try
    {
        $User = Get-GAUser -UserName $Email -ErrorAction Stop
    }
    catch
    {
    }
    if ($User -eq $null)
    {
        #Add if not there
        New-GAUser -UserName $Email -GivenName $S.GivenName -FamilyName $S.Surname -PasswordLength 8 -IncludeInDirectory $true `
        -OrgUnitPath “/Students From AD” -ChangePasswordAtNextLogin $false
        Write-Host (“Added ” + $Email + ” to Google Apps”)
    }
    else
    {
        Write-Host “”
    }
}

The rest of the script is concerned with finding an account in Google Apps and creating a new account if the existing one isn’t found. Get-GAUser will throw an exception if the account isn’t found so I have to catch this with a try-catch block although I could also have used a Trap block. I haven’t tried to determine which exception is being thrown and occasionally there will be an instance of an exception even though the account does exist indicating some other type of exception other than “not found”. This results in an error in the next section in these few cases. The code around this block assumes the $User instance will be null if Get-GAUser doesn’t find the account. The last section simply calls New-GAUser to set up the new account. We didn’t specify a password and have let Google create a random one. Later we will provision passwords into Active Directory and let Google Apps Password Sync automatically provision them into Google Apps, so this isn’t an issue.
Deprovision-Students

A simple script that moves students accounts to a different sub organisation in Google Apps if they are not found or disabled in AD.

import-module ActiveDirectory
import-module gShell

$PC = New-GAUserPropertyCollection
#Retrieve all students from Google Apps
$GAStudents = Get-GAUser -All

foreach ($G in $GAStudents)
{
    # Skip if they are not in the Students organisation
    if ($G.OrgUnitPath -notlike “/Students From AD*”)
    {
        continue
    }

    $Email = $G.PrimaryEmail
    # Look them up in AD
    $ADStudents = Get-ADUser -Filter {EmailAddress -eq $Email} -SearchBase “ou=HCS Students,ou=HCS,dc=hcs,dc=local” -SearchScope Subtree
    if ($ADStudents -eq $null)
    {
        Set-GAUser -Username $Email -PropertyCollection $PC -OrgUnitPath “/Students Not In AD”
        Write-Host ($Email + ” is not in AD”)
    }
    else
    { # If found but disabled then move them as well
        foreach ($S in $ADStudents)
        {
            if ($S.Enabled -eq $False)
            {
                Set-GAUser -Username $Email -PropertyCollection $PC -OrgUnitPath “/Students Not In AD”
                Write-Host ($Email + ” is not in AD”)
            }
        }
    }
}

We are using Set-GAUser to move the account in Google Apps. Due to a bug in this cmdlet we have to create a redundant GAUserPropertyCollection object and use that in our calls. It is important if you use the extra properties in your Google Apps accounts to make sure that calls to Set-GAUser do not erase these properties by passing an empty instance (as in this case). I don’t know if this is a risk or not because I haven’t checked in the documentation to see what happens in such a case.
Classify-Students / Reclassify-Students
These two scripts are both concerned with reorganising students between different sub organisations in Google and are essentially identical apart from one line which I will mention below.

import-module ActiveDirectory
import-module gShell

$PC = New-GAUserPropertyCollection
#Retrieve all students from Google Apps
$GAStudents = Get-GAUser -All

foreach ($G in $GAStudents)
{
    # Skip if they are not in the Students organisation
    if ($G.OrgUnitPath -ne “/Students From AD”)
    {
        continue
    }

    $Email = $G.PrimaryEmail
    # Look them up in AD
    $ADStudents = Get-ADUser -Filter {EmailAddress -eq $Email} -SearchBase “ou=HCS Students,ou=HCS,dc=hcs,dc=local” -SearchScope Subtree -Properties Division
    if ($ADStudents -ne $null)
    {
        foreach ($S in $ADStudents)
        {
            $OrgPath = “/Students From AD/” + $S.Division
            Set-GAUser -Username $Email -PropertyCollection $PC -OrgUnitPath $OrgPath
            Write-Host ($Email + ” moved to ” + $OrgPath)
        }
    }
}

These scripts only differ in the Google Apps OrgUnitPath which in the Classify script doesn’t have the wildclass * on the end of it and in the case of Reclassify, does. The former script is for first time classification of new accounts and the second for reclassifying existing accounts but you could just use Reclassify for both functions, however if you have a lot of accounts it needlessly wastes time calling gShell to essentially do nothing to the account since it doesn’t need moving. (Note however my comment about GA-UserPropertyCollection objects from the previous scripts)
We use AD calls here to retrieve the classification property (in this case the class that the student is enrolled in) and move the account in Google Apps to a sub organisation named after that class.

Posted

in

by

Tags: