Public/Target/Get-TargetDataGoogle.ps1

<#
.SYNOPSIS
    Retrieves Google Workspace users, groups, group memberships, and organizational units.
 
.DESCRIPTION
    Connects to Google Workspace using the provided authentication headers and configuration.
    Returns a single object containing:
      - All users (with a CurrentGroups property listing their group memberships)
      - All groups (excluding "classroom_teachers")
      - All organizational units
 
    For each group, retrieves its members and builds a hashtable mapping user emails to their group memberships.
    Each user object in the returned collection has a CurrentGroups property containing the list of groups they belong to.
 
.OUTPUTS
    PSCustomObject
    An object with properties:
        - Users: Array of user objects, each with a CurrentGroups property.
        - Groups: Array of group objects (excluding "classroom_teachers").
        - OrgUnits: Array of organizational unit objects.
 
.EXAMPLE
    $googleData = Get-TargetDataGoogle
    $googleData.Users
    $googleData.Groups
    $googleData.OrgUnits
 
.NOTES
    Author: Sam Cattanach
    Requires: Google API access, supporting functions (Get-GoogleData, Write-Log)
#>


function Get-TargetDataGoogle {
    [CmdletBinding()]
    param ()

    Write-Log -Message "Google: Starting Google Target Data Retrieval" -Level Info

    #region Import Configuration
    try { $IDConfig = Get-IDBridgeConfig } catch { Throw $_ }
    #endregion Import Configuration

    #region Import Google Headers
    #Import Google API Headers (with access token)
    try { $headers = Get-GoogleHeaders } catch { Throw $_ }
    #endregion Import Google Headers

    #region Get Google Users
    try {
        Write-Log -Message "Google: Fetching Users" -Level Trace
        $googleUsers = Get-GoogleData -GoogleHeaders $headers -APIUri "https://www.googleapis.com/admin/directory/v1/users?customer=my_customer&maxResults=500"  -ErrorAction Stop

        Write-Log -Message "Google: Successfully fetched users" -Level Trace
    }
    catch {
        Throw $_
    }

    #Get Students & Staff Only
    $googleStudents = $googleUsers | Where-Object {$_.orgUnitPath -like "*/Students/*" -and $_.orgUnitPath -notlike "*Trash*"}
    $googleStaff = $googleUsers | Where-Object {$_.orgUnitPath -like "*/Staff/*" -and $_.orgUnitPath -notlike "*Trash*"}

    #endregion Get Google Users




    #region Google Groups and Memberships
    #Get Google Groups - Stores data in $googleGroups
    try {
        Write-Log -Message "Google: Fetching Groups" -Level Trace
        $googleGroups = Get-GoogleData -GoogleHeaders $headers -APIUri "https://www.googleapis.com/admin/directory/v1/groups?customer=my_customer&maxResults=500" -ErrorAction Stop

        #Remove Classroom Teachers Group
        $googleGroups = $googleGroups | Where-Object {$_.email -notlike "classroom_teachers@*"}

        Write-Log -Message "Google: Successfully fetched groups" -Level Trace
    }
    catch {
        Throw $_
    }





    <# This section has been modified to use ForEach-Object -Parallel for better performance
    #Get Google Group Memberships - Stores memberships in $userGoogleGroupsCurrent
    #Hashtable to store users and their groups
    $userGoogleGroupsCurrent = @{}
 
    #Loop through each group and retrieve its members
    foreach ($item in $googleGroups | Where-Object {$_.directMembersCount -ne 0}) {
        Write-Log -Message ("Google: Getting users for Group: " + $item.email) -Level Trace
 
        try {
            #Get group Memebers
            $groupMemberResults = $null
            $groupMemberResults = Get-GoogleData -GoogleHeaders $headers -APIUri ("https://admin.googleapis.com/admin/directory/v1/groups/" + $item.email + "/members?customer=my_customer&maxResults=500") -ErrorAction Stop
             
            foreach ($member in $groupMemberResults) {
                #Add user to hashtable, create entry if it doesn't exist
                if (-not $userGoogleGroupsCurrent.ContainsKey($member.email)) {
                    $userGoogleGroupsCurrent[$member.email] = @()
                }
 
                #Add the group to the user's list
                $userGoogleGroupsCurrent[$member.email] += $item.email
            }
        }
        catch {
            Throw (Write-Log -Message ("Google: No users retrieved for Group: " + $item.email) -Level Error)
        }
    }
 
    Write-Log -Message "Google: Successfully retrieved group memberships"
    #>





    Write-Log -Message "Google: Fetching Group Members" -Level Trace
    #Get Google Group Memberships - Stores memberships in $userGoogleGroupsCurrent
    $sharedLogs = [hashtable]::Synchronized(@{})

    foreach ($item in $googleGroups) {
        $item | Add-Member -MemberType NoteProperty -Name Headers -Value $headers -Force
        $item | Add-Member -MemberType NoteProperty -Name GetGoogleData -Value (Get-Command Get-GoogleData).Definition -Force
        $item | Add-Member -MemberType NoteProperty -Name TraceLogging -Value $IDConfig.Debug.TraceLogging -Force
    }

    #Loop through each group and retrieve its members
    $finalResults = $googleGroups | Where-Object {$_.directMembersCount -ne 0} | ForEach-Object -Parallel  {
        $null = New-Item -Path function: -Name 'Get-GoogleData' -Value ($_.GetGoogleData) -force

        $headers = $_.headers
        $email = $_.email

        if ($_.TraceLogging) {
            ($using:sharedLogs).(Get-Date -Format "yyyy-MM-dd_HH:mm:ss:ffff") = [PSCustomObject]@{
                Message    = ("Google: Getting users for Group: " + $email)
                Level      = "trace"
            }
        }

        try {
            #Get group Memebers
            $groupMemberResults = $null
            $groupMemberResults = Get-GoogleData -GoogleHeaders $headers -APIUri ("https://admin.googleapis.com/admin/directory/v1/groups/" + $email + "/members?customer=my_customer&maxResults=500") -ErrorAction Stop

            [PSCustomObject]@{
                GroupEmail = $email
                Members    = $groupMemberResults
            }

            if ($_.TraceLogging) {
                ($using:sharedLogs).(Get-Date -Format "yyyy-MM-dd_HH:mm:ss:ffff") = [PSCustomObject]@{
                    Message    = ("Google: Received $($groupMemberResults.count) users for Group: " + $email)
                    Level      = "trace"
                }
            }
        }
        catch {
            ($using:sharedLogs).(Get-Date -Format "yyyy-MM-dd_HH:mm:ss:ffff") = [PSCustomObject]@{
                Message    = ("Google: No users retrieved for Group: " + $email)
                Level      = "error"
            }

            ($using:sharedLogs).(Get-Date -Format "yyyy-MM-dd_HH:mm:ss:ffff") = [PSCustomObject]@{
                Message    = $_
                Level      = "error"
            }
        }
    } -ThrottleLimit 10

    #Write Logs from Parallel Processing
    foreach ($logEntry in $sharedLogs.GetEnumerator() | Sort-Object Name) {
        $entry = $logEntry.Value
        Write-Log -Message $entry.Message -Level $entry.Level
    }

    #Check for errors during parallel processing
    if ($sharedLogs.GetEnumerator() -like "*error*") {
        Throw "Errors occurred while retrieving group memberships. Check the log for details."
    }

    #Process final results into hashtable
    $userGoogleGroupsCurrent = @{}
    foreach ($item in $finalResults) {
        foreach ($member in $item.Members) {
            #Add user to hashtable, create entry if it doesn't exist
            if (-not $userGoogleGroupsCurrent.ContainsKey($member.email)) {
                $userGoogleGroupsCurrent[$member.email] = @()
            }

            #Add the group to the user's list
            $userGoogleGroupsCurrent[$member.email] += $item.GroupEmail
        }
    }
    Write-Log -Message "Google: Successfully fetched group members" -Level Trace








    #Add Groups to User Object
    foreach ($user in $googleUsers) {
        #Check if the user is in the hashtable and add groups if they exist
        if ($userGoogleGroupsCurrent.ContainsKey($user.primaryEmail)) {
            $user | Add-Member -MemberType NoteProperty -Name CurrentGroups -Value $userGoogleGroupsCurrent[$user.primaryEmail]
        } else {
            $user | Add-Member -MemberType NoteProperty -Name CurrentGroups -Value @()
        }
    }
    #endregion Google Groups and Memberships




    #region Get Google Org Units
    try {
        Write-Log -Message "Google: Fetching Org Units" -Level Trace
        $googleOrgUnits = Get-GoogleData -GoogleHeaders $headers -APIUri "https://admin.googleapis.com/admin/directory/v1/customer/my_customer/orgunits?type=all&maxResults=500" -ErrorAction Stop

        Write-Log -Message ("Google: Fetched $($googleOrgUnits.count) organizational units") -Level Trace
    }
    catch {
        Throw $_
    }
    #endregion Get Google Org Units




    #region Get Duplicate IDs
    #Get Google users with duplicate organization external ID
    $duplicateUsers = ($googleUsers | ForEach-Object {
        $pkID = ($_.externalIds | Where-Object { $_.Type -eq "organization" }).value
        if ($pkID) {
            [PSCustomObject]@{
                UPN = $_.primaryEmail
                FullName = $_.name.fullName
                OrgID = $pkID
            }
        }
    } | Group-Object -Property OrgID | Where-Object { $_.Count -gt 1 }).group

    if ($duplicateUsers) {
        Write-Log -Message ("Google: Users found with Duplicate External IDs: " + ($duplicateUsers | ConvertTo-Json -Compress))
    }
    #endregion Get Duplicate IDs




    #region Lookup Table Creation
    # Build the lookup tables once to make the search faster
    $googleUsersLookupByID = @{}
    foreach ($gUser in $googleUsers) {
        foreach ($extId in $gUser.externalIDs) {
            if ($extId.value -notin $duplicateUsers.OrgID) {
                $googleUsersLookupByID[$extId.value] = $gUser
            }
        }
    }
    #endregion Lookup Table Creation

    Write-Log -Message "Google: Finished Google Target Data Retrieval" -Level Info




    #Return a single object with all data
    return [PSCustomObject]@{
        Users             = $googleUsers
        Groups            = $googleGroups
        OrgUnits          = $googleOrgUnits
        DuplicateUsers    = $duplicateUsers
        LookupByID        = $googleUsersLookupByID
        Students          = $googleStudents
        Staff             = $googleStaff
    }
}