GoogleAdminDirectory.psm1

$script:GroupBaseUrl = "https://www.googleapis.com/admin/directory/v1/groups"
$script:UserBaseUrl = "https://www.googleapis.com/admin/directory/v1/users"
$script:OUBaseUrl = "https://www.googleapis.com/admin/directory/v1/customer"
$script:UserAgent = "PowerShell"
$script:UserAgentGzip = "PowerShell (gzip)"

#region Groups

Function New-GoogleDirectoryGroup {
    <#
        .SYNOPSIS
            Creates a new GSuite group.
 
        .DESCRIPTION
            This cmdlet creates a new GSuite group.
 
        .PARAMETER Email
            The group's email address.
 
        .PARAMETER Name
            The group's display name.
 
        .PARAMETER Description
            An optional description for the group.
 
        .PARAMETER PassThru
            If specified, the newly created group is passed to the pipeline.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $NewGroup = New-GoogleDirectoryGroup -Email examplegroup@google.com -Name "Example Group" -Description "A new group" -ClientId $Id -PassThru -Persist
 
            This example creates a new group and returns the newly created group details to the pipeline. The call is authenticated with
            an access token stored in a client profile, which is refreshed if necessary. Any updated tokens are persisted to disk.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$Email,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNull()]
        [System.String]$Name,

        [Parameter(Position = 2)]
        [ValidateNotNull()]
        [System.String]$Description,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base"
        [System.Collections.Hashtable]$Group = @{"email" = $Email; "name" = $Name}

        if (-not [System.String]::IsNullOrEmpty($Description))
        {
            $Group.Add("description", $Description)
        }

        [System.String]$Body = ConvertTo-Json -InputObject $Group -Compress -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Set-GoogleDirectoryGroup {
    <#
        .SYNOPSIS
            Updates a GSuite group properties.
 
        .DESCRIPTION
            This cmdlet updates a GSuite group.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER Email
            The group's new email address.
 
        .PARAMETER Name
            The group's new display name.
 
        .PARAMETER Description
            An updated description for the group.
 
        .PARAMETER PassThru
            If specified, the updated group is passed to the pipeline.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Set-GoogleDirectoryGroup -GroupKey NNN -Email updatedemail@google.com -ClientId $Id -Persist
 
            Updates the group uniquely identified by NNN with a new email, updatedemail@google.com. The call is authenticated with
            an access token stored in a client profile, which is refreshed if necessary. Any updated tokens are persisted to disk.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$GroupKey,

        [Parameter(Position = 1)]
        [ValidateNotNull()]
        [System.String]$Email,

        [Parameter(Position = 2)]
        [ValidateNotNull()]
        [System.String]$Name,

        [Parameter(Position = 3)]
        [System.String]$Description,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey"
        
        [System.Collections.Hashtable]$Group = @{}

        if ($PSBoundParameters.ContainsKey("GroupEmail"))
        {
            $Group.Add("email", $Email)
        }
        
        if ($PSBoundParameters.ContainsKey("GroupName"))
        {
            $Group.Add("name", $Name)
        }

        if ($PSBoundParameters.ContainsKey("Description"))
        {
            $Group.Add("description", $Description)
        }

        if ($Group -eq @{})
        {
            Write-Error -Exception (New-Object -TypeName System.ArgumentException("You must specify a property to update")) -ErrorAction Stop
        }

        [System.String]$Body = ConvertTo-Json -InputObject $Group -Compress -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Put -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function New-GoogleDirectoryGroupAlias {
    <#
        .SYNOPSIS
            Creates a GSuite group alias.
 
        .DESCRIPTION
            This cmdlet creates a new alias for a GSuite group.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER Alias
            The alias to create.
 
        .PARAMETER PassThru
            If specified, the updated group information is passed to the pipeline.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            New-GoogleDirectoryGroupAlias -GroupKey NNN -Alias bestgroupalias@google.com -ClientId $Id -Persist
 
            Creates a new alias for the group identified by the unique id NNN.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$GroupKey,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNull()]
        [System.String]$Alias,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey/aliases"

        [System.String]$Body = ConvertTo-Json -InputObject @{"alias" = $Alias } -Compress -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryGroupAlias {
    <#
        .SYNOPSIS
            Gets all GSuite group aliases for a specified group.
 
        .DESCRIPTION
            This cmdlet gets all of the aliases assigned to the specified group.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Aliases = Get-GoogleDirectoryGroupAlias -GroupKey NNN -ClientId $Id -Persist
 
            Gets a list of all group aliases for the group identified by NNN.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collection.Hashtable[]
 
            Each hashtable will follow this format:
 
            {
              "kind": "admin#directory#alias",
              "id": string,
              "etag": etag,
              "primaryEmail": string,
              "alias": string
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$GroupKey,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey/aliases"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent

            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            [System.Collections.Hashtable[]]$Aliases = @()

            foreach ($Alias in $ParsedResponse.aliases)
            {
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $Alias

                $Aliases += $Temp
            }

            Write-Output -InputObject $Aliases
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Remove-GoogleDirectoryGroupAlias {
    <#
        .SYNOPSIS
            Deletes a GSuite group alias.
 
        .DESCRIPTION
            This cmdlet deletes a specified alias from the GSuite group.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER AliasId
            The id of the alias to delete.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Remove-GoogleDirectoryGroupAlias -GroupKey NNN -AliasId "mygroupalias@google.com" -ClientId $Id -Persist
 
            Removes the specified alias from the group.
 
        .INPUTS
            None
         
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$GroupKey,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNull()]
        [System.String]$AliasId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey/aliases/$AliasId"

        try
        {
            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Delete -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryGroup {
    <#
        .SYNOPSIS
            Gets a GSuite group.
 
        .DESCRIPTION
            This cmdlet retrieves details about a specified GSuite group.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Group = Get-GoogleDirectoryGroup -GroupKey NNN -ClientId $Id -Persist
 
            Gets details about the group specified by key NNN.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collections.Hashtable
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$GroupKey,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent

            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

            Write-Output -InputObject $Temp
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryGroupList {
    <#
        .SYNOPSIS
            Gets a list of GSuite groups.
 
        .DESCRIPTION
            This cmdlet retrieves details about GSuite groups based on a customer id or domain id. The cmdlet defaults to using
            my_customer as the customer id, which uses the customer id of the user making the API call.
 
        .PARAMETER MaxResults
            The maximum number of results returned in a single call. Specifying a non-zero value for this parameter will page
            the results so that multiple HTTP calls are made to retrieve all of the results.
 
        .PARAMETER Domain
            Retrieves all groups for this sub-domain.
 
        .PARAMETER CustomerId
            Retrieves all groups in the account for this customer. This is the default and uses the value my_customer, which
            represents the customer id of the administrator making the API call.
         
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Groups = Get-GoogleDirectoryGroupList -ClientId $Id -Persist -UseCompression
 
            Gets a listing of all of the groups for the admin using the cmdlet. The results are returned using gzip to minimize
            bandwidth utilization.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collections.Hashtable[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable[]])]
    Param(
        [Parameter(Mandatory = $true, ParameterSetName = "TokenDefault")]
        [Parameter(Mandatory = $true, ParameterSetName = "TokenDomain")]
        [Parameter(Mandatory = $true, ParameterSetName = "TokenCustomerId")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "ProfileDefault")]
        [Parameter(ParameterSetName = "ProfileDomain")]
        [Parameter(ParameterSetName = "ProfileCustomerId")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "ProfileDefault")]
        [Parameter(ParameterSetName = "ProfileDomain")]
        [Parameter(ParameterSetName = "ProfileCustomerId")]
        [Switch]$Persist,

        [Parameter()]
        [System.UInt32]$MaxResults,

        [Parameter(Mandatory = $true, ParameterSetName = "TokenDomain")]
        [Parameter(Mandatory = $true, ParameterSetName = "ProfileDomain")]
        [ValidateNotNullOrEmpty()]
        [System.String]$Domain,

        [Parameter(Mandatory = $true, ParameterSetName = "TokenCustomerId")]
        [Parameter(Mandatory = $true, ParameterSetName = "ProfileCustomerId")]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("ProfileDomain", "ProfileDefault", "ProfileCustomerId") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -like "Profile*")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        switch -regex ($PSCmdlet.ParameterSetName)
        {
            "^.*Default$" {
                $Base += "?customer=my_customer"
                break
            }
            "^.*Domain$" {
                $Base += "?domain=$([System.Uri]::EscapeUriString($Domain))"
                break
            }
            "^.*CustomerId$" {
                $Base += "?customer=$([System.Uri]::EscapeUriString($CustomerId))"
                break
            }
            default {
                Write-Error -Message "Unknown parameter set $($PSCmdlet.ParameterSetName) for $($MyInvocation.MyCommand)." -ErrorAction Stop
            }
        }

        if ($PSBoundParameters.ContainsKey("MaxResults") -and $MaxResults -gt 0)
        {
            $Base += "&maxResults=$MaxResults"
        }

        $NextToken = $null
        [System.String]$Url = $Base
        [System.Collections.Hashtable[]]$Groups = @()

        $Headers = @{"Authorization" = "Bearer $BearerToken"}
        $UserAgent = $script:UserAgent

        if ($UseCompression)
        {
            $UserAgent = $script:UserAgentGzip
            $Headers.Add("Accept-Encoding", "gzip")
        }

        do {
            try
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -ErrorAction Stop -UserAgent $UserAgent
                
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
                
                foreach ($Group in $ParsedResponse.Groups)
                {
                    [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $Group

                    $Groups += $Temp
                }
                
                $NextToken = $ParsedResponse.nextPageToken
                $Url = "$Base&pageToken=$NextToken"
            }
            catch [System.Net.WebException] 
            {
                $NextToken = $null

                [System.Net.WebException]$Ex = $_.Exception
                [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
                [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
                [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
                [System.String]$Content = $Reader.ReadToEnd()
                [System.Int32]$StatusCode = $Response.StatusCode.value__
                [System.String]$Message = "$StatusCode : $Content"

                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                    Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $Message"
                }
            }
            catch [Exception] 
            {
                $NextToken = $null

                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    Write-Error -Exception $_.Exception -ErrorAction Stop
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $_.Exception.Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
                }
            }

        } while ($NextToken -ne $null)

        Write-Output -InputObject $Groups 
    }

    End {

    }
}

Function Get-GoogleDirectoryGroupsForUser {
    <#
        .SYNOPSIS
            Gets a list of GSuite groups assigned to a specified user.
 
        .DESCRIPTION
            This cmdlet retrieves details about GSuite groups assigned to a specified user.
 
        .PARAMETER MaxResults
            The maximum number of results returned in a single call. Specifying a non-zero value for this parameter will page
            the results so that multiple HTTP calls are made to retrieve all of the results.
 
        .PARAMETER UserId
            The id of the member to retrieve groups for. A member can either be a user or a group. The userKey can be the user's primary email address,
            the user's alias email address, a group's primary email address, a group's email alias, or the user's unique id.
         
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Groups = Get-GoogleDirectoryGroupList -ClientId $Id -Persist -UseCompression
 
            Gets a listing of all of the groups for the admin using the cmdlet. The results are returned using gzip to minimize
            bandwidth utilization.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collections.Hashtable[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$UserId,

        [Parameter()]
        [System.UInt32]$MaxResults,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Base = "$Base`?userKey=$UserId"
        $NextToken = $null
        [System.Collections.Hashtable[]]$Groups = @()

        $Headers = @{"Authorization" = "Bearer $BearerToken"}
        $UserAgent = $script:UserAgent

        if ($UseCompression)
        {
            $UserAgent = $script:UserAgentGzip
            $Headers.Add("Accept-Encoding", "gzip")
        }

        do
        {
            try
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent

                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                foreach ($Group in $ParsedResponse.Groups)
                {
                    [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $Group

                    $Groups += $Temp
                }

                $NextToken = $ParsedResponse.nextPageToken
                $Url = "$Base&pageToken=$NextToken"
            }
            catch [System.Net.WebException]
            {
                $NextToken = $null

                [System.Net.WebException]$Ex = $_.Exception
                [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
                [System.Int32]$StatusCode = $Response.StatusCode.value__
                [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
                [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
                [System.String]$Content = $Reader.ReadToEnd()
                
                [System.String]$Message = "$StatusCode : $Content"

                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                    Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $Message"
                }
            }
            catch [Exception] 
            {
                $NextToken = $null

                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    Write-Error -Exception $_.Exception -ErrorAction Stop
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $_.Exception.Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
                }
            }
        } while ($NextToken -ne $null)

        Write-Output -InputObject $Groups
    }

    End {
    }
}

Function Remove-GoogleDirectoryGroup {
    <#
        .SYNOPSIS
            Deletes a GSuite group.
 
        .DESCRIPTION
            This cmdlet deletes a GSuite group.
 
        .PARAMETER GroupKey
            The unique Id of the group to delete.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Remove-GoogleDirectoryGroup -GroupKey NNN -ClientId $Id -Persist
 
            Deletes the specified group.
 
        .INPUTS
            None
         
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/6/2018
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "HIGH")]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$GroupKey,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$Force
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey"

        try
        {
            $ConfirmMessage = "You are about to delete GSuite group $GroupKey."
            $WhatIfDescription = "Deleted group $GroupKey."
            $ConfirmCaption = "Delete GSuite Group"

            if ($Force -or $PSCmdlet.ShouldProcess($WhatIfDescription, $ConfirmMessage, $ConfirmCaption))
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Delete -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

#endregion

#region Group Membership

Function Add-GoogleDirectoryGroupMember {
    <#
        .SYNOPSIS
            Adds a member GSuite group.
 
        .DESCRIPTION
            This cmdlet adds a member to a GSuite group.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER Role
            The role to add the member as, either MEMBER, OWNER, or MANAGER. The default is MEMBER.
 
        .PARAMETER UserId
            The Id of the user to add to the group.
 
        .PARAMETER PassThru
            If specified, the member's membership information is passed to the pipeline.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $MembershipInfo = Add-GoogleDirectoryGroupMember -GroupKey NNN -UserId user@google.com -ClientId $Id -Persist -PassThru
 
            This example adds user@google.com to the group identified by NNN and returns the user's membership info for the group to
            the pipeline. The call is authenticated with an access token stored in a client profile, which is refreshed if necessary.
            Any updated tokens are persisted to disk.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
            This is an example of the member's membership information output:
            {
               "kind": "directory#member",
               "id": "group member's unique ID",
               "email": "liz@example.com",
               "role": "MEMBER",
               "type": "GROUP"
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$GroupKey,

        [Parameter()]
        [ValidateSet("OWNER", "MANAGER", "MEMBER")]
        [System.String]$Role = "MEMBER",

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey/members"

        [System.String]$Body = ConvertTo-Json -InputObject @{"email" = $UserId; "role" = $Role } -Compress -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Body $Body -Headers $Headers -UserAgent $UserAgent
            
            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content

                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Set-GoogleDirectoryGroupMemberRole {
    <#
        .SYNOPSIS
            Sets the role of a group member.
 
        .DESCRIPTION
            This cmdlet sets the role of an existing GSuite group member.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER Role
            The role the member will be set to, either MEMBER, OWNER, or MANAGER.
 
        .PARAMETER UserId
            The Id of the user whose role will be modified.
 
        .PARAMETER PassThru
            If specified, the member's membership information is passed to the pipeline.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $MembershipInfo = Set-GoogleDirectoryGroupMemberRole -GroupKey NNN -UserId user@google.com -Role MANAGER -ClientId $Id -Persist -PassThru
 
            This example changes the member user@google.com from MEMBER to MANAGER in the group identified by NNN and returns the user's membership info for the group to
            the pipeline. The call is authenticated with an access token stored in a client profile, which is refreshed if necessary.
            Any updated tokens are persisted to disk.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
            This is an example of the member's membership information output:
            {
               "kind": "directory#member",
               "id": "group member's unique ID",
               "email": "liz@example.com",
               "role": "MEMBER",
               "type": "GROUP"
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$GroupKey,

        [Parameter(Mandatory = $true)]
        [ValidateSet("OWNER", "MANAGER", "MEMBER")]
        [System.String]$Role,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression,

        [Parameter()]
        [Switch]$PassThru
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey/members/$UserId"

        [System.String]$Body = ConvertTo-Json -InputObject @{"email" = $UserId; "role" = $Role } -Compress -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Put -Body $Body -Headers $Headers -UserAgent $UserAgent
            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content

            [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

            Write-Output -InputObject $Temp
                
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryGroupMembership {
    <#
        .SYNOPSIS
            Gets the membership of a GSuite group.
 
        .DESCRIPTION
            This cmdlet gets the membership of a GSuite group.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER Roles
            Filter the results to members with the specified roles. If this is not specified, no filter is applied.
 
        .PARAMETER MaxResults
            The maximum number of results returned in a single call. Specifying a non-zero value for this parameter will page
            the results so that multiple HTTP calls are made to retrieve all of the results.
             
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Membership = Get-GoogleDirectoryGroupMembership -GroupKey NNN -ClientId $Id -Persist -UseCompression
 
            This example gets the membership of the group specified by NNN and compresses the returned results. The call
            is authenticated with an access token stored in a client profile, which is refreshed if necessary. Any updated tokens are persisted to disk.
 
        .INPUTS
            System.String
         
        .OUTPUTS
            System.Collections.Hashtable[]
 
            This is an example of the membership information output:
            {
                "kind": "directory#member",
                "id": "group member's unique ID",
                "email": "liz@example.com",
                "role": "MANAGER",
                "type": "GROUP"
            },
            {
                "kind": "directory#member",
                "id": "group member's unique ID",
                "email": "radhe@example.com",
                "role": "MANAGER",
                "type": "MEMBER"
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable[]])]
    Param(
        [Parameter()]
        [System.UInt32]$MaxResults,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$GroupKey,

        [Parameter()]
        [ValidateSet("OWNER", "MANAGER", "MEMBER")]
        [System.String[]]$Roles = @("MEMBER"),

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$RoleQuery = [System.Uri]::EscapeUriString(($Roles -join ","))
        [System.String]$Url = "$Base/$GroupKey/members?roles=$RoleQuery"

        if ($PSBoundParameters.ContainsKey("MaxResults") -and $MaxResults -gt 0)
        {
            $Url += "&maxResults=$MaxResults"
        }

        $NextToken = $null
        $Temp = $Url

        [System.Collections.Hashtable[]]$Members = @()

        $Headers = @{"Authorization" = "Bearer $BearerToken"}
        $UserAgent = $script:UserAgent

        if ($UseCompression)
        {
            $UserAgent = $script:UserAgentGzip
            $Headers.Add("Accept-Encoding", "gzip")
        }

        do {
            try
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Temp -Method Get -Headers $Headers -UserAgent $UserAgent
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content

                foreach ($Member in $ParsedResponse.Members)
                {
                    [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $Member

                    $Members += $Temp
                }

                $NextToken = $ParsedResponse.nextPageToken
                $Temp = "$Url&pageToken=$NextToken"
            }
            catch [System.Net.WebException]
            {
                $NextToken = $null

                [System.Net.WebException]$Ex = $_.Exception
                [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
                [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
                [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
                [System.String]$Content = $Reader.ReadToEnd()
                [System.Int32]$StatusCode = $Response.StatusCode.value__
                [System.String]$Message = "$StatusCode : $Content"

                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                    Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $Message"
                }
            }
            catch [Exception] 
            {
                $NextToken = $null

                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    Write-Error -Exception $_.Exception -ErrorAction Stop
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $_.Exception.Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
                }
            }
        } while ($NextToken -ne $null)

        Write-Output -InputObject $Members 
    }

    End {
    }
}

Function Remove-GoogleDirectoryGroupMember {
    <#
        .SYNOPSIS
            Removes a member from a GSuite group.
 
        .DESCRIPTION
            This cmdlet removes a member from a GSuite group.
 
        .PARAMETER GroupKey
            The unique Id of the group.
 
        .PARAMETER UserId
            The Id of the user to remove from the group.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Remove-GoogleDirectoryGroupMember -GroupKey NNN -UserId user@google.com -ClientId $Id -Persist -UseCompression
 
            This example removes user@google.com from the group specified by NNN. The call is authenticated with an access
            token stored in a client profile, which is refreshed if necessary. Any updated tokens are persisted to disk.
 
        .INPUTS
            None
         
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$GroupKey,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:GroupBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$GroupKey/members/$UserId"

        try
        {
            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Delete -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

#endregion

#region Users

Function New-GoogleDirectoryUser {
    <#
        .SYNOPSIS
            Creates a new GSuite user account.
 
        .DESCRIPTION
            This cmdlet creates a new user account.
 
            - If the Google account has purchased mail licenses, the new user account is automatically assigned a mailbox. This assignment may take a few minutes to be completed and activated.
            - Editing a read-only field in a request, such as isAdmin, is silently ignored by the API service.
            - The maximum number of domains allowed in an account is 600 (1 primary domain + 599 additional domains)
            - If a user was not assigned to a specific organizational unit when the user account was created, the account is in the top-level organizational unit. A user's organizational unit determines which G Suite services the user has access to. If the user is moved to a new organization, the user's access changes. For more information about organization structures, see the administration help center. For more infomation about moving a user to a different organization, see Update a user.
            - A password is required for new user accounts. If a hashFunction is specified, the password must be a valid hash key. For more information, see the API Reference.
            - For users on a flexible plan for G Suite, creating users using this API will have monetary impact, and will result in charges to your customer billing account. For more information, see the API billing information.
            - A G Suite account can include any of your domains. In a multiple domain account, users in one domain can share services with users in other account domains. For more information about users in multiple domains, see the API multiple domain information.
 
        .PARAMETER User
            The properties of the user account to create. Only the required properties need to be specified.
 
            {
            "primaryEmail": "liz@example.com",
            "name": {
             "givenName": "Elizabeth",
             "familyName": "Smith"
            },
            "suspended": false,
            "password": "new user password",
            "hashFunction": "SHA-1",
            "changePasswordAtNextLogin": false,
            "ipWhitelisted": false,
            "ims": [
             {
              "type": "work",
              "protocol": "gtalk",
              "im": "liz_im@talk.example.com",
              "primary": true
             }
            ],
            "emails": [
             {
              "address": "liz@example.com",
              "type": "home",
              "customType": "",
              "primary": true
             }
            ],
            "addresses": [
             {
              "type": "work",
              "customType": "",
              "streetAddress": "1600 Amphitheatre Parkway",
              "locality": "Mountain View",
              "region": "CA",
              "postalCode": "94043"
             }
            ],
            "externalIds": [
             {
              "value": "12345",
              "type": "custom",
              "customType": "employee"
             }
            ],
            "relations": [
             {
              "value": "Mom",
              "type": "mother",
              "customType": ""
             },
             {
              "value": "manager",
              "type": "referred_by",
              "customType": ""
             }
            ],
            "organizations": [
             {
              "name": "Google Inc.",
              "title": "SWE",
              "primary": true,
              "type": "work",
              "description": "Software engineer"
             }
            ],
            "phones": [
             {
              "value": "+1 nnn nnn nnnn",
              "type": "work"
             }
            ],
            "orgUnitPath": "/corp/engineering",
            "includeInGlobalAddressList": true
            }
 
        .PARAMETER MaximumRetries
            If the query rate for creation requests is too high, you might receive HTTP 503 responses from the API server
            indicating that your quota has been exceeded. This value is the number of times the request will be retried
            using an exponential backoff.
 
        .PARAMETER PassThru
            If specified, the new user account properties are returned to the pipeline.
         
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $NewUser = New-GoogleDirectoryUser -User $User -ClientId $Id -Persist -UseCompression -PassThru
 
            Creates a new GSuite user with the properties specified in the $User variable.
 
        .INPUTS
            System.Collections.Hashtable
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNull()]
        [System.Collections.Hashtable]$User,

        [Parameter()]
        [ValidateRange(1, 5)]
        [System.Int32]$MaximumRetries = 3,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId"
        [System.String]$Body = ConvertTo-Json -InputObject $UserProperties -Compress -Depth 3

        $Success = $false
        $Counter = 0

        $Headers = @{"Authorization" = "Bearer $BearerToken"}
        $UserAgent = $script:UserAgent

        if ($UseCompression)
        {
            $UserAgent = $script:UserAgentGzip
            $Headers.Add("Accept-Encoding", "gzip")
        }

        while (-not $Success -and $Counter -le $MaximumRetries)
        {
            try
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Body $Body -Headers $Headers -UserAgent $UserAgent

                if ($Response.StatusCode -eq 201)
                {
                    if ($PassThru)
                    {
                        [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                        [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                        Write-Output -InputObject $Temp
                    }
                }
                else
                {
                    # Exponential backoff
                    [System.Double]$MilliSeconds = [System.Math]::Pow(2,  $Counter) * 1000
                    [System.Double]$RandomMilliseconds = Get-Random -Minimum 0 -Maximum 1000
                    $MilliSeconds += $RandomMilliseconds

                    Start-Sleep -Milliseconds $MilliSeconds

                    $Counter++
                }
            }
            catch [System.Net.WebException]
            {
                [System.Net.WebException]$Ex = $_.Exception
                [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
                [System.Int32]$StatusCode = $Response.StatusCode.value__

                # A 503 is returned for creation request rate being too high, use an
                # exponential backoff and try again
                # Enter here if we didn't get a 503, or we have used up all the retries
                if ($StatusCode -ne 503)
                {
                    [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
                    [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
                    [System.String]$Content = $Reader.ReadToEnd()
                
                    [System.String]$Message = "$StatusCode : $Content"

                    if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                    {
                        [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                        Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
                    }
                    elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                    {
                        Write-Warning -Message $Message
                    }
                    else
                    {
                        Write-Verbose -Message "[ERROR] : $Message"
                    }

                    # Break out of the while loop
                    break
                }
                else
                {
                    # Exponential backoff
                    [System.Double]$MilliSeconds = [System.Math]::Pow(2,  $Counter) * 1000
                    [System.Double]$RandomMilliseconds = Get-Random -Minimum 0 -Maximum 1000
                    $MilliSeconds += $RandomMilliseconds

                    Start-Sleep -Milliseconds $MilliSeconds

                    $Counter++
                }
            }
            catch [Exception] 
            {
                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    Write-Error -Exception $_.Exception -ErrorAction Stop
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $_.Exception.Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
                }

                # Break out of the while loop
                break 
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryUser {
    <#
        .SYNOPSIS
            Gets the properties of a GSuite user account.
 
        .DESCRIPTION
            This gets a GSuite user account.
 
        .PARAMETER UserId
            The id of the user to retrieve. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .INPUTS
            System.String
         
        .OUTPUTS
            System.Collections.Hashtable
 
            This is an example of the user output:
            {
            "primaryEmail": "liz@example.com",
            "name": {
             "givenName": "Elizabeth",
             "familyName": "Smith"
            },
            "suspended": false,
            "password": "new user password",
            "hashFunction": "SHA-1",
            "changePasswordAtNextLogin": false,
            "ipWhitelisted": false,
            "ims": [
             {
              "type": "work",
              "protocol": "gtalk",
              "im": "liz_im@talk.example.com",
              "primary": true
             }
            ],
            "emails": [
             {
              "address": "liz@example.com",
              "type": "home",
              "customType": "",
              "primary": true
             }
            ],
            "addresses": [
             {
              "type": "work",
              "customType": "",
              "streetAddress": "1600 Amphitheatre Parkway",
              "locality": "Mountain View",
              "region": "CA",
              "postalCode": "94043"
             }
            ],
            "externalIds": [
             {
              "value": "12345",
              "type": "custom",
              "customType": "employee"
             }
            ],
            "relations": [
             {
              "value": "Mom",
              "type": "mother",
              "customType": ""
             },
             {
              "value": "manager",
              "type": "referred_by",
              "customType": ""
             }
            ],
            "organizations": [
             {
              "name": "Google Inc.",
              "title": "SWE",
              "primary": true,
              "type": "work",
              "description": "Software engineer"
             }
            ],
            "phones": [
             {
              "value": "+1 nnn nnn nnnn",
              "type": "work"
             }
            ],
            "orgUnitPath": "/corp/engineering",
            "includeInGlobalAddressList": true
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType()]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent
            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

            Write-Output -InputObject $Temp
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Set-GoogleDirectoryUser {
    <#
        .SYNOPSIS
            Updates a user account properties.
 
        .DESCRIPTION
            This cmdlet updates a user account properties.
 
            In general, Google recommends to not use the user email address as a key for persistent data since the email address is subject to change.
 
            - Renaming a user account changes the user's primary email address and the domain used when retrieving this user's information. Before renaming a user, we recommend that you log out the user from all browser sessions and services. For instance, you can get the user on your support desk telephone line during the rename process to ensure they have logged out.
            - The process of renaming a user account can take up to 10 minutes to propagate across all services.
            - When a user is renamed, the old user name is retained as a alias to ensure continuous mail delivery in the case of email forwarding settings and will not be available as a new user name.
            - In general, we also recommend to not use the user email address as a key for persistent data since the email address is subject to change.
 
        .PARAMETER UserId
            The Id of the user to be updated. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER UserProperties
            For an update request, you only need to submit the updated information in your request. Even though
            the example data listed below is verbose, you do not need to enter all of the user's properties.
 
            {
            "primaryEmail": "liz@example.com",
            "name": {
             "givenName": "Liz",
             "familyName": "Smith"
            },
            "suspended": false,
            "password": "updated password",
            "hashFunction": "SHA-1",
            "changePasswordAtNextLogin": true,
            "ipWhitelisted": false,
            "ims": [
             {
              "type": "work",
              "protocol": "gtalk",
              "im": "newim@talk.example.com",
              "primary": true
             },
             {
              "type": "home",
              "protocol": "aim",
              "im": "newaim@aim.example.com",
              "primary": false
             }
            ],
            "emails": [
             {
              "address": "liz@example.com",
              "type": "home",
              "customType": "",
              "primary": true
             }
            ],
            "addresses": [
             {
              "type": "work",
              "customType": "",
              "streetAddress": "1600 Amphitheatre Parkway",
              "locality": "Mountain View",
              "region": "CA",
              "postalCode": "94043"
             }
            ],
            "externalIds": [
             {
              "value": "12345",
              "type": "custom",
              "customType": "employee"
             }
            ],
            "relations": [
             {
              "value": "susan",
              "type": "friend",
              "customType": ""
             }
            ],
            "organizations": [
             {
              "name": "Google Inc.",
              "title": "SWE",
              "primary": true,
              "type": "work",
              "description": "Software engineer"
             }
            ],
            "phones": [
             {
              "value": "+1 206 555 nnnn",
              "type": "work"
             },
             {
              "value": "+1 602 555 nnnn",
              "type": "home"
             }
            ],
             "orgUnitPath": "/corp/engineering",
             "includeInGlobalAddressList": true
            }
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .PARAMETER PassThru
            If specified, the updated user properties are returned to the pipeline.
 
        .EXAMPLE
            Set-GoogleDirectoryUser -UserId liz@example.com -UserProperties @{"name" = @{ "givenName" = "Elizabeth"; "familyName" = "Smith"} } -ClientId $Id -Persist
 
            Updates the name for the user liz@example.com.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Collections.Hashtable]$UserProperties,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId"
        [System.String]$Body = ConvertTo-Json -InputObject $UserProperties -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Put -Body $Body -Headers $Headers -UserAgent $UserAgent
            
            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryUserList {
    <#
        .SYNOPSIS
            Gets a list of user account details.
 
        .DESCRIPTION
            This cmdlet retrieves all users in a domain or for a specific customer Id. By default, the customer Id for the
            current administrator is used and 100 results are returned per API call in alphabetical order of the user's email address.
         
            The cmdlet will continue to make API calls on your behalf until all user accounts are retrieved. This can be a lot of data
            and the use of the -UseCompression parameter is highly suggested.
 
        .PARAMETER MaxResults
            The maximum number of results returned in a single call. Specifying a non-zero value for this parameter will page
            the results so that multiple HTTP calls are made to retrieve all of the results. This defaults to 100.
 
        .PARAMETER Domain
            Retrieves all users for this sub-domain.
 
        .PARAMETER CustomerId
            Retrieves all users in the account for this customer. This is the default and uses the value my_customer, which
            represents the customer id of the administrator making the API call.
 
        .PARAMETER OrderBy
            Specifies the property used to order the results. This defaults to email.
 
        .PARAMETER SortOrder
            If an OrderBy property is specified, then this specifies the order the results are sorted in. If an OrderBy property
            is not specified, this property is ignored.
 
        .PARAMETER Query
            The optional query query string allows searching over many fields in a user profile, including both core and custom fields.
            See https://developers.google.com/admin-sdk/directory/v1/guides/search-users for examples.
 
        .PARAMETER ShowDeleted
            If this is specified, users deleted within the last 5 days are shown instead of non-deleted users.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .PARAMETER PassThru
            If specified, the updated user properties are returned to the pipeline.
 
        .EXAMPLE
            $Users = Get-GoogleDirectoryUserList -ClientId $Id -Persist -UseCompression
 
            Gets all users in the same account as the administrator making the call. The results are compressed in flight.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collections.Hashtable[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    Param(
         [Parameter()]
        [System.UInt32]$MaxResults,

        [Parameter(Mandatory = $true, ParameterSetName = "TokenDomain")]
        [Parameter(Mandatory = $true, ParameterSetName = "ProfileDomain")]
        [ValidateNotNullOrEmpty()]
        [System.String]$Domain,

        [Parameter(Mandatory = $true, ParameterSetName = "TokenCustomerId")]
        [Parameter(Mandatory = $true, ParameterSetName = "ProfileCustomerId")]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId,

        [Parameter(ParameterSetName = "TokenDefault")]
        [Parameter(ParameterSetName = "ProfileDefault")]
        [Parameter(ParameterSetName = "TokenCustomerId")]
        [Parameter(ParameterSetName = "ProfileCustomerId")]
        [ValidateSet("email", "givenName", "familyName")]
        [System.String]$OrderBy,

        [Parameter(ParameterSetName = "TokenDefault")]
        [Parameter(ParameterSetName = "ProfileDefault")]
        [Parameter(ParameterSetName = "TokenCustomerId")]
        [Parameter(ParameterSetName = "ProfileCustomerId")]
        [ValidateSet("ascending", "descending")]
        [System.String]$SortOrder,

        [Parameter(ParameterSetName = "TokenDefault")]
        [Parameter(ParameterSetName = "ProfileDefault")]
        [Parameter(ParameterSetName = "TokenCustomerId")]
        [Parameter(ParameterSetName = "ProfileCustomerId")]
        [ValidateNotNullOrEmpty()]
        [System.String]$Query,

        [Parameter()]
        [Switch]$ShowDeleted,

        [Parameter(Mandatory = $true, ParameterSetName = "TokenDefault")]
        [Parameter(Mandatory = $true, ParameterSetName = "TokenDomain")]
        [Parameter(Mandatory = $true, ParameterSetName = "TokenCustomerId")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "ProfileDefault")]
        [Parameter(ParameterSetName = "ProfileDomain")]
        [Parameter(ParameterSetName = "ProfileCustomerId")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "ProfileDefault")]
        [Parameter(ParameterSetName = "ProfileDomain")]
        [Parameter(ParameterSetName = "ProfileCustomerId")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("ProfileDomain", "ProfileDefault", "ProfileCustomerId") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -like "Profile*")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        switch -regex ($PSCmdlet.ParameterSetName)
        {
            "^.*Default$" {
                $Base += "?customer=my_customer"
                break
            }
            "^.*Domain$" {
                $Base += "?domain=$([System.Uri]::EscapeUriString($Domain))"
                break
            }
            "^.*CustomerId$" {
                $Base += "?customer=$([System.Uri]::EscapeUriString($CustomerId))"
                break
            }
            default {
                Write-Error -Message "Unknown parameter set $($PSCmdlet.ParameterSetName) for $($MyInvocation.MyCommand)." -ErrorAction Stop
            }
        }

        if ($PSBoundParameters.ContainsKey("MaxResults") -and $MaxResults -gt 0)
        {
            $Base += "&maxResults=$MaxResults"
        }

        if ($PSBoundParameters.ContainsKey("OrderBy"))
        {
            $Base += "&orderBy=$OrderBy"

            if ($PSBoundParameters.ContainsKey("SortOrder"))
            {
                $Base += "&sortOrder=$SortOrder"
            }
        }

        if ($ShowDeleted)
        {
            $Base += "&showDeleted=true"
        }

        $NextToken = $null
        [System.String]$Url = $Base
        [System.Collections.Hashtable[]]$Users = @()

        $Headers = @{"Authorization" = "Bearer $BearerToken"}
        $UserAgent = $script:UserAgent

        if ($UseCompression)
        {
            $UserAgent = $script:UserAgentGzip
            $Headers.Add("Accept-Encoding", "gzip")
        }

        do {
            try
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -ErrorAction Stop -UserAgent $UserAgent
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content

                foreach ($User in $ParsedResponse.Users)
                {
                    [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $User

                    $Users += $Temp
                }

                $NextToken = $ParsedResponse.nextPageToken
                $Url = "$Base&pageToken=$NextToken"
            }
            catch [System.Net.WebException] 
            {
                $NextToken = $null

                [System.Net.WebException]$Ex = $_.Exception
                [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
                [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
                [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
                [System.String]$Content = $Reader.ReadToEnd()
                [System.Int32]$StatusCode = $Response.StatusCode.value__
                [System.String]$Message = "$StatusCode : $Content"

                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                    Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $Message"
                }
            }
            catch [Exception] 
            {
                $NextToken = $null

                if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
                {
                    Write-Error -Exception $_.Exception -ErrorAction Stop
                }
                elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
                {
                    Write-Warning -Message $_.Exception.Message
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
                }
            }

        } while ($NextToken -ne $null)

        Write-Output -InputObject $Users 
    }

    End {

    }
}

Function Remove-GoogleDirectoryUser {
    <#
        .SYNOPSIS
            Deletes a GSuite user.
 
        .DESCRIPTION
            This cmdlet deletes a GSuite user. After the user is deleted, they will no longer be able to login in. A deleted
            user account can be restored within 5 days of deletion.
 
        .PARAMETER UserId
            The Id of the user to be deleted. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Remove-GoogleDirectoryUser -UserId liz@example.com -ClientId $Id -Persist -Force
 
            This deletes the user liz@example.com using stored client credentials and bypasses confirmation.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "HIGH")]
    [OutputType()]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$Force
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId"

        try
        {
            $ConfirmMessage = "You are about to delete GSuite user $UserId."
            $WhatIfDescription = "Deleted user $UserId"
            $ConfirmCaption = "Delete GSuite User"

            if ($Force -or $PSCmdlet.ShouldProcess($WhatIfDescription, $ConfirmMessage, $ConfirmCaption))
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Delete -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Restore-GoogleDirectoryUser {
    <#
        .SYNOPSIS
            Restores a deleted GSuite user.
 
        .DESCRIPTION
            This cmdlet restores a deleted GSuite user that has been deleted within the last 5 days.
 
        .PARAMETER UserId
            The Id of the user to restore. The UserId is the unique user id found in the response of the retrieve
            users deleted within the past 5 days operation. The user's primary email address or one of the user's
            alias email addresses cannot be used in the UserId for this operation.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Restore-GoogleDirectoryUser -UserId 12309329403209438205 -ClientId $Id -Persist -Force
 
            This restores (undeletes) the user 12309329403209438205 using stored client credentials and bypasses confirmation.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "HIGH")]
    [OutputType()]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$Force
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId/undelete"

        try
        {
            $ConfirmMessage = "You are about to restore GSuite user $UserId."
            $WhatIfDescription = "Restored user $UserId"
            $ConfirmCaption = "Restore GSuite User"

            if ($Force -or $PSCmdlet.ShouldProcess($WhatIfDescription, $ConfirmMessage, $ConfirmCaption))
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Invoke-GoogleDirectoryMakeAdmin {
    <#
        .SYNOPSIS
            Makes a GSuite users a super administrator.
 
        .DESCRIPTION
            This cmdlet elevates a user's permissions to the super administrator role.
 
        .PARAMETER UserId
            The Id of the user to make a super administrator. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Invoke-GoogleDirectoryMakeAdmin -UserId liz@example.com -ClientId $Id -Persist -Force
 
            This makes the user liz@example.com a super administrator using stored client credentials and bypasses confirmation.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "HIGH")]
    [OutputType([System.Boolean])]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$Force
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId/makeAdmin"

        try
        {
            $ConfirmMessage = "You are about to make $UserId a super administrator."
            $WhatIfDescription = "Made user $UserId a super administrator"
            $ConfirmCaption = "Make Super Administrator"

            if ($Force -or $PSCmdlet.ShouldProcess($WhatIfDescription, $ConfirmMessage, $ConfirmCaption))
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                if ($Response.StatusCode -eq 200)
                {
                    [System.Boolean]$Success = $ParsedResponse.status

                    Write-Output -InputObject $Success
                }
                else
                {
                    Write-Verbose -Message "[ERROR] : Make super administrator did not return a 200 response: $(ConvertTo-Json -InputObject $ParsedResponse)"
                    Write-Output -InputObject $false
                }
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

#endregion

#region User Aliases

Function New-GoogleDirectoryUserAlias {
    <#
        .SYNOPSIS
            Creates a new alias for a GSuite user.
 
        .DESCRIPTION
            This cmdlet creates a new alias for a GSuite user. The maximum number of aliases per user is 30.
 
        .PARAMETER UserId
            The Id of the user to create an alias for. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER UserAlias
            The email alias to create for the user, like elizabeth@example.com.
 
        .PARAMETER PassThru
            If specified the new user alias is returned to the pipeline.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .EXAMPLE
            New-GoogleDirectoryUserAlias -UserId liz@example.com -UserAlias elizabeth@example.com -ClientId $Id -Persist
 
            This creates a new alias, elizabeth@example.com, for the user liz@example.com using stored client credentials and bypasses confirmation.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or System.Collections.Hashtable
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNull()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNull()]
        [System.String]$UserAlias,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId/aliases"
        [System.String]$Body = ConvertTo-Json -InputObject @{"alias" = $UserAlias} -Compress -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
                
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryUserAlias {
    <#
        .SYNOPSIS
            Retrieves all of the aliases for a GSuite user.
 
        .DESCRIPTION
            This cmdlet retrieves all of the aliases for a GSuite user. All user aliases are returned in
            alphabetical order. There is no page size such as the maxResults query string or pagination used
            for the 'Retrieve all aliases' response. The returned aliases are the editable user email alias in
            the account's primary domain or subdomains.
 
        .PARAMETER UserId
            The Id of the user to get the aliases of. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .EXAMPLE
            $Aliases = Get-GoogleDirectoryUserAlias -UserId liz@example.com -ClientId $Id -Persist
 
            Gets all of the aliases for the user liz@example.com using stored client credentials and bypasses confirmation.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            System.Collections.Hashtable[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable[]])]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId/aliases"
        [System.Collections.Hashtable[]]$Aliases = @()

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent
            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            foreach ($Alias in $ParsedResponse.aliases)
            {
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $Alias

                $Aliases += $Temp
            }

            Write-Output -InputObject $Aliases
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Remove-GoogleDirectoryUserAlias {
    <#
        .SYNOPSIS
            Deletes an alias for a GSuite user.
 
        .DESCRIPTION
            This cmdlet deletes a specified alias for a GSuite user.
 
        .PARAMETER UserId
            The Id of the user to delete an alias for. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER UserAlias
            The alias to delete. This parameter is the alias' email address that is being deleted.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Remove-GoogleDirectoryUserAlias -UserId liz@example.com -UserAlias elizabeth@example.com -ClientId $Id -Persist -Force
 
            Deletes the elizabeth@example.com alias for the user liz@example.com using stored client credentials and bypasses confirmation.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserAlias,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId/aliases/$UserAlias"
        [System.Collections.Hashtable[]]$Aliases = @()

        try
        {
            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Delete -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell            
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

#endregion

#region User Photos

Function Get-GoogleDirectoryUserPhoto {
    <#
        .SYNOPSIS
            Gets one photo thumbnail for a GSuite user.
 
        .DESCRIPTION
            This cmdlet retrieves one photo thumbnail, the lastest Gmail Chat profile photo for a user. The details of the photo and
            the photo base64 data are returned to the pipeline. Optionally, the photo data can also be written out to a file. If you choose
            to write the photo data to a file, you should check the mimeType property returned with the object to make sure you use
            the correct file extension.
 
            The output photoData property of the object returned to the pipeline is web safe base64 encoded, meaning:
 
            - The slash (/) character is replaced with the underscore (_) character.
            - The plus sign (+) character is replaced with the hyphen (-) character.
            - The equals sign (=) character is replaced with the asterisk (*).
            - For padding, the period (.) character is used instead of the RFC-4648 baseURL definition which uses the equals sign (=) for padding. This is done to simplify URL-parsing.
            - Whatever the size of the photo being uploaded, the API downsizes it proportionally to 96x96 pixels.
         
        .PARAMETER UserId
            The Id of the user to get the photo of. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER OutFile
            The path to a file where the photo data will be written. If the directory path does not exist, it will be created.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .EXAMPLE
            $PhotoData = Get-GoogleDirectoryUserPhoto -UserId liz@example.com -ClientId $Id -Persist
                 
            Gets the photo data for the user liz@example.com using stored client credentials and bypasses confirmation.
 
        .EXAMPLE
            $PhotoData = Get-GoogleDirectoryUserPhoto -OutFile "c:\users\liz\desktop\thumbnail.txt" -UserId liz@example.com -ClientId $Id -Persist
            $MimeType = $PhotoData["mimeType"]
 
            Gets the photo data for the user liz@example.com using stored client credentials and bypasses confirmation. The base64 data
            is written to the specified file. The mimetype should be checked to adjust the extension of the output file to make it viewable.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            System.Collections.Hashtable
         
            This is a JSON representation of the output data:
            {
             "kind": "directory#user#photo",
             "id": "the unique user id",
             "primaryEmail": "liz@example.com",
             "mimeType": "the photo mime type",
             "height": "the photo height in pixels",
             "width": "the photo width in pixels",
             "photoData": "web safe base64 encoded photo data"
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$OutFile,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -like "Profile*")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId/photos/thumbnail"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent
            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            if (-not [System.String]::IsNullOrEmpty($ParsedResponse.photoData))
            {
                # Replace the padding at the end, "." is used, so needs to be replace with "="
                for ($i = $ParsedResponse.photoData.Length - 1; $i -ge 0; $i += -1)
                {
                    if ($ParsedResponse.photoData[$i] -eq '.')
                    {
                        $ParsedResponse.photoData[$i] = '='
                    }
                    else
                    {
                        break
                    }
                }

                # Remove the google web safe base64 encoding
                $ParsedResponse.photoData = $ParsedResponse.photoData.Replace("_", "/").Replace("-", "+").Replace("*", "=")

                if (-not [System.String]::IsNullOrEmpty($OutFile))
                {
                    # This will create the file and folder structure if not already present
                    if (-not (Test-Path -Path $OutFile))
                    {
                        New-Item -Path $OutFile -ItemType File -Force | Out-Null
                    }

                    Set-Content -Path $OutFile -Value ([System.Convert]::FromBase64String($ParsedResponse.photoData)) -Encoding Byte
                }
            }

            [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

            Write-Output -InputObject $Temp
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Set-GoogleDirectoryUserPhoto {
    <#
        .SYNOPSIS
            Sets the photo for a GSuite user.
 
        .DESCRIPTION
            This cmdlet updates a user's photo. In this version of the API, a photo is the user's
            latest Gmail Chat profile photo. This is different from the Google+ profile photo.
            When updating a photo, the height and width are ignored by the API.
         
        .PARAMETER UserId
            The Id of the user to set the photo of. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER Path
            The path to the file containing the photo to be uploaded. The file data will be converted to web safe base64.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .EXAMPLE
            $PhotoData = Set-GoogleDirectoryUserPhoto -UserId liz@example.com -Path c:\users\liz\desktop\photo.jpg -PassThru -ClientId $Id -Persist
                 
            Sets the photo data for the user liz@example.com using stored client credentials and bypasses confirmation. The uploaded photo
            information is returned to the pipeline.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or System.Collections.Hashtable
         
            This is a JSON representation of the output data:
            {
             "kind": "directory#user#photo",
             "id": "the unique user id",
             "primaryEmail": "liz@example.com",
             "mimeType": "the photo mime type",
             "height": "the photo height in pixels",
             "width": "the photo width in pixels",
             "photoData": "web safe base64 encoded photo data"
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true)]
        [ValidateScript({
            Test-Path -Path $_
        })]
        [System.String]$Path,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId/photos/thumbnail"

        [System.String]$Data = [System.Convert]::ToBase64String((Get-Content -Path $Path -Encoding Byte -Raw))

        # Replace the padding at the end, "." is used for google, so needs to replace "="
        for ($i = $ParsedResponse.photoData.Length - 1; $i -ge 0; $i += -1)
        {
            if ($ParsedResponse.photoData[$i] -eq '=')
            {
                $ParsedResponse.photoData[$i] = '.'
            }
            else
            {
                break
            }
        }
        
        # Now replace the other characters
        $Data = $Data.Replace("/", "_").Replace("+", "-").Replace("=", "*")

        [System.String]$Body = ConvertTo-Json -InputObject (@{"photoData" = $Data}) -Compress

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Put -Body $Body -Headers $Headers -UserAgent $UserAgent
            
            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                if (-not [System.String]::IsNullOrEmpty($ParsedResponse.photoData))
                {
                    # Replace the padding at the end, "." is used, so needs to be replace with "="
                    for ($i = $ParsedResponse.photoData.Length - 1; $i -ge 0; $i += -1)
                    {
                        if ($ParsedResponse.photoData[$i] -eq '.')
                        {
                            $ParsedResponse.photoData[$i] = '='
                        }
                        else
                        {
                            break
                        }
                    }

                    # Remove the google web safe base64 encoding
                    $ParsedResponse.photoData = $ParsedResponse.photoData.Replace("_", "/").Replace("-", "+").Replace("*", "=")
                }

                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Remove-GoogleDirectoryUserPhoto {
    <#
        .SYNOPSIS
            Deletes a GSuite user's photo.
 
        .DESCRIPTION
            This cmdlet deletes a user's photo. Once deleted, the user's photo is not shown.
            Wherever a user's photo is required, a silhouette will be shown instead.
 
        .PARAMETER UserId
            The Id of the user to delete the photo of. The UserId can be the user's primary email address, the unique user id, or one of the user's alias email addresses.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Remove-GoogleDirectoryUserPhoto -UserId liz@example.com -ClientId $Id -Persist
                 
            Deletes the photo for the user liz@example.com using stored client credentials and bypasses confirmation.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:UserBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$UserId/photos/thumbnail"

        try
        {
            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Delete -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

#endregion

#region Organizational Units

Function New-GoogleDirectoryOU {
    <#
        .SYNOPSIS
            Creates a GSuite organizational unit.
 
        .DESCRIPTION
            This cmdlet creates a new organizational unit.
 
        .PARAMETER Name
            The name of the new organizational unit.
 
        .PARAMETER Description
            An optional description of the organizational unit.
 
        .PARAMETER ParentOrgUnit
            The parent organizational unit to this new unit. This parameter must be the path of an existing organizational unit.
 
        .PARAMETER BlockInheritance
            If set, the new organizational unit will not receive policies inherited from its parent.
 
        .PARAMETER CustomerId
            If you are an administrator creating an organizational unit, use my_customer. This is the default. If you
            are reseller creating an organizational unit for a resold customer, use customerId. To retrieve the
            customerId, use the Retrieve a user operation.
 
        .PARAMETER PassThru
            If specified, the new organizational unit properties are returned to the pipeline.
         
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $OU = New-GoogleDirectoryOU -Name Sales -ParentOrgUnit "/my_org/business" -ClientId $Id -Persist -UseCompression -PassThru
 
            Creates a new organizational unit called sales unit the parent /my_org/business.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
            This is a JSON representation of the output:
 
            {
                "kind": "directory#orgUnit",
                "name": "sales_support",
                "description": "The sales support team",
                "orgUnitPath": "/corp/support/sales_support",
                "parentOrgUnitPath": "/corp/support",
                "blockInheritance": false
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Name,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$Description,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.String]$ParentOrgUnitPath,

        [Parameter()]
        [Switch]$BlockInheritance,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        if (-not $ParentOrgUnitPath.StartsWith("/"))
        {
            $ParentOrgUnitPath = "/$ParentOrgUnitPath"
        }

        if ($ParentOrgUnitPath.EndsWith("/"))
        {
            $ParentOrgUnitPath = $ParentOrgUnitPath.TrimEnd("/")
        }

        [System.String]$Url = "$Base/$CustomerId/orgunits"

        [System.Collections.Hashtable]$OU = @{"name" = $Name; "parentOrgUnitPath" = $ParentOrgUnitPath}

        if ($BlockInheritance)
        {
            $OU.Add("blockInheritance", $true)
        }

        if ($PSBoundParameters.ContainsKey("Description"))
        {
            $OU.Add("description", $Description)
        }

        [System.String]$Body = ConvertTo-Json -InputObject $OU -Compress -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Set-GoogleDirectoryOU {
    <#
        .SYNOPSIS
            Updates a GSuite organizational unit.
 
        .DESCRIPTION
            This cmdlet updates an organizational unit.
 
            - You only need to submit the updated information in your request. You do not need to enter
                all of the group's properties in the request.
            - If a user was not assigned to a specific organizational unit when the user account was created,
                the account is in the top-level organizational unit.
            - You can move an organizational unit to another part of your account's organization structure
                by setting the parentOrgUnitPath property in the request. It is important to note, that moving
                an organizational unit can change the services and settings for the users in the organizational
                unit being moved.
 
        .PARAMETER OrgUnitPath
            The path to the organizational unit to be modified.
 
        .PARAMETER Name
            The new name of the organizational unit.
 
        .PARAMETER Description
            The new description for the organizational unit.
 
        .PARAMETER ParentOrgUnit
            The new parent organization unit for this OU. Setting this parameter will move the OU within the OU tree structure.
            This parameter must be the path of an existing organizational unit.
 
        .PARAMETER BlockInheritance
            If set to true, the new organizational unit will not receive policies inherited from its parent. If set to false,
            the OU will received policies inherited from the OU tree.
 
        .PARAMETER CustomerId
            If you are an administrator updating an organizational unit, use my_customer. This is the default. If you
            are reseller updating an organizational unit for a resold customer, use customerId. To retrieve the
            customerId, use the Retrieve a user operation.
 
        .PARAMETER PassThru
            If specified, the updated organizational unit is returned to the pipeline.
         
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $OU = Set-GoogleDirectoryOU -Name CommercialSales -OrgUnitPath "/my_org/business/sales" -ClientId $Id -Persist -UseCompression -PassThru
 
            This examples updates the name of the OU 'sales' to 'commercialsales'.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
            This is a JSON representation of the output:
 
            {
                "kind": "directory#orgUnit",
                "name": "sales_support",
                "description": "The sales support team",
                "orgUnitPath": "/corp/support/sales_support",
                "parentOrgUnitPath": "/corp/support",
                "blockInheritance": false
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$OrgUnitPath,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$Name,

        # This can be empty to remove the description
        [Parameter()]
        [ValidateNotNull()]
        [System.String]$Description,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$ParentOrgUnitPath,

        [Parameter()]
        [System.Boolean]$BlockInheritance,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$PassThru,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        if ([System.String]::IsNullOrEmpty($ParentOrgUnitPath) -and 
            [System.String]::IsNullOrEmpty($Description) -and 
            -not $PSBoundParameters.ContainsKey("BlockInheritance") -and
            [System.String]::IsNullOrEmpty($Name))
        {
            Write-Error -Exception (New-Object -TypeName System.ArgumentException("You must specify a property to update for the OU.")) -ErrorAction Stop
        }

        if ($OrgUnitPath.StartsWith("/"))
        {
            $OrgUnitPath = $OrgUnitPath.TrimStart("/")
        }

        if ($OrgUnitPath.EndsWith("/"))
        {
            $OrgUnitPath = $OrgUnitPath.TrimEnd("/")
        }

        [System.String]$Url = "$Base/$CustomerId/orgunits/$([System.Uri]::EscapeUriString($OrgUnitPath))"

        [System.Collections.Hashtable]$OU = @{}

        if ($PSBoundParameters.ContainsKey("BlockInheritance"))
        {
            $OU.Add("blockInheritance", $BlockInheritance)
        }

        if ($PSBoundParameters.ContainsKey("Description"))
        {
            $OU.Add("description", $Description)
        }

        if (-not [System.String]::IsNullOrEmpty($Name))
        {
            $OU.Add("name", $Name)
        }

        if (-not [System.String]::IsNullOrEmpty($ParentOrgUnitPath))
        {
            if (-not $ParentOrgUnitPath.StartsWith("/"))
            {
                $ParentOrgUnitPath = "/$ParentOrgUnitPath"
            }

            if ($ParentOrgUnitPath.EndsWith("/"))
            {
                $ParentOrgUnitPath = $ParentOrgUnitPath.TrimEnd("/")
            }

            $OU.Add("parentOrgUnitPath", $ParentOrgUnitPath)
        }

        [System.String]$Body = ConvertTo-Json -InputObject $OU -Compress -Depth 3

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Put -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryOU {
    <#
        .SYNOPSIS
            Gets a GSuite organizational unit.
 
        .DESCRIPTION
            This cmdlet gets an organizational unit details.
 
        .PARAMETER OrgUnitPath
            The path to the organizational unit to retrieve.
 
        .PARAMETER CustomerId
            If you are an administrator getting an organizational unit, use my_customer. This is the default. If you
            are reseller getting an organizational unit for a resold customer, use customerId. To retrieve the
            customerId, use the Retrieve a user operation.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $OU = Get-GoogleDirectoryOU -OrgUnitPath "/my_org/business/sales" -ClientId $Id -Persist -UseCompression
 
            This examples gets the org unit details for the 'sales' OU.
 
        .INPUTS
            System.String
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
            This is a JSON representation of the output:
 
            {
                "kind": "directory#orgUnit",
                "name": "sales_support",
                "description": "The sales support team",
                "orgUnitPath": "/corp/support/sales_support",
                "parentOrgUnitPath": "/corp/support",
                "blockInheritance": false
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$OrgUnitPath,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        if ($OrgUnitPath.StartsWith("/"))
        {
            $OrgUnitPath = $OrgUnitPath.TrimStart("/")
        }

        if ($OrgUnitPath.EndsWith("/"))
        {
            $OrgUnitPath = $OrgUnitPath.TrimEnd("/")
        }

        [System.String]$Url = "$Base/$CustomerId/orgunits/$([System.Uri]::EscapeUriString($OrgUnitPath))"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent

            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

            Write-Output -InputObject $Temp
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryOUChildren {
    <#
        .SYNOPSIS
            Gets a GSuite organizational unit children organizational units.
 
        .DESCRIPTION
            This cmdlet gets the children of an organizational unit. This defaults only to direct children, but
            if the -All parameter is specified, all children are enumerated recursively through the tree structure.
 
        .PARAMETER OrgUnitPath
            The path to the organizational unit to retrieve children from.
 
        .PARAMETER All
            If specified, all of the children in the tree structure are returned, not just the direct children of the OU.
 
        .PARAMETER CustomerId
            If you are an administrator, use my_customer. This is the default. If you
            are reseller getting an OU for a resold customer, use customerId. To retrieve the
            customerId, use the Retrieve a user operation.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Children = Get-GoogleDirectoryOUChildren -OrgUnitPath "/my_org/business/sales" -ClientId $Id -Persist -UseCompression
 
            This examples gets the direct children of the 'sales' OU.
 
        .EXAMPLE
            $Children = Get-GoogleDirectoryOUChildren -OrgUnitPath "/my_org/business/sales" -All -ClientId $Id -Persist -UseCompression
 
            This examples gets all of the children of the 'sales' OU.
 
        .INPUTS
            System.String
         
        .OUTPUTS
            System.Collections.Hashtable[]
 
            This is a JSON representation of the output:
 
            [
                {
                    "kind": "directory#orgUnit",
                    "name": "sales",
                    "description": "The corporate sales team",
                    "orgUnitPath": "/corp/sales",
                    "parentOrgUnitPath": "/corp",
                    "blockInheritance": false
                },
                {
                    "kind": "directory#orgUnit",
                    "name": "frontline sales",
                    "description": "The frontline sales team",
                    "orgUnitPath": "/corp/sales/frontline sales",
                    "parentOrgUnitPath": "/corp/sales",
                    "blockInheritance": false
                },
                {
                    "kind": "directory#orgUnit",
                    "name": "support",
                    "description": "The corporate support team",
                    "orgUnitPath": "/corp/support",
                    "parentOrgUnitPath": "/corp",
                    "blockInheritance": false
                },
                {
                    "kind": "directory#orgUnit",
                    "name": "sales_support",
                    "description": "The BEST support team",
                    "orgUnitPath": "/corp/support/sales_support",
                    "parentOrgUnitPath": "/corp/support",
                    "blockInheritance": false
                }
          ]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$OrgUnitPath,

        [Parameter()]
        [Switch]$All,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        if ($OrgUnitPath.StartsWith("/"))
        {
            $OrgUnitPath = $OrgUnitPath.TrimStart("/")
        }

        if ($OrgUnitPath.EndsWith("/"))
        {
            $OrgUnitPath = $OrgUnitPath.TrimEnd("/")
        }

        [System.String]$Url = "$Base/$CustomerId/orgunits?orgUnitPath=$([System.Uri]::EscapeUriString($OrgUnitPath))"

        if ($All)
        {
            $Url += "&type=all"
        }
        else
        {
            $Url += "&type=children"
        }

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent

            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            [System.Collections.Hashtable[]]$OUs = @()

            foreach ($OU in $ParsedResponse.organizationalUnits)
            {
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                $OUs += $Temp
            }

            Write-Output -InputObject $OUs
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Remove-GoogleDirectoryOU {
    <#
        .SYNOPSIS
            Delets a GSuite organizational unit.
 
        .DESCRIPTION
            This cmdlet deletes an orgaizational unit. You can only delete organizational units
            that do not have any child organizational units or any users assigned to them. You
            need to reassign users to other organizational units and remove any child organizational
            units before deleting.
 
        .PARAMETER OrgUnitPath
            The path to the organizational unit to delete.
 
        .PARAMETER CustomerId
            If you are an administrator deleting an organizational unit, use my_customer. This is the default. If you
            are reseller deleting an organizational unit for a resold customer, use customerId. To retrieve the
            customerId, use the Retrieve a user operation.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            Remove-GoogleDirectoryOU -OrgUnitPath "/my_org/business/sales" -Force -ClientId $Id -Persist -UseCompression
 
            This examples deletes the 'sales' OU and bypasses confirmation.
 
        .INPUTS
            System.String
         
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/12/2018
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "HIGH")]
    [OutputType()]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$OrgUnitPath,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$Force
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        if ($OrgUnitPath.StartsWith("/"))
        {
            $OrgUnitPath = $OrgUnitPath.TrimStart("/")
        }

        if ($OrgUnitPath.EndsWith("/"))
        {
            $OrgUnitPath = $OrgUnitPath.TrimEnd("/")
        }

        [System.String]$Url = "$Base/$CustomerId/orgunits/$([System.Uri]::EscapeUriString($OrgUnitPath))"

        try
        {
            $ConfirmMessage = "You are about to delete GSuite OU $OrgUnitPath."
            $WhatIfDescription = "Deleted OU $OrgUnitPath."
            $ConfirmCaption = "Delete GSuite OU"

            if ($Force -or $PSCmdlet.ShouldProcess($WhatIfDescription, $ConfirmMessage, $ConfirmCaption))
            {
                [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Delete -Headers @{"Authorization" = "Bearer $BearerToken"} -UserAgent PowerShell

                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

#endregion

#region Roles

Function Get-GoogleDirectoryPrivileges {
    <#
        .SYNOPSIS
            Gets a list of supported privileges.
 
        .DESCRIPTION
            This cmdlet gets a list of supported privileges.
 
        .PARAMETER CustomerId
            If you are an administrator getting privileges in your own domain, use my_customer as the customer ID. This is the default.
            If you are reseller getting privileges for one of your customers, use the customer ID returned by the Retrieve a user operation.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Privileges = Get-GoogleDirectoryPrivileges -ClientId $Id -Persist -UseCompression
 
            This examples gets the supported privileges for the account supporting the current administrator.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collections.Hashtable[]
 
            This is a JSON representation of the output:
            [
                {
                  "kind": "admin\#directory\#privilege",
                  "etag": ...,
                  "serviceId": "02afmg282jiquyg",
                  "privilegeName": "APP_ADMIN",
                  "isOuScopable": false
                },
                {
                  "kind": "admin\#directory\#privilege",
                  "etag": ...,
                  "serviceId": "04f1mdlm0ki64aw",
                  "privilegeName": "MANAGE_USER_SETTINGS",
                  "isOuScopable": true,
                  "childPrivileges": [
                    {
                      "kind": "admin\#directory\#privilege",
                      "etag": ...,
                      "serviceId": "04f1mdlm0ki64aw",
                      "privilegeName": "MANAGE_APPLICATION_SETTINGS",
                      "isOuScopable": true
                    }
                  ]
                },
            ...
          ]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/20/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$CustomerId/roles/ALL/privileges"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent

            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            [System.Collections.Hashtable[]]$Results = @()

            foreach ($Item in $ParsedResponse.items)
            {
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $Item

                $Results += $Temp
            }

            Write-Output -InputObject $Results
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Get-GoogleDirectoryRoleList {
    <#
        .SYNOPSIS
            Gets a list of existing GSuite roles.
 
        .DESCRIPTION
            This cmdlet gets a list of existing roles.
 
        .PARAMETER CustomerId
            If you are an administrator getting roles in your own domain, use my_customer as the customer ID. This is the default.
            If you are reseller getting roles for one of your customers, use the customer ID returned by the Retrieve a user operation.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Roles = Get-GoogleDirectoryRoleList -ClientId $Id -Persist -UseCompression
 
            This examples gets the existing roles for the account supporting the current administrator.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collections.Hashtable[]
 
            This is a JSON representation of the output:
            [
                {
                    "kind": "admin\#directory\#role",
                    "etag": ... ,
                    "roleId": "3894208461012993",
                    "roleName": "_SEED_ADMIN_ROLE",
                    "roleDescription": "Google Apps Administrator Seed Role",
                    "rolePrivileges": [
                        {
                          "privilegeName": "SUPER_ADMIN",
                          "serviceId": "01ci93xb3tmzyin"
                        },
                        {
                          "privilegeName": "ROOT_APP_ADMIN",
                          "serviceId": "00haapch16h1ysv"
                        },
                        {
                          "privilegeName": "ADMIN_APIS_ALL",
                          "serviceId": "00haapch16h1ysv"
                        },
                        ...
                    ],
                    "isSystemRole": true,
                    "isSuperAdminRole": true
                },
                {
                  "kind": "admin\#directory\#role",
                  "etag": "\"sxH3n22L0-77khHtQ7tiK6I21Yo/bTXiZXfuK1NGr_f4paosCWXuHmw\"",
                  "roleId": "3894208461012994",
                  "roleName": "_GROUPS_ADMIN_ROLE",
                  "roleDescription": "Groups Administrator",
                  "rolePrivileges": [
                    {
                      "privilegeName": "CHANGE_USER_GROUP_MEMBERSHIP",
                      "serviceId": "01ci93xb3tmzyin"
                    },
                    {
                      "privilegeName": "USERS_RETRIEVE",
                      "serviceId": "00haapch16h1ysv"
                    },
                    {
                      "privilegeName": "GROUPS_ALL",
                      "serviceId": "00haapch16h1ysv"
                    },
                    {
                      "privilegeName": "ADMIN_DASHBOARD",
                      "serviceId": "01ci93xb3tmzyin"
                    },
                    {
                      "privilegeName": "ORGANIZATION_UNITS_RETRIEVE",
                      "serviceId": "00haapch16h1ysv"
                    }
                  ],
                  "isSystemRole": true
                },
            ...
          ]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/20/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$CustomerId/roles"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent

            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
            [System.Collections.Hashtable[]]$Results = @()

            foreach ($Item in $ParsedResponse.items)
            {
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $Item

                $Results += $Temp
            }

            Write-Output -InputObject $Results
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function New-GoogleDirectoryRole {
    <#
        .SYNOPSIS
            Creates a new GSuite role.
 
        .DESCRIPTION
            This cmdlet creates a new GSuite role.
 
        .PARAMETER Name
            The name of the role to create.
 
        .PARAMETER Privileges
            The privileges to assign to the role. This is an array of items where each item has a privilege name and service id. For
            example:
             
            $Priv = @{
                "privilegeName": "USERS_ALL";
                "serviceId": "00haapch16h1ysv"
            }
         
        .PARAMETER CustomerId
            If you are an administrator creating a role in your own domain, use my_customer as the customer ID. This is the default.
            If you are reseller creating a role for one of your customers, use the customer ID returned by the Retrieve a user operation.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Role = New-GoogleDirectoryRole -Name UserAdminRole -Privileges @(@{"privilegeName": "USERS_ALL"; "serviceId": "00haapch16h1ysv"}) -ClientId $Id -Persist -UseCompression -PassThru
 
            This examples creates a new role called UserAdminRole with the USERS_ALL privilege and returns the new role to the pipeline.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collections.Hashtable
 
            This is a JSON representation of the output:
            {
              "kind": "admin\#directory\#role",
              "etag": "\"sxH3n22L0-77khHtQ7tiK6I21Yo/uX9tXw0qyijC9nUKgCs08wo8aEM\"",
              "roleId": "3894208461013031",
              "roleName": "My New Role",
              "rolePrivileges": [
                {
                  "privilegeName": "GROUPS_ALL",
                  "serviceId": "00haapch16h1ysv"
                },
                {
                  "privilegeName": "USERS_ALL",
                  "serviceId": "00haapch16h1ysv"
                }
              ]
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/20/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Name,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [System.Collections.Hashtable[]]$Privileges,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression,

        [Parameter()]
        [Switch]$PassThru
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$CustomerId/roles"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [System.Collections.Hashtable]$BodyContent = @{"roleName" = $Name; "rolePrivileges" = @()}

            foreach ($Item in $Privileges)
            {
                if ($Item.ContainsKey("privilegeName") -and $Item.ContainsKey("serviceId"))
                {
                    $BodyContent["rolePrivileges"] += @{"privilegeName" = $Item["privilegeName"]; "serviceId" = $Item["serviceId"]}
                }
                else
                {
                    Write-Error -Exception (New-Object -TypeName System.ArgumentException("Privileges", "A privilege was supplied that did not possess a privilegeName and serviceId property.")) -ErrorAction Stop
                }
            }

            [System.String]$Body = ConvertTo-Json -InputObject $BodyContent -Depth 3 -Compress

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function New-GoogleDirectoryRoleAssignment {
    <#
        .SYNOPSIS
            Creates a new GSuite role assignment.
 
        .DESCRIPTION
            This cmdlet assigns a role to a user.
 
        .PARAMETER RoleId
            The id of the role to which the user will be assigned.
 
        .PARAMETER UserId
            The id of the user who will be assigned to the role.
 
        .PARAMETER ScopeType
            The scope of the assignment.
 
        .PARAMETER CustomerId
            If you are an administrator creating a role assignment in your own domain, use my_customer as the customer ID. This is the default.
            If you are reseller creating a role assignment for one of your customers, use the customer ID returned by the Retrieve a user operation.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $RoleAssignment = New-GoogleDirectoryRoleAssignment -RoleId "3894208461012995" -UserId "100662996240850794412" -ClientId $Id -Persist -UseCompression -PassThru
 
            This examples assigns the specified role to the specified user and returns the response to the pipeline.
 
        .INPUTS
            None
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
            This is a JSON representation of the output:
            {
              "kind": "admin\#directory\#roleAssignment",
              "etag": "\"sxH3n22L0-77khHtQ7tiK6I21Yo/VdrrUEz7GyXqlr9I9JL0wGZn8yE\"",
              "roleAssignmentId": "3894208461013211",
              "roleId": "3894208461012995",
              "assignedTo": "100662996240850794412",
              "scopeType": "CUSTOMER"
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/20/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String]$RoleId,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserId,

        [Parameter()]
        [ValidateSet("CUSTOMER")]
        [System.String]$ScopeType = "CUSTOMER",

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression,

        [Parameter()]
        [Switch]$PassThru
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$CustomerId/roleassignments"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [System.String]$Body = ConvertTo-Json -InputObject (@{"roleId" = $RoleId; "assignedTo" = $UserId; "scopeType" = $ScopeType}) -Depth 3 -Compress

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Post -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content
            
                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

#endregion

#region Customers

Function Get-GoogleDirectoryCustomer {
    <#
        .SYNOPSIS
            Gets a GSuite customer.
 
        .DESCRIPTION
            This cmdlet gets a specified GSuite customer.
 
        .PARAMETER CustomerId
            The CustomerId can be the unique customerId, or my_customer to indicate the current customer. This defaults
            to my_customer.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Customer = Get-GoogleDirectoryCustomer -ClientId $Id -Persist -UseCompression
 
            This examples gets the current customer details.
 
        .INPUTS
            None
         
        .OUTPUTS
            System.Collections.Hashtable
 
            This is a JSON representation of the output:
            {
              "etag": "\"spqlTgq5LGeoin0BH1d0f4rpI98/LnbnRK_ZWu_omowg36CZgTKECrY\"",
              "kind": "admin#directory#customer",
              "alternateEmail": "marty.mcfly@gmail.com",
              "id": "C03xgje4y",
              "customerDomain": "amatchmadeinspace.com",
              "postalAddress": {
                "organizationName": "A Match Made in Space, LLC",
                "countryCode": "US"
              },
              "customerCreationTime": "2015-10-21T20:42:35.224Z"
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/20/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$CustomerId"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Get -Headers $Headers -UserAgent $UserAgent

            [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content

            [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

            Write-Output -InputObject $Temp
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

Function Set-GoogleDirectoryCustomer {
    <#
        .SYNOPSIS
            Updates a GSuite customer.
 
        .DESCRIPTION
            This cmdlet updates a specified GSuite customer.
 
        .PARAMETER CustomerId
            The CustomerId can be the unique customerId, or my_customer to indicate the current customer. This defaults
            to my_customer.
 
        .PARAMETER CustomerDetails
            The updated information for the customer. This can include any of the following fields:
 
            {
              "alternateEmail": "marty.mcfly@gmail.com",
              "customerDomain": "amatchmadeinspace.com",
              "language": "EN",
              "postalAddress": {
                "organizationName": "A Match Made in Space, LLC",
                "phoneNumber": "+15558675309"
              }
            }
 
        .PARAMETER PassThru
            If specified, the updated customer details are passed back to the pipeline.
 
        .PARAMETER UseCompression
            If specified, the returned data is compressed using gzip.
 
        .PARAMETER BearerToken
            The bearer token to use to authenticate the request.
 
        .PARAMETER ClientId
            The client Id of the stored profile that contains the bearer token used to authenticate
            the request. The cmdlet will automatically update or refresh the access token if necessary (and is
            possible based on the other data stored in the profile).
 
        .PARAMETER ProfileLocation
            The location where stored credentials are located. If this is not specified, the default location will be used.
 
        .PARAMETER Persist
            Indicates that the newly retrieved token(s) or refreshed token and associated client data like client secret
            are persisted to disk.
 
        .EXAMPLE
            $Customer = Set-GoogleDirectoryCustomer -CustomerId C03xgje4y -CustomerDetails @{"customerDomain" = "fluxcapacitor.com"} -ClientId $Id -Persist -UseCompression -PassThru
 
            This examples updates the customer primary domain name.
 
        .INPUTS
            System.Collections.Hashtable
         
        .OUTPUTS
            None or System.Collections.Hashtable
 
            This is a JSON representation of the output:
            {
              "etag": "\"spqlTgq5LGeoin0BH1d0f4rpI98/LnbnRK_ZWu_omowg36CZgTKECrY\"",
              "kind": "admin#directory#customer",
              "alternateEmail": "marty.mcfly@gmail.com",
              "id": "C03xgje4y",
              "customerDomain": "amatchmadeinspace.com",
              "postalAddress": {
                "organizationName": "A Match Made in Space, LLC",
                "countryCode": "US"
              },
              "customerCreationTime": "2015-10-21T20:42:35.224Z"
            }
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/20/2018
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    Param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]$CustomerId = "my_customer",

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNull()]
        [System.Collections.Hashtable]$CustomerDetails,

        [Parameter(Mandatory = $true, ParameterSetName = "Token")]
        [ValidateNotNullOrEmpty()]
        [System.String]$BearerToken,

        [Parameter(ParameterSetName = "Profile")]
        [System.String]$ProfileLocation,

        [Parameter(ParameterSetName = "Profile")]
        [Switch]$Persist,

        [Parameter()]
        [Switch]$UseCompression,

        [Parameter()]
        [Switch]$PassThru
    )

    DynamicParam {
        New-DynamicParameter -Name "ClientId" -Type ([System.String]) -Mandatory -ParameterSets @("Profile") -ValidateNotNullOrEmpty -ValidateSet (Get-GoogleOAuth2Profile -ProfileLocation $ProfileLocation)
    }

    Begin {
    }

    Process {
        [System.String]$Base = $script:OUBaseUrl

        if ($PSCmdlet.ParameterSetName -eq "Profile")
        {
            $ClientId = $PSBoundParameters["ClientId"]
            [System.Collections.Hashtable]$Token = Get-GoogleOAuth2Token -ClientId $ClientId -ProfileLocation $ProfileLocation -Persist:$Persist -ErrorAction Stop
            $BearerToken = $Token["access_token"]
        }

        [System.String]$Url = "$Base/$CustomerId"

        try
        {
            $Headers = @{"Authorization" = "Bearer $BearerToken"}
            $UserAgent = $script:UserAgent

            if ($UseCompression)
            {
                $UserAgent = $script:UserAgentGzip
                $Headers.Add("Accept-Encoding", "gzip")
            }

            [System.String]$Body = ConvertTo-Json -InputObject $CustomerDetails -Depth 3 -Compress

            [Microsoft.PowerShell.Commands.WebResponseObject]$Response = Invoke-WebRequest -Uri $Url -Method Put -Body $Body -Headers $Headers -UserAgent $UserAgent

            if ($PassThru)
            {
                [PSCustomObject]$ParsedResponse = ConvertFrom-Json -InputObject $Response.Content

                [System.Collections.Hashtable]$Temp = Convert-PSCustomToHashtable -InputObject $ParsedResponse

                Write-Output -InputObject $Temp
            }
        }
        catch [System.Net.WebException]
        {
            [System.Net.WebException]$Ex = $_.Exception
            [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]($Ex.Response)
            [System.Int32]$StatusCode = $Response.StatusCode.value__
            [System.IO.Stream]$Stream = $Ex.Response.GetResponseStream()
            [System.IO.StreamReader]$Reader = New-Object -TypeName System.IO.StreamReader($Stream, [System.Text.Encoding]::UTF8)
            [System.String]$Content = $Reader.ReadToEnd()
                
            [System.String]$Message = "$StatusCode : $Content"

            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                [System.Web.HttpException]$NewEx = New-Object -TypeName System.Web.HttpException($Content, $StatusCode)
                Write-Error -Exception $NewEx -Category NotSpecified -ErrorId $StatusCode
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $Message"
            }
        }
        catch [Exception] 
        {
            if ($ErrorActionPreference -eq [System.Management.Automation.ActionPreference]::Stop)
            {
                Write-Error -Exception $_.Exception -ErrorAction Stop
            }
            elseif ($ErrorActionPreference -ne [System.Management.Automation.ActionPreference]::SilentlyContinue)
            {
                Write-Warning -Message $_.Exception.Message
            }
            else
            {
                Write-Verbose -Message "[ERROR] : $($_.Exception.Message)"
            }
        }
    }

    End {
    }
}

#endregion

Function Convert-PSCustomToHashtable {
    [CmdletBinding()]
    [OutputType()]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [PSCustomObject]$InputObject
    )

    Begin {
    }

    Process {
        [System.Collections.Hashtable]$Result = @{}

        foreach($Property in ($InputObject | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name))
        {
            $Value = $InputObject.$Property

            [System.Type]$Type = $Value.GetType()

            if ($Type.IsPrimitive -or $Value -is [System.String])
            {
                $Result.Add($Property, $Value)
            }
            elseif($Type.GetInterfaces().Contains([System.Collections.IEnumerable]))
            {
                $Arr = @()
                foreach ($Item in $Value)
                {
                    if ($Item -is [PSCustomObject])
                    {
                        $Arr += (Convert-PSCustomToHashtable -InputObject $Item)
                    }
                    else
                    {
                        $Arr += $Item
                    }
                }

                $Result.Add($Property, $Arr)
            }
            elseif ($Value -is [PSCustomObject])
            {
                $Result.Add($Property, (Convert-PSCustomToHashtable -InputObject $Value))
            }
            else
            {
                $Result.Add($Property, $Value)
            }
        }

        Write-Output -InputObject $Result
    }

    End {
    }
}