PSGSuite.psm1

Param
(
    [parameter(Position = 0,ValueFromRemainingArguments = $true)]
    [AllowNull()]
    [Byte[]]
    $EncryptionKey = $null,
    [parameter(Position = 1)]
    [AllowNull()]
    [String]
    $ConfigName
)
$ModuleRoot = $PSScriptRoot
New-Variable -Name PSGSuiteKey -Value $EncryptionKey -Scope Global -Force
function Convert-Base64 {
    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [ValidateSet("NormalString","Base64String","WebSafeBase64String")]
        [ValidateScript( {if ($_ -eq $To) {
                    throw "The 'From' parameter must not be the same as the 'To' parameter"
                }
                else {
                    $true
                }})]
        [String]
        $From,
        [parameter(Mandatory = $true,Position = 1)]
        [ValidateSet("NormalString","Base64String","WebSafeBase64String")]
        [ValidateScript( {if ($_ -eq $From) {
                    throw "The 'To' parameter must not be the same as the 'From' parameter"
                }
                else {
                    $true
                }})]
        [String]
        $To,
        [parameter(Mandatory = $true,Position = 2,ValueFromPipeline = $true)]
        [String]
        $String,
        [parameter(Mandatory = $false)]
        [String]
        $OutFile
    )
    if ($From -eq "NormalString") {
        $String = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($String))
    }
    elseif ($From -eq "WebSafeBase64String") {
        $String = $String.Replace('_', '/').Replace('-', '+').Replace('|','=')
        switch ($String.Length % 4) {
            2 {
                $String += "=="
            }
            3 {
                $String += "="
            }
        }
    }
    if ($To -eq "NormalString") {
        $String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($String))
    }
    elseif ($To -eq "WebSafeBase64String") {
        $String = $String.TrimEnd("=").Replace('+', '-').Replace('/', '_');
    }
    if ($OutFile) {
        $String | Set-Content $OutFile -Force
    }
    else {
        return $String
    }
}

function Convert-DateToEpoch {
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true)]
        [datetime]
        $Date
    )
    Begin {
        $UnixEpoch = [timezone]::CurrentTimeZone.ToLocalTime([datetime]'1/1/1970')
    }
    Process {
        $result = (("$(($Date - $UnixEpoch).TotalMilliseconds)" -split "\.") -split "\,")[0]
    }
    End {
        return $result
    }
}

function Convert-EpochToDate {
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true)]
        [string]
        $EpochString
    )
    Begin {
        $UnixEpoch = [timezone]::CurrentTimeZone.ToLocalTime([datetime]'1/1/1970')
    }
    Process {
        try {
            $result = $UnixEpoch.AddSeconds($EpochString)
        }
        catch {
            try {
                $result = $UnixEpoch.AddMilliseconds($EpochString)
            }
            catch {
                $result = $UnixEpoch.AddTicks($EpochString)
            }
        }
    }
    End {
        return $result
    }
}

function Get-MimeType {
    Param
    (
        [parameter(Mandatory=$true, ValueFromPipeline=$true,Position = 0)]
        [System.IO.FileInfo]
        $File
    )
    $mimeHash = @{
        arj = 'application/arj'
        bmp = 'image/bmp'
        cab = 'application/cab'
        csv = 'text/plain'
        doc = 'application/msword'
        gif = 'image/gif'
        htm = 'text/html'
        html = 'text/html'
        jpg = 'image/jpeg'
        js = 'text/js'
        log = 'text/plain'
        md = 'text/plain'
        mp3 = 'audio/mpeg'
        ods = 'application/vnd.oasis.opendocument.spreadsheet'
        pdf  = 'application/pdf'
        php = 'application/x-httpd-php'
        png = 'image/png'
        rar = 'application/rar'
        swf = 'application/x-shockwave-flash'
        tar = 'application/tar'
        tmpl = 'text/plain'
        txt = 'text/plain'
        xls = 'application/vnd.ms-excel'
        xlsx = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        xml = 'text/xml'
        zip = 'application/zip'
    }
    if ($File.PSIsContainer) {
        'application/vnd.google-apps.folder'
    }
    elseif ($mime = $mimeHash["$($file.Extension.TrimStart('.'))"]) {
        $mime
    }
    else {
        'application/octet-stream'
    }
}

function Import-GoogleSDK {
    [CmdletBinding()]
    Param()
    Process {
        $lib = Resolve-Path "$($script:ModuleRoot)\lib"
        $refs = @()
        $sdkPath = if ($PSVersionTable.PSVersion.Major -lt 6) {
            Write-Verbose "Importing the SDK's for net45"
            "$lib\net45"
        }
        else {
            Write-Verbose "Importing the SDK's for netstandard1.3"
            "$lib\netstandard1.3"
        }
        Get-ChildItem $sdkPath -Filter "*.dll" | Where-Object {$_.Name -notin $refs} | ForEach-Object {
            $sdk = $_.Name
            try {
                Add-Type -Path $_.FullName -ErrorAction Stop
            }
            catch [System.Reflection.ReflectionTypeLoadException] {
                Write-Host "Message: $($_.Exception.Message)"
                Write-Host "StackTrace: $($_.Exception.StackTrace)"
                Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)"
            }
            catch {
                Write-Error "$($sdk): $($_.Exception.Message)"
            }
        }
    }
}


function Import-SpecificConfiguration {
    <#
    .Synopsis
    Import the full, layered configuration for the module.
    Allows for specification of scoped module to load, in case different scopes have different encryption levels
    .Description
    Imports the DefaultPath Configuration file, and then imports the Machine, Roaming (enterprise), and local config files, if they exist.
    Each configuration file is layered on top of the one before (so only needs to set values which are different)
    .Example
    $Configuration = Import-Configuration
 
    This example shows how to use Import-Configuration in your module to load the cached data
 
    .Example
    $Configuration = Get-Module Configuration | Import-Configuration
 
    This example shows how to use Import-Configuration in your module to load data cached for another module
    #>

    [CmdletBinding(DefaultParameterSetName = '__CallStack')]
    param(
        # A callstack. You should not ever pass this.
        # It is used to calculate the defaults for all the other parameters.
        [Parameter(ParameterSetName = "__CallStack")]
        [System.Management.Automation.CallStackFrame[]]$CallStack = $(Get-PSCallStack),

        # The Module you're importing configuration for
        [Parameter(ParameterSetName = "__ModuleInfo", ValueFromPipeline = $true)]
        [System.Management.Automation.PSModuleInfo]$Module = $(
            $mi = ($CallStack)[0].InvocationInfo.MyCommand.Module
            if ($mi -and $mi.ExportedCommands.Count -eq 0) {
                if ($mi2 = Get-Module $mi.ModuleBase -ListAvailable | Where-Object Name -eq $mi.Name | Where-Object ExportedCommands | Select-Object -First 1) {
                    return $mi2
                }
            }
            return $mi
        ),

        # An optional module qualifier (by default, this is blank)
        [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("Author")]
        [String]$CompanyName = $(
            if ($Module) {
                $Name = $Module.CompanyName -replace "[$([Regex]::Escape(-join[IO.Path]::GetInvalidFileNameChars()))]","_"
                if ($Name -eq "Unknown" -or -not $Name) {
                    $Name = $Module.Author
                    if ($Name -eq "Unknown" -or -not $Name) {
                        $Name = "AnonymousModules"
                    }
                }
                $Name
            }
            else {
                "AnonymousScripts"
            }
        ),

        # The name of the module or script
        # Will be used in the returned storage path
        [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [String]$Name = $(if ($Module) {
                $Module.Name
            }),

        # The full path (including file name) of a default Configuration.psd1 file
        # By default, this is expected to be in the same folder as your module manifest, or adjacent to your script file
        [Parameter(ParameterSetName = "ManualOverride", ValueFromPipelineByPropertyName = $true)]
        [Alias("ModuleBase")]
        [String]$DefaultPath = $(if ($Module) {
                Join-Path $Module.ModuleBase Configuration.psd1
            }),

        [Parameter(ParameterSetName = "Path",Mandatory = $true)]
        [ValidateScript( {Test-Path $_})]
        [string]$Path,

        [Parameter(Mandatory = $false)]
        [ValidateSet("User", "Machine", "Enterprise", $null)]
        [string]$Scope = $Script:ConfigScope,

        # The version for saved settings -- if set, will be used in the returned path
        # NOTE: this is *never* calculated, if you use version numbers, you must manage them on your own
        [Version]$Version,

        # If set (and PowerShell version 4 or later) preserve the file order of configuration
        # This results in the output being an OrderedDictionary instead of Hashtable
        [Switch]$Ordered
    )
    begin {
        Write-Verbose "Module Name $Name"
    }
    process {
        if (!$Name) {
            throw "Could not determine the configuration name. When you are not calling Import-Configuration from a module, you must specify the -Author and -Name parameter"
        }

        if (Test-Path $DefaultPath -Type Container) {
            $DefaultPath = Join-Path $DefaultPath Configuration.psd1
        }
        $Configuration = if (Test-Path $DefaultPath) {
            Import-Metadata $DefaultPath -ErrorAction Ignore -Ordered:$Ordered
            Write-Verbose "Default config found: $DefaultPath"
        }
        else {
            @{}
        }

        $Parameters = @{
            CompanyName = $CompanyName
            Name        = $Name
        }
        if ($Version) {
            $Parameters.Version = $Version
        }
        if ($Path) {
            Write-Verbose "Importing configuration from specific path: $Path"
            Import-Metadata "$(Resolve-Path $Path)" -ErrorAction Ignore -Ordered:$Ordered
        }
        else {
            if (!$Scope -or $Scope -eq "Machine") {
                $MachinePath = Get-StoragePath @Parameters -Scope Machine
                $MachinePath = Join-Path $MachinePath Configuration.psd1
                $Machine = if (Test-Path $MachinePath) {
                    Import-Metadata $MachinePath -ErrorAction Ignore -Ordered:$Ordered
                    Write-Verbose "Machine config found: $MachinePath"
                }
                else {
                    @{}
                }
            }

            if (!$Scope -or $Scope -eq "Enterprise") {
                $EnterprisePath = Get-StoragePath @Parameters -Scope Enterprise
                $EnterprisePath = Join-Path $EnterprisePath Configuration.psd1
                $Enterprise = if (Test-Path $EnterprisePath) {
                    Import-Metadata $EnterprisePath -ErrorAction Ignore -Ordered:$Ordered
                    Write-Verbose "Enterprise config found: $EnterprisePath"
                }
                else {
                    @{}
                }
            }

            if (!$Scope -or $Scope -eq "User") {
                $LocalUserPath = Get-StoragePath @Parameters -Scope User
                $LocalUserPath = Join-Path $LocalUserPath Configuration.psd1
                $LocalUser = if (Test-Path $LocalUserPath) {
                    Import-Metadata $LocalUserPath -ErrorAction Ignore -Ordered:$Ordered
                    Write-Verbose "LocalUser config found: $LocalUserPath"
                }
                else {
                    @{}
                }
            }
            switch ($Scope) {
                Machine {
                    Write-Verbose "Importing configuration at scope: $Scope"
                    $Configuration | Update-Object $Machine
                }
                Enterprise {
                    Write-Verbose "Importing configuration at scope: $Scope"
                    $Configuration | Update-Object $Enterprise
                }
                User {
                    Write-Verbose "Importing configuration at scope: $Scope"
                    $Configuration | Update-Object $LocalUser
                }
                Default {
                    Write-Verbose "Importing layered configuration"
                    $Configuration |
                        Update-Object $Machine |
                        Update-Object $Enterprise |
                        Update-Object $LocalUser
                }
            }
        }
    }
}


function New-MimeMessage {
    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $To,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $From,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Subject,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Body,
        [parameter(Mandatory = $false)]
        [string[]]
        $CC,
        [parameter(Mandatory = $false)]
        [string[]]
        $BCC,
        [parameter(Mandatory = $false)]
        [ValidateScript( {Test-Path $_})]
        [string[]]
        $Attachment,
        [parameter(Mandatory = $false)]
        [switch]
        $BodyAsHtml,
        [parameter(Mandatory = $false)]
        [switch]
        $ReturnConstructedMessage
    )
    $message = [MimeKit.MimeMessage]::new()
    $message.From.Add($From)
    $message.Subject = $Subject
    foreach ($T in $To) {
        $message.To.Add($T)
    }
    if ($CC) {
        foreach ($C in $CC) {
            $message.Cc.Add($C)
        }
    }
    if ($BCC) {
        foreach ($B in $BCC) {
            $message.Bcc.Add($B)
        }
    }
    if ($BodyAsHtml) {
        $TextPart = [MimeKit.TextPart]::new("html")
    }
    else {
        $TextPart = [MimeKit.TextPart]::new("plain")
    }
    $TextPart.Text = $Body
    if ($Attachment) {
        [System.Reflection.Assembly]::LoadWithPartialName('System.IO') | Out-Null
        $Multipart = [MimeKit.Multipart]::new("mixed")
        $Multipart.Add($TextPart)
        foreach ($Attach in $Attachment) {
            $MimeType = (Get-MimeType -File $Attach) -split "/"
            $MimePart = [MimeKit.MimePart]::new($MimeType[0], $MimeType[1])
            $MimePart.ContentObject = [MimeKit.ContentObject]::new([IO.File]::OpenRead($Attach), [MimeKit.ContentEncoding]::Default)
            $MimePart.ContentDisposition = [MimeKit.ContentDisposition]::new([MimeKit.ContentDisposition]::Attachment)
            $MimePart.ContentTransferEncoding = [MimeKit.ContentEncoding]::Base64
            $MimePart.FileName = [IO.Path]::GetFileName($Attach)
            $Multipart.Add($MimePart)
        }
        $message.Body = $Multipart
    }
    else {
        $message.Body = $TextPart
    }
    if ($ReturnConstructedMessage) {
        return $message.ToString()
    }
    else {
        return $message
    }
}

function Out-TruncatedString {
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $String,

        [Parameter(Position = 1)]
        [ValidateRange(0,[int32]::MaxValue)]
        [int] $Length = 0
    )

    $outString = $String

    if ($Length -gt 0) {
        if ($String.Length -gt $Length) {
            $outString = $String.Substring(0,($Length - 3)) + '...'
        }
    }

    Write-Output $outString
}

function Read-MimeMessage {
    [cmdletbinding(DefaultParameterSetName = "String")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ParameterSetName = "String")]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $String,
        [parameter(Mandatory = $true,ValueFromPipeline = $true,ParameterSetName = "EmlFile")]
        [ValidateScript( {Test-Path $_})]
        [string[]]
        $EmlFile
    )
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            String {
                foreach ($str in $String) {
                    $stream = [System.IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($str))
                    [MimeKit.MimeMessage]::Load($stream)
                    $stream.Dispose()
                }
            }
            EmlFile {
                foreach ($str in $EmlFile) {
                    [MimeKit.MimeMessage]::Load($str)
                }
            }
        }
    }
}

function Read-Prompt {
    [CmdletBinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        $Options,
        [parameter(Mandatory = $false)]
        [String]
        $Title = "Picky Choosy Time",
        [parameter(Mandatory = $false)]
        [String]
        $Message = "Which do you prefer?",
        [parameter(Mandatory = $false)]
        [Int]
        $Default = 0
    )
    Process {
        $opt = @()
        foreach ($option in $Options) {
            switch ($option.GetType().Name) {
                Hashtable {
                    foreach ($key in $option.Keys) {
                        $opt += New-Object System.Management.Automation.Host.ChoiceDescription "$($key)","$($option[$key])"
                    }
                }
                String {
                    $opt += New-Object System.Management.Automation.Host.ChoiceDescription "$option",$null
                }
            }
        }    
        $choices = [System.Management.Automation.Host.ChoiceDescription[]] $opt
        $answer = $host.ui.PromptForChoice($Title, $Message, $choices, $Default)
        $choices[$answer].Label -replace "&"
    }
}

function Test-FileLock {
    param (
        [parameter(Mandatory = $false)]
        [string]
        $Path
    )
    if ($Path) {
        $oFile = New-Object System.IO.FileInfo $Path
        if ((Test-Path -Path $Path) -eq $false) {
            return $false
        }
        try {
            $oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
            if ($oStream) {
                $oStream.Close()
            }
            return $false
        }
        catch {
            return $true
        }
    }
}

function ThrowTerm {
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $Message
    )
    New-Object System.Management.Automation.ErrorRecord(
        (New-Object Exception $Message),
        'PowerShell.Module.Error',
        [System.Management.Automation.ErrorCategory]::OperationStopped,
        $null
    )
}

function Write-InlineProgress {
    <#
        .SYNOPSIS
            Display an inline progress bar in the PowerShell console.
        .DESCRIPTION
            Display an inline progress bar in the PowerShell console.
        .NOTES
            Be sure to always call the function with either the -Stop or -Completed switch after the progress bar is finished.
            Be sure to NOT output anything while the progress bar is updating - or it WILL "break"!
            This function will not work when run in PowerShell ISE.
 
            Author: ÃËœyvind Kallstad
            Date: 28.04.2016
            Version: 1.0
        .LINK
            https://communary.wordpress.com/
    #>

    [CmdletBinding(DefaultParameterSetName = 'normal')]
    param (
        # Describe the activity being performed.
        [Parameter(Position = 0, ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [string] $Activity,

        # Minimum padding for the activity text. When set to 0 (default) the size of the activity text
        # is automatically adjusted based on the window width.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [ValidateRange(0,[int]::MaxValue)]
        [int] $ActivityPadding = 0,

        # Display seconds remaining.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [ValidateRange(0,[int]::MaxValue)]
        [int] $SecondsRemaining,

        # Display seconds elapsed.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [ValidateRange(0,[int]::MaxValue)]
        [int] $SecondsElapsed,

        # Define the percent complete value for the progress bar.
        [Parameter(ParameterSetName = 'normal')]
        [ValidateRange(0,100)]
        [int] $PercentComplete,

        # Display the percent complete.
        # Note! If the window width is below 40 the percent value will not be displayed.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [switch] $ShowPercent = $true,

        # Stop without any update to the progress bar.
        [Parameter(ParameterSetName = 'stop')]
        [switch] $Stop,

        # Output last progress result.
        [Parameter(ParameterSetName = 'stop')]
        [switch] $OutputLastProgress,

        # Stop the progress bar with a final update.
        [Parameter(ParameterSetName = 'completed')]
        [switch] $Completed,

        # Customize the progress character.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [ValidateLength(1,1)]
        [ValidateNotNull()]
        [string] $ProgressCharacter = '#',

        # Customize the progress fill character.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [ValidateLength(1,1)]
        [ValidateNotNull()]
        [string] $ProgressFillCharacter = '#',

        # Customize the fill character.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [ValidateLength(1,1)]
        [ValidateNotNull()]
        [string] $ProgressFill = '.',

        # Customize the bracket before the progress bar.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [ValidateLength(0,1)]
        [string] $BarBracketStart = '[',

        # Customize the bracket after the progress bar.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [ValidateLength(0,1)]
        [string] $BarBracketEnd = ']',

        # Use Write-Output instead of Console.Write
        # If you want to support transcripts, you need to use this parameter on the last update of the
        # progress bar so that it will be written to the transcript file. The same goes for any error handling if
        # you want the last status of the progress bar to be written to the transcript.
        [Parameter(ParameterSetName = 'normal')]
        [Parameter(ParameterSetName = 'completed')]
        [switch] $UseWriteOutput
    )

    # this function only works when run from the console
    if ($Host.Name -notlike '*ISE*') {
        if ($Stop) {
            if ($OutputLastProgress) {
                Write-Host (($script:lastProgressString).ToString()) -NoNewline
            }
            else {
                Remove-Variable -Name 'lastProgressString' -Scope 'Script' -ErrorAction SilentlyContinue
                [console]::WriteLine()
            }
            [console]::CursorVisible = $true
        }
        else {
            if ($Completed) {
                $PercentComplete = 100
                if ($PSBoundParameters.ContainsKey('SecondsRemaining')) {
                    # have to force it to 0 or it will display the last value before it finished
                    $SecondsRemaining = 0
                }
            }

            # if the buffer if full, we need to resize it to make sure that the progress bar don't break
            if (($host.UI.RawUI.CursorPosition.y + 1) -ge ($host.UI.RawUI.BufferSize.Height)) {
                $size = New-Object System.Management.Automation.Host.Size(($host.UI.RawUI.BufferSize.Width), (($host.UI.RawUI.BufferSize.Height + 1000)))
                $host.UI.RawUI.BufferSize = $size
            }

            $cursorPosition = $host.UI.RawUI.CursorPosition
            #$cursorPositionY = $host.UI.RawUI.CursorPosition.Y
            [console]::CursorVisible=$false
            $windowWidth = [console]::WindowWidth

            # if screen is very small, don't display the percent
            if ($windowWidth -le 40) {$ShowPercent = $false}

            # calculate the size of the activity part of the output string
            if ($ActivityPadding -eq 0) {
                $activityPart = [math]::Floor($windowWidth / 4)
            }
            else {
                $activityPart = $ActivityPadding
            }

            # if activity string is longer than the allocated part length, truncate it
            if ($Activity.Length -gt $activityPart) {
                $Activity = Out-TruncatedString -String $Activity -Length $activityPart
            }

            $progressString = New-Object System.Text.StringBuilder -ArgumentList $windowWidth

            # add activity text to the progress string
            [void]$progressString.Append("$($Activity.PadRight($ActivityPart, ' ')) ")

            # add seconds elapsed to the progress string
            if ($PSBoundParameters.ContainsKey('SecondsElapsed')) {
                [void]$progressString.Append([timespan]::FromSeconds($SecondsElapsed).ToString() + ' ')
            }

            # add seconds remaining to the progress string
            if ($PSBoundParameters.ContainsKey('SecondsRemaining')) {
                [void]$progressString.Append([timespan]::FromSeconds($SecondsRemaining).ToString() + ' ')
            }

            # add the start bracket for the progress bar to the progress string
            [void]$progressString.Append($BarBracketStart)

            # calculate the width of the progress bar
            # the 5 is to account for the space of the percent information
            if ($ShowPercent) {
                $progressBarWidth = $windowWidth - (($progressString.Length) + 5)
            }
            else {
                $progressBarWidth = $windowWidth - ($progressString.Length) + 1
            }

            # add one to the progress bar width if no end bracket is used
            if (-not ($BarBracketEnd)) {
                $progressBarWidth++
            }

            # calculate the bar character percentage and how much of the bar is filled and how much is not filled
            $barCharacterInPercent = ($progressBarWidth - 2) / 100
            $barProgressed = [math]::Floor($PercentComplete * $barCharacterInPercent)
            $barNotProgressed = ($progressBarWidth - 2) - $barProgressed

            # add the progress bar to progress string
            if ($barProgressed -gt 0) {
                if ($barNotProgressed -gt 0) {
                    [void]$progressString.Append(($ProgressFillCharacter * ($barProgressed - 1)))
                    [void]$progressString.Append($ProgressCharacter)
                }
                else {
                    [void]$progressString.Append(($ProgressFillCharacter * $barProgressed))
                }
            }
            [void]$progressString.Append("$($ProgressFill * $barNotProgressed)$($BarBracketEnd)")

            # add the percent complete to the progress string
            if ($ShowPercent) {
                [void]$progressString.Append(" $($PercentComplete.ToString().PadLeft(3, ' '))% ")
            }

            # if not already present, create a string builder to hold the last progress string
            if (-not ($script:lastProgressString)) {
                $script:lastProgressString = New-Object System.Text.StringBuilder($windowWidth)
            }

            # only update the progress string if it's different from the last (optimization)
            if (-not($script:lastProgressString.ToString() -eq $progressString.ToString())) {
                if ($UseWriteOutput) {
                    Write-Output ($progressString.ToString())
                }
                else {
                    [console]::Write(($progressString.ToString()))
                }

                $host.UI.RawUI.CursorPosition = $cursorPosition
                #$host.UI.RawUI.CursorPosition = 0, $cursorPositionY
                [void]$script:lastProgressString.Clear()
                [void]$script:lastProgressString.Append($progressString.ToString())
            }

            if ($Completed) {
                # do some clean-up and jump to the next line
                Remove-Variable -Name 'lastProgressString' -Scope 'Script' -ErrorAction SilentlyContinue
                [console]::CursorVisible = $true
                [console]::WriteLine()
            }
        }
    }
    else {
        Write-Warning 'This function is not compatible with PowerShell ISE.'
    }
}

function Get-GSGroupMemberListPrivate {
    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('GroupEmail','Group','Email')]
        [String[]]
        $Identity,
        [parameter(Mandatory = $false)]
        [ValidateSet("Owner","Manager","Member")]
        [String[]]
        $Roles,
        [parameter(Mandatory = $false)]
        [ValidateRange(1,200)]
        [Alias('MaxResults')]
        [Int]
        $PageSize = "200"
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($Id in $Identity) {
            try {
                if ($Id -notlike "*@*.*") {
                    $Id = "$($Id)@$($Script:PSGSuite.Domain)"
                }
                $request = $service.Members.List($Id)
                if ($PageSize) {
                    $request.MaxResults = $PageSize
                }
                if ($Roles) {
                    Write-Verbose "Getting all members of group '$Id' in the following role(s): $($Roles -join ',')"
                    $request.Roles = "$($Roles -join ',')"
                }
                else {
                    Write-Verbose "Getting all members of group '$Id'"
                }
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    if ($null -ne $result.MembersValue) {
                        $result.MembersValue | Add-Member -MemberType NoteProperty -Name 'Group' -Value $Id -PassThru  | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Email} -PassThru -Force
                    }
                    $request.PageToken = $result.NextPageToken
                    [int]$retrieved = ($i + $result.MembersValue.Count) - 1
                    Write-Verbose "Retrieved $retrieved members..."
                    [int]$i = $i + $result.MembersValue.Count
                }
                until (!$result.NextPageToken)
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

function Get-GSOrganizationalUnitListPrivate {
    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [Alias('OrgUnitPath','BaseOrgUnitPath')]
        [String]
        $SearchBase,
        [parameter(Mandatory = $false)]
        [Alias('Type')]
        [ValidateSet('Subtree','OneLevel','All','Children')]
        [String]
        $SearchScope = 'All'
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.orgunit'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Getting all Organizational Units"
            $request = $service.Orgunits.List($Script:PSGSuite.CustomerId)
            $request.Type = switch ($SearchScope) {
                Subtree {
                    'All'
                }
                OneLevel {
                    'Children'
                }
                default {
                    $SearchScope
                }
            }
            if ($SearchBase) {
                $request.OrgUnitPath = $SearchBase
            }
            $request.Execute() | Select-Object -ExpandProperty OrganizationUnits
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

function Get-GSResourceListPrivate {
    [CmdletBinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('Calendars','Buildings','Features')]
        [String[]]
        $Resource = @('Calendars','Buildings','Features'),
        [parameter(Mandatory = $false)]
        [Alias('Query')]
        [String[]]
        $Filter,
        [parameter(Mandatory = $false)]
        [String[]]
        $OrderBy,
        [parameter(Mandatory = $false)]
        [ValidateRange(1,500)]
        [Alias("MaxResults")]
        [Int]
        $PageSize = "500"
    )
    Begin {
        if ($MyInvocation.InvocationName -eq 'Get-GSCalendarResourceList') {
            $Resource = 'Calendars'
        }
        $propHash = @{
            Calendars  = 'Items'
            Buildings = 'BuildingsValue'
            Features = 'FeaturesValue'
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.resource.calendar'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($R in $Resource) {
                $request = $service.Resources.$R.List($(if($Script:PSGSuite.CustomerID) {$Script:PSGSuite.CustomerID}else {'my_customer'}))
                if ($R -eq 'Calendars') {
                    if ($PageSize) {
                        $request.MaxResults = $PageSize
                    }
                    if ($OrderBy) {
                        $request.OrderBy = "$($OrderBy -join ", ")"
                    }
                    if ($Filter) {
                        if ($Filter -eq '*') {
                            $Filter = ""
                        }
                        else {
                            $Filter = "$($Filter -join " ")"
                        }
                        $Filter = $Filter -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False"
                        $request.Query = $Filter.Trim()
                        Write-Verbose "Getting Resource $R matching filter: `"$($Filter.Trim())`""
                    }
                    else {
                        Write-Verbose "Getting all Resource $R"
                    }
                }
                else {
                    Write-Verbose "Getting all Resource $R"
                }
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    $result.$($propHash[$R]) | ForEach-Object {
                        $obj = $_
                        $_Id = switch ($R) {
                            Calendars {
                                $obj.ResourceId
                            }
                            Buildings {
                                $obj.BuildingId
                            }
                            Features {
                                $obj.Name
                            }
                        }
                        $_ | Add-Member -MemberType NoteProperty -Name 'Id' -Value $_Id -PassThru | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $R -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Id} -PassThru -Force
                    }
                    if ($result.NextPageToken) {
                        $request.PageToken = $result.NextPageToken
                    }
                    [int]$retrieved = ($i + $result.$($propHash[$R]).Count) - 1
                    Write-Verbose "Retrieved $retrieved resources..."
                    [int]$i = $i + $result.$($propHash[$R]).Count
                }
                until (!$result.NextPageToken)
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

function Get-GSShortUrlListPrivate {
    [cmdletbinding()]
    Param (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory=$false)]
        [ValidateSet("Full","Analytics_Clicks")]
        [string]
        $Projection = "Full"
    )
    Process {
        foreach ($U in $User) {
            if ($U -ceq 'me') {
                $U = $Script:PSGSuite.AdminEmail
            }
            elseif ($U -notlike "*@*.*") {
                $U = "$($U)@$($Script:PSGSuite.Domain)"
            }
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/urlshortener'
                ServiceType = 'Google.Apis.Urlshortener.v1.UrlshortenerService'
                User        = "$U"
            }
            $service = New-GoogleService @serviceParams
            try {
                Write-Verbose "Getting Short Url list for User '$U'"
                $request = $service.Url.List()
                $result = $request.Execute()
                if ($null -ne $result.Items) {
                    $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}


function Get-GSUserASPListPrivate {
    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($U in $User) {
            try {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Getting ASP list for User '$U'"
                $request = $service.Asps.List($U)
                $result = $request.Execute()
                if ($null -ne $result.Items) {
                    $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

function Get-GSUserLicenseListPrivate {
    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false)]
        [ValidateSet("Google-Apps","Google-Drive-storage","Google-Vault")]
        [string[]]
        $ProductID = @("Google-Apps","Google-Drive-storage","Google-Vault"),
        [parameter(Mandatory = $false)]
        [Alias("SkuId")]
        [ValidateSet("G-Suite-Enterprise","Google-Apps-Unlimited","Google-Apps-For-Business","Google-Apps-For-Postini","Google-Apps-Lite","Google-Drive-storage-20GB","Google-Drive-storage-50GB","Google-Drive-storage-200GB","Google-Drive-storage-400GB","Google-Drive-storage-1TB","Google-Drive-storage-2TB","Google-Drive-storage-4TB","Google-Drive-storage-8TB","Google-Drive-storage-16TB","Google-Vault","Google-Vault-Former-Employee","1010020020")]
        [string]
        $License,
        [parameter(Mandatory = $false)]
        [Alias("MaxResults")]
        [ValidateRange(1,1000)]
        [Int]
        $PageSize = "1000"
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/apps.licensing'
            ServiceType = 'Google.Apis.Licensing.v1.LicensingService'
        }
        $service = New-GoogleService @serviceParams
        if ($License) {
            $ProductID = @{
                '1010020020'                   = 'Google-Apps'
                'G-Suite-Enterprise'           = 'Google-Apps'
                'Google-Apps-Unlimited'        = 'Google-Apps'
                'Google-Apps-For-Business'     = 'Google-Apps'
                'Google-Apps-For-Postini'      = 'Google-Apps'
                'Google-Apps-Lite'             = 'Google-Apps'
                'Google-Vault'                 = 'Google-Vault'
                'Google-Vault-Former-Employee' = 'Google-Vault'
                'Google-Drive-storage-20GB'    = 'Google-Drive-storage'
                'Google-Drive-storage-50GB'    = 'Google-Drive-storage'
                'Google-Drive-storage-200GB'   = 'Google-Drive-storage'
                'Google-Drive-storage-400GB'   = 'Google-Drive-storage'
                'Google-Drive-storage-1TB'     = 'Google-Drive-storage'
                'Google-Drive-storage-2TB'     = 'Google-Drive-storage'
                'Google-Drive-storage-4TB'     = 'Google-Drive-storage'
                'Google-Drive-storage-8TB'     = 'Google-Drive-storage'
                'Google-Drive-storage-16TB'    = 'Google-Drive-storage'
            }[$License]
        }
        $response = @()
    }
    Process {
        try {
            foreach ($prodId in $ProductID) {
                if ($License) {
                    if ($License -eq "G-Suite-Enterprise") {
                        $License = "1010020020"
                    }
                    $request = $service.LicenseAssignments.ListForProductAndSku($prodId,$License,$Script:PSGSuite.Domain)
                }
                else {
                    $request = $service.LicenseAssignments.ListForProduct($prodId,$Script:PSGSuite.Domain)
                }
                if ($PageSize) {
                    $request.MaxResults = $PageSize
                }
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    $response += $result.Items
                    $request.PageToken = $result.NextPageToken
                    [int]$retrieved = ($i + $result.Items.Count) - 1
                    if ($License) {
                        Write-Verbose "Retrieved $retrieved licenses for product '$prodId' & sku '$License'..."
                    }
                    else {
                        Write-Verbose "Retrieved $retrieved licenses for product '$prodId'..."
                    }
                    [int]$i = $i + $result.Items.Count
                }
                until (!$result.NextPageToken)
            }
            Write-Verbose "Retrieved $($response.Count) total licenses"
            return $response
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

function Get-GSUserSchemaListPrivate {
    [cmdletbinding()]
    Param( )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.userschema'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $request = $service.Schemas.List($Script:PSGSuite.CustomerId)
            $result = $request.Execute()
            if ($null -ne $result.SchemasValue) {
                $result.SchemasValue
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

function Get-GSUserTokenListPrivate {
    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($U in $User) {
            try {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Getting Token list for User '$U'"
                $request = $service.Tokens.List($U)
                $result = $request.Execute()
                if ($null -ne $result.Items) {
                    $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

function Get-GSToken {
    <#
    .Synopsis
    Requests an Access Token for REST API authentication. Defaults to 3600 seconds token expiration time.
 
    .DESCRIPTION
    Requests an Access Token for REST API authentication. Defaults to 3600 seconds token expiration time.
 
    .EXAMPLE
    $Token = Get-GSToken -Scopes 'https://www.google.com/m8/feeds' -AdminEmail $User
    $headers = @{
        Authorization = "Bearer $($Token)"
        'GData-Version' = '3.0'
    }
    #>

    Param
    (
        [parameter(Mandatory = $true)]
        [Alias('Scope')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Scopes,
        [parameter(Mandatory = $false)]
        [Alias('User')]
        [ValidateNotNullOrEmpty()]
        [String]
        $AdminEmail = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String]
        $AppEmail = $Script:PSGSuite.AppEmail,
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String]
        $P12KeyPath = $Script:PSGSuite.P12KeyPath
    )
    function Invoke-URLEncode ($Object) {
        ([String]([System.Convert]::ToBase64String($Object))).TrimEnd('=').Replace('+','-').Replace('/','_')
    }
    $googleCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("$P12KeyPath", "notasecret",[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable )
    $rsaPrivate = $googleCert.PrivateKey
    $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
    $rsa.ImportParameters($rsaPrivate.ExportParameters($true))
    $rawheader = [Ordered]@{
        alg = "RS256"
        typ = "JWT"
    } | ConvertTo-Json -Compress
    $header = Invoke-URLEncode ([System.Text.Encoding]::UTF8.GetBytes($rawheader))
    [string]$now = Get-Date (Get-Date).ToUniversalTime() -UFormat "%s"
    [int]$createDate = $now -replace "(\..*|\,.*)"
    [int]$expiryDate = $createDate + 3600
    $rawclaims = [Ordered]@{
        iss   = "$AppEmail"
        sub   = "$AdminEmail"
        scope = "$($Scopes -join " ")"
        aud   = "https://www.googleapis.com/oauth2/v4/token"
        exp   = "$expiryDate"
        iat   = "$createDate"
    } | ConvertTo-Json
    $claims = Invoke-URLEncode ([System.Text.Encoding]::UTF8.GetBytes($rawclaims))
    $toSign = [System.Text.Encoding]::UTF8.GetBytes($header + "." + $claims)
    $sig = Invoke-URLEncode ($rsa.SignData($toSign,"SHA256"))
    $jwt = $header + "." + $claims + "." + $sig
    $fields = [Ordered]@{
        grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
        assertion  = $jwt
    }
    try {
        Write-Verbose "Acquiring access token..."
        $response = Invoke-RestMethod -Uri "https://www.googleapis.com/oauth2/v4/token" -Method Post -Body $fields -ContentType "application/x-www-form-urlencoded" -ErrorAction Stop -Verbose:$false | Select-Object -ExpandProperty access_token
        Write-Verbose "Access token acquired!"
        return $response
    }
    catch {
        Write-Verbose "Failed to acquire access token!"
        $PSCmdlet.ThrowTerminatingError($_)
    }
}

Export-ModuleMember -Function 'Get-GSToken'

function New-GoogleService {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true,Position = 0)]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Scope,
        [Parameter(Mandatory = $true,Position = 1)]
        [String]
        $ServiceType,
        [Parameter(Mandatory = $false,Position = 2)]
        [Alias('AdminEmail')]
        [String]
        $User = $script:PSGSuite.AdminEmail
    )
    Process {
        try {
            if ($script:PSGSuite.P12KeyPath) {
                Write-Verbose "Building ServiceAccountCredential from P12Key as user '$User'"
                $certificate = New-Object 'System.Security.Cryptography.X509Certificates.X509Certificate2' -ArgumentList (Resolve-Path $script:PSGSuite.P12KeyPath),"notasecret",([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
                $credential = New-Object 'Google.Apis.Auth.OAuth2.ServiceAccountCredential' (New-Object 'Google.Apis.Auth.OAuth2.ServiceAccountCredential+Initializer' $script:PSGSuite.AppEmail -Property @{
                        User   = $User
                        Scopes = [string[]]$Scope
                    }
                ).FromCertificate($certificate)
            }
            elseif ($script:PSGSuite.ClientSecretsPath) {
                Write-Verbose "Building UserCredentials from ClientSecrets as user '$User'"
                $stream = New-Object System.IO.FileStream $script:PSGSuite.ClientSecretsPath,'Open','Read'
                $credPath = Join-Path (Resolve-Path (Join-Path "~" ".scrthq")) "PSGSuite"
                $credential = [Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker]::AuthorizeAsync(
                    [Google.Apis.Auth.OAuth2.GoogleClientSecrets]::Load($stream).Secrets,
                    [string[]]$Scope,
                    $User,
                    [System.Threading.CancellationToken]::None,
                    [Google.Apis.Util.Store.FileDataStore]::new($credPath,$true)
                ).Result
                $stream.Close()
            }
            else {
                $PSCmdlet.ThrowTerminatingError((ThrowTerm "The current config '$($script:PSGSuite.ConfigName)' does not contain a P12KeyPath or a ClientSecretsPath! PSGSuite is unable to build a credential object for the service without a path to a credential file! Please update the configuration to include a path at least one of the two credential types."))
            }
            New-Object "$ServiceType" (New-Object 'Google.Apis.Services.BaseClientService+Initializer' -Property @{
                    HttpClientInitializer = $credential
                    ApplicationName       = "PSGSuite - $env:USERNAME"
                }
            )
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
}

Export-ModuleMember -Function 'New-GoogleService'

function Add-GSCalendarSubscription {
    <#
    .SYNOPSIS
    Adds a calendar to a users calendar list (aka subscribes to the specified calendar)
     
    .DESCRIPTION
    Adds a calendar to a users calendar list (aka subscribes to the specified calendar)
     
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
     
    .PARAMETER CalendarID
    The calendar ID of the calendar you would like to subscribe the user to
     
    .PARAMETER Selected
    Whether the calendar content shows up in the calendar UI. Optional. The default is False.
     
    .PARAMETER Hidden
    Whether the calendar has been hidden from the list. Optional. The default is False.
     
    .PARAMETER DefaultReminderMethod
    The method used by this reminder. Defaults to email.
     
    Possible values are:
    * "email" - Reminders are sent via email.
    * "sms" - Reminders are sent via SMS. These are only available for G Suite customers. Requests to set SMS reminders for other account types are ignored.
    * "popup" - Reminders are sent via a UI popup.
     
    .PARAMETER DefaultReminderMinutes
    Number of minutes before the start of the event when the reminder should trigger. Defaults to 30 minutes.
     
    Valid values are between 0 and 40320 (4 weeks in minutes).
     
    .PARAMETER DefaultNotificationMethod
    The method used to deliver the notification. Defaults to email.
     
    Possible values are:
    * "email" - Reminders are sent via email.
    * "sms" - Reminders are sent via SMS. This value is read-only and is ignored on inserts and updates. SMS reminders are only available for G Suite customers.
     
    .PARAMETER DefaultNotificationType
    The type of notification. Defaults to eventChange.
     
    Possible values are:
    * "eventCreation" - Notification sent when a new event is put on the calendar.
    * "eventChange" - Notification sent when an event is changed.
    * "eventCancellation" - Notification sent when an event is cancelled.
    * "eventResponse" - Notification sent when an event is changed.
    * "agenda" - An agenda with the events of the day (sent out in the morning).
     
    .PARAMETER Color
    The color of the calendar.
     
    .PARAMETER SummaryOverride
    The summary that the authenticated user has set for this calendar.
     
    .EXAMPLE
    Add-GSCalendarSubscription -User me -CalendarId john.smith@domain.com -Selected -Color Cyan
 
    Adds the calendar 'john.smith@domain.com' to the AdminEmail user's calendar list
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User,
        [parameter(Mandatory = $true)]
        [String[]]
        $CalendarId,
        [parameter(Mandatory = $false)]
        [Switch]
        $Selected,
        [parameter(Mandatory = $false)]
        [Switch]
        $Hidden,
        [parameter(Mandatory = $false)]
        [ValidateSet('email','sms','popup')]
        [String]
        $DefaultReminderMethod = 'email',
        [parameter(Mandatory = $false)]
        [ValidateRange(0,40320)]
        [Int]
        $DefaultReminderMinutes = 30,
        [parameter(Mandatory = $false)]
        [ValidateSet('email','sms')]
        [String]
        $DefaultNotificationMethod = 'email',
        [parameter(Mandatory = $false)]
        [ValidateSet('eventCreation','eventChange','eventCancellation','eventResponse','agenda')]
        [String]
        $DefaultNotificationType = 'eventChange',
        [parameter(Mandatory = $false)]
        [ValidateSet("Periwinkle","Seafoam","Lavender","Coral","Goldenrod","Beige","Cyan","Grey","Blue","Green","Red")]
        [String]
        $Color,
        [parameter(Mandatory = $false)]
        [String]
        $SummaryOverride
    )
    Begin {
        $colorHash = @{
            Periwinkle = 1
            Seafoam    = 2
            Lavender   = 3
            Coral      = 4
            Goldenrod  = 5
            Beige      = 6
            Cyan       = 7
            Grey       = 8
            Blue       = 9
            Green      = 10
            Red        = 11
        }
    }
    Process {
        try {
            if ($User -ceq 'me') {
                $User = $Script:PSGSuite.AdminEmail
            }
            elseif ($User -notlike "*@*.*") {
                $User = "$($User)@$($Script:PSGSuite.Domain)"
            }
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/calendar'
                ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
                User        = $User
            }
            $service = New-GoogleService @serviceParams
            foreach ($calId in $CalendarID) {
                $body = New-Object 'Google.Apis.Calendar.v3.Data.CalendarListEntry' -Property @{
                    Id = $calId
                    Selected = $Selected
                    Hidden = $Hidden
                }
                $DefaultReminders = New-Object 'Google.Apis.Calendar.v3.Data.EventReminder' -Property @{
                    Method = $DefaultReminderMethod
                    Minutes = $DefaultReminderMinutes
                }
                $body.DefaultReminders = [Google.Apis.Calendar.v3.Data.EventReminder[]]$DefaultReminders
                $DefaultNotification = New-Object 'Google.Apis.Calendar.v3.Data.CalendarNotification'
                $DefaultNotification.Method = $DefaultNotificationMethod
                $DefaultNotification.Type = $DefaultNotificationType
                $body.NotificationSettings = New-Object 'Google.Apis.Calendar.v3.Data.CalendarListEntry+NotificationSettingsData' -Property @{
                    Notifications = [Google.Apis.Calendar.v3.Data.CalendarNotification[]]$DefaultNotification
                }
                foreach ($key in $PSBoundParameters.Keys) {
                    switch ($key) {
                        Color {
                            $body.ColorId = $colorHash[$Color]
                        }
                        Default {
                            if ($body.PSObject.Properties.Name -contains $key) {
                                $body.$key = $PSBoundParameters[$key]
                            }
                        }
                    }
                }
                Write-Verbose "Subscribing user '$User' to Calendar '$($calId)'"
                $request = $service.CalendarList.Insert($body)
                $request.Execute()
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSCalendarSubscription'

function Get-GSCalendarACL {
    <#
    .SYNOPSIS
    Gets the ACL calendar for a calendar
     
    .DESCRIPTION
    Gets the ACL for a calendar
     
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
     
    .PARAMETER CalendarId
    The calendar ID of the calendar you would like to list ACLS for.
 
    Defaults to the user's primary calendar
 
    .PARAMETER RuleId
    The Id of the Rule you would like to retrieve specifically. Leave empty to return the full ACL list instead.
     
    .PARAMETER PageSize
    Maximum number of events returned on one result page.
     
    .EXAMPLE
    Get-GSCalendarACL -User me -CalendarID "primary"
     
    This gets the ACL on the primary calendar of the AdminUser.
    #>

    [cmdletbinding(DefaultParameterSetName = 'List')]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [String]
        $CalendarId = "primary",
        [parameter(Mandatory = $false, ParameterSetName = 'Get')]
        [String]
        $RuleId,
        [parameter(Mandatory = $false, ParameterSetName = 'List')]
        [ValidateRange(1,2500)]
        [Int]
        $PageSize = 2500
    )
    Process {
        foreach ($U in $User) {
            if ($U -ceq 'me') {
                $U = $Script:PSGSuite.AdminEmail
            }
            elseif ($U -notlike "*@*.*") {
                $U = "$($U)@$($Script:PSGSuite.Domain)"
            }
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/calendar'
                ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
                User        = $U
            }
            $service = New-GoogleService @serviceParams
            try {
                switch ($PSCmdlet.ParameterSetName) {
                    Get {
                        Write-Verbose "Getting ACL Id '$RuleId' of calendar '$CalendarId' for user '$U'"
                        $request = $service.Acl.Get($CalendarId,$RuleId)
                        $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $CalendarId -PassThru
                    }
                    List {
                        $request = $service.Acl.List($CalendarId)
                        foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne 'CalendarId'}) {
                            switch ($key) {
                                Default {
                                    if ($request.PSObject.Properties.Name -contains $key) {
                                        $request.$key = $PSBoundParameters[$key]
                                    }
                                }
                            }
                        }
                        if ($PageSize) {
                            $request.MaxResults = $PageSize
                        }
                        Write-Verbose "Getting ACL List of calendar '$CalendarId' for user '$U'"
                        [int]$i = 1
                        do {
                            $result = $request.Execute()
                            $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $CalendarId -PassThru
                            if ($result.NextPageToken) {
                                $request.PageToken = $result.NextPageToken
                            }
                            [int]$retrieved = ($i + $result.Items.Count) - 1
                            Write-Verbose "Retrieved $retrieved Calendar Events..."
                            [int]$i = $i + $result.Items.Count
                        }
                        until (!$result.NextPageToken)
                    }
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSCalendarACL'

function Get-GSCalendarEventList {
    <#
    .SYNOPSIS
    Gets the calendar events for a user
     
    .DESCRIPTION
    Gets the calendar events for a user
     
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
     
    .PARAMETER CalendarId
    The calendar ID of the calendar you would like to list events from.
 
    Defaults to the user's primary calendar
     
    .PARAMETER Filter
    Free text search terms to find events that match these terms in any field, except for extended properties.
     
    .PARAMETER OrderBy
    The order of the events returned in the result.
 
    Acceptable values are:
    * "startTime": Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)
    * "updated": Order by last modification time (ascending).
     
    .PARAMETER MaxAttendees
    The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned.
     
    .PARAMETER PageSize
    Maximum number of events returned on one result page.
     
    .PARAMETER ShowDeleted
    Whether to include deleted events (with status equals "cancelled") in the result. Cancelled instances of recurring events (but not the underlying recurring event) will still be included if showDeleted and singleEvents are both False. If showDeleted and singleEvents are both True, only single instances of deleted events (but not the underlying recurring events) are returned.
     
    .PARAMETER ShowHiddenInvitations
    Whether to include hidden invitations in the result.
     
    .PARAMETER SingleEvents
    Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves.
     
    .PARAMETER TimeMin
    Lower bound (inclusive) for an event's end time to filter by. If TimeMax is set, TimeMin must be smaller than timeMax.
     
    .PARAMETER TimeMax
    Upper bound (exclusive) for an event's start time to filter by. If TimeMin is set, TimeMax must be greater than timeMin.
     
    .EXAMPLE
    Get-GSCalendarEventList -TimeMin (Get-Date "01-21-2018 00:00:00") -TimeMax (Get-Date "01-28-2018 23:59:59") -SingleEvents
     
    This gets the single events on the primary calendar of the Admin for the week of Jan 21-28, 2018.
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [String]
        $CalendarId = "primary",
        [parameter(Mandatory = $false)]
        [Alias('Q','Query')]
        [String]
        $Filter,
        [parameter(Mandatory = $false)]
        [ValidateSet("StartTime","Updated")]
        [String]
        $OrderBy,
        [parameter(Mandatory = $false)]
        [Int]
        $MaxAttendees,
        [parameter(Mandatory = $false)]
        [ValidateRange(1,2500)]
        [Int]
        $PageSize = 2500,
        [parameter(Mandatory = $false)]
        [switch]
        $ShowDeleted,
        [parameter(Mandatory = $false)]
        [switch]
        $ShowHiddenInvitations,
        [parameter(Mandatory = $false)]
        [switch]
        $SingleEvents,
        [parameter(Mandatory = $false)]
        [DateTime]
        $TimeMin,
        [parameter(Mandatory = $false)]
        [DateTime]
        $TimeMax
    )
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                $serviceParams = @{
                    Scope       = 'https://www.googleapis.com/auth/calendar'
                    ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
                    User        = $U
                }
                $service = New-GoogleService @serviceParams
                foreach ($calId in $CalendarId) {
                    $request = $service.Events.List($calId)
                    foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne 'CalendarId'}) {
                        switch ($key) {
                            Filter {
                                $request.Q = $Filter
                            }
                            Default {
                                if ($request.PSObject.Properties.Name -contains $key) {
                                    $request.$key = $PSBoundParameters[$key]
                                }
                            }
                        }
                    }
                    if ($PageSize) {
                        $request.MaxResults = $PageSize
                    }
                    if ($Filter) {
                        Write-Verbose "Getting all Calendar Events matching filter '$Filter' on calendar '$calId' for user '$U'"
                    }
                    else {
                        Write-Verbose "Getting all Calendar Events on calendar '$calId' for user '$U'"
                    }
                    [int]$i = 1
                    do {
                        $result = $request.Execute()
                        $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru
                        if ($result.NextPageToken) {
                            $request.PageToken = $result.NextPageToken
                        }
                        [int]$retrieved = ($i + $result.Items.Count) - 1
                        Write-Verbose "Retrieved $retrieved Calendar Events..."
                        [int]$i = $i + $result.Items.Count
                    }
                    until (!$result.NextPageToken)
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSCalendarEventList'

function Get-GSCalendarSubscription {
    <#
    .SYNOPSIS
    Gets a subscribed calendar from a users calendar list. Returns the full calendar list if no CalendarId is specified.
     
    .DESCRIPTION
    Gets a subscribed calendar from a users calendar list. Returns the full calendar list if no CalendarId is specified.
     
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
     
    .PARAMETER CalendarID
    The calendar ID of the calendar you would like to get info for. If left blank, returns the list of calendars the user is subscribed to.
     
    .EXAMPLE
    Get-GSCalendarSubscription
 
    Gets the AdminEmail user's calendar list
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,Position = 1)]
        [String[]]
        $CalendarId
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/calendar'
            ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        if ($PSBoundParameters.Keys -contains 'CalendarId') {
            foreach ($calId in $CalendarID) {
                try {
                    Write-Verbose "Getting subscribed calendar '$($calId)' for user '$User'"
                    $request = $service.CalendarList.Get($calId)
                    $request.Execute()
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
        else {
            try {
                Write-Verbose "Getting subscribed calendar list for user '$User'"
                $request = $service.CalendarList.List()
                $request.Execute() | Select-Object -ExpandProperty Items
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSCalendarSubscription'

function New-GSCalendarACL {
    <#
    .SYNOPSIS
    Adds Google User to Calendar
 
    .DESCRIPTION
    Adds Google User to Calendar
 
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config.
 
    .PARAMETER CalendarID
    The Id of the calendar you would like to share
 
    Defaults to the user's primary calendar.
 
    .PARAMETER Role
    The role assigned to the scope.
 
    Available values are:
    * "none" - Provides no access.
    * "freeBusyReader" - Provides read access to free/busy information.
    * "reader" - Provides read access to the calendar. Private events will appear to users with reader access, but event details will be hidden.
    * "writer" - Provides read and write access to the calendar. Private events will appear to users with writer access, and event details will be visible.
    * "owner" - Provides ownership of the calendar. This role has all of the permissions of the writer role with the additional ability to see and manipulate ACLs.
 
    .PARAMETER Value
    The email address of a user or group, or the name of a domain, depending on the scope type. Omitted for type "default".
 
    .PARAMETER Type
    The type of the scope.
 
    Available values are:
    * "default" - The public scope. This is the default value.
    * "user" - Limits the scope to a single user.
    * "group" - Limits the scope to a group.
    * "domain" - Limits the scope to a domain.
 
    Note: The permissions granted to the "default", or public, scope apply to any user, authenticated or not.
 
    .EXAMPLE
    New-GSCalendarACL -CalendarID jennyappleseed@domain.com -Role reader -Value Jonnyappleseed@domain.com -Type user
 
    Gives Jonnyappleseed@domain.com reader access to jennyappleseed's calendar.
    #>

    [cmdletbinding(DefaultParameterSetName = "AttendeeEmails")]
    Param
    (
        [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail", "UserKey", "Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = "me",
        [parameter(Mandatory = $false)]
        [String[]]
        $CalendarId = "primary",
        [parameter(Mandatory = $true)]
        [ValidateSet("owner", "writer", "reader", "none", "freeBusyReader")]
        [String]
        $Role,
        [parameter(Mandatory = $true)]
        [String]
        $Value,
        [parameter(Mandatory = $false)]
        [ValidateSet("default", "user", "group", "domain")]
        [String]
        $Type = "user"
    )
    Process {
        foreach ($U in $User) {
            if ($U -ceq 'me') {
                $U = $Script:PSGSuite.AdminEmail
            }
            elseif ($U -notlike "*@*.*") {
                $U = "$($U)@$($Script:PSGSuite.Domain)"
            }
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/calendar'
                ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
                User        = $U
            }
            $service = New-GoogleService @serviceParams
            foreach ($calId in $CalendarID) {
                try {
                    $body = New-Object 'Google.Apis.Calendar.v3.Data.AclRule'
                    $scopeData = New-Object "Google.Apis.Calendar.v3.Data.AclRule+ScopeData"
                    foreach ($key in $PSBoundParameters.Keys) {
                        switch ($key) {
                            Role {
                                $body.Role = $PSBoundParameters[$key]
                            }
                            Type {
                                $scopeData.Type = $PSBoundParameters[$key]
                            }
                            Value {
                                $scopeData.Value = $PSBoundParameters[$key]
                            }
                            Default {
                                if ($body.PSObject.Properties.Name -contains $key) {
                                    $body.$key = $PSBoundParameters[$key]
                                }
                            }
                        }
                    }
                    Write-Verbose "Inserting new ACL for type '$Type' with value '$Value' in role '$Role' on calendar '$calId' for user '$U'"
                    $body.Scope = $scopeData
                    $request = $service.Acl.Insert($body, $calId)
                    $request.Execute()
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
    }
}

Export-ModuleMember -Function 'New-GSCalendarACL'

function New-GSCalendarEvent {
    <#
    .SYNOPSIS
    Creates a new calendar event
 
    .DESCRIPTION
    Creates a new calendar event
 
    .PARAMETER Summary
    Event summary
 
    .PARAMETER Description
    Event description
 
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config.
 
    .PARAMETER CalendarID
    The calendar ID of the calendar you would like to list events from.
 
    Defaults to the user's primary calendar.
 
    .PARAMETER AttendeeEmails
    The email addresses of the attendees to add.
 
    NOTE: This performs simple adds without additional attendee options. If additional options are needed, use the Attendees parameter instead.
 
    .PARAMETER Attendees
    The EventAttendee object(s) to add. Use Add-GSEventAttendee with this parameter for best results.
 
    .PARAMETER Location
    Event location
 
    .PARAMETER EventColor
    Color of the event as seen in Calendar
 
    .PARAMETER DisableReminder
    When $true, disables inheritance of the default Reminders from the Calendar the event was created on.
 
    .PARAMETER LocalStartDateTime
    Start date and time of the event. Lowest precendence of the three StartDate parameters.
 
    Defaults to the time the function is ran.
 
    .PARAMETER LocalEndDateTime
    End date and time of the event. Lowest precendence of the three EndDate parameters.
 
    Defaults to 30 minutes after the time the function is ran.
 
    .PARAMETER StartDate
    String representation of the start date. Middle precendence of the three StartDate parameters.
 
    .PARAMETER EndDate
    String representation of the end date. Middle precendence of the three EndDate parameters.
 
    .PARAMETER UTCStartDateTime
    String representation of the start date in UTC. Highest precendence of the three StartDate parameters.
 
    .PARAMETER UTCEndDateTime
    String representation of the end date in UTC. Highest precendence of the three EndDate parameters.
 
    .EXAMPLE
    New-GSCalendarEvent "Go to the gym" -StartDate (Get-Date "21:00:00") -EndDate (Get-Date "22:00:00")
 
    Creates an event titled "Go to the gym" for 9-10PM the day the function is ran.
    #>

    [cmdletbinding(DefaultParameterSetName = "AttendeeEmails")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $Summary,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $CalendarID = "primary",
        [parameter(Mandatory = $false,ParameterSetName = "AttendeeEmails")]
        [String[]]
        $AttendeeEmails,
        [parameter(Mandatory = $false,ParameterSetName = "AttendeeObjects")]
        [Google.Apis.Calendar.v3.Data.EventAttendee[]]
        $Attendees,
        [parameter(Mandatory = $false)]
        [String]
        $Location,
        [parameter(Mandatory = $false)]
        [ValidateSet("Periwinkle","Seafoam","Lavender","Coral","Goldenrod","Beige","Cyan","Grey","Blue","Green","Red")]
        [String]
        $EventColor,
        [parameter(Mandatory = $false)]
        [Switch]
        $DisableReminder,
        [parameter(Mandatory = $false)]
        [DateTime]
        $LocalStartDateTime = (Get-Date),
        [parameter(Mandatory = $false)]
        [DateTime]
        $LocalEndDateTime = (Get-Date).AddMinutes(30),
        [parameter(Mandatory = $false)]
        [String]
        $StartDate,
        [parameter(Mandatory = $false)]
        [String]
        $EndDate,
        [parameter(Mandatory = $false)]
        [String]
        $UTCStartDateTime,
        [parameter(Mandatory = $false)]
        [String]
        $UTCEndDateTime
    )
    Begin {
        $colorHash = @{
            Periwinkle = 1
            Seafoam    = 2
            Lavender   = 3
            Coral      = 4
            Goldenrod  = 5
            Beige      = 6
            Cyan       = 7
            Grey       = 8
            Blue       = 9
            Green      = 10
            Red        = 11
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                $serviceParams = @{
                    Scope       = 'https://www.googleapis.com/auth/calendar'
                    ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
                    User        = $U
                }
                $service = New-GoogleService @serviceParams
                if ($PSCmdlet.ParameterSetName -eq 'AttendeeEmails' -and $PSBoundParameters.Keys -contains 'AttendeeEmails') {
                    [Google.Apis.Calendar.v3.Data.EventAttendee[]]$Attendees = $AttendeeEmails | ForEach-Object {
                        Add-GSEventAttendee -Email $_
                    }
                }
                $body = New-Object 'Google.Apis.Calendar.v3.Data.Event'
                if ($Attendees) {
                    $body.Attendees = [Google.Apis.Calendar.v3.Data.EventAttendee[]]$Attendees
                }
                foreach ($key in $PSBoundParameters.Keys) {
                    switch ($key) {
                        EventColor {
                            $body.ColorId = $colorHash[$EventColor]
                        }
                        DisableReminder {
                            $reminder = New-Object 'Google.Apis.Calendar.v3.Data.Event+RemindersData' -Property @{
                                UseDefault = (-not $DisableReminder)
                            }
                            $body.Reminders = $reminder
                        }
                        Default {
                            if ($body.PSObject.Properties.Name -contains $key) {
                                $body.$key = $PSBoundParameters[$key]
                            }
                        }
                    }
                }
                $body.Start = if ($UTCStartDateTime) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        DateTime = $UTCStartDateTime
                    }
                }
                elseif ($StartDate) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        Date = (Get-Date $StartDate -Format "yyyy-MM-dd")
                    }
                }
                else {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        DateTime = $LocalStartDateTime
                    }
                }
                $body.End = if ($UTCEndDateTime) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        DateTime = $UTCEndDateTime
                    }
                }
                elseif ($EndDate) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        Date = (Get-Date $EndDate -Format "yyyy-MM-dd")
                    }
                }
                else {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        DateTime = $LocalEndDateTime
                    }
                }
                foreach ($calId in $CalendarID) {
                    Write-Verbose "Creating Calendar Event '$($Summary)' on calendar '$calId' for user '$U'"
                    $request = $service.Events.Insert($body,$calId)
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'New-GSCalendarEvent'

function Remove-GSCalendarEvent {
    <#
    .SYNOPSIS
    Removes a calendar event
 
    .DESCRIPTION
    Removes a calendar event
 
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config.
 
    .PARAMETER CalendarID
    The calendar ID of the calendar you would like to list events from.
 
    Defaults to the user's primary calendar.
 
    .PARAMETER EventID
    The EventID to remove
 
    .EXAMPLE
    Remove-GSCalendarEvent -User user@domain.com -EventID _60q30c1g60o30e1i60o4ac1g60rj8gpl88rj2c1h84s34h9g60s30c1g60o30c1g84o3eg9n8gq32d246gq48d1g64o30c1g60o30c1g60o30c1g60o32c1g60o30c1g8csjihhi6oq3igi28h248ghk6ks4agq161144ga46gr4aci488p0
 
    Removes the specified event from user@domain.com's calendar.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [String]
        $CalendarID = "primary",
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String[]]
        $EventID
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/calendar'
            ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($E in $EventId) {
            try {
                if ($PSCmdlet.ShouldProcess("Deleting Event Id '$E' from user '$User'")) {
                    Write-Verbose "Deleting Event Id '$E' from user '$User'"
                    $request = $service.Events.Delete($CalendarID, $E)
                    $request.Execute()
                    Write-Verbose "Label Id '$E' deleted successfully from user '$User'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSCalendarEvent'

function Remove-GSCalendarSubscription {
    <#
    .SYNOPSIS
    Removes a calendar from a users calendar list (aka unsubscribes from the specified calendar)
     
    .DESCRIPTION
    Removes a calendar from a users calendar list (aka unsubscribes from the specified calendar)
     
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
     
    .PARAMETER CalendarID
    The calendar ID of the calendar you would like to unsubscribe the user from
     
    .EXAMPLE
    Remove-GSCalendarSubscription -User me -CalendarId john.smith@domain.com
 
    Removes the calendar 'john.smith@domain.com' from the AdminEmail user's calendar list
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User,
        [parameter(Mandatory = $true,Position = 1)]
        [String[]]
        $CalendarId
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/calendar'
            ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($calId in $CalendarID) {
            try {
                if ($PSCmdlet.ShouldProcess("Unsubscribing user '$User' from Calendar '$($calId)'")) {
                    Write-Verbose "Unsubscribing user '$User' from Calendar '$($calId)'"
                    $request = $service.CalendarList.Delete($calId)
                    $request.Execute()
                    Write-Verbose "User '$User' has been successfully unsubscribed from calendar '$calId'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSCalendarSubscription'

function Update-GSCalendarEvent {
    <#
    .SYNOPSIS
    Updates an event
 
    .DESCRIPTION
    Updates an event
 
    .PARAMETER EventID
    The unique Id of the event to update
 
    .PARAMETER CalendarID
    The Id of the calendar
 
    Defaults to the user's primary calendar.
 
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config.
 
    .PARAMETER Summary
    Event summary
 
    .PARAMETER Description
    Event description
 
    .PARAMETER AttendeeEmails
    The email addresses of the attendees to add.
 
    NOTE: This performs simple adds without additional attendee options. If additional options are needed, use the Attendees parameter instead.
 
    .PARAMETER Attendees
    The EventAttendee object(s) to add. Use Add-GSEventAttendee with this parameter for best results.
 
    .PARAMETER Location
    Event location
 
    .PARAMETER EventColor
    Color of the event as seen in Calendar
 
    .PARAMETER DisableReminder
    When $true, disables inheritance of the default Reminders from the Calendar the event was created on.
 
    .PARAMETER LocalStartDateTime
    Start date and time of the event. Lowest precendence of the three StartDate parameters.
 
    Defaults to the time the function is ran.
 
    .PARAMETER LocalEndDateTime
    End date and time of the event. Lowest precendence of the three EndDate parameters.
 
    Defaults to 30 minutes after the time the function is ran.
 
    .PARAMETER StartDate
    String representation of the start date. Middle precendence of the three StartDate parameters.
 
    .PARAMETER EndDate
    String representation of the end date. Middle precendence of the three EndDate parameters.
 
    .PARAMETER UTCStartDateTime
    String representation of the start date in UTC. Highest precendence of the three StartDate parameters.
 
    .PARAMETER UTCEndDateTime
    String representation of the end date in UTC. Highest precendence of the three EndDate parameters.
 
    .EXAMPLE
    New-GSCalendarEvent "Go to the gym" -StartDate (Get-Date "21:00:00") -EndDate (Get-Date "22:00:00")
 
    Creates an event titled "Go to the gym" for 9-10PM the day the function is ran.
    #>

    [cmdletbinding(DefaultParameterSetName = "AttendeeEmails")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String[]]
        $EventId,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $CalendarId = "primary",
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [String]
        $Summary,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false,ParameterSetName = "AttendeeEmails")]
        [String[]]
        $AttendeeEmails,
        [parameter(Mandatory = $false,ParameterSetName = "AttendeeObjects")]
        [Google.Apis.Calendar.v3.Data.EventAttendee[]]
        $Attendees,
        [parameter(Mandatory = $false)]
        [String]
        $Location,
        [parameter(Mandatory = $false)]
        [ValidateSet("Periwinkle","Seafoam","Lavender","Coral","Goldenrod","Beige","Cyan","Grey","Blue","Green","Red")]
        [String]
        $EventColor,
        [parameter(Mandatory = $false)]
        [Switch]
        $DisableReminder,
        [parameter(Mandatory = $false)]
        [DateTime]
        $LocalStartDateTime,
        [parameter(Mandatory = $false)]
        [DateTime]
        $LocalEndDateTime,
        [parameter(Mandatory = $false)]
        [String]
        $StartDate,
        [parameter(Mandatory = $false)]
        [String]
        $EndDate,
        [parameter(Mandatory = $false)]
        [String]
        $UTCStartDateTime,
        [parameter(Mandatory = $false)]
        [String]
        $UTCEndDateTime
    )
    Begin {
        $colorHash = @{
            Periwinkle = 1
            Seafoam    = 2
            Lavender   = 3
            Coral      = 4
            Goldenrod  = 5
            Beige      = 6
            Cyan       = 7
            Grey       = 8
            Blue       = 9
            Green      = 10
            Red        = 11
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                $serviceParams = @{
                    Scope       = 'https://www.googleapis.com/auth/calendar'
                    ServiceType = 'Google.Apis.Calendar.v3.CalendarService'
                    User        = $U
                }
                $service = New-GoogleService @serviceParams
                if ($PSCmdlet.ParameterSetName -eq 'AttendeeEmails' -and $PSBoundParameters.Keys -contains 'AttendeeEmails') {
                    [Google.Apis.Calendar.v3.Data.EventAttendee[]]$Attendees = $AttendeeEmails | ForEach-Object {
                        Add-GSEventAttendee -Email $_
                    }
                }
                $body = New-Object 'Google.Apis.Calendar.v3.Data.Event'
                if ($Attendees) {
                    $body.Attendees = [Google.Apis.Calendar.v3.Data.EventAttendee[]]$Attendees
                }
                foreach ($key in $PSBoundParameters.Keys) {
                    switch ($key) {
                        EventColor {
                            $body.ColorId = $colorHash[$EventColor]
                        }
                        DisableReminder {
                            $reminder = New-Object 'Google.Apis.Calendar.v3.Data.Event+RemindersData' -Property @{
                                UseDefault = (-not $DisableReminder)
                            }
                            $body.Reminders = $reminder
                        }
                        Default {
                            if ($body.PSObject.Properties.Name -contains $key) {
                                $body.$key = $PSBoundParameters[$key]
                            }
                        }
                    }
                }
                $body.Start = if ($UTCStartDateTime) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        DateTime = $UTCStartDateTime
                    }
                }
                elseif ($StartDate) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        Date = (Get-Date $StartDate -Format "yyyy-MM-dd")
                    }
                }
                elseif ($LocalStartDateTime) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        DateTime = $LocalStartDateTime
                    }
                }
                $body.End = if ($UTCEndDateTime) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        DateTime = $UTCEndDateTime
                    }
                }
                elseif ($EndDate) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        Date = (Get-Date $EndDate -Format "yyyy-MM-dd")
                    }
                }
                elseif ($LocalEndDateTime) {
                    New-Object 'Google.Apis.Calendar.v3.Data.EventDateTime' -Property @{
                        DateTime = $LocalEndDateTime
                    }
                }
                foreach ($calId in $CalendarID) {
                    foreach ($evId in $EventId) {
                        Write-Verbose "Updating Calendar Event '$evId' on calendar '$calId' for user '$U'"
                        $request = $service.Events.Patch($body,$calId,$evId)
                        $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru | Add-Member -MemberType NoteProperty -Name 'EventId' -Value $evId -PassThru
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Update-GSCalendarEvent'

function Get-GSChatMember {
    <#
    .SYNOPSIS
    Gets Chat member information
     
    .DESCRIPTION
    Gets Chat member information
 
    .PARAMETER Member
    Resource name of the membership to be retrieved, in the form "spaces/members".
 
    Example: spaces/AAAAMpdlehY/members/105115627578887013105
     
    .PARAMETER Space
    The resource name of the space for which membership list is to be fetched, in the form "spaces".
 
    Example: spaces/AAAAMpdlehY
     
    .EXAMPLE
    Get-GSChatMember -Space 'spaces/AAAAMpdlehY'
 
    Gets the list of human members in the Chat space specified
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $true,ParameterSetName = "Get")]
        [string[]]
        $Member,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "List")]
        [Alias('Parent','Name')]
        [string[]]
        $Space
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/chat.bot'
            ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($mem in $Member) {
                    try {
                        $request = $service.Spaces.Members.Get($mem)
                        Write-Verbose "Getting Member '$mem'"
                        $request.Execute()
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                foreach ($sp in $Space) {
                    try {   
                        if ($sp -notlike "spaces/*") {
                            try {
                                $sp = Get-GSChatConfig -SpaceName $sp -ErrorAction Stop
                            }
                            catch {
                                $sp = "spaces/$sp"
                            }
                        }
                        $request = $service.Spaces.Members.List($sp)
                        Write-Verbose "Getting Member List of Chat Space '$sp'"
                        [int]$i = 1
                        do {
                            $result = $request.Execute()
                            $result.Memberships
                            if ($result.NextPageToken) {
                                $request.PageToken = $result.NextPageToken
                            }
                            [int]$retrieved = ($i + $result.Memberships.Count) - 1
                            Write-Verbose "Retrieved $retrieved Memberships..."
                            [int]$i = $i + $result.Memberships.Count
                        }
                        until (!$result.NextPageToken)
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSChatMember'

function Get-GSChatMessage {
    <#
    .SYNOPSIS
    Gets a Chat message
     
    .DESCRIPTION
    Gets a Chat message
 
    .PARAMETER Name
    Resource name of the message to be retrieved, in the form "spaces/messages".
 
    Example: spaces/AAAAMpdlehY/messages/UMxbHmzDlr4.UMxbHmzDlr4
     
    .EXAMPLE
    Get-GSChatMessage -Name 'spaces/AAAAMpdlehY/messages/UMxbHmzDlr4.UMxbHmzDlr4'
 
    Gets the Chat message specified
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [Alias('Id')]
        [string[]]
        $Name
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/chat.bot'
            ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($msg in $Name) {
            try {
                $request = $service.Spaces.Messages.Get($msg)
                Write-Verbose "Getting Message '$msg'"
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSChatMessage'

function Get-GSChatSpace {
    <#
    .SYNOPSIS
    Gets a Chat space
     
    .DESCRIPTION
    Gets a Chat space
     
    .PARAMETER Space
    The resource name of the space for which membership list is to be fetched, in the form "spaces".
 
    If left blank, returns the list of spaces the bot is a member of
 
    Example: spaces/AAAAMpdlehY
     
    .EXAMPLE
    Get-GSChatSpace
 
    Gets the list of Chat spaces the bot is a member of
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("Name")]
        [string[]]
        $Space
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/chat.bot'
            ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        if ($Space) {
            foreach ($sp in $Space) {
                try {
                    if ($sp -notlike "spaces/*") {
                        try {
                            $sp = Get-GSChatConfig -SpaceName $sp -ErrorAction Stop
                        }
                        catch {
                            $sp = "spaces/$sp"
                        }
                    }
                    $request = $service.Spaces.Get($sp)
                    Write-Verbose "Getting Chat Space '$sp'"
                    $request.Execute()
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
        else {
            try {
                $spaceArray = @()
                $request = $service.Spaces.List()
                Write-Verbose "Getting Chat Space List"
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    $result.Spaces
                    $spaceArray += $result.Spaces
                    if ($result.NextPageToken) {
                        $request.PageToken = $result.NextPageToken
                    }
                    [int]$retrieved = ($i + $result.Spaces.Count) - 1
                    Write-Verbose "Retrieved $retrieved Spaces..."
                    [int]$i = $i + $result.Spaces.Count
                }
                until (!$result.NextPageToken)
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
    End {
        Write-Verbose "Updating PSGSuite Config with Space list"
        $spaceHashArray = @()
        $spaceArray | ForEach-Object {
            if ($_.DisplayName) {
                $spaceHashArray += @{$_.DisplayName = $_.Name}
                
            }
            else {
                $member = Get-GSChatMember -Space $_.Name -Verbose:$false
                $id = $member.Member.Name
                $primaryEmail = (Get-GSUser -User ($id.Replace('users/',''))).PrimaryEmail
                $spaceHashArray += @{
                    $id = $_.Name
                    $member.Member.DisplayName = $_.Name
                    $primaryEmail = $_.Name
                }
            }
        }
        Set-PSGSuiteConfig -Space $spaceHashArray -Verbose:$false
    }
}
Export-ModuleMember -Function 'Get-GSChatSpace'

function Remove-GSChatMessage {
    <#
    .SYNOPSIS
    Removes a Chat message
     
    .DESCRIPTION
    Removes a Chat message
 
    .PARAMETER Name
    Resource name of the message to be removed, in the form "spaces/messages".
 
    Example: spaces/AAAAMpdlehY/messages/UMxbHmzDlr4.UMxbHmzDlr4
     
    .EXAMPLE
    Remove-GSChatMessage -Name 'spaces/AAAAMpdlehY/messages/UMxbHmzDlr4.UMxbHmzDlr4'
 
    Removes the Chat message specified after confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [Alias('Id')]
        [string[]]
        $Name
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/chat.bot'
            ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($msg in $Name) {
            try {
                if ($PSCmdlet.ShouldProcess("Removing Message '$msg'")) {
                    $request = $service.Spaces.Messages.Delete($msg)
                    $request.Execute()
                    Write-Verbose "Successfully removed Message '$msg'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSChatMessage'

function Send-GSChatMessage {
    <#
    .SYNOPSIS
    Sends a Chat message
     
    .DESCRIPTION
    Sends a Chat message
 
    .PARAMETER Text
    Plain-text body of the message.
 
    .PARAMETER Thread
    The thread the message belongs to, in the form "spaces/threads".
 
    Example: spaces/AAAA3dnRkmI/threads/_EyIp5BthJk
 
    .PARAMETER FallbackText
    A plain-text description of the message's cards, used when the actual cards cannot be displayed (e.g. mobile notifications).
 
    .PARAMETER PreviewText
    Text for generating preview chips. This text will not be displayed to the user, but any links to images, web pages, videos, etc. included here will generate preview chips.
 
    .PARAMETER ActionResponseType
    Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted.
 
    The ActionResponseType is the type of bot response.
 
    Available values are:
    * NEW_MESSAGE: Post as a new message in the topic.
    * UPDATE_MESSAGE: Update the bot's own message. (Only after CARD_CLICKED events.)
    * REQUEST_CONFIG: Privately ask the user for additional auth or config.
 
    .PARAMETER ActionResponseUrl
    Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted.
 
    The ActionResponseUrl is the URL for users to auth or config. (Only for REQUEST_CONFIG response types.)
     
    .PARAMETER Parent
    The resource name of the space to send the message to, in the form "spaces".
 
    Example: spaces/AAAAMpdlehY
 
    .PARAMETER ThreadKey
    Opaque thread identifier string that can be specified to group messages into a single thread. If this is the first message with a given thread identifier, a new thread is created. Subsequent messages with the same thread identifier will be posted into the same thread. This relieves bots and webhooks from having to store the Hangouts Chat thread ID of a thread (created earlier by them) to post further updates to it.
 
    .PARAMETER Webhook
    The Url of the Webhook for the space to send the message to.
 
    You can safely store an encrypted dictionary of Webhooks in the PSGSuite Config by passing a hashtable to the `-Webhook` parameter, i.e.:
        Set-PSGSuiteConfig -Webhook @{JobReports = 'https://chat.googleapis.com/v1/spaces/xxxxxxxxxx/messages?key=xxxxxxxxxxxxxxxxxx&token=xxxxxxxxxxxxxxxxxx'}
     
    To retrieve a stored Webhook, you can use `Get-GSChatWebhook`, i.e.:
        Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    If section widgets are passed directly to this function, a new section without a SectionHeader will be created and the widgets will be added to it
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    [cmdletbinding(DefaultParameterSetName = "Webhook")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [string[]]
        $Text,
        [parameter(Mandatory = $false)]
        [string]
        $Thread,
        [parameter(Mandatory = $false)]
        [string[]]
        $FallbackText,
        [parameter(Mandatory = $false)]
        [string]
        $PreviewText,
        [parameter(Mandatory = $false)]
        [ValidateSet('NEW_MESSAGE','UPDATE_MESSAGE','REQUEST_CONFIG')]
        [string]
        $ActionResponseType,
        [parameter(Mandatory = $false)]
        [string]
        $ActionResponseUrl,
        [parameter(Mandatory = $true,ParameterSetName = "SDK")]
        [string[]]
        $Parent,
        [parameter(Mandatory = $false,ParameterSetName = "SDK")]
        [parameter(Mandatory = $false,ParameterSetName = "Rest")]
        [string]
        $ThreadKey,
        [parameter(Mandatory = $true,ParameterSetName = "Rest")]
        [string[]]
        $RestParent,
        [parameter(Mandatory = $true,ParameterSetName = "Webhook")]
        [string[]]
        $Webhook,
        [parameter(Mandatory = $false,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment,
        [parameter(Mandatory = $true,ParameterSetName = "BodyPassThru")]
        [switch]
        $BodyPassThru
    )
    Begin {
        $addlSections = @()
        $addlCardActions = @()
        $addlSectionWidgets = @()
        switch ($PSCmdlet.ParameterSetName) {
            default {
                $body = @{}
                foreach ($key in $PSBoundParameters.Keys) {
                    switch ($key) {
                        Text {
                            $body['text'] = ($Text -join "`n")
                        }
                        PreviewText {
                            $body['previewText'] = $PSBoundParameters[$key]
                        }
                        FallbackText {
                            $body['fallbackText'] = ($PSBoundParameters[$key] -join "`n")
                        }
                        Thread {
                            $body['thread'] = @{
                                name = $Thread
                            }
                        }
                        ActionResponseType {
                            if (!$body['actionResponse']) {
                                $body['actionResponse'] = @{}
                            }
                            $body['actionResponse']['type'] = $PSBoundParameters[$key]
                        }
                        ActionResponseUrl {
                            if (!$body['actionResponse']) {
                                $body['actionResponse'] = @{}
                            }
                            $body['actionResponse']['url'] = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            SDK {
                $serviceParams = @{
                    Scope       = 'https://www.googleapis.com/auth/chat.bot'
                    ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService'
                }
                $service = New-GoogleService @serviceParams
                $body = New-Object 'Google.Apis.HangoutsChat.v1.Data.Message'
                foreach ($key in $PSBoundParameters.Keys) {
                    switch ($key) {
                        Text {
                            $body.Text = ($PSBoundParameters[$key] -join "`n")
                        }
                        PreviewText {
                            $body.PreviewText = $PSBoundParameters[$key]
                        }
                        FallbackText {
                            $body.FallbackText = ($PSBoundParameters[$key] -join "`n")
                        }
                        Thread {
                            $body.Thread = New-Object 'Google.Apis.HangoutsChat.v1.Data.Thread' -Property @{
                                Name = $Thread
                            }
                        }
                        ActionResponseType {
                            if (!$body.ActionResponse) {
                                $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse'
                            }
                            $body.ActionResponse.Type = $PSBoundParameters[$key]
                        }
                        ActionResponseUrl {
                            if (!$body.ActionResponse) {
                                $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse'
                            }
                            $body.ActionResponse.Url = $PSBoundParameters[$key]
                        }
                    }
                }
            }
        }
    }
    Process {
        foreach ($segment in $MessageSegment) {
            switch -RegEx ($segment['SDK'].PSTypeNames[0]) {
                '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Card' {
                    switch ($PSCmdlet.ParameterSetName) {
                        default {
                            if (!$body['cards']) {
                                $body['cards'] = @()
                            }
                            $body['cards'] += $segment['Webhook']
                        }
                        SDK {
                            if (!$body.Cards) {
                                $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]'
                            }
                            $body.Cards.Add($segment['SDK']) | Out-Null
                        }
                    }
                }
                '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Section' {
                    $addlSections += $segment
                }
                '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.CardAction' {
                    $addlCardActions += $segment
                }
                default {
                    Write-Verbose "Matched a $($segment['SDK'].PSTypeNames[0]) in the MessageSegments!"
                    $addlSectionWidgets += $segment
                }
            }
        }
    }
    End {
        switch ($PSCmdlet.ParameterSetName) {
            default {
                if ($addlCardActions -or $addlSections -or $addlSectionWidgets) {
                    if (!$body['cards']) {
                        $cardless = $true
                        $body['cards'] = @()
                    }
                    if ($addlSections) {
                        $body['cards'] += ($addlSections | Add-GSChatCard)['Webhook']
                        $cardless = $false
                    }
                    if ($addlSectionWidgets) {
                        if ($cardless) {
                            $body['cards'] += ($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['Webhook']
                        }
                        else {
                            $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['Webhook']
                            if (!$body['cards'][-1]['sections']) {
                                $body['cards'][-1]['sections'] = @()
                            }
                            $body['cards'][-1]['sections'] += $newSection
                        }
                        $cardless = $false
                    }
                    if ($addlCardActions) {
                        if ($cardless) {
                            $body['cards'] += ($addlCardActions | Add-GSChatCard)['Webhook']
                        }
                        elseif (!$body['cards'][-1]['cardActions']) {
                            $body['cards'][-1]['cardActions'] = @()
                        }
                        foreach ($cardAction in $addlCardActions) {
                            $body['cards'][-1]['cardActions'] += $cardAction['Webhook']
                        }
                    }
                }
                switch ($PSCmdlet.ParameterSetName) {
                    Webhook {
                        $body = $body | ConvertTo-Json -Depth 15
                        foreach ($hook in $Webhook) {
                            try {
                                if ($hook -notlike "https://chat.googleapis.com/v1/spaces/*") {
                                    $hook = Get-GSChatConfig -WebhookName $hook -ErrorAction Stop
                                }
                                Write-Verbose "Sending Chat Message via Webhook to '$($hook -replace "\?key\=.*",'')'"
                                Invoke-RestMethod -Method Post -Uri ([Uri]$hook) -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'Webhook' -Value $hook -PassThru
                            }
                            catch {
                                if ($ErrorActionPreference -eq 'Stop') {
                                    $PSCmdlet.ThrowTerminatingError($_)
                                }
                                else {
                                    Write-Error $_
                                }
                            }
                        }
                    }
                    Rest {
                        $body = $body | ConvertTo-Json -Depth 15
                        foreach ($restPar in $RestParent) {
                            try {
                                if ($restPar -notlike "spaces/*") {
                                    try {
                                        $restPar = Get-GSChatConfig -SpaceName $restPar -ErrorAction Stop
                                    }
                                    catch {
                                        $restPar = "spaces/$restPar"
                                    }
                                }
                                $header = @{
                                    Authorization = "Bearer $(Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false)"
                                }
                                $hook = "https://chat.googleapis.com/v1/$($restPar)/messages"
                                if ($PSBoundParameters.Keys -contains 'ThreadKey') {
                                    $hook = "$($hook)?threadKey=$ThreadKey"
                                    $addlText = " in ThreadKey '$ThreadKey'"
                                }
                                else {
                                    $addlText = ""
                                }
                                Write-Verbose "Sending Chat Message via REST API to parent '$restPar'$addlText"
                                Invoke-RestMethod -Method Post -Uri ([Uri]$hook) -Headers $header -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'RestParent' -Value $restPar -PassThru
                            }
                            catch {
                                if ($ErrorActionPreference -eq 'Stop') {
                                    $PSCmdlet.ThrowTerminatingError($_)
                                }
                                else {
                                    Write-Error $_
                                }
                            }
                        }
                    }
                    BodyPassThru {
                        $newBody = @{
                            token = (Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false)
                            body = $body
                        }
                        $newBody = $newBody | ConvertTo-Json -Depth 20 -Compress
                        return $newBody
                    }
                }
            }
            SDK {
                if ($addlCardActions -or $addlSections -or $addlSectionWidgets) {
                    if (!$body.Cards) {
                        $cardless = $true
                        $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]'
                    }
                    if ($addlSections) {
                        $body.Cards.Add(($addlSections | Add-GSChatCard)['SDK']) | Out-Null
                        $cardless = $false
                    }
                    if ($addlSectionWidgets) {
                        if ($cardless) {
                            $body.Cards.Add(($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['SDK']) | Out-Null
                        }
                        else {
                            $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['SDK']
                            if (!$body.Cards[-1].Sections) {
                                $body.Cards[-1].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]'
                            }
                            $body.Cards[-1].Sections.Add($newSection) | Out-Null
                        }
                        $cardless = $false
                    }
                    if ($addlCardActions) {
                        if ($cardless) {
                            $body.Cards.Add(($addlCardActions | Add-GSChatCard)['SDK'])
                        }
                        elseif (!$body.Cards[-1].CardActions) {
                            $body.Cards[-1].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]'
                        }
                        foreach ($cardAction in $addlCardActions) {
                            $body.Cards[-1].CardActions.Add($cardAction['SDK']) | Out-Null
                        }
                    }
                }
                foreach ($par in $Parent){
                    try {
                        if ($par -notlike "spaces/*") {
                            try {
                                $par = Get-GSChatConfig -SpaceName $par -ErrorAction Stop
                            }
                            catch {
                                $par = "spaces/$par"
                            }
                        }
                        $request = $service.Spaces.Messages.Create($body,$par)
                        if ($PSBoundParameters.Keys -contains 'ThreadKey') {
                            $request.ThreadKey = $ThreadKey
                            $addlText = " in ThreadKey '$ThreadKey'"
                        }
                        else {
                            $addlText = ""
                        }
                        Write-Verbose "Sending Chat Message via SDK to Space '$par'$addlText"
                        $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Parent' -Value $par -PassThru | Add-Member -MemberType NoteProperty -Name 'ThreadKey' -Value $ThreadKey -PassThru
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Send-GSChatMessage'

function Update-GSChatMessage {
    <#
    .SYNOPSIS
    Updates a Chat message, i.e. for a CardClicked response
     
    .DESCRIPTION
    Updates a Chat message, i.e. for a CardClicked response
 
    .PARAMETER MessageId
    Resource name, in the form "spaces/messages".
 
    Example: spaces/89L51AAAAAE/messages/kbZTbcol8H4.kbZTbcol8H4
 
    .PARAMETER UpdateMask
    Required. The field paths to be updated.
 
    Currently supported field paths: "text", "cards".
 
    .PARAMETER Text
    Plain-text body of the message.
 
    .PARAMETER FallbackText
    A plain-text description of the message's cards, used when the actual cards cannot be displayed (e.g. mobile notifications).
 
    .PARAMETER PreviewText
    Text for generating preview chips. This text will not be displayed to the user, but any links to images, web pages, videos, etc. included here will generate preview chips.
 
    .PARAMETER ActionResponseType
    Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted.
 
    The ActionResponseType is the type of bot response.
 
    Available values are:
    * NEW_MESSAGE: Post as a new message in the topic.
    * UPDATE_MESSAGE: Update the bot's own message. (Only after CARD_CLICKED events.)
    * REQUEST_CONFIG: Privately ask the user for additional auth or config.
 
    .PARAMETER ActionResponseUrl
    Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted.
 
    The ActionResponseUrl is the URL for users to auth or config. (Only for REQUEST_CONFIG response types.)
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    If section widgets are passed directly to this function, a new section without a SectionHeader will be created and the widgets will be added to it
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    [cmdletbinding(DefaultParameterSetName = "Update")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Update")]
        [string]
        $MessageId,
        [parameter(Mandatory = $true,ParameterSetName = "BodyPassThru")]
        [switch]
        $BodyPassThru,
        [parameter(Mandatory = $false,Position = 1)]
        [ValidateSet("text","cards")]
        [string[]]
        $UpdateMask,
        [parameter(Mandatory = $false)]
        [string[]]
        $Text,
        [parameter(Mandatory = $false)]
        [string[]]
        $FallbackText,
        [parameter(Mandatory = $false)]
        [string]
        $PreviewText,
        [parameter(Mandatory = $false)]
        [ValidateSet('NEW_MESSAGE','UPDATE_MESSAGE','REQUEST_CONFIG')]
        [string]
        $ActionResponseType = 'UPDATE_MESSAGE',
        [parameter(Mandatory = $false)]
        [string]
        $ActionResponseUrl,
        [parameter(Mandatory = $false,ParameterSetName = "Update")]
        [switch]
        $UseRest,
        [parameter(Mandatory = $false,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment
    )
    Begin {
        $addlSections = @()
        $addlCardActions = @()
        $addlSectionWidgets = @()
        if ($UseRest -or $BodyPassThru) {
            $body = @{
                actionResponse = @{
                    type = $ActionResponseType
                }
            }
            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    Text {
                        if ($UpdateMask -notcontains 'text') {
                            $UpdateMask += 'text'
                        }
                        $body['text'] = ($Text -join "`n")
                    }
                    PreviewText {
                        $body['previewText'] = $PSBoundParameters[$key]
                    }
                    FallbackText {
                        $body['fallbackText'] = ($PSBoundParameters[$key] -join "`n")
                    }
                    ActionResponseUrl {
                        if (!$body['actionResponse']) {
                            $body['actionResponse'] = @{}
                        }
                        $body['actionResponse']['url'] = $PSBoundParameters[$key]
                    }
                }
            }
        }
        else {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/chat.bot'
                ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService'
            }
            $service = New-GoogleService @serviceParams
            $body = New-Object 'Google.Apis.HangoutsChat.v1.Data.Message'
            $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse'
            $body.ActionResponse.Type = $ActionResponseType
            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    Text {
                        if ($UpdateMask -notcontains 'text') {
                            $UpdateMask += 'text'
                        }
                        $body.Text = ($PSBoundParameters[$key] -join "`n")
                    }
                    PreviewText {
                        $body.PreviewText = $PSBoundParameters[$key]
                    }
                    FallbackText {
                        $body.FallbackText = ($PSBoundParameters[$key] -join "`n")
                    }
                    ActionResponseUrl {
                        if (!$body.ActionResponse) {
                            $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse'
                        }
                        $body.ActionResponse.Url = $PSBoundParameters[$key]
                    }
                }
            }
        }
    }
    Process {
        if ($MessageSegment) {
            if ($UpdateMask -notcontains 'cards') {
                $UpdateMask += 'cards'
            }
            foreach ($segment in $MessageSegment) {
                switch -RegEx ($segment['SDK'].PSTypeNames[0]) {
                    '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Card' {
                        if ($UseRest -or $BodyPassThru) {
                            if (!$body['cards']) {
                                $body['cards'] = @()
                            }
                            $body['cards'] += $segment['Webhook']
                        }
                        else {
                            if (!$body.Cards) {
                                $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]'
                            }
                            $body.Cards.Add($segment['SDK']) | Out-Null
                        }
                    }
                    '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Section' {
                        $addlSections += $segment
                    }
                    '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.CardAction' {
                        $addlCardActions += $segment
                    }
                    default {
                        Write-Verbose "Matched a $($segment['SDK'].PSTypeNames[0]) in the MessageSegments!"
                        $addlSectionWidgets += $segment
                    }
                }
            }
        }
    }
    End {
        if ($UseRest -or $BodyPassThru) {
            if ($addlCardActions -or $addlSections -or $addlSectionWidgets) {
                if (!$body['cards']) {
                    $cardless = $true
                    $body['cards'] = @()
                }
                if ($addlSections) {
                    $body['cards'] += ($addlSections | Add-GSChatCard)['Webhook']
                    $cardless = $false
                }
                if ($addlSectionWidgets) {
                    if ($cardless) {
                        $body['cards'] += ($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['Webhook']
                    }
                    else {
                        $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['Webhook']
                        if (!$body['cards'][-1]['sections']) {
                            $body['cards'][-1]['sections'] = @()
                        }
                        $body['cards'][-1]['sections'] += $newSection
                    }
                    $cardless = $false
                }
                if ($addlCardActions) {
                    if ($cardless) {
                        $body['cards'] += ($addlCardActions | Add-GSChatCard)['Webhook']
                    }
                    elseif (!$body['cards'][-1]['cardActions']) {
                        $body['cards'][-1]['cardActions'] = @()
                    }
                    foreach ($cardAction in $addlCardActions) {
                        $body['cards'][-1]['cardActions'] += $cardAction['Webhook']
                    }
                }
            }
            if ($BodyPassThru) {
                $newBody = @{
                    token = (Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false)
                    body = $body
                    updateMask = ($UpdateMask -join ',')
                }
                $newBody = $newBody | ConvertTo-Json -Depth 20 -Compress
                return $newBody
            }
            else {
                $body = $body | ConvertTo-Json -Depth 20
                try {
                    $header = @{
                        Authorization = "Bearer $(Get-GSToken -P12KeyPath $Script:PSGSuite.P12KeyPath -Scopes "https://www.googleapis.com/auth/chat.bot" -AppEmail $Script:PSGSuite.AppEmail -AdminEmail $Script:PSGSuite.AdminEmail -Verbose:$false)"
                    }
                    $hook = "https://chat.googleapis.com/v1/$($MessageId)?updateMask=$($UpdateMask -join ',')"
                    Write-Verbose "Updating Chat Message via REST API to parent '$MessageId'"
                    Invoke-RestMethod -Method Put -Uri ([Uri]$hook) -Headers $header -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'MessageId' -Value $MessageId -PassThru
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
        else {
            if ($addlCardActions -or $addlSections -or $addlSectionWidgets) {
                if (!$body.Cards) {
                    $cardless = $true
                    $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]'
                }
                if ($addlSections) {
                    $body.Cards.Add(($addlSections | Add-GSChatCard)['SDK']) | Out-Null
                    $cardless = $false
                }
                if ($addlSectionWidgets) {
                    if ($cardless) {
                        $body.Cards.Add(($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['SDK']) | Out-Null
                    }
                    else {
                        $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['SDK']
                        if (!$body.Cards[-1].Sections) {
                            $body.Cards[-1].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]'
                        }
                        $body.Cards[-1].Sections.Add($newSection) | Out-Null
                    }
                    $cardless = $false
                }
                if ($addlCardActions) {
                    if ($cardless) {
                        $body.Cards.Add(($addlCardActions | Add-GSChatCard)['SDK'])
                    }
                    elseif (!$body.Cards[-1].CardActions) {
                        $body.Cards[-1].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]'
                    }
                    foreach ($cardAction in $addlCardActions) {
                        $body.Cards[-1].CardActions.Add($cardAction['SDK']) | Out-Null
                    }
                }
            }
            try {
                $request = $service.Spaces.Messages.Update($body,$MessageId)
                $request.UpdateMask = $UpdateMask
                Write-Verbose "Updating Chat Message Id '$MessageId'"
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'MessageId' -Value $MessageId -PassThru
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSChatMessage'

function Add-GSCourseParticipant {
    <#
    .SYNOPSIS
    Adds students and/or teachers to a course
 
    .DESCRIPTION
    Adds students and/or teachers to a course
 
    .PARAMETER CourseId
    Identifier of the course to add participants to. This identifier can be either the Classroom-assigned identifier or an alias.
 
    .PARAMETER Student
    Identifier of the user.
 
    This identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER Teacher
    Identifier of the user.
 
    This identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Add-GSCourseParticipant -CourseId 'architecture-101' -Student plato@athens.edu,aristotle@athens.edu -Teacher zeus@athens.edu
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [String]
        $CourseId,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('PrimaryEmail','Email','Mail')]
        [String[]]
        $Student,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $Teacher,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.rosters'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($part in $Student | Where-Object {-not [String]::IsNullOrEmpty($_)}) {
            try {
                $body = New-Object 'Google.Apis.Classroom.v1.Data.Student'
                if ( -not ($part -as [decimal])) {
                    if ($part -ceq 'me') {
                        $part = $Script:PSGSuite.AdminEmail
                    }
                    elseif ($part -notlike "*@*.*") {
                        $part = "$($part)@$($Script:PSGSuite.Domain)"
                    }
                }
                $body.UserId = $part
                Write-Verbose "Adding Student '$part' to Course '$CourseId'"
                $request = $service.Courses.Students.Create($body,$CourseId)
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
        foreach ($part in $Teacher | Where-Object {-not [String]::IsNullOrEmpty($_)}) {
            try {
                $body = New-Object 'Google.Apis.Classroom.v1.Data.Teacher'
                try {
                    [decimal]$part | Out-Null
                }
                catch {
                    if ($part -ceq 'me') {
                        $part = $Script:PSGSuite.AdminEmail
                    }
                    elseif ($part -notlike "*@*.*") {
                        $part = "$($part)@$($Script:PSGSuite.Domain)"
                    }
                }
                $body.UserId = $part
                Write-Verbose "Adding Teacher '$part' to Course '$CourseId'"
                $request = $service.Courses.Teachers.Create($body,$CourseId)
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Add-GSCourseParticipant'

function Confirm-GSCourseInvitation {
    <#
    .SYNOPSIS
    Accepts an invitation, removing it and adding the invited user to the teachers or students (as appropriate) of the specified course. Only the invited user may accept an invitation.
 
    .DESCRIPTION
    Accepts an invitation, removing it and adding the invited user to the teachers or students (as appropriate) of the specified course. Only the invited user may accept an invitation.
 
    .PARAMETER Id
    Identifier of the invitation to accept.
 
    .PARAMETER User
    Email or email name part of the invited user.
 
    .EXAMPLE
    Confirm-GSCourseInvitation -Id $inviteId -User aristotle@athens.edu
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [String]
        $Id,
        [parameter(Mandatory = $true)]
        [String]
        $User
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.rosters'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Accepting Invitation '$Id' for user '$User'"
            $request = $service.Invitations.Accept($Id)
            $request.Execute()
            Write-Verbose "The Invitation has been successfully accepted for user '$User'"
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Confirm-GSCourseInvitation'

function Get-GSClassroomUserProfile {
    <#
    .SYNOPSIS
    Gets a classroom user profile
 
    .DESCRIPTION
    Gets a classroom user profile
 
    .PARAMETER UserId
    Identifier of the profile to return. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER Fields
    The specific fields to fetch
 
    .EXAMPLE
    Get-GSClassroomUserProfile -UserId aristotle@athens.edu
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id','PrimaryEmail','Mail','UserKey')]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $UserId,
        [parameter(Mandatory = $false)]
        [String[]]
        $Fields = '*'
    )
    Begin {
        $serviceParams = @{
            Scope       = @(
                'https://www.googleapis.com/auth/classroom.rosters'
                'https://www.googleapis.com/auth/classroom.profile.emails'
                'https://www.googleapis.com/auth/classroom.profile.photos'
            )
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($part in $UserId) {
            try {
                if ( -not ($part -as [decimal])) {
                    if ($part -ceq 'me') {
                        $part = $Script:PSGSuite.AdminEmail
                    }
                    elseif ($part -notlike "*@*.*") {
                        $part = "$($part)@$($Script:PSGSuite.Domain)"
                    }
                }
                Write-Verbose "Getting Classroom User Profile for '$part'"
                $request = $service.UserProfiles.Get($part)
                $request.Fields = "$($Fields -join ",")"
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSClassroomUserProfile'

function Get-GSCourse {
    <#
    .SYNOPSIS
    Gets a classroom course or list of courses
 
    .DESCRIPTION
    Gets a classroom course or list of courses
 
    .PARAMETER Id
    Identifier of the course to return. This identifier can be either the Classroom-assigned identifier or an alias.
 
    If excluded, returns the list of courses.
 
    .PARAMETER Teacher
    Restricts returned courses to those having a teacher with the specified identifier. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER Student
    Restricts returned courses to those having a student with the specified identifier. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER CourseStates
    Restricts returned courses to those in one of the specified states.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Get-GSCourse -Teacher aristotle@athens.edu
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ParameterSetName = "Get")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Id,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [String]
        $Teacher,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [String]
        $Student,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Google.Apis.Classroom.v1.CoursesResource+ListRequest+CourseStatesEnum[]]
        $CourseStates,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.courses'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        if ($PSBoundParameters.Keys -contains 'Id') {
            foreach ($I in $Id) {
                try {
                    Write-Verbose "Getting Course '$Id'"
                    $request = $service.Courses.Get($Id)
                    $request.Execute()
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
        else {
            try {
                Write-Verbose "Getting Course List"
                $request = $service.Courses.List()
                foreach ($s in $CourseStates) {
                    $request.CourseStates += $s
                }
                if ($PSBoundParameters.Keys -contains 'Student') {
                    $request.StudentId = $PSBoundParameters['Student']
                }
                if ($PSBoundParameters.Keys -contains 'Teacher') {
                    $request.TeacherId = $PSBoundParameters['Teacher']
                }
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    if ($null -ne $result.Courses) {
                        $result.Courses
                    }
                    $request.PageToken = $result.NextPageToken
                    [int]$retrieved = ($i + $result.Courses.Count) - 1
                    Write-Verbose "Retrieved $retrieved Courses..."
                    [int]$i = $i + $result.Courses.Count
                }
                until (!$result.NextPageToken)
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSCourse'

function Get-GSCourseAlias {
    <#
    .SYNOPSIS
    Gets the list of aliases for a course.
 
    .DESCRIPTION
    Gets the list of aliases for a course.
 
    .PARAMETER CourseId
    Identifier of the course to alias. This identifier can be either the Classroom-assigned identifier or an alias.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Get-GSCourseAlias -CourseId 'architecture-101'
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $CourseId,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.courses'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Getting Alias list for Course '$CourseId'"
            $request = $service.Courses.Aliases.List($CourseId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSCourseAlias'

function Get-GSCourseInvitation {
    <#
    .SYNOPSIS
    Gets a course invitation or list of invitations
 
    .DESCRIPTION
    Gets a course invitation or list of invitations
 
    .PARAMETER Id
    Identifier of the invitation to return.
 
    .PARAMETER CourseId
    Restricts returned invitations to those for a course with the specified identifier. This identifier can be either the Classroom-assigned identifier or an alias.
 
    .PARAMETER UserId
    Restricts returned invitations to those for a specific user. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Get-GSCourseInvitation -CourseId philosophy-101
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $true,ParameterSetName = "Get")]
        [String[]]
        $Id,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [String]
        $CourseId,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [String]
        $UserId,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.rosters'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($part in $Id) {
                    try {
                        Write-Verbose "Getting Invitation ID '$part'"
                        $request = $service.Invitations.Get($part)
                        $request.Execute()
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                try {
                    if ($PSBoundParameters.Keys -notcontains 'CourseId' -and $PSBoundParameters.Keys -notcontains 'UserId') {
                        Write-Error "You must specify a CourseId and/or a UserId!"
                    }
                    else {
                        $request =  $service.Invitations.List()
                        $verbMsg = ""
                        if ($PSBoundParameters.Keys -contains 'CourseId') {
                            $verbMsg += " [Course: $CourseId]"
                            $request.CourseId = $CourseId
                        }
                        if ($PSBoundParameters.Keys -contains 'UserId') {
                            if ( -not ($UserId -as [decimal])) {
                                if ($UserId -ceq 'me') {
                                    $UserId = $Script:PSGSuite.AdminEmail
                                }
                                elseif ($UserId -notlike "*@*.*") {
                                    $UserId = "$($UserId)@$($Script:PSGSuite.Domain)"
                                }
                            }
                            $verbMsg += " [User: $UserId]"
                            $request.UserId = $UserId
                        }
                        Write-Verbose "Getting List of Invitations for$($verbMsg)"
                        [int]$retrieved = 0
                        [int]$i = 1
                        do {
                            $result = $request.Execute()
                            if ($null -ne $result.Invitations) {
                                $result.Invitations
                            }
                            [int]$retrieved = ($i + $result.Invitations.Count) - 1
                            [int]$i = $i + $result.Invitations.Count
                            $request.PageToken = $result.NextPageToken
                            Write-Verbose "Retrieved $retrieved Invitations..."
                        }
                        until (!$result.NextPageToken)
                    }
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSCourseInvitation'

function Get-GSCourseParticipant {
    <#
    .SYNOPSIS
    Gets a course participant or list of participants (teachers/students)
 
    .DESCRIPTION
    Gets a course participant or list of participants (teachers/students)
 
    .PARAMETER CourseId
    Identifier of the course to get participants of. This identifier can be either the Classroom-assigned identifier or an alias.
 
    .PARAMETER Role
    The Role for which you would like to list participants for.
 
    Available values are:
 
    * Student
    * Teacher
 
    The default value for this parameter is @('Teacher','Student')
 
    .PARAMETER Teacher
    Restricts returned courses to those having a teacher with the specified identifier. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER Student
    Restricts returned courses to those having a student with the specified identifier. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER User
    The user to authenticate the request as
 
    .PARAMETER Fields
    The specific fields to fetch
 
    .EXAMPLE
    Get-GSCourseParticipant -Teacher aristotle@athens.edu
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [ValidateNotNullOrEmpty()]
        [String]
        $CourseId,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateSet('Teacher','Student')]
        [String[]]
        $Role = @('Teacher','Student'),
        [parameter(Mandatory = $false,ParameterSetName = "Get")]
        [String[]]
        $Teacher,
        [parameter(Mandatory = $false,ParameterSetName = "Get")]
        [String[]]
        $Student,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [String[]]
        $Fields = '*'
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = @(
                'https://www.googleapis.com/auth/classroom.rosters'
                'https://www.googleapis.com/auth/classroom.profile.emails'
                'https://www.googleapis.com/auth/classroom.profile.photos'
            )
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($part in $Student) {
                    try {
                        if ( -not ($part -as [decimal])) {
                            if ($part -ceq 'me') {
                                $part = $Script:PSGSuite.AdminEmail
                            }
                            elseif ($part -notlike "*@*.*") {
                                $part = "$($part)@$($Script:PSGSuite.Domain)"
                            }
                        }
                        Write-Verbose "Getting Student '$part' for Course '$CourseId'"
                        $request = $service.Courses.Students.Get($CourseId,$part)
                        $request.Fields = "$($Fields -join ",")"
                        $request.Execute()
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
                foreach ($part in $Teacher) {
                    try {
                        try {
                            [decimal]$part | Out-Null
                        }
                        catch {
                            if ($part -ceq 'me') {
                                $part = $Script:PSGSuite.AdminEmail
                            }
                            elseif ($part -notlike "*@*.*") {
                                $part = "$($part)@$($Script:PSGSuite.Domain)"
                            }
                        }
                        Write-Verbose "Getting Teacher '$part' for Course '$CourseId'"
                        $request = $service.Courses.Teachers.Get($CourseId,$part)
                        $request.Fields = "$($Fields -join ",")"
                        $request.Execute()
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                foreach ($Ro in $Role) {
                    try {
                        Write-Verbose "Getting List of $($Ro)s for Course '$CourseId'"
                        $request = switch ($Ro) {
                            Teacher {
                                $service.Courses.Teachers.List($CourseId)
                            }
                            Student {
                                $service.Courses.Students.List($CourseId)
                            }
                        }
                        $request.Fields = "$($Fields -join ",")"
                        [int]$retrieved = 0
                        [int]$i = 1
                        do {
                            $result = $request.Execute()
                            switch ($Ro) {
                                Teacher {
                                    if ($null -ne $result.Teachers) {
                                        $result.Teachers
                                    }
                                    [int]$retrieved = ($i + $result.Teachers.Count) - 1
                                    [int]$i = $i + $result.Teachers.Count
                                }
                                Student {
                                    if ($null -ne $result.Students) {
                                        $result.Students
                                    }
                                    [int]$retrieved = ($i + $result.Students.Count) - 1
                                    [int]$i = $i + $result.Students.Count
                                }
                            }
                            $request.PageToken = $result.NextPageToken
                            Write-Verbose "Retrieved $retrieved $($Ro)s..."
                        }
                        until (!$result.NextPageToken)
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSCourseParticipant'

function Get-GSStudentGuardian {
    <#
    .SYNOPSIS
    Gets a guardian or list of guardians for a student.
 
    .DESCRIPTION
    Gets a guardian or list of guardians for a student.
 
    .PARAMETER StudentId
    The identifier of the student to get guardian info for. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
    * the string literal "-", indicating that results should be returned for all students that the requesting user is permitted to view guardians for. [Default]
        * **This is only allowed when excluding the `GuardianId` parameter to perform a List request!**
 
    .PARAMETER GuardianId
    The id field from a Guardian.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Get-GSStudentGuardian
 
    Gets the list of guardians for all students.
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Student')]
        [String[]]
        $StudentId = "-",
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Guardian')]
        [String[]]
        $GuardianId,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.guardianlinks.students'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($stuId in $StudentId) {
            try {
                if ($stuId -ne '-') {
                    if ( -not ($stuId -as [decimal])) {
                        if ($stuId -ceq 'me') {
                            $stuId = $Script:PSGSuite.AdminEmail
                        }
                        elseif ($stuId -notlike "*@*.*") {
                            $stuId = "$($stuId)@$($Script:PSGSuite.Domain)"
                        }
                    }
                }
                elseif ($PSBoundParameters.Keys -contains 'GuardianId') {
                    Write-Error "You must specify a valid StudentId when including a GuardianId! Current value '$stuId'"
                }
                if ($PSBoundParameters.Keys -contains 'GuardianId') {
                    foreach ($guard in $GuardianId) {
                        try {
                            Write-Verbose "Getting Guardian '$guard' for Student '$stuId'"
                            $request = $service.UserProfiles.Guardians.Get($stuId,$guard)
                            $request.Execute()
                        }
                        catch {
                            if ($ErrorActionPreference -eq 'Stop') {
                                $PSCmdlet.ThrowTerminatingError($_)
                            }
                            else {
                                Write-Error $_
                            }
                        }
                    }
                }
                else {
                    try {
                        if ($stuId -eq '-') {
                            Write-Verbose "Listing all Guardians"
                        }
                        else {
                            Write-Verbose "Listing Guardians for Student '$stuId'"
                        }
                        $request = $service.UserProfiles.Guardians.List($stuId)
                        [int]$i = 1
                        do {
                            $result = $request.Execute()
                            if ($null -ne $result.Guardians) {
                                $result.Guardians
                            }
                            $request.PageToken = $result.NextPageToken
                            [int]$retrieved = ($i + $result.Guardians.Count) - 1
                            Write-Verbose "Retrieved $retrieved Guardians..."
                            [int]$i = $i + $result.Guardians.Count
                        }
                        until (!$result.NextPageToken)
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSStudentGuardian'

function Get-GSStudentGuardianInvitation {
    <#
    .SYNOPSIS
    Gets a guardian invitation or list of guardian invitations.
 
    .DESCRIPTION
    Gets a guardian invitation or list of guardian invitations.
 
    .PARAMETER InvitationId
    The id field of the GuardianInvitation being requested.
 
    .PARAMETER StudentId
    The identifier of the student whose guardian invitation is being requested. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
    * the string literal "-", indicating that results should be returned for all students that the requesting user is permitted to view guardian invitations. [Default]
        * **This is only allowed when excluding the `InvitationId` parameter to perform a List request!**
 
    .PARAMETER GuardianEmail
    If specified, only results with the specified GuardianEmail will be returned.
 
    .PARAMETER State
    If specified, only results with the specified state values will be returned. Otherwise, results with a state of PENDING will be returned.
 
    The State can be one of the following:
 
    * PENDING
    * COMPLETE
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Get-GSStudentGuardianInvitation -StudentId aristotle@athens.edu
 
    Gets the list of guardian invitations for this student.
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $true,ParameterSetName = "Get")]
        [Alias('Id')]
        [String[]]
        $InvitationId,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Student')]
        [String[]]
        $StudentId = "-",
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true,ParameterSetName = "List")]
        [Alias('Guardian')]
        [String]
        $GuardianEmail,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateSet('PENDING','COMPLETE')]
        [Google.Apis.Classroom.v1.UserProfilesResource+GuardianInvitationsResource+ListRequest+States[]]
        $States,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.guardianlinks.students'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($stuId in $StudentId) {
            try {
                if ($stuId -ne '-') {
                    if ( -not ($stuId -as [decimal])) {
                        if ($stuId -ceq 'me') {
                            $stuId = $Script:PSGSuite.AdminEmail
                        }
                        elseif ($stuId -notlike "*@*.*") {
                            $stuId = "$($stuId)@$($Script:PSGSuite.Domain)"
                        }
                    }
                }
                elseif ($PSCmdlet.ParameterSetName -eq 'Get') {
                    Write-Error "You must specify a valid StudentId when using InvitationId! Current value '$stuId'"
                }
                switch ($PSCmdlet.ParameterSetName) {
                    Get {
                        foreach ($invId in $InvitationId) {
                            try {
                                Write-Verbose "Getting Guardian Invitation '$invId' for Student '$stuId'"
                                $request = $service.UserProfiles.GuardianInvitations.Get($stuId,$invId)
                                $request.Execute()
                            }
                            catch {
                                if ($ErrorActionPreference -eq 'Stop') {
                                    $PSCmdlet.ThrowTerminatingError($_)
                                }
                                else {
                                    Write-Error $_
                                }
                            }
                        }
                    }
                    List {
                        try {
                            if ($stuId -eq '-') {
                                Write-Verbose "Listing all Guardian Invitations"
                            }
                            else {
                                Write-Verbose "Listing Guardian Invitations for Student '$stuId'"
                            }
                            $request = $service.UserProfiles.GuardianInvitations.List($stuId)
                            foreach ($s in $States) {
                                $request.States += $s
                            }
                            if ($PSBoundParameters.Keys -contains 'GuardianEmail') {
                                $request.InvitedEmailAddress = $GuardianEmail
                            }
                            [int]$i = 1
                            do {
                                $result = $request.Execute()
                                if ($null -ne $result.GuardianInvitations) {
                                    $result.GuardianInvitations
                                }
                                $request.PageToken = $result.NextPageToken
                                [int]$retrieved = ($i + $result.GuardianInvitations.Count) - 1
                                Write-Verbose "Retrieved $retrieved Guardian Invitations..."
                                [int]$i = $i + $result.GuardianInvitations.Count
                            }
                            until (!$result.NextPageToken)
                        }
                        catch {
                            if ($ErrorActionPreference -eq 'Stop') {
                                $PSCmdlet.ThrowTerminatingError($_)
                            }
                            else {
                                Write-Error $_
                            }
                        }
                    }
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSStudentGuardianInvitation'

function New-GSCourse {
    <#
    .SYNOPSIS
    Creates a course.
 
    .DESCRIPTION
    Creates a course.
 
    .PARAMETER Name
    Name of the course. For example, "10th Grade Biology". The name is required. It must be between 1 and 750 characters and a valid UTF-8 string.
 
    .PARAMETER OwnerId
    The identifier of the owner of a course.
 
    When specified as a parameter of a create course request, this field is required. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER Id
    Identifier for this course assigned by Classroom.
 
    When creating a course, you may optionally set this identifier to an alias string in the request to create a corresponding alias. The id is still assigned by Classroom and cannot be updated after the course is created.
 
    .PARAMETER Section
    Section of the course. For example, "Period 2". If set, this field must be a valid UTF-8 string and no longer than 2800 characters.
 
    .PARAMETER DescriptionHeading
    Optional heading for the description. For example, "Welcome to 10th Grade Biology." If set, this field must be a valid UTF-8 string and no longer than 3600 characters.
 
    .PARAMETER Description
    Optional description. For example, "We'll be learning about the structure of living creatures from a combination of textbooks, guest lectures, and lab work. Expect to be excited!" If set, this field must be a valid UTF-8 string and no longer than 30,000 characters.
 
    .PARAMETER Room
    Optional room location. For example, "301". If set, this field must be a valid UTF-8 string and no longer than 650 characters.
 
    .PARAMETER CourseState
    State of the course. If unspecified, the default state is PROVISIONED
 
    Available values are:
    * ACTIVE - The course is active.
    * ARCHIVED - The course has been archived. You cannot modify it except to change it to a different state.
    * PROVISIONED - The course has been created, but not yet activated. It is accessible by the primary teacher and domain administrators, who may modify it or change it to the ACTIVE or DECLINED states. A course may only be changed to PROVISIONED if it is in the DECLINED state.
    * DECLINED - The course has been created, but declined. It is accessible by the course owner and domain administrators, though it will not be displayed in the web UI. You cannot modify the course except to change it to the PROVISIONED state. A course may only be changed to DECLINED if it is in the PROVISIONED state.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    New-GSCourse -Name "The Rebublic" -OwnerId plato@athens.edu -Id the-republic-s01 -Section s01 -DescriptionHeading "The definition of justice, the order and character of the just city-state and the just man" -Room academy-01
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [ValidateLength(1,750)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [Alias('Teacher')]
        [String]
        $OwnerId,
        [parameter(Mandatory = $false)]
        [Alias('Alias')]
        [String]
        $Id,
        [parameter(Mandatory = $false)]
        [ValidateLength(1,2800)]
        [String]
        $Section,
        [parameter(Mandatory = $false)]
        [ValidateLength(1,3600)]
        [Alias('Heading')]
        [String]
        $DescriptionHeading,
        [parameter(Mandatory = $false)]
        [ValidateLength(1,30000)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [String]
        $Room,
        [parameter(Mandatory = $false)]
        [Alias('Status')]
        [ValidateSet('PROVISIONED','ACTIVE','ARCHIVED','DECLINED')]
        [String]
        $CourseState,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.courses'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Creating new Course '$Name'"
            $body = New-Object 'Google.Apis.Classroom.v1.Data.Course'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    OwnerId {
                        try {
                            [decimal]$PSBoundParameters[$prop] | Out-Null
                        }
                        catch {
                            if ($PSBoundParameters[$prop] -ceq 'me') {
                                $PSBoundParameters[$prop] = $Script:PSGSuite.AdminEmail
                            }
                            elseif ($PSBoundParameters[$prop] -notlike "*@*.*") {
                                $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)"
                            }
                        }
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            if ($PSBoundParameters.Keys -notcontains 'OwnerId') {
                $body.OwnerId = $Script:PSGSuite.AdminEmail
            }
            $request = $service.Courses.Create($body)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'New-GSCourse'

function New-GSCourseAlias {
    <#
    .SYNOPSIS
    Creates a course alias.
 
    .DESCRIPTION
    Creates a course alias.
 
    .PARAMETER Alias
    Alias string
 
    .PARAMETER CourseId
    Identifier of the course to alias. This identifier can be either the Classroom-assigned identifier or an alias.
 
    .PARAMETER Scope
    An alias uniquely identifies a course. It must be unique within one of the following scopes:
 
    * Domain - A domain-scoped alias is visible to all users within the alias creator's domain and can be created only by a domain admin. A domain-scoped alias is often used when a course has an identifier external to Classroom.
    * Project - A project-scoped alias is visible to any request from an application using the Developer Console project ID that created the alias and can be created by any project. A project-scoped alias is often used when an application has alternative identifiers. A random value can also be used to avoid duplicate courses in the event of transmission failures, as retrying a request will return ALREADY_EXISTS if a previous one has succeeded.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    New-GSCourseAlias -Alias "abc123" -CourseId 'architecture-101' -Scope Domain
 
    .EXAMPLE
    New-GSCourseAlias -Alias "d:abc123" -CourseId 'architecture-101'
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $Alias,
        [parameter(Mandatory = $true,Position = 1)]
        [String]
        $CourseId,
        [parameter(Mandatory = $false)]
        [ValidateSet('Domain','Project')]
        [String]
        $Scope = $(if($Alias -match "^p\:"){'Project'}else{'Domain'}),
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.courses'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
        $formatted = if ($Alias -match "^(d\:|p\:)") {
            $Alias
            $Scope = if ($Alias -match "^d\:") {
                'Domain'
            }
            else {
                'Project'
            }
        }
        else {
            switch ($Scope) {
                Domain {
                    'd:' + ($Alias -replace "^(d\:|p\:)","")
                }
                Project {
                    'p:' + ($Alias -replace "^(d\:|p\:)","")
                }
            }

        }
    }
    Process {
        try {
            Write-Verbose "Creating new Alias '$Alias' for Course '$CourseId' at '$Scope' scope"
            $body = New-Object 'Google.Apis.Classroom.v1.Data.CourseAlias' -Property @{
                Alias = $formatted
            }
            $request = $service.Courses.Aliases.Create($body,$CourseId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'New-GSCourseAlias'

function New-GSCourseInvitation {
    <#
    .SYNOPSIS
    Creates a course invitation.
 
    .DESCRIPTION
    Creates a course invitation.
 
    .PARAMETER CourseId
    Identifier of the course to invite the user to. This identifier can be either the Classroom-assigned identifier or an alias.
 
    .PARAMETER UserId
    Identifier of the user to invite. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER Role
    Role to invite the user to have from the following:
 
    * STUDENT
    * TEACHER
    * OWNER
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    New-GSCourseInvitation -CourseId philosophy-101 -UserId aristotle@athens.edu -Role TEACHER
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [String]
        $CourseId,
        [parameter(Mandatory = $false,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('PrimaryEmail','Email','Mail')]
        [String[]]
        $UserId,
        [parameter(Mandatory = $false)]
        [ValidateSet('STUDENT','TEACHER','OWNER')]
        [String]
        $Role = 'STUDENT',
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.rosters'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($U in $UserId) {
            try {
                if ( -not ($U -as [decimal])) {
                    if ($U -ceq 'me') {
                        $U = $Script:PSGSuite.AdminEmail
                    }
                    elseif ($U -notlike "*@*.*") {
                        $U = "$($U)@$($Script:PSGSuite.Domain)"
                    }
                }
                Write-Verbose "Inviting User '$U' to Course '$CourseId' for Role '$Role'"
                $body = New-Object 'Google.Apis.Classroom.v1.Data.Invitation'
                foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                    $body.$prop = $PSBoundParameters[$prop]
                }
                if ($PSBoundParameters.Keys -notcontains 'Role') {
                    $body.Role = $Role
                }
                $request = $service.Invitations.Create($body)
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'New-GSCourseInvitation'

function New-GSStudentGuardianInvitation {
    <#
    .SYNOPSIS
    Creates a guardian invitation, and sends an email to the guardian asking them to confirm that they are the student's guardian.
 
    .DESCRIPTION
    Creates a guardian invitation, and sends an email to the guardian asking them to confirm that they are the student's guardian.
 
    .PARAMETER StudentId
    Identifier of the user to invite. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
 
    .PARAMETER GuardianEmail
    The email address of the guardian to invite.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    New-GSStudentGuardianInvitation -StudentId aristotle@athens.edu -GuardianEmail zeus@olympus.io
 
    .EXAMPLE
    Import-Csv .\Student_Guardian_List.csv | New-GSStudentGuardianInvitation
 
    Process a CSV with two columns containing headers "Student" and "Guardian" and send the invites accordingly, i.e.
 
    | StudentId | GuardianEmail |
    |:--------------------:|:---------------:|
    | aristotle@athens.edu | zeus@olympus.io |
    | plato@athens.edu | hera@olympus.io |
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Student')]
        [String]
        $StudentId,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Guardian')]
        [String]
        $GuardianEmail,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.guardianlinks.students'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ( -not ($StudentId -as [decimal])) {
                if ($StudentId -ceq 'me') {
                    $StudentId = $Script:PSGSuite.AdminEmail
                }
                elseif ($StudentId -notlike "*@*.*") {
                    $StudentId = "$($StudentId)@$($Script:PSGSuite.Domain)"
                }
            }
            $body = New-Object 'Google.Apis.Classroom.v1.Data.GuardianInvitation' -Property @{
                StudentId = $StudentId
                InvitedEmailAddress = $GuardianEmail
            }
            Write-Verbose "Inviting Guardian '$GuardianEmail' for Student '$StudentId'"
            $request = $service.UserProfiles.GuardianInvitations.Create($body,$StudentId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'New-GSStudentGuardianInvitation'

function Remove-GSCourse {
    <#
    .SYNOPSIS
    Removes an existing course.
 
    .DESCRIPTION
    Removes an existing course.
 
    .PARAMETER Id
    Identifier for this course assigned by Classroom.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Remove-GSCourse -Id the-republic-s01 -Confirm:$false
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [Alias('Alias')]
        [String]
        $Id,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.courses'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($PSCmdlet.ShouldProcess("Removing Course '$Id'")) {
                Write-Verbose "Removing Course '$Id'"
                $request = $service.Courses.Delete($Id)
                $request.Execute()
                Write-Verbose "Course '$Id' has been successfully removed"
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSCourse'

function Remove-GSCourseAlias {
    <#
    .SYNOPSIS
    Removes a course alias.
 
    .DESCRIPTION
    Removes a course alias.
 
    .PARAMETER Alias
    Alias string
 
    .PARAMETER CourseId
    Identifier of the course to alias. This identifier can be either the Classroom-assigned identifier or an alias.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Remove-GSCourseAlias -Alias "d:abc123" -CourseId 'architecture-101'
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $Alias,
        [parameter(Mandatory = $true,Position = 1)]
        [String]
        $CourseId,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.courses'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($PSCmdlet.ShouldProcess("Removing Alias '$Alias' for Course '$CourseId'")) {
                Write-Verbose "Removing Alias '$Alias' for Course '$CourseId'"
                $request = $service.Courses.Aliases.Delete($CourseId,$Alias)
                $request.Execute()
                Write-Verbose "Alias '$Alias' for Course '$CourseId' has been successfully removed"
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSCourseAlias'

function Remove-GSCourseInvitation {
    <#
    .SYNOPSIS
    Deletes an invitation.
 
    .DESCRIPTION
    Deletes an invitation.
 
    .PARAMETER Id
    Identifier of the invitation to delete.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Remove-GSCourseInvitation -Id $inviteId -Confirm:$false
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $Id,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.rosters'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($I in $Id) {
            try {
                if ($PSCmdlet.ShouldProcess("Removing Invitation '$I'")) {
                    Write-Verbose "Removing Invitation '$I'"
                    $request = $service.Invitations.Delete($I)
                    $request.Execute()
                    Write-Verbose "Invitation '$I' has been successfully removed"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSCourseInvitation'

function Remove-GSCourseParticipant {
    <#
    .SYNOPSIS
    Removes students and/or teachers from a course
 
    .DESCRIPTION
    Removes students and/or teachers from a course
 
    .PARAMETER CourseId
    Identifier of the course to remove participants from. This identifier can be either the Classroom-assigned identifier or an alias.
 
    .PARAMETER Student
    Identifier of the user.
 
    This identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER Teacher
    Identifier of the user.
 
    This identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Remove-GSCourseParticipant -CourseId 'architecture-101' -Student plato@athens.edu,aristotle@athens.edu -Teacher zeus@athens.edu
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $CourseId,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('PrimaryEmail','Email','Mail')]
        [String[]]
        $Student,
        [parameter(Mandatory = $false)]
        [String[]]
        $Teacher,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.rosters'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($part in $Student) {
            try {
                if ( -not ($part -as [decimal])) {
                    if ($part -ceq 'me') {
                        $part = $Script:PSGSuite.AdminEmail
                    }
                    elseif ($part -notlike "*@*.*") {
                        $part = "$($part)@$($Script:PSGSuite.Domain)"
                    }
                }
                if ($PSCmdlet.ShouldProcess("Removing Student '$part' from Course '$CourseId'")) {
                    Write-Verbose "Removing Student '$part' from Course '$CourseId'"
                    $request = $service.Courses.Students.Delete($CourseId,$part)
                    $request.Execute()
                    Write-Verbose "Student '$part' has successfully been removed from Course '$CourseId'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
        foreach ($part in $Teacher) {
            try {
                try {
                    [decimal]$part | Out-Null
                }
                catch {
                    if ($part -ceq 'me') {
                        $part = $Script:PSGSuite.AdminEmail
                    }
                    elseif ($part -notlike "*@*.*") {
                        $part = "$($part)@$($Script:PSGSuite.Domain)"
                    }
                }
                if ($PSCmdlet.ShouldProcess("Removing Teacher '$part' from Course '$CourseId'")) {
                    Write-Verbose "Removing Teacher '$part' from Course '$CourseId'"
                    $request = $service.Courses.Teachers.Delete($CourseId,$part)
                    $request.Execute()
                    Write-Verbose "Teacher '$part' has successfully been removed from Course '$CourseId'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSCourseParticipant'

function Remove-GSStudentGuardian {
    <#
    .SYNOPSIS
    Removes a guardian.
 
    .DESCRIPTION
    Removes a guardian.
 
    .PARAMETER StudentId
    The identifier of the student to get guardian info for. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER GuardianId
    The id field from a Guardian.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Remove-GSStudentGuardian -StudentId aristotle@athens.edu -GuardianId $guardianId
 
    Removes the guardian for artistotle@athens.edu.
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Student')]
        [String]
        $StudentId,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Guardian')]
        [String]
        $GuardianId,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.guardianlinks.students'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ( -not ($StudentId -as [decimal])) {
                if ($StudentId -ceq 'me') {
                    $StudentId = $Script:PSGSuite.AdminEmail
                }
                elseif ($StudentId -notlike "*@*.*") {
                    $StudentId = "$($StudentId)@$($Script:PSGSuite.Domain)"
                }
            }
            if ($PSCmdlet.ShouldProcess("Removing Guardian '$GuardianId' from Student '$StudentId'")) {
                Write-Verbose "Removing Guardian '$GuardianId' from Student '$StudentId'"
                $request = $service.UserProfiles.Guardians.Delete($StudentId,$GuardianId)
                $request.Execute()
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSStudentGuardian'

function Update-GSCourse {
    <#
    .SYNOPSIS
    Updates an existing course.
 
    .DESCRIPTION
    Updates an existing course.
 
    .PARAMETER Id
    Identifier for this course assigned by Classroom.
 
    .PARAMETER Name
    Name of the course. For example, "10th Grade Biology". The name is required. It must be between 1 and 750 characters and a valid UTF-8 string.
 
    .PARAMETER OwnerId
    The identifier of the owner of a course.
 
    When specified as a parameter of a create course request, this field is required. The identifier can be one of the following:
 
    * the numeric identifier for the user
    * the email address of the user
    * the string literal "me", indicating the requesting user
 
    .PARAMETER Section
    Section of the course. For example, "Period 2". If set, this field must be a valid UTF-8 string and no longer than 2800 characters.
 
    .PARAMETER DescriptionHeading
    Optional heading for the description. For example, "Welcome to 10th Grade Biology." If set, this field must be a valid UTF-8 string and no longer than 3600 characters.
 
    .PARAMETER Description
    Optional description. For example, "We'll be learning about the structure of living creatures from a combination of textbooks, guest lectures, and lab work. Expect to be excited!" If set, this field must be a valid UTF-8 string and no longer than 30,000 characters.
 
    .PARAMETER Room
    Optional room location. For example, "301". If set, this field must be a valid UTF-8 string and no longer than 650 characters.
 
    .PARAMETER CourseState
    State of the course. If unspecified, the default state is PROVISIONED
 
    Available values are:
    * ACTIVE - The course is active.
    * ARCHIVED - The course has been archived. You cannot modify it except to change it to a different state.
    * PROVISIONED - The course has been created, but not yet activated. It is accessible by the primary teacher and domain administrators, who may modify it or change it to the ACTIVE or DECLINED states. A course may only be changed to PROVISIONED if it is in the DECLINED state.
    * DECLINED - The course has been created, but declined. It is accessible by the course owner and domain administrators, though it will not be displayed in the web UI. You cannot modify the course except to change it to the PROVISIONED state. A course may only be changed to DECLINED if it is in the PROVISIONED state.
 
    .PARAMETER User
    The user to authenticate the request as
 
    .EXAMPLE
    Update-GSCourse -Id the-republic-s01 -Name "The Rebublic 101"
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [Alias('Alias')]
        [String]
        $Id,
        [parameter(Mandatory = $false)]
        [ValidateLength(1,750)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [Alias('Teacher')]
        [String]
        $OwnerId,
        [parameter(Mandatory = $false)]
        [ValidateLength(1,2800)]
        [String]
        $Section,
        [parameter(Mandatory = $false)]
        [ValidateLength(1,3600)]
        [Alias('Heading')]
        [String]
        $DescriptionHeading,
        [parameter(Mandatory = $false)]
        [ValidateLength(1,30000)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [String]
        $Room,
        [parameter(Mandatory = $false)]
        [Alias('Status')]
        [ValidateSet('PROVISIONED','ACTIVE','ARCHIVED','DECLINED')]
        [String]
        $CourseState,
        [parameter(Mandatory = $false)]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/classroom.courses'
            ServiceType = 'Google.Apis.Classroom.v1.ClassroomService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
        $UpdateMask = @()
    }
    Process {
        try {
            Write-Verbose "Updating Course ID '$Id'"
            $body = New-Object 'Google.Apis.Classroom.v1.Data.Course'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    Id {}
                    OwnerId {
                        $UpdateMask += $($prop.Substring(0,1).ToLower() + $prop.Substring(1))
                        try {
                            [decimal]$PSBoundParameters[$prop] | Out-Null
                        }
                        catch {
                            if ($PSBoundParameters[$prop] -ceq 'me') {
                                $PSBoundParameters[$prop] = $Script:PSGSuite.AdminEmail
                            }
                            elseif ($PSBoundParameters[$prop] -notlike "*@*.*") {
                                $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)"
                            }
                        }
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                    Default {
                        $UpdateMask += $($prop.Substring(0,1).ToLower() + $prop.Substring(1))
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            $request = $service.Courses.Patch($body,$Id)
            $request.UpdateMask = $($UpdateMask -join ",")
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Update-GSCourse'

function Get-GSChatConfig {
    <#
    .SYNOPSIS
    Returns the specified Chat space and webhook dictionaries from the PSGSuite config to use with Send-GSChatMessage
 
    .DESCRIPTION
    Returns the specified Chat space and webhook dictionaries from the PSGSuite config to use with Send-GSChatMessage
 
    .PARAMETER WebhookName
    The key that the Webhook Url is stored as in the Config. If left blank, returns the full Chat configuration from the Config
 
    .PARAMETER SpaceName
    The key that the Space ID is stored as in the Config. If left blank, returns the full Chat configuration from the Config
 
    .PARAMETER ConfigName
    The name of the Config to return the Chat config items from
 
    .EXAMPLE
    Send-GSChatMessage -Text "Testing webhook" -Webhook (Get-GSChatConfig MyRoom)
     
    Sends a Chat message with text to the Webhook Url named 'MyRoom' found in the config
    #>

    [CmdletBinding(DefaultParameterSetName = "Webhooks")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ParameterSetName = "Webhooks")]
        [String[]]
        $WebhookName,
        [parameter(Mandatory = $false,Position = 0,ParameterSetName = "Spaces")]
        [String[]]
        $SpaceName,
        [parameter(Mandatory = $false,Position = 1)]
        [String[]]
        $ConfigName
    )
    if ($PSBoundParameters.Keys -contains 'ConfigName') {
        $currentConfig = Get-PSGSuiteConfig -ConfigName $ConfigName -PassThru -NoImport
    }
    else {
        $currentConfig = Get-PSGSuiteConfig -PassThru
    }
    switch ($PSCmdlet.ParameterSetName) {
        Webhooks {
            if ($PSBoundParameters.Keys -contains 'WebhookName') {
                foreach ($hook in $WebhookName) {
                    Write-Verbose "Getting webhook for '$hook' from ConfigName '$($currentConfig.ConfigName)'"
                    if ($found = $currentConfig.Chat['Webhooks'][$hook]) {
                        $found
                    }
                    else {
                        Write-Error "$hook was not found in the Webhook dictionary stored in ConfigName '$($currentConfig.ConfigName)'!"
                    }
                }
            }
            else {
                Write-Verbose "Getting full Chat config from ConfigName '$($currentConfig.ConfigName)'"
                $currentConfig.Chat
            }
        }
        Spaces {
            foreach ($hook in $SpaceName) {
                Write-Verbose "Getting space Id for '$hook' from ConfigName '$($currentConfig.ConfigName)'"
                if ($found = $currentConfig.Chat['Spaces'][$hook]) {
                    $found
                }
                else {
                    Write-Error "$hook was not found in the Spaces dictionary stored in ConfigName '$($currentConfig.ConfigName)'!"
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSChatConfig'

function Get-PSGSuiteConfig {
    <#
    .SYNOPSIS
    Loads the specified PSGSuite config
     
    .DESCRIPTION
    Loads the specified PSGSuite config
     
    .PARAMETER ConfigName
    The config name to load
     
    .PARAMETER Path
    The path of the config to load if non-default.
 
    This can be used to load either a legacy XML config from an older version of PSGSuite or a specific .PSD1 config created with version 2.0.0 or greater
     
    .PARAMETER Scope
    The config scope to load
     
    .PARAMETER PassThru
    If specified, returns the config after loading it
     
    .EXAMPLE
    Get-PSGSuiteConfig personalDomain -PassThru
 
    This will load the config named "personalDomain" and return it as a PSObject.
    #>

    [cmdletbinding(DefaultParameterSetName = "ConfigurationModule")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ParameterSetName = "ConfigurationModule")]
        [String]
        $ConfigName,
        [Parameter(Mandatory = $false,ParameterSetName = "Path")]
        [ValidateScript( {Test-Path $_})]
        [String]
        $Path,
        [Parameter(Mandatory = $false,Position = 1)]
        [ValidateSet("User", "Machine", "Enterprise", $null)]
        [string]
        $Scope = $Script:ConfigScope,
        [Parameter(Mandatory = $false)]
        [Switch]
        $PassThru,
        [Parameter(Mandatory = $false)]
        [Switch]
        $NoImport
    )
    function Decrypt {
        param($String)
        if ($String -is [System.Security.SecureString]) {
            [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
                [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR(
                    $string))
        }
        elseif ($String -is [System.String]) {
            $String
        }
    }
    $script:ConfigScope = $Scope
    switch ($PSCmdlet.ParameterSetName) {
        ConfigurationModule {
            $fullConf = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $Script:ConfigScope
            if (!$ConfigName) {
                $choice = $fullConf["DefaultConfig"]
                Write-Verbose "Importing default config: $choice"
            }
            else {
                $choice = $ConfigName
                Write-Verbose "Importing config: $choice"
            }
            $encConf = [PSCustomObject]($fullConf[$choice])
        }
        Path {
            $encConf = switch ((Get-Item -Path $Path).Extension) {
                '.xml' {
                    Import-Clixml -Path $Path
                    $choice = "LegacyXML"
                }
                '.psd1' {
                    Import-SpecificConfiguration -Path $Path
                    $choice = "CustomConfigurationFile"
                }
            }
        }
    }
    $decryptedConfig = $encConf |
        Select-Object -Property @{l = 'ConfigName';e = {$choice}},
                                @{l = 'P12KeyPath';e = {Decrypt $_.P12KeyPath}},
                                @{l = 'ClientSecretsPath';e = {Decrypt $_.ClientSecretsPath}},
                                @{l = 'AppEmail';e = {Decrypt $_.AppEmail}},
                                @{l = 'AdminEmail';e = {Decrypt $_.AdminEmail}},
                                @{l = 'CustomerID';e = {Decrypt $_.CustomerID}},
                                @{l = 'Domain';e = {Decrypt $_.Domain}},
                                @{l = 'Preference';e = {Decrypt $_.Preference}},
                                @{l = 'ServiceAccountClientID';e = {Decrypt $_.ServiceAccountClientID}},
                                @{l = 'Chat';e = {
                                    $dict = @{
                                        Webhooks = @{}
                                        Spaces = @{}
                                    }
                                    foreach ($key in $_.Chat.Webhooks.Keys) {
                                        $dict['Webhooks'][$key] = (Decrypt $_.Chat.Webhooks[$key])
                                    }
                                    foreach ($key in $_.Chat.Spaces.Keys) {
                                        $dict['Spaces'][$key] = (Decrypt $_.Chat.Spaces[$key])
                                    }
                                    $dict
                                }},
                                @{l = 'ConfigPath';e = {if ($_.ConfigPath) {
                                            $_.ConfigPath
                                        }
                                        elseif ($Path) {
                                            "$(Resolve-Path $Path)"
                                        }
                                        else {
                                            $null
                                        }
                                    }
                                }
    Write-Verbose "Retrieved configuration '$choice'"
    if (!$NoImport) {
        $script:PSGSuite = $decryptedConfig
    }
    if ($PassThru) {
        $decryptedConfig
    }
}
Export-ModuleMember -Function 'Get-PSGSuiteConfig'

function Set-PSGSuiteConfig {
    <#
    .SYNOPSIS
    Creates or updates a config
     
    .DESCRIPTION
    Creates or updates a config
     
    .PARAMETER ConfigName
    The friendly name for the config you are creating or updating
     
    .PARAMETER P12KeyPath
    The path to the P12 Key file downloaded from the Google Developer's Console. If both P12KeyPath and ClientSecretsPath are specified, P12KeyPath takes precedence
     
    .PARAMETER ClientSecretsPath
    The path to the Client Secrets JSON file downloaded from the Google Developer's Console. Using the ClientSecrets JSON will prompt the user to complete OAuth2 authentication in their browser on the first run and store the retrieved Refresh and Access tokens in the user's home directory. If P12KeyPath is also specified, ClientSecretsPath will be ignored.
     
    .PARAMETER AppEmail
    The application email from the Google Developer's Console. This typically looks like the following:
 
    myProjectName@myProject.iam.gserviceaccount.com
     
    .PARAMETER AdminEmail
    The email of the Google Admin running the functions. This will typically be your email.
     
    .PARAMETER CustomerID
    The Customer ID for your customer. If unknown, you can retrieve it by running Get-GSUser after creating a base config with at least either the P12KeyPath or ClientSecretsPath, the AppEmail and the AdminEmail.
     
    .PARAMETER Domain
    The domain that you primarily manage for this CustomerID
     
    .PARAMETER Preference
    Some functions allow you to specify whether you are running in the context of the customer or a specific domain in the customer's realm. This allows you to set your preference.
 
    Available values are:
    * CustomerID
    * Domain
     
    .PARAMETER ServiceAccountClientID
    The Service Account's Client ID from the Google Developer's Console. This is optional and is only used as a reference for yourself to prevent needing to check the Developer's Console for the ID when verifying API Client Access.
 
    .PARAMETER Webhook
    Web
     
    .PARAMETER Scope
    The scope at which you would like to set this config.
 
    Available values are:
    * Machine (this would create the config in a location accessible by all users on the machine)
    * Enterprise (this would create the config in the Roaming AppData folder for the user or it's *nix equivalent)
    * User (this would create the config in the Local AppData folder for the user or it's *nix equivalent)
     
    .PARAMETER SetAsDefaultConfig
    If passed, sets the ConfigName as the default config to load on module import
     
    .PARAMETER NoImport
    The default behavior when using Set-PSGSuiteConfig is that the new/updated config is imported as active. If -NoImport is passed, this saves the config but retains the previously loaded config as active.
     
    .EXAMPLE
    Set-PSGSuiteConfig -ConfigName "personal" -P12KeyPath C:\Keys\PersonalKey.p12 -AppEmail "myProjectName@myProject.iam.gserviceaccount.com" -AdminEmail "admin@domain.com" -CustomerID "C83030001" -Domain "domain.com" -Preference CustomerID -ServiceAccountClientID 1175798883298324983498 -SetAsDefaultConfig
 
    This builds a config names "personal" and sets it as the default config
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [ValidateScript( {
                if ($_ -eq "DefaultConfig") {
                    throw "You must specify a ConfigName other than 'DefaultConfig'. That is a reserved value."
                }
                elseif ($_ -notmatch '^[a-zA-Z]+[a-zA-Z0-9]*$') {
                    throw "You must specify a ConfigName that starts with a letter and does not contain any spaces, otherwise the Configuration will break"
                }
                else {
                    $true
                }
            })]
        [string]
        $ConfigName = $Script:ConfigName,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $P12KeyPath,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $ClientSecretsPath,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $AppEmail,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $AdminEmail,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $CustomerID,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $Domain,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [ValidateSet("CustomerID","Domain")]
        [string]
        $Preference,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $ServiceAccountClientID,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Hashtable[]]
        $Webhook,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Hashtable[]]
        $Space,
        [parameter(Mandatory = $false)]
        [ValidateSet("User", "Machine", "Enterprise", $null)]
        [string]
        $Scope = $script:ConfigScope,
        [parameter(Mandatory = $false)]
        [switch]
        $SetAsDefaultConfig,
        [parameter(Mandatory = $false)]
        [switch]
        $NoImport
    )
    Begin {
        Function Encrypt {
            param($string)
            if ($string -is [System.Security.SecureString]) {
                $string
            }
            elseif ($string -is [System.String] -and $String -notlike '') {
                ConvertTo-SecureString -String $string -AsPlainText -Force
            }
        }
    }
    Process {
        $script:ConfigScope = $Scope
        $params = @{}
        if ($PSBoundParameters.Keys -contains "Verbose") {
            $params["Verbose"] = $PSBoundParameters["Verbose"]
        }
        $configHash = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' @params
        if (!$ConfigName) {
            $ConfigName = if ($configHash["DefaultConfig"]){
                $configHash["DefaultConfig"]
            }
            else {
                "default"
                $configHash["DefaultConfig"] = "default"
            }
        }
        Write-Verbose "Setting config name '$ConfigName'"
        $configParams = @('P12KeyPath','ClientSecretsPath','AppEmail','AdminEmail','CustomerID','Domain','Preference','ServiceAccountClientID','Webhook','Space')
        if ($SetAsDefaultConfig -or !$configHash["DefaultConfig"]) {
            $configHash["DefaultConfig"] = $ConfigName
        }
        if (!$configHash[$ConfigName]) {
            $configHash.Add($ConfigName,(@{}))
        }
        foreach ($key in ($PSBoundParameters.Keys | Where-Object {$configParams -contains $_})) {
            switch ($key) {
                Webhook {
                    if ($configHash["$ConfigName"].Keys -notcontains 'Chat') {
                        $configHash["$ConfigName"]['Chat'] = @{
                            Webhooks = @{}
                            Spaces = @{}
                        }
                    }
                    foreach ($cWebhook in $PSBoundParameters[$key]) {
                        foreach ($cWebhookKey in $cWebhook.Keys) {
                            $configHash["$ConfigName"]['Chat']['Webhooks'][$cWebhookKey] = (Encrypt $cWebhook[$cWebhookKey])
                        }
                    }
                }
                Space {
                    if ($configHash["$ConfigName"].Keys -notcontains 'Chat') {
                        $configHash["$ConfigName"]['Chat'] = @{
                            Webhooks = @{}
                            Spaces = @{}
                        }
                    }
                    $configHash["$ConfigName"]['Chat']['Spaces'] = @{}
                    foreach ($cWebhook in $PSBoundParameters[$key]) {
                        foreach ($cWebhookKey in $cWebhook.Keys) {
                            $configHash["$ConfigName"]['Chat']['Spaces'][$cWebhookKey] = (Encrypt $cWebhook[$cWebhookKey])
                        }
                    }
                }
                default {
                    $configHash["$ConfigName"][$key] = (Encrypt $PSBoundParameters[$key])
                }
            }
        }
        $configHash["$ConfigName"]['ConfigPath'] = (Join-Path $(Get-Module PSGSuite | Get-StoragePath -Scope $Script:ConfigScope) "Configuration.psd1")
        $configHash | Export-Configuration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $script:ConfigScope
    }
    End {
        if (!$NoImport) {
            Get-PSGSuiteConfig -ConfigName $ConfigName -Verbose:$false
        }
    }
}
Export-ModuleMember -Function 'Set-PSGSuiteConfig'

function Show-PSGSuiteConfig {
    <#
    .SYNOPSIS
    Returns the currently loaded config
     
    .DESCRIPTION
    Returns the currently loaded config
     
    .EXAMPLE
    Show-PSGSuiteConfig
    #>

    [CmdletBinding()]
    Param()
    Write-Verbose "Showing current PSGSuite config"
    $script:PSGSuite
}
Export-ModuleMember -Function 'Show-PSGSuiteConfig'

function Switch-PSGSuiteConfig {
    <#
    .SYNOPSIS
    Switches the active config
     
    .DESCRIPTION
    Switches the active config
     
    .PARAMETER ConfigName
    The friendly name of the config you would like to set as active for the session
     
    .PARAMETER Domain
    The domain name for the config you would like to set as active for the session
     
    .PARAMETER SetToDefault
    If passed, also sets the specified config as the default so it's loaded on the next module import
     
    .EXAMPLE
    Switch-PSGSuiteConfig newCustomer
 
    Switches the config to the "newCustomer" config
    #>

    [CmdletBinding(DefaultParameterSetName = "ConfigName")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "ConfigName")]
        [ValidateNotNullOrEmpty()]
        [String]
        $ConfigName,
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Domain")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Domain,
        [parameter(Mandatory = $false)]
        [switch]
        $SetToDefault
    )
    if ($script:PSGSuite.Domain -eq $Domain) {
        Write-Verbose "Current config is already set to domain '$Domain' --- retaining current config. If you would like to import a different config for the same domain, please use the -ConfigName parameter instead"
        if ($SetToDefault) {
            Write-Verbose "Setting config name '$($script:PSGSuite.ConfigName)' for domain '$($script:PSGSuite.Domain)' as default"
            Set-PSGSuiteConfig -ConfigName $($script:PSGSuite.ConfigName) -SetAsDefaultConfig -Verbose:$false
        }
    }
    elseif ($script:PSGSuite.ConfigName -eq $ConfigName) {
        Write-Verbose "Current config is already set to '$ConfigName' --- retaining current config"
        if ($SetToDefault) {
            Write-Verbose "Setting config name '$($script:PSGSuite.ConfigName)' for domain '$($script:PSGSuite.Domain)' as default"
            Set-PSGSuiteConfig -ConfigName $($script:PSGSuite.ConfigName) -SetAsDefaultConfig -Verbose:$false
        }
    }
    else {
        function Decrypt {
            param($String)
            if ($String -is [System.Security.SecureString]) {
                [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
                    [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR(
                        $string))
            }
            elseif ($String -is [System.String]) {
                $String
            }
        }
        $fullConf = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $Script:ConfigScope -Verbose:$false
        $defaultConfigName = $fullConf['DefaultConfig']
        $choice = switch ($PSCmdlet.ParameterSetName) {
            Domain {
                Write-Verbose "Switching active domain to '$Domain'"
                $fullConf.Keys | Where-Object {(Decrypt $fullConf[$_]['Domain']) -eq $Domain}
            }
            ConfigName {
                Write-Verbose "Switching active config to '$ConfigName'"
                $fullConf.Keys | Where-Object {$_ -eq $ConfigName}
            }
        }
        if ($choice) {
            $script:PSGSuite = [PSCustomObject]($fullConf[$choice]) |
                Select-Object -Property @{l = 'ConfigName';e = {$choice}},
                                        @{l = 'P12KeyPath';e = {Decrypt $_.P12KeyPath}},
                                        @{l = 'ClientSecretsPath';e = {Decrypt $_.ClientSecretsPath}},
                                        @{l = 'AppEmail';e = {Decrypt $_.AppEmail}},
                                        @{l = 'AdminEmail';e = {Decrypt $_.AdminEmail}},
                                        @{l = 'CustomerID';e = {Decrypt $_.CustomerID}},
                                        @{l = 'Domain';e = {Decrypt $_.Domain}},
                                        @{l = 'Preference';e = {Decrypt $_.Preference}},
                                        @{l = 'ServiceAccountClientID';e = {Decrypt $_.ServiceAccountClientID}},
                                        @{l = 'Webhook';e = {
                                            $dict = @{}
                                            foreach ($key in $_.Webhook.Keys) {
                                                $dict[$key] = (Decrypt $_.Webhook[$key])
                                            }
                                            $dict
                                        }},
                                        ConfigPath
            if ($SetToDefault) {
                if ($defaultConfigName -ne $choice) {
                    Write-Verbose "Setting config name '$choice' for domain '$($script:PSGSuite.Domain)' as default"
                    Set-PSGSuiteConfig -ConfigName $choice -SetAsDefaultConfig -Verbose:$false
                    $env:PSGSuiteDefaultDomain = $script:PSGSuite.Domain
                    [Environment]::SetEnvironmentVariable("PSGSuiteDefaultDomain", $script:PSGSuite.Domain, "User")
                }
                else {
                    Write-Warning "Config name '$choice' for domain '$($script:PSGSuite.Domain)' is already set to default --- no action taken"
                }
            }
        }
        else {
            switch ($PSCmdlet.ParameterSetName) {
                Domain {
                    Write-Warning "No config found for domain '$Domain'! Retaining existing config for domain '$($script:PSGSuite.Domain)'"
                }
                ConfigName {
                    Write-Warning "No config named '$ConfigName' found! Retaining existing config '$($script:PSGSuite.ConfigName)'"
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Switch-PSGSuiteConfig'

Function Get-GSContactList {
    <#
    .SYNOPSIS
    Gets all contacts for the specified user
 
    .DESCRIPTION
    Gets all contacts for the specified user
 
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config.
 
    .EXAMPLE
    Get-GSContactList -User user@domain.com
 
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail", "UserKey", "Mail")]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $User = $Script:PSGSuite.AdminEmail
    )
    Process {
        foreach ($U in $User) {
            try {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                $Token = Get-GSToken -Scopes 'https://www.google.com/m8/feeds' -AdminEmail $U
                $Uri = "https://www.google.com/m8/feeds/contacts/$($U)/full?max-results=5000"
                $headers = @{
                    Authorization = "Bearer $($Token)"
                    'GData-Version' = '3.0'
                }
                Write-Verbose "Getting all contacts for user '$U'"
                $Raw = @()
                do {
                    $Response = Invoke-WebRequest -Method Get -Uri ([Uri]$Uri) -Headers $headers -ContentType 'application/xml' -Verbose:$false
                    $Feed = [xml]$Response.Content
                    $Raw += $feed.feed.entry
                    $Uri = $Feed.Feed.Link | Where-Object {$_.rel -eq "next"} | Select-Object -ExpandProperty Href
                    Write-Verbose "Retrieved $($Raw.Count) contacts..."
                }
                until (-not $Uri)
                If ($Raw) {
                    ForEach ($i in $Raw) {
                        [PSCustomObject]@{
                            User           = $U
                            Id             = ($i.id.Split("/")[-1])
                            Title          = $i.title
                            FullName       = $i.name.fullName
                            GivenName      = $i.name.givenName
                            FamilyName     = $i.name.familyName
                            EmailAddresses = $(if($i.email.address){$i.email.address}else{$null})
                            PhoneNumber    = $i.phonenumber
                            Updated        = $i.updated
                            Edited         = $i.edited.'#text'
                            Path           = $(if($i.email.rel){$i.email.rel}else{$null})
                            Etag           = $i.etag
                            FullObject     = $i
                        }
                    }
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSContactList'

Function Remove-GSContact {
    <#
    .SYNOPSIS
    Removes the specified contact
 
    .DESCRIPTION
    Removes the specified contact
 
    .PARAMETER ContactID
    The ContactID to be removed.
 
    .PARAMETER User
    The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config.
 
    .PARAMETER Etag
    The Etag string from a Get-GSContactList object. Used to ensure that no changes have been made to the contact since it was viewed in order to prevent data loss.
 
    Defaults to special Etag value *, which can be used to bypass this verification and process the update regardless of updates from other clients.
 
    .EXAMPLE
    Recommended to use Get-GSContactList to find and pipe desired contacts to Remove-GSContact:
 
    Get-GSContactList -User user@domain.com | Where-Object {"@baddomain.com" -match $_.EmailAddresses} | Remove-GSContact
 
    Removes all contacts for user@domain.com that have an email address on the baddomain.com domain.
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $false, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias("Id")]
        [string[]]
        $ContactId,
        [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail", "UserKey", "Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]
        $Etag = '*'
    )
    Process {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $Token = Get-GSToken -Scopes 'https://www.google.com/m8/feeds' -AdminEmail $User
        $headers = @{
            Authorization   = "Bearer $($Token)"
            'GData-Version' = '3.0'
            'If-Match'      = $Etag
        }
        foreach ($Id in $ContactID) {
            if ($PSCmdlet.ShouldProcess("Removing contact ID '$Id' for $User")) {
                Write-Verbose "Removing contact ID '$Id' for $User"
                try {
                    $Uri = "https://www.google.com/m8/feeds/contacts/$($User)/full/$($Id)"
                    $Response = Invoke-WebRequest -Method "Delete" -Uri ([Uri]$Uri) -Headers $headers -Verbose:$false
                    If ($Response.StatusCode -eq "200") {
                        Write-Verbose "Successfully deleted contact ID '$Id' for $User"
                    }
                    Else {
                        Write-Verbose "HTTP $($Response.StatusCode): $($Response.StatusDescription)"
                    }
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSContact'

function Get-GSDataTransferApplication {
    <#
    .SYNOPSIS
    Gets the list of available Data Transfer Applications and their parameters
     
    .DESCRIPTION
    Gets the list of available Data Transfer Applications and their parameters
     
    .PARAMETER ApplicationId
    The Application Id of the Data Transfer Application you would like to return info for specifically. Exclude to return the full list
     
    .PARAMETER PageSize
    PageSize of the result set.
 
    Defaults to 500 (although it's typically a much smaller number for most Customers)
     
    .EXAMPLE
    Get-GSDataTransferApplication
 
    Gets the list of available Data Transfer Applications
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [String[]]
        $ApplicationId,
        [parameter(Mandatory = $false)]
        [ValidateRange(1,500)]
        [Int]
        $PageSize = 500
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.datatransfer'
            ServiceType = 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($ApplicationId) {
                foreach ($I in $ApplicationId) {
                    $request = $service.Applications.Get($I)
                    $request.Execute()
                }
            }
            else {
                $request = $service.Applications.List()
                $request.CustomerId = $Script:PSGSuite.CustomerID
                if ($PageSize) {
                    $request.MaxResults = $PageSize
                }
                Write-Verbose "Getting all Data Transfer Applications"
                $response = @()
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    $response += $result.Applications
                    if ($result.NextPageToken) {
                        $request.PageToken = $result.NextPageToken
                    }
                    [int]$retrieved = ($i + $result.Applications.Count) - 1
                    Write-Verbose "Retrieved $retrieved Data Transfer Applications..."
                    [int]$i = $i + $result.Applications.Count
                }
                until (!$result.NextPageToken)
                return $response
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSDataTransferApplication'

function Start-GSDataTransfer {
    <#
    .SYNOPSIS
    Starts a Data Transfer from one user to another
 
    .DESCRIPTION
    Starts a Data Transfer from one user to another
 
    .PARAMETER OldOwnerUserId
    The email or unique Id of the owner you are transferring data *FROM*
 
    .PARAMETER NewOwnerUserId
    The email or unique Id of the owner you are transferring data *TO*
 
    .PARAMETER ApplicationId
    The application Id that you would like to transfer data for
 
    .PARAMETER PrivacyLevel
    The privacy level for the data you'd like to transfer
 
    Available values are:
    * "SHARED": all shared content owned by the user
    * "PRIVATE": all private (unshared) content owned by the user
 
    .EXAMPLE
    Start-GSDataTransfer -OldOwnerUserId joe -NewOwnerUserId mark -ApplicationId 55656082996 -PrivacyLevel SHARED,PRIVATE
 
    Transfers all of Joe's data to Mark
    #>

    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$true,Position=0)]
        [string]
        $OldOwnerUserId,
        [parameter(Mandatory=$true,Position=1)]
        [string]
        $NewOwnerUserId,
        [parameter(Mandatory=$true,Position=2,ValueFromPipelineByPropertyName=$true)]
        [alias("id")]
        [string]
        $ApplicationId,
        [parameter(Mandatory=$true)]
        [ValidateSet("SHARED","PRIVATE")]
        [string[]]
        $PrivacyLevel
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.datatransfer'
            ServiceType = 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService'
        }
        $service = New-GoogleService @serviceParams
        $OldOwnerUserId = try {
            [bigint]$OldOwnerUserId
        }
        catch {
            Write-Verbose "Resolving Old Owner's UserId from '$OldOwnerUserId'"
            [bigint](Get-GSUser -User $OldOwnerUserId -Projection Basic -Verbose:$false | Select-Object -ExpandProperty id)
        }
        $NewOwnerUserId = try {
            [bigint]$NewOwnerUserId
        }
        catch {
            Write-Verbose "Resolving New Owner's UserId from '$NewOwnerUserId'"
            [bigint](Get-GSUser -User $NewOwnerUserId -Projection Basic -Verbose:$false | Select-Object -ExpandProperty id)
        }
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.DataTransfer' -Property @{
                OldOwnerUserId = $OldOwnerUserId
                NewOwnerUserId = $NewOwnerUserId
            }
            $AppDataTransfers = New-Object 'Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationDataTransfer' -Property @{
                ApplicationId = $ApplicationId
            }
            if ($PrivacyLevel) {
                $AppDataTransfers.ApplicationTransferParams = [Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationTransferParam[]](New-Object 'Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationTransferParam' -Property @{
                    Key = 'PRIVACY_LEVEL'
                    Value = [String[]]$PrivacyLevel
                })
            }
            $body.ApplicationDataTransfers = [Google.Apis.Admin.DataTransfer.datatransfer_v1.Data.ApplicationDataTransfer[]]$AppDataTransfers
            $request = $service.Transfers.Insert($body)
            Write-Verbose "Starting Data Transfer from User Id '$OldOwnerUserId' to User Id '$NewOwnerUserId'"
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Start-GSDataTransfer'

function Add-GSGmailDelegate {
    <#
    .SYNOPSIS
    Adds a delegate with its verification status set directly to accepted, without sending any verification email. The delegate user must be a member of the same G Suite organization as the delegator user.
 
    .DESCRIPTION
    Adds a delegate with its verification status set directly to accepted, without sending any verification email. The delegate user must be a member of the same G Suite organization as the delegator user.
 
    Gmail imposes limtations on the number of delegates and delegators each user in a G Suite organization can have. These limits depend on your organization, but in general each user can have up to 25 delegates and up to 10 delegators.
 
    Note that a delegate user must be referred to by their primary email address, and not an email alias.
 
    Also note that when a new delegate is created, there may be up to a one minute delay before the new delegate is available for use.
 
    .PARAMETER User
    User's email address to delegate access to.
 
    .PARAMETER Delegate
    Delegate's email address to receive delegate access.
 
    .EXAMPLE
    Add-GSGmailDelegate -User tony@domain.com -Delegate peter@domain.com
 
    Provide Peter delegate access to Tony's inbox.
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [Alias("From","Delegator")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User,
        [parameter(Mandatory = $true,Position = 1)]
        [Alias("To")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Delegate
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        if ($Delegate -notlike "*@*.*") {
            $Delegate = "$($Delegate)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.sharing'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Adding delegate access to user '$User's inbox for delegate '$Delegate'"
            $body = New-Object 'Google.Apis.Gmail.v1.Data.Delegate' -Property @{
                DelegateEmail = $Delegate
            }
            $request = $service.Users.Settings.Delegates.Create($body,$User)
            $request.Execute()
        }
        catch {
            $origError = $_
            if ($group = Get-GSGroup -Group $User -Verbose:$false -ErrorAction SilentlyContinue) {
                Write-Warning "$User is a group email, not a user account. You can only manage delegate access for a user's inbox. Please add $Delegate to the group $User instead."
            }
            elseif ($group = Get-GSGroup -Group $Delegate -Verbose:$false -ErrorAction SilentlyContinue) {
                Write-Warning "$Delegate is a group email, not a user account. You can only delegate access to other users."
            }
            else {
                $dele = Get-GSGmailDelegates -User $User -NoGroupCheck -ErrorAction SilentlyContinue -Verbose:$false
                if ($dele.DelegateEmail -contains $Delegate -and $dele.VerificationStatus -eq 'accepted') {
                    Write-Warning "'$Delegate' already has delegate access to user '$User's inbox. No action needed."
                }
                elseif ($dele.DelegateEmail -contains $Delegate -and $dele.VerificationStatus -ne 'accepted') {
                    Write-Warning "$Delegate was already invited for delegated access to user '$User's inbox, but VerificationStatus is currently '$($dele.VerificationStatus)'"
                }
                else {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($origError)
                    }
                    else {
                        Write-Error $origError
                    }
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Add-GSGmailDelegate'

function Get-GSGmailDelegate {
    <#
    .SYNOPSIS
    Gets delegates for the specified account.
 
    .DESCRIPTION
    Gets delegates for the specified account.
 
    .PARAMETER User
    User's email to get delegates for.
 
    .PARAMETER Delegate
    The specific delegate to get. If excluded returns the list of delegates for the user.
 
    .PARAMETER NoGroupCheck
    By default, this will check if the User email is a group email which cannot be delegated if the attempt to delegate access fails.
 
    Include this switch to prevent the group check and return the original error.
 
    .EXAMPLE
    Get-GSGmailDelegate -User tony@domain.com
 
    Gets the list of users who have delegate access to Tony's inbox.
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [Alias("From","Delegator")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,Position = 1)]
        [Alias("To")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Delegate,
        [parameter(Mandatory = $false)]
        [switch]
        $NoGroupCheck
    )
    Process {
        foreach ($U in $User) {
            if ($U -ceq 'me') {
                $U = $Script:PSGSuite.AdminEmail
            }
            elseif ($U -notlike "*@*.*") {
                $U = "$($U)@$($Script:PSGSuite.Domain)"
            }
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
                ServiceType = 'Google.Apis.Gmail.v1.GmailService'
                User        = $U
            }
            $service = New-GoogleService @serviceParams
            if ($PSBoundParameters.Keys -contains 'Delegate') {
                try {
                    Write-Verbose "Getting Gmail Delegate '$Delegate' for user '$U'"
                    $request = $service.Users.Settings.Delegates.Get($U,$Delegate)
                    $request.Execute()
                }
                catch {
                    $origError = $_
                    if (!$NoGroupCheck -and ($group = Get-GSGroup -Group $U -Verbose:$false -ErrorAction SilentlyContinue)) {
                        Write-Warning "$U is a group, not a user. You can only manage delegates for a user."
                    }
                    else {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($origError)
                        }
                        else {
                            Write-Error $origError
                        }
                    }
                }
            }
            else {
                try {
                    Write-Verbose "Getting Gmail Delegate list for user '$U'"
                    $request = $service.Users.Settings.Delegates.List($U)
                    $res = $request.Execute()
                    if ($res.Delegates) {
                        $res.Delegates | Add-Member -MemberType NoteProperty -Name Delegator -Value $U -Force -PassThru
                    }
                    else {
                        Write-Warning "No delegates found for user '$U'"
                    }
                }
                catch {
                    $origError = $_
                    if (!$NoGroupCheck -and ($group = Get-GSGroup -Group $U -Verbose:$false -ErrorAction SilentlyContinue)) {
                        Write-Warning "$U is a group, not a user. You can only manage delegates for a user."
                    }
                    else {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($origError)
                        }
                        else {
                            Write-Error $origError
                        }
                    }
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSGmailDelegate'

function Remove-GSGmailDelegate {
    <#
    .SYNOPSIS
    Removes the specified delegate (which can be of any verification status), and revokes any verification that may have been required for using it.
 
    .DESCRIPTION
    Removes the specified delegate (which can be of any verification status), and revokes any verification that may have been required for using it.
 
    Note that a delegate user must be referred to by their primary email address, and not an email alias.
 
    .PARAMETER User
    User's email address to remove delegate access to
 
    .PARAMETER Delegate
    Delegate's email address to remove
 
    .EXAMPLE
    Remove-GSGmailDelegate -User tony@domain.com -Delegate peter@domain.com
 
    Removes Peter's access to Tony's inbox.
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [Alias("From","Delegator")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User,
        [parameter(Mandatory = $true,Position = 1)]
        [Alias("To")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Delegate
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        if ($Delegate -notlike "*@*.*") {
            $Delegate = "$($Delegate)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.sharing'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        if ($PSCmdlet.ShouldProcess("Removing delegate access for '$Delegate' from user '$User's inbox")) {
            try {
                Write-Verbose "Removing delegate access for '$Delegate' from user '$User's inbox"
                $request = $service.Users.Settings.Delegates.Delete($User,$Delegate)
                $request.Execute()
                Write-Verbose "Successfully removed delegate access for user '$User's inbox for delegate '$Delegate'"
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($origError)
                }
                else {
                    Write-Error $origError
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSGmailDelegate'

function Add-GSDocContent {
    <#
    .SYNOPSIS
    Adds content to a Google Doc via appending new text. This does not overwrite existing content
     
    .DESCRIPTION
    Adds content to a Google Doc via appending new text. This does not overwrite existing content
     
    .PARAMETER FileID
    The unique Id of the file to add content to
     
    .PARAMETER Value
    The content to add
     
    .PARAMETER User
    The email or unique Id of the owner of the Drive file
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    $newLogStrings | Add-GSDocContent -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976'
 
    Appends the strings in the $newLogStrings variable to the existing the content of the specified Google Doc.
    #>

    [CmdLetBinding()]
    Param
    (      
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $FileID,
        [parameter(Mandatory = $true,ValueFromPipeline = $true)]
        [String[]]
        $Value,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
        $stream = New-Object 'System.IO.MemoryStream'
        $writer = New-Object 'System.IO.StreamWriter' $stream
        $currentContent = Get-GSDocContent -FileID $FileID -User $User -Verbose:$false
        $concatStrings = @($currentContent)
    }
    Process {
        foreach ($string in $Value) {
            $concatStrings += $string
        }
    }
    End {
        try {
            $concatStrings = $concatStrings -join "`n"
            $writer.Write($concatStrings)
            $writer.Flush()
            $contentType = 'text/plain'
            $body = New-Object 'Google.Apis.Drive.v3.Data.File'
            $request = $service.Files.Update($body,$FileId,$stream,$contentType)
            $request.QuotaUser = $User
            $request.ChunkSize = 512KB
            $request.SupportsTeamDrives = $true
            Write-Verbose "Adding content to File '$FileID'"
            $request.Upload() | Out-Null
            $stream.Close()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSDocContent'

function Add-GSDrivePermission {
    <#
    .SYNOPSIS
    Adds a new permission to a Drive file
 
    .DESCRIPTION
    Adds a new permission to a Drive file
 
    .PARAMETER User
    The owner of the Drive file
 
    Defaults to the AdminEmail user
 
    .PARAMETER FileId
    The unique Id of the Drive file you would like to add the permission to
 
    .PARAMETER Role
    The role/permission set you would like to give the email $EmailAddress
 
    Available values are:
    * "Owner"
    * "Writer"
    * "Commenter"
    * "Reader"
    * "Organizer"
 
    .PARAMETER Type
    The type of the grantee
 
    Available values are:
    * "User": a user email
    * "Group": a group email
    * "Domain": the entire domain
    * "Anyone": public access
 
    .PARAMETER EmailAddress
    The email address of the user or group to which this permission refers
 
    .PARAMETER Domain
    The domain to which this permission refers
 
    .PARAMETER ExpirationTime
    The time at which this permission will expire.
 
    Expiration times have the following restrictions:
    * They can only be set on user and group permissions
    * The time must be in the future
    * The time cannot be more than a year in the future
 
    .PARAMETER EmailMessage
    A plain text custom message to include in the notification email
 
    .PARAMETER SendNotificationEmail
    Whether to send a notification email when sharing to users or groups.
 
    This defaults to **FALSE** for users and groups in PSGSuite, and is not allowed for other requests.
 
    **It must not be disabled for ownership transfers**
 
    .PARAMETER AllowFileDiscovery
    Whether the permission allows the file to be discovered through search.
 
    This is only applicable for permissions of type domain or anyone
 
    .PARAMETER TransferOwnership
    Confirms transfer of ownership if the Role is set to 'Owner'. You can also force the same behavior by passing -Confirm:$false instead
 
    .PARAMETER UseDomainAdminAccess
    Whether the request should be treated as if it was issued by a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the item belongs
 
    .EXAMPLE
    Add-GSDrivePermission -FileId "1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976" -Role Owner -Type User -EmailAddress joe -SendNotificationEmail -Confirm:$false
 
    Adds user joe@domain.com as the new owner of the file Id and sets the AdminEmail user as a Writer on the file
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High",DefaultParameterSetName = "Email")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $true)]
        [String]
        $FileId,
        [parameter(Mandatory = $true)]
        [ValidateSet("Owner","Writer","Commenter","Reader","Organizer")]
        [String]
        $Role,
        [parameter(Mandatory = $true)]
        [ValidateSet("User","Group","Domain","Anyone")]
        [String]
        $Type,
        [parameter(Mandatory = $false,ParameterSetName = "Email")]
        [String]
        $EmailAddress,
        [parameter(Mandatory = $false,ParameterSetName = "Domain")]
        [String]
        $Domain,
        [parameter(Mandatory = $false)]
        [DateTime]
        $ExpirationTime,
        [parameter(Mandatory = $false)]
        [string]
        $EmailMessage,
        [parameter(Mandatory = $false)]
        [Switch]
        $SendNotificationEmail,
        [parameter(Mandatory = $false)]
        [Switch]
        $AllowFileDiscovery,
        [parameter(Mandatory = $false)]
        [Alias('ConfirmTransferOfOwnership','TransferOfOwnership')]
        [switch]
        $TransferOwnership,
        [parameter(Mandatory = $false)]
        [switch]
        $UseDomainAdminAccess
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($Role -eq "Owner" -and !$TransferOwnership) {
                if ($PSCmdlet.ShouldProcess("Confirm transfer of ownership of FileId '$FileID' from user '$User' to user '$EmailAddress'")) {
                    $PSBoundParameters['TransferOwnership'] = $true
                    $TransferOwnership = $true
                }
                else {
                    throw "The TransferOwnership parameter is required when setting the 'Owner' role."
                }
            }
            if (($Type -eq "User" -or $Type -eq "Group") -and !$EmailAddress) {
                throw "The EmailAddress parameter is required for types 'User' or 'Group'."
            }
            if (($Type -eq "User" -or $Type -eq "Group") -and ($PSBoundParameters.Keys -contains 'AllowFileDiscovery')) {
                Write-Warning "The AllowFileDiscovery parameter is only applicable for types 'Domain' or 'Anyone' This parameter will be excluded from this request."
                $PSBoundParameters.Remove('AllowFileDiscovery') | Out-Null
            }
            if ($TransferOwnership -and !$SendNotificationEmail) {
                $PSBoundParameters['SendNotificationEmail'] = $true
                Write-Warning "Setting SendNotificationEmail to 'True' to prevent errors (required for Ownership transfers)"
            }
            $body = New-Object 'Google.Apis.Drive.v3.Data.Permission'
            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    EmailAddress {
                        if ($EmailAddress -ceq 'me') {
                            $EmailAddress = $Script:PSGSuite.AdminEmail
                        }
                        elseif ($EmailAddress -notlike "*@*.*") {
                            $EmailAddress = "$($EmailAddress)@$($Script:PSGSuite.Domain)"
                        }
                        $body.EmailAddress = $EmailAddress
                    }
                    Role {
                        $body.$key = ($PSBoundParameters[$key]).ToLower()
                    }
                    Type {
                        $body.$key = ($PSBoundParameters[$key]).ToLower()
                    }
                    Default {
                        if ($body.PSObject.Properties.Name -contains $key) {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $request = $service.Permissions.Create($body,$FileId)
            $request.SupportsTeamDrives = $true
            foreach ($key in $PSBoundParameters.Keys) {
                if ($request.PSObject.Properties.Name -contains $key -and $key -ne 'FileId') {
                    $request.$key = $PSBoundParameters[$key]
                }
            }
            if ($PSBoundParameters.Keys -notcontains 'SendNotificationEmail') {
                $request.SendNotificationEmail = $false
            }
            Write-Verbose "Adding Drive Permission of '$Role' for user '$EmailAddress' on Id '$FileID'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Add-GSDrivePermission'

function Copy-GSDriveFile {
    <#
    .SYNOPSIS
    Make a copy of a file in Drive
 
    .DESCRIPTION
    Make a copy of a file in Drive
 
    .PARAMETER FileID
    The unique Id of the file to copy
 
    .PARAMETER User
    The email or unique Id of the owner of the Drive file
 
    Defaults to the AdminEmail user
 
    .PARAMETER Name
    The name of the new Drive file copy
 
    .PARAMETER Description
    The description of the new Drive file copy
 
    .PARAMETER Parents
    The parent Ids of the new Drive file copy
 
    .PARAMETER Projection
    The defined subset of fields to be returned
 
    Available values are:
    * "Minimal"
    * "Standard"
    * "Full"
    * "Access"
 
    .PARAMETER Fields
    The specific fields to returned
 
    .EXAMPLE
    Copy-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -Name "New Daily Checklist"
 
    Copies the Drive file Id to a new Drive file named 'New Daily Checklist'
    #>

    [cmdletbinding(DefaultParameterSetName = "Depth")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $FileID,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [String[]]
        $Parents,
        [parameter(Mandatory = $false,ParameterSetName = "Depth")]
        [Alias('Depth')]
        [ValidateSet("Minimal","Standard","Full","Access")]
        [String]
        $Projection = "Full",
        [parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")]
        [String[]]
        $Fields
    )
    Begin {
        if ($Projection) {
            $fs = switch ($Projection) {
                Standard {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink")
                }
                Access {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare")
                }
                Full {
                    @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")
                }
            }
        }
        elseif ($Fields) {
            $fs = $Fields
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Drive.v3.Data.File'
            if ($Name) {
                $body.Name = $Name
            }
            if ($Description) {
                $body.Description = $Description
            }
            if ($Parents) {
                $body.Parents = [String[]]$Parents
            }
            $request = $service.Files.Copy($body,$FileID)
            $request.SupportsTeamDrives = $true
            if ($fs) {
                $request.Fields = "$($fs -join ",")"
            }
            Write-Verbose "Copying drive file id '$FileID'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Copy-GSDriveFile'

function Export-GSDriveFile {
    <#
    .SYNOPSIS
    Exports a Drive file as if you chose "Export" from the File menu when viewing the file
 
    .DESCRIPTION
    Exports a Drive file as if you chose "Export" from the File menu when viewing the file
 
    .PARAMETER FileID
    The unique Id of the file to export
 
    .PARAMETER User
    The email or unique Id of the owner of the Drive file
 
    Defaults to the AdminEmail user
 
    .PARAMETER Type
    The type of local file you would like to export the Drive file as
 
    Available values are:
    * "CSV"
    * "HTML"
    * "JPEG"
    * "JSON"
    * "MSExcel"
    * "MSPowerPoint"
    * "MSWordDoc"
    * "OpenOfficeDoc"
    * "OpenOfficeSheet"
    * "PDF"
    * "PlainText"
    * "PNG"
    * "RichText"
    * "SVG"
 
    .PARAMETER OutFilePath
    The directory path that you would like to export the Drive file to
 
    Defaults to the current working directory
 
    .PARAMETER Projection
    The defined subset of fields to be returned
 
    Available values are:
    * "Minimal"
    * "Standard"
    * "Full"
    * "Access"
 
    .PARAMETER Fields
    The specific fields to returned
 
    .EXAMPLE
    Export-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -Type CSV -OutFilePath .\SheetExport.csv
 
    Exports the Drive file as a CSV to the current working directory
    #>

    [CmdLetBinding(DefaultParameterSetName = "Depth")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $FileID,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $true)]
        [ValidateSet("CSV","EPUB","HTML","HTMLZipped","JPEG","JSON","MSExcel","MSPowerPoint","MSWordDoc","OpenOfficeDoc","OpenOfficePresentation","OpenOfficeSheet","PDF","PlainText","PNG","RichText","SVG","TSV")]
        [String]
        $Type,
        [parameter(Mandatory = $false)]
        [String]
        $OutFilePath,
        [parameter(Mandatory = $false,ParameterSetName = "Depth")]
        [Alias('Depth')]
        [ValidateSet("Minimal","Standard","Full","Access")]
        [String]
        $Projection = "Full",
        [parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")]
        [String[]]
        $Fields,
        [parameter(Mandatory = $false)]
        [Switch]
        $Force
    )
    Begin {
        if ($Projection) {
            $fs = switch ($Projection) {
                Standard {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink")
                }
                Access {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare")
                }
                Full {
                    @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")
                }
            }
        }
        elseif ($Fields) {
            $fs = $Fields
        }
        $mimeHash = @{
            CSV                    = "text/csv"
            EPUB                   = "application/epub+zip"
            HTML                   = "text/html"
            HTMLZipped             = "application/zip"
            JPEG                   = "image/jpeg"
            JSON                   = "application/vnd.google-apps.script+json"
            MSExcel                = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            MSPowerPoint           = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
            MSWordDoc              = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
            OpenOfficeDoc          = "application/vnd.oasis.opendocument.text"
            OpenOfficePresentation = "application/vnd.oasis.opendocument.presentation"
            OpenOfficeSheet        = "application/x-vnd.oasis.opendocument.spreadsheet"
            PDF                    = "application/pdf"
            PlainText              = "text/plain"
            PNG                    = "image/png"
            RichText               = "application/rtf"
            SVG                    = "image/svg+xml"
            TSV                    = "text/tab-separated-values"
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $request = $service.Files.Export($FileID,($mimeHash[$Type]))
            if ($fs) {
                $request.Fields = $($fs -join ",")
            }
            if ($OutFilePath) {
                if ((Test-Path $OutFilePath) -and !$Force) {
                    throw "File '$OutFilePath' already exists. If you would like to overwrite it, use the -Force parameter."
                }
                else {
                    Write-Verbose "Saving file to path '$OutFilePath'"
                    $stream = [System.IO.File]::Create($OutFilePath)
                    $request.Download($stream)
                    $stream.Close()
                }
            }
            else {
                Write-Verbose "Getting content of File '$FileID' as Type '$Type'"
                $request.Execute()
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Export-GSDriveFile'

function Get-GSDocContent {
    <#
    .SYNOPSIS
    Gets the content of a Google Doc and returns it as an array of strings. Supports HTML or PlainText
     
    .DESCRIPTION
    Gets the content of a Google Doc and returns it as an array of strings. Supports HTML or PlainText
     
    .PARAMETER FileID
    The unique Id of the file to get content of
     
    .PARAMETER User
    The email or unique Id of the owner of the Drive file
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSDocContent -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976'
 
    Exports the Drive file as a CSV to the current working directory
    #>

    [CmdLetBinding()]
    Param
    (      
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $FileID,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [ValidateSet("HTML","PlainText")]
        [String]
        $Type
    )
    Begin {
        $typeParam = @{}
        if ($PSBoundParameters.Keys -notcontains 'Type') {
            $typeParam['Type'] = "PlainText"
        }
    }
    Process {
        try {
            (Export-GSDriveFile @PSBoundParameters -Projection Minimal @typeParam) -split "`n"
            Write-Verbose "Content retrieved for File '$FileID'"
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSDocContent'

function Get-GSDriveFile {
    <#
    .SYNOPSIS
    Gets information about or downloads a Drive file
 
    .DESCRIPTION
    Gets information about or downloads a Drive file
 
    .PARAMETER FileId
    The unique Id of the file to get
 
    .PARAMETER User
    The email or unique Id of the owner of the Drive file
 
    Defaults to the AdminEmail user
 
    .PARAMETER OutFilePath
    The directory path that you would like to download the Drive file to. If excluded, only the Drive file information will be returned
 
    .PARAMETER Projection
    The defined subset of fields to be returned
 
    Available values are:
    * "Minimal"
    * "Standard"
    * "Full"
    * "Access"
 
    .PARAMETER Fields
    The specific fields to returned
 
    .EXAMPLE
    Get-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976'
 
    Gets the information for the file
 
    .EXAMPLE
    Get-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -OutFilePath (Get-Location).Path
 
    Gets the information for the file and saves the file in the current working directory
    #>

    [cmdletbinding(DefaultParameterSetName = "Depth")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String[]]
        $FileId,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [Alias('SaveFileTo')]
        [ValidateScript({(Get-Item $_).PSIsContainer})]
        [String]
        $OutFilePath,
        [parameter(Mandatory = $false,ParameterSetName = "Depth")]
        [Alias('Depth')]
        [ValidateSet("Minimal","Standard","Full","Access")]
        [String]
        $Projection = "Full",
        [parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")]
        [String[]]
        $Fields
    )
    Begin {
        if ($Projection) {
            $fs = switch ($Projection) {
                Standard {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink")
                }
                Access {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare")
                }
                Full {
                    @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")
                }
            }
        }
        elseif ($Fields) {
            $fs = $Fields
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($file in $FileId) {
                $request = $service.Files.Get($file)
                $request.SupportsTeamDrives = $true
                if ($fs) {
                    $request.Fields = $($fs -join ",")
                }
                $res = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                if ($OutFilePath -and $res.FileExtension) {
                    $resPath = Resolve-Path $OutFilePath
                    $filePath = Join-Path $resPath "$($res.Name).$($res.FileExtension)"
                    Write-Verbose "Saving file to path '$filePath'"
                    $stream = [System.IO.File]::Create($filePath)
                    $request.Download($stream)
                    $stream.Close()
                }
                $res
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSDriveFile'

function Get-GSDriveFileList {
    <#
    .SYNOPSIS
    Gets the list of Drive files owned by the user
     
    .DESCRIPTION
    Gets the list of Drive files owned by the user
     
    .PARAMETER User
    The email or unique Id of the user whose Drive files you are trying to list
 
    Defaults to the AdminEmail user
     
    .PARAMETER Filter
    A query for filtering the file results. See the "Search for Files and Team Drives" guide for the supported syntax: https://developers.google.com/drive/v3/web/search-parameters
 
    PowerShell filter syntax here is supported as "best effort". Please use Google's filter operators and syntax to ensure best results
     
    .PARAMETER TeamDriveId
    ID of Team Drive to search
     
    .PARAMETER ParentFolderId
    ID of parent folder to search to add to the filter
     
    .PARAMETER IncludeTeamDriveItems
    Whether Team Drive items should be included in results. (Default: false)
     
    .PARAMETER Corpora
    Comma-separated list of bodies of items (files/documents) to which the query applies. Supported bodies are 'User', 'Domain', 'TeamDrive' and 'AllTeamDrives'. 'AllTeamDrives' must be combined with 'User'; all other values must be used in isolation. Prefer 'User' or 'TeamDrive' to 'AllTeamDrives' for efficiency.
     
    .PARAMETER Spaces
    A comma-separated list of spaces to query within the corpus. Supported values are 'Drive', 'AppDataFolder' and 'Photos'.
     
    .PARAMETER OrderBy
    A comma-separated list of sort keys. Valid keys are 'createdTime', 'folder', 'modifiedByMeTime', 'modifiedTime', 'name', 'name_natural', 'quotaBytesUsed', 'recency', 'sharedWithMeTime', 'starred', and 'viewedByMeTime'.
     
    .PARAMETER PageSize
    The page size of the result set
     
    .EXAMPLE
    Get-GSDriveFileList joe
 
    Gets Joe's Drive file list
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [Alias('Q','Query')]
        [String[]]
        $Filter,
        [parameter(Mandatory = $false)]
        [String]
        $TeamDriveId,
        [parameter(Mandatory = $false)]
        [String]
        $ParentFolderId,
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeTeamDriveItems,
        [parameter(Mandatory = $false)]
        [ValidateSet('user','domain','teamDrive')]
        [String]
        $Corpora,
        [parameter(Mandatory = $false)]
        [ValidateSet('drive','appDataFolder','photos')]
        [String[]]
        $Spaces,
        [parameter(Mandatory = $false)]
        [ValidateSet('createdTime','folder','modifiedByMeTime','modifiedTime','name','quotaBytesUsed','recency','sharedWithMeTime','starred','viewedByMeTime')]
        [String[]]
        $OrderBy,
        [parameter(Mandatory = $false)]
        [Alias('MaxResults')]
        [ValidateRange(1,1000)]
        [Int]
        $PageSize = "1000"
    )
    Begin {
        if ($TeamDriveId) {
            $PSBoundParameters['Corpora'] = 'teamDrive'
            $PSBoundParameters['IncludeTeamDriveItems'] = $true
        }
        if ($ParentFolderId) {
            if ($Filter) {
                $Filter += "'$ParentFolderId' in parents"
                $PSBoundParameters['Filter'] += "'$ParentFolderId' in parents"
            }
            else {
                $Filter = "'$ParentFolderId' in parents"
                $PSBoundParameters['Filter'] = "'$ParentFolderId' in parents"
            }
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $request = $service.Files.List()
            $request.SupportsTeamDrives = $true
            $request.Fields = 'files,kind,nextPageToken'
            if ($PageSize) {
                $request.PageSize = $PageSize
            }
            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    Filter {
                        $FilterFmt = $PSBoundParameters[$key] -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False" -replace " -in "," in " -replace " -le ",'<=' -replace " -ge ",">=" -replace " -gt ",'>' -replace " -lt ",'<' -replace " -ne ","!=" -replace " -and "," and " -replace " -or "," or " -replace " -not "," not "
                        $request.Q = $($FilterFmt -join " ")
                    }
                    Spaces {
                        $request.$key = $($PSBoundParameters[$key] -join ",")
                    }
                    Default {
                        if ($request.PSObject.Properties.Name -contains $key) {
                            $request.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            if ($Filter) {
                Write-Verbose "Getting all Drive Files matching filter '$Filter' for user '$User'"
            }
            else {
                Write-Verbose "Getting all Drive Files for user '$User'"
            }
            [int]$i = 1
            do {
                $result = $request.Execute()
                $result.Files | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                if ($result.NextPageToken) {
                    $request.PageToken = $result.NextPageToken
                }
                [int]$retrieved = ($i + $result.Files.Count) - 1
                Write-Verbose "Retrieved $retrieved Files..."
                [int]$i = $i + $result.Files.Count
            }
            until (!$result.NextPageToken)
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSDriveFileList'

function Get-GSDriveFileUploadStatus {
    <#
    .SYNOPSIS
    Gets the current Drive file upload status
     
    .DESCRIPTION
    Gets the current Drive file upload status
     
    .PARAMETER Id
    The upload Id for the task you'd like to retrieve the status of
     
    .PARAMETER InProgress
    If passed, only returns upload statuses that are not 'Failed' or 'Completed'. If nothing is returned when passing this parameter, all tracked uploads have stopped
     
    .EXAMPLE
    Get-GSDriveFileUploadStatus -InProgress
 
    Gets the upload status for all tasks currently in progress
    #>

    [CmdletBinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Int[]]
        $Id,
        [parameter(Mandatory = $false)]
        [Switch]
        $InProgress
    )
    Begin {
        Write-Verbose "Getting Drive File Upload status"
    }
    Process {
        if ($script:DriveUploadTasks) {
            foreach ($task in $script:DriveUploadTasks) {
                $elapsed = ((Get-Date) - $task.StartTime)
                $progress = {$task.Request.GetProgress()}.InvokeReturnAsIs()
                $bytesSent = $progress.BytesSent
                $remaining = try {
                    New-TimeSpan -Seconds $(($elapsed.TotalSeconds / ($bytesSent / ($task.Length))) - $elapsed.TotalSeconds) -ErrorAction Stop
                }
                catch {
                    New-TimeSpan
                }
                $percentComplete = if ($bytesSent) {
                    [Math]::Round((($bytesSent / $task.Length) * 100),4)
                }
                else {
                    0
                }
                if ($Id) {
                    if ($Id -contains $task.Id) {
                        $obj = [PSCustomObject]@{
                            Id = $task.Id
                            Status = $progress.Status
                            PercentComplete = $percentComplete
                            Remaining = $remaining
                            StartTime = $task.StartTime
                            Elapsed = $elapsed
                            File = $task.File.FullName
                            Length = $task.Length
                            Parents = $task.Parents
                            BytesSent = $bytesSent
                            FileLocked = $(Test-FileLock -Path $task.File)
                            User = $task.User
                            Exception = $progress.Exception
                        }
                        if (!$InProgress -or $obj.Status -notin @('Failed','Completed')) {
                            $obj
                        }
                    }
                }
                else {
                    $obj = [PSCustomObject]@{
                        Id = $task.Id
                        Status = $progress.Status
                        PercentComplete = $percentComplete
                        Remaining = $remaining
                        StartTime = $task.StartTime
                        Elapsed = $elapsed
                        File = $task.File.FullName
                        Length = $task.Length
                        Parents = $task.Parents
                        BytesSent = $bytesSent
                        FileLocked = $(Test-FileLock -Path $task.File)
                        User = $task.User
                        Exception = $progress.Exception
                    }
                    if (!$InProgress -or $obj.Status -notin @('Failed','Completed')) {
                        $obj
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSDriveFileUploadStatus'

function Get-GSDrivePermission {
    <#
    .SYNOPSIS
    Gets permission information for a Drive file
     
    .DESCRIPTION
    Gets permission information for a Drive file
     
    .PARAMETER User
    The email or unique Id of the user whose Drive file permission you are trying to get
 
    Defaults to the AdminEmail user
     
    .PARAMETER FileId
    The unique Id of the Drive file
     
    .PARAMETER PermissionId
    The unique Id of the permission you are trying to get. If excluded, the list of permissions for the Drive file will be returned instead
     
    .PARAMETER PageSize
    The page size of the result set
     
    .EXAMPLE
    Get-GSDrivePermission -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976'
 
    Gets the list of permissions for the file Id
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail, 
        [parameter(Mandatory = $true)]
        [String]
        $FileId, 
        [parameter(Mandatory = $false,ParameterSetName = "Get")]
        [String[]]
        $PermissionId,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Alias('MaxResults')]
        [ValidateRange(1,100)]
        [Int]
        $PageSize = "100"
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($PermissionId) {
                foreach ($per in $PermissionId) {
                    $request = $service.Permissions.Get($FileId,$per)
                    $request.SupportsTeamDrives = $true
                    $request.Fields = "*"
                    Write-Verbose "Getting Permission Id '$per' on File '$FileId' for user '$User'"
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'FileId' -Value $FileId -PassThru
                }
            }
            else {
                $request = $service.Permissions.List($FileId)
                $request.SupportsTeamDrives = $true
                $request.PageSize = $PageSize
                $request.Fields = "*"
                Write-Verbose "Getting Permission list on File '$FileId' for user '$User'"
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    $result.Permissions | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'FileId' -Value $FileId -PassThru
                    if ($result.NextPageToken) {
                        $request.PageToken = $result.NextPageToken
                    }
                    [int]$retrieved = ($i + $result.Permissions.Count) - 1
                    Write-Verbose "Retrieved $retrieved Permissions..."
                    [int]$i = $i + $result.Permissions.Count
                }
                until (!$result.NextPageToken)
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSDrivePermission'

function Get-GSDriveProfile {
    <#
    .SYNOPSIS
    Gets Drive profile for the user
     
    .DESCRIPTION
    Gets Drive profile for the user
     
    .PARAMETER User
    The user to get profile of
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSDriveProfile
 
    Gets the Drive profile of the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,Position = 1)]
        [ValidateSet('AppInstalled','ExportFormats','FolderColorPalette','ImportFormats','Kind','MaxImportSizes','MaxUploadSize','StorageQuota','TeamDriveThemes','User')]
        [string[]]
        $Fields = @('AppInstalled','ExportFormats','FolderColorPalette','ImportFormats','Kind','MaxImportSizes','MaxUploadSize','StorageQuota','TeamDriveThemes','User')
    )
    Begin {
        $fieldDict = @{
            AppInstalled = 'appInstalled'
            ExportFormats = 'exportFormats'
            FolderColorPalette = 'folderColorPalette'
            ImportFormats = 'importFormats'
            Kind = 'kind'
            MaxImportSizes = 'maxImportSizes'
            MaxUploadSize = 'maxUploadSize'
            StorageQuota = 'storageQuota'
            TeamDriveThemes = 'teamDriveThemes'
            User = 'user'
        }
    }
    Process {
        foreach ($U in $User) {
            if ($U -ceq 'me') {
                $U = $Script:PSGSuite.AdminEmail
            }
            elseif ($U -notlike "*@*.*") {
                $U = "$($U)@$($Script:PSGSuite.Domain)"
            }
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/drive'
                ServiceType = 'Google.Apis.Drive.v3.DriveService'
                User        = $U
            }
            $service = New-GoogleService @serviceParams
            try {
                $request = $service.About.Get()
                $request.Fields = "$(($Fields | ForEach-Object {$fieldDict[$_]}) -join ",")"
                Write-Verbose "Getting Drive profile for user '$U'"
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSDriveProfile'

function Get-GSTeamDrive {
    <#
    .SYNOPSIS
    Gets information about a Team Drive
     
    .DESCRIPTION
    Gets information about a Team Drive
     
    .PARAMETER TeamDriveId
    The unique Id of the Team Drive. If excluded, the list of Team Drives will be returned
     
    .PARAMETER User
    The email or unique Id of the user with access to the Team Drive
     
    .PARAMETER Filter
    Query string for searching Team Drives. See the "Search for Files and Team Drives" guide for the supported syntax: https://developers.google.com/drive/v3/web/search-parameters
 
    PowerShell filter syntax here is supported as "best effort". Please use Google's filter operators and syntax to ensure best results
     
    .PARAMETER PageSize
    The page size of the result set
     
    .EXAMPLE
     
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")]
        [Alias('Id')]
        [String[]]
        $TeamDriveId,
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Alias('Q','Query')]
        [String]
        $Filter,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateRange(1,100)]
        [Int]
        $PageSize = "100"
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Get {
                    foreach ($id in $TeamDriveId) {
                        $request = $service.Teamdrives.Get($id)
                        Write-Verbose "Getting Team Drive '$id' for user '$User'"
                        $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                    }
                }
                List {
                    $request = $service.Teamdrives.List()
                    $request.PageSize = $PageSize
                    if ($Filter) {
                        $FilterFmt = $Filter -replace " -eq ","=" -replace " -like "," contains " -replace " -match "," contains " -replace " -contains "," contains " -creplace "'True'","True" -creplace "'False'","False" -replace " -in "," in " -replace " -le ",'<=' -replace " -ge ",">=" -replace " -gt ",'>' -replace " -lt ",'<' -replace " -ne ","!=" -replace " -and "," and " -replace " -or "," or " -replace " -not "," not "
                        $request.UseDomainAdminAccess = $true
                        $request.Q = $($FilterFmt -join " ")
                    }
                    Write-Verbose "Getting Team Drives for user '$User'"
                    $request.Execute() | Select-Object -ExpandProperty TeamDrives | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSTeamDrive'

function New-GSDriveFile {
    <#
    .SYNOPSIS
    Creates a blank Drive file
 
    .DESCRIPTION
    Creates a blank Drive file
 
    .PARAMETER User
    The email or unique Id of the user who you are creating the Drive file for
 
    Defaults to the AdminEmail user
 
    .PARAMETER Name
    The name of the new Drive file
 
    .PARAMETER Parents
    The parent folder Id of the new Drive file
 
    .PARAMETER MimeType
    The Google Mime Type of the new Drive file
 
    Available values are:
    * "Audio"
    * "Docs"
    * "Drawing"
    * "DriveFile"
    * "DriveFolder"
    * "Form"
    * "FusionTables"
    * "Map"
    * "Photo"
    * "Slides"
    * "AppsScript"
    * "Sites"
    * "Sheets"
    * "Unknown"
    * "Video"
 
    .PARAMETER CustomMimeType
    The custom Mime Type of the new Drive file
 
    .PARAMETER Projection
    The defined subset of fields to be returned
 
    Available values are:
    * "Minimal"
    * "Standard"
    * "Full"
    * "Access"
 
    .PARAMETER Fields
    The specific fields to returned
 
    .EXAMPLE
    New-GSDriveFile -Name "Training Docs" -MimeType DriveFolder
 
    Creates a new folder in Drive named "Training Docs" in the root OrgUnit for the AdminEmail user
    #>

    [cmdletbinding(DefaultParameterSetName = "BuiltIn")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $true)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [Alias('ParentId')]
        [String[]]
        $Parents,
        [parameter(Mandatory = $true,ParameterSetName = "BuiltIn")]
        [Alias('Type')]
        [ValidateSet("Audio","Docs","Drawing","DriveFile","DriveFolder","Form","FusionTables","Map","Photo","Slides","AppsScript","Sites","Sheets","Unknown","Video")]
        [String]
        $MimeType,
        [parameter(Mandatory = $true,ParameterSetName = "Custom")]
        [String]
        $CustomMimeType,
        [parameter(Mandatory = $false)]
        [Alias('Depth')]
        [ValidateSet("Minimal","Standard","Full","Access")]
        [String]
        $Projection = "Full",
        [parameter(Mandatory = $false)]
        [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")]
        [String[]]
        $Fields
    )
    Begin {
        $mimeHash = @{
            Audio        = "application/vnd.google-apps.audio"
            Docs         = "application/vnd.google-apps.document"
            Drawing      = "application/vnd.google-apps.drawing"
            DriveFile    = "application/vnd.google-apps.file"
            DriveFolder  = "application/vnd.google-apps.folder"
            Form         = "application/vnd.google-apps.form"
            FusionTables = "application/vnd.google-apps.fusiontable"
            Map          = "application/vnd.google-apps.map"
            Photo        = "application/vnd.google-apps.photo"
            Slides       = "application/vnd.google-apps.presentation"
            AppsScript   = "application/vnd.google-apps.script"
            Sites        = "application/vnd.google-apps.sites"
            Sheets       = "application/vnd.google-apps.spreadsheet"
            Unknown      = "application/vnd.google-apps.unknown"
            Video        = "application/vnd.google-apps.video"
        }
        if ($Projection) {
            $fs = switch ($Projection) {
                Standard {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink")
                }
                Access {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare")
                }
                Full {
                    @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")
                }
            }
        }
        elseif ($Fields) {
            $fs = $Fields
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Drive.v3.Data.File'
            if ($MimeType) {
                $body.MimeType = $mimeHash[$MimeType]
            }
            elseif ($CustomMimeType) {
                $body.MimeType = $CustomMimeType
            }
            if ($Name) {
                $body.Name = [String]$Name
            }
            if ($Parents) {
                $body.Parents = [String[]]$Parents
            }
            $request = $service.Files.Create($body)
            $request.SupportsTeamDrives = $true
            if ($fs) {
                $request.Fields = $($fs -join ",")
            }
            Write-Verbose "Creating file '$Name' for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'New-GSDriveFile'

function New-GSTeamDrive {
    <#
    .SYNOPSIS
    Creates a new Team Drive
     
    .DESCRIPTION
    Creates a new Team Drive
     
    .PARAMETER Name
    The name of the Team Drive
     
    .PARAMETER User
    The user to create the Team Drive for (must have permissions to create Team Drives)
     
    .PARAMETER RequestId
    An ID, such as a random UUID, which uniquely identifies this user's request for idempotent creation of a Team Drive. A repeated request by the same user and with the same request ID will avoid creating duplicates by attempting to create the same Team Drive. If the Team Drive already exists a 409 error will be returned.
     
    .PARAMETER CanAddChildren
    Whether the current user can add children to folders in this Team Drive
     
    .PARAMETER CanChangeTeamDriveBackground
    Whether the current user can change the background of this Team Drive
     
    .PARAMETER CanComment
    Whether the current user can comment on files in this Team Drive
     
    .PARAMETER CanCopy
    Whether the current user can copy files in this Team Drive
     
    .PARAMETER CanDeleteTeamDrive
    Whether the current user can delete this Team Drive. Attempting to delete the Team Drive may still fail if there are untrashed items inside the Team Drive
     
    .PARAMETER CanDownload
    Whether the current user can download files in this Team Drive
     
    .PARAMETER CanEdit
    Whether the current user can edit files in this Team Drive
     
    .PARAMETER CanListChildren
    Whether the current user can list the children of folders in this Team Drive
     
    .PARAMETER CanManageMembers
    Whether the current user can add members to this Team Drive or remove them or change their role
     
    .PARAMETER CanReadRevisions
    Whether the current user can read the revisions resource of files in this Team Drive
     
    .PARAMETER CanRemoveChildren
    Whether the current user can remove children from folders in this Team Drive
     
    .PARAMETER CanRename
    Whether the current user can rename files or folders in this Team Drive
     
    .PARAMETER CanRenameTeamDrive
    Whether the current user can rename this Team Drive
     
    .PARAMETER CanShare
    Whether the current user can share files or folders in this Team Drive
     
    .EXAMPLE
    New-GSTeamDrive -Name "Training Docs"
 
    Creates a new Team Drive named "Training Docs"
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $Name,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String]
        $RequestId = (New-Guid).ToString('N'),
        [parameter(Mandatory = $false)]
        [Switch]
        $CanAddChildren,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanChangeTeamDriveBackground,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanComment,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanCopy,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanDeleteTeamDrive,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanDownload,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanEdit,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanListChildren,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanManageMembers,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanReadRevisions,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanRemoveChildren,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanRename,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanRenameTeamDrive,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanShare
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Drive.v3.Data.TeamDrive'
            $capabilities = New-Object 'Google.Apis.Drive.v3.Data.TeamDrive+CapabilitiesData'
            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    Default {
                        if ($capabilities.PSObject.Properties.Name -contains $key) {
                            $capabilities.$key = $PSBoundParameters[$key]
                        }
                        elseif ($body.PSObject.Properties.Name -contains $key) {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $body.Capabilities = $capabilities
            $request = $service.Teamdrives.Create($body,$RequestId)
            Write-Verbose "Creating Team Drive '$Name' for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'RequestId' -Value $RequestId -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSTeamDrive'

function Remove-GSDrivePermission {
    <#
    .SYNOPSIS
    Removes a permission from a Drive file
     
    .DESCRIPTION
    Removes a permission from a Drive file
     
    .PARAMETER User
    The email or unique Id of the user whose Drive file permission you are trying to get
 
    Defaults to the AdminEmail user
     
    .PARAMETER FileId
    The unique Id of the Drive file
     
    .PARAMETER PermissionId
    The unique Id of the permission you are trying to remove.
     
    .EXAMPLE
    Remove-GSDrivePermission -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -PermissionID 'sdfadsfsdafasd'
 
    Removes the permission from the drive.
 
    .EXAMPLE
    Get-GSDrivePermission -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' | ? {$_.Type -eq 'group'} | Remove-GSDrivePermission
 
    Gets the permissions assigned to groups and removes them.
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail, 
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [String]
        $FileId, 
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]        
        [String]
        $PermissionId
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($PSCmdlet.ShouldProcess("Removing Drive Permission Id '$PermissionId' from FileId '$FileID'")) {
                $request = $service.Permissions.Delete($FileId,$PermissionId)
                $request.SupportsTeamDrives = $true
                $request.Execute()
                Write-Verbose "Successfully removed Drive Permission Id '$PermissionId' from FileId '$FileID'"
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSDrivePermission'

function Remove-GSTeamDrive {
    <#
    .SYNOPSIS
    Removes a Team Drive
     
    .DESCRIPTION
    Removes a Team Drive
     
    .PARAMETER TeamDriveId
    The Id of the Team Drive to remove
     
    .PARAMETER User
    The email or unique Id of the user with permission to delete the Team Drive
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Remove-TeamDrive -TeamDriveId "0AJ8Xjq3FcdCKUk9PVA" -Confirm:$false
 
    Removes the Team Drive '0AJ8Xjq3FcdCKUk9PVA', skipping confirmation
    #>

    [cmdletbinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String[]]
        $TeamDriveId,
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($id in $TeamDriveId) {
                if ($PSCmdlet.ShouldProcess("Deleting Team Drive '$id' from user '$User'")) {
                    Write-Verbose "Deleting Team Drive '$id' from user '$User'"
                    $request = $service.Teamdrives.Delete($id)
                    $request.Execute()
                    Write-Verbose "Team Drive '$id' successfully deleted from user '$User'"
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSTeamDrive'

function Set-GSDocContent {
    <#
    .SYNOPSIS
    Sets the content of a Google Doc. This overwrites any existing content on the Doc
     
    .DESCRIPTION
    Sets the content of a Google Doc. This overwrites any existing content on the Doc
     
    .PARAMETER FileID
    The unique Id of the file to set content on
     
    .PARAMETER Value
    The content to set
     
    .PARAMETER User
    The email or unique Id of the owner of the Drive file
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    $logStrings | Set-GSDocContent -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976'
 
    Sets the content of the specified Google Doc to the strings in the $logStrings variable. Any existing content on the doc will be overwritten.
    #>

    [CmdLetBinding()]
    Param
    (      
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $FileID,
        [parameter(Mandatory = $true,ValueFromPipeline = $true)]
        [String[]]
        $Value,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
        $stream = New-Object 'System.IO.MemoryStream'
        $writer = New-Object 'System.IO.StreamWriter' $stream
        $concatStrings = @()
    }
    Process {
        foreach ($string in $Value) {
            $concatStrings += $string
        }
    }
    End {
        try {
            $concatStrings = $concatStrings -join "`n"
            $writer.Write($concatStrings)
            $writer.Flush()
            $contentType = 'text/plain'
            $body = New-Object 'Google.Apis.Drive.v3.Data.File'
            $request = $service.Files.Update($body,$FileId,$stream,$contentType)
            $request.QuotaUser = $User
            $request.ChunkSize = 512KB
            $request.SupportsTeamDrives = $true
            Write-Verbose "Setting content for File '$FileID'"
            $request.Upload() | Out-Null
            $stream.Close()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Set-GSDocContent'

function Start-GSDriveFileUpload {
    <#
    .SYNOPSIS
    Starts uploading a file or list of files to Drive asynchronously
     
    .DESCRIPTION
    Starts uploading a file or list of files to Drive asynchronously. Allows full folder structure uploads by passing a folder as -Path and including the -Recurse parameter
     
    .PARAMETER Path
    The path of the file or folder to upload
     
    .PARAMETER Name
    The new name of the file once uploaded
 
    Defaults to the existing name of the file or folder
     
    .PARAMETER Description
    The description of the file or folder in Drive
     
    .PARAMETER Parents
    The unique Id of the parent folder in Drive to upload the file to
 
    Defaults to the root folder in My Drive
     
    .PARAMETER Recurse
    If $true and there is a Directory passed to -Path, this will rebuild the folder structure in Drive under the Parent Id and upload the files within accordingly
     
    .PARAMETER Wait
    If $true, waits for all uploads to complete and shows progress around the total upload
     
    .PARAMETER RetryCount
    How many times uploads should be retried when using the -Wait parameter
 
    Defaults to 10
     
    .PARAMETER ThrottleLimit
    The limit of files to upload per batch while waiting
     
    .PARAMETER User
    The email or unique Id of the user to upload the files for
     
    .EXAMPLE
    Start-GSDriveFileUpload -Path "C:\Scripts","C:\Modules" -Recurse -Wait
 
    Starts uploading the Scripts and Modules folders and the files within them and waits for the uploads to complete, showing progress as files are uploaded
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName')]
        [ValidateScript( {Test-Path $_})]
        [String[]]
        $Path,
        [parameter(Mandatory = $false)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [String[]]
        $Parents,
        [parameter(Mandatory = $false)]
        [Switch]
        $Recurse,
        [parameter(Mandatory = $false)]
        [Switch]
        $Wait,
        [parameter(Mandatory = $false)]
        [Int]
        $RetryCount = 10,
        [parameter(Mandatory = $false)]
        [ValidateRange(1,1000)]
        [Int]
        $ThrottleLimit = 20,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
        $taskList = [System.Collections.ArrayList]@()
        $fullTaskList = [System.Collections.ArrayList]@()
        $start = Get-Date
        $folIdHash = @{}
        $throttleCount = 0
        $totalThrottleCount = 0
        $totalFiles = 0
    }
    Process {
        try {
            foreach ($file in $Path) {
                $details = Get-Item $file
                if ($details.PSIsContainer) {
                    $newFolPerms = @{
                        Name    = $details.Name
                        Type    = 'DriveFolder'
                        Verbose = $false
                    }
                    if ($PSBoundParameters.Keys -contains 'Parents') {
                        $newFolPerms['Parents'] = $PSBoundParameters['Parents']
                    }
                    Write-Verbose "Creating new Drive folder '$($details.Name)'"
                    $id = New-GSDriveFile @newFolPerms | Select-Object -ExpandProperty Id
                    $folIdHash[$details.FullName.TrimEnd('\').TrimEnd('/')] = $id
                    if ($Recurse) {
                        $recurseList = Get-ChildItem $details.FullName -Recurse
                        $recDirs = $recurseList | Where-Object {$_.PSIsContainer} | Sort-Object FullName
                        if ($recDirs) {
                            Write-Verbose "Creating recursive folder structure under '$($details.Name)'"
                            $recDirs | ForEach-Object {
                                $parPath = "$(Split-Path $_.FullName -Parent)"
                                $newFolPerms = @{
                                    Name    = $_.Name
                                    Type    = 'DriveFolder'
                                    Parents = [String[]]$folIdHash[$parPath]
                                    Verbose = $false
                                }
                                $id = New-GSDriveFile @newFolPerms | Select-Object -ExpandProperty Id
                                $folIdHash[$_.FullName] = $id
                            }
                        }
                        $details = $recurseList | Where-Object {!$_.PSIsContainer} | Sort-Object FullName
                        $checkFolIdHash = $true
                        $totalFiles = [int]$totalFiles + $details.Count
                    }
                }
                else {
                    $totalFiles++
                    $checkFolIdHash = $false
                }
                foreach ($detPart in $details) {
                    $throttleCount++
                    $contentType = Get-MimeType $detPart
                    $body = New-Object 'Google.Apis.Drive.v3.Data.File' -Property @{
                        Name = [String]$detPart.Name
                    }
                    if (!$checkFolIdHash -and ($PSBoundParameters.Keys -contains 'Parents')) {
                        if ($Parents) {
                            $body.Parents = [String[]]$Parents
                        }
                    }
                    elseif ($checkFolIdHash) {
                        $parPath = "$(Split-Path $detPart.FullName -Parent)"
                        $body.Parents = [String[]]$folIdHash[$parPath]
                    }
                    if ($Description) {
                        $body.Description = $Description
                    }
                    $stream = New-Object 'System.IO.FileStream' $detPart.FullName,'Open','Read'
                    $request = $service.Files.Create($body,$stream,$contentType)
                    $request.QuotaUser = $User
                    $request.SupportsTeamDrives = $true
                    $request.ChunkSize = 512KB
                    $upload = $request.UploadAsync()
                    $task = $upload.ContinueWith([System.Action[System.Threading.Tasks.Task]] {$stream.Dispose()})
                    Write-Verbose "[$($detPart.Name)] Upload Id $($upload.Id) has started"
                    if (!$Script:DriveUploadTasks) {
                        $Script:DriveUploadTasks = [System.Collections.ArrayList]@()
                    }
                    $script:DriveUploadTasks += [PSCustomObject]@{
                        Id        = $upload.Id
                        File      = $detPart
                        Length    = $detPart.Length
                        SizeInMB  = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero)
                        StartTime = $(Get-Date)
                        Parents   = $body.Parents
                        User      = $User
                        Upload    = $upload
                        Request   = $request
                    }
                    $taskList += [PSCustomObject]@{
                        Id       = $upload.Id
                        File     = $detPart
                        SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero)
                        User     = $User
                    }
                    $fullTaskList += [PSCustomObject]@{
                        Id       = $upload.Id
                        File     = $detPart
                        SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero)
                        User     = $User
                    }
                    if ($throttleCount -ge $ThrottleLimit) {
                        $totalThrottleCount += $throttleCount
                        if ($Wait) {
                            Watch-GSDriveUpload -Id $taskList.Id -CountUploaded $totalThrottleCount -TotalUploading $totalFiles
                            $throttleCount = 0
                            $taskList = [System.Collections.ArrayList]@()
                        }
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
    End {
        if (!$Wait) {
            $fullTaskList
        }
        else {
            Watch-GSDriveUpload -Id $fullTaskList.Id -CountUploaded $totalFiles -TotalUploading $totalFiles
            $fullStatusList = Get-GSDriveFileUploadStatus -Id $fullTaskList.Id
            $failedFiles = $fullStatusList | Where-Object {$_.Status -eq "Failed"}
            if (!$failedFiles) {
                Write-Verbose "All files uploaded to Google Drive successfully! Total time: $("{0:c}" -f ((Get-Date) - $start) -replace "\..*")"
            }
            elseif ($RetryCount) {
                $totalRetries = 0
                do {
                    $throttleCount = 0
                    $totalThrottleCount = 0
                    $taskList = [System.Collections.ArrayList]@()
                    $fullTaskList = [System.Collections.ArrayList]@()
                    $details = Get-Item $failedFiles.File
                    $totalFiles = [int]$totalFiles + $details.Count
                    $totalRetries++
                    Write-Verbose "~ ~ ~ RETRYING [$totalFiles] FAILED FILES [Retry # $totalRetries / $RetryCount] ~ ~ ~"
                    $details = Get-Item $failedFiles.File
                    foreach ($detPart in $details) {
                        $throttleCount++
                        $contentType = Get-MimeType $detPart
                        $body = New-Object 'Google.Apis.Drive.v3.Data.File' -Property @{
                            Name = [String]$detPart.Name
                        }
                        $parPath = "$(Split-Path $detPart.FullName -Parent)"
                        $body.Parents = [String[]]$folIdHash[$parPath]
                        if ($Description) {
                            $body.Description = $Description
                        }
                        $stream = New-Object 'System.IO.FileStream' $detPart.FullName,'Open','Read'
                        $request = $service.Files.Create($body,$stream,$contentType)
                        $request.QuotaUser = $User
                        $request.SupportsTeamDrives = $true
                        $request.ChunkSize = 512KB
                        $upload = $request.UploadAsync()
                        $task = $upload.ContinueWith([System.Action[System.Threading.Tasks.Task]] {$stream.Dispose()})
                        Write-Verbose "[$($detPart.Name)] Upload Id $($upload.Id) has started"
                        if (!$Script:DriveUploadTasks) {
                            $Script:DriveUploadTasks = [System.Collections.ArrayList]@()
                        }
                        $script:DriveUploadTasks += [PSCustomObject]@{
                            Id        = $upload.Id
                            File      = $detPart
                            Length    = $detPart.Length
                            SizeInMB  = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero)
                            StartTime = $(Get-Date)
                            Parents   = $body.Parents
                            User      = $User
                            Upload    = $upload
                            Request   = $request
                        }
                        $taskList += [PSCustomObject]@{
                            Id       = $upload.Id
                            File     = $detPart
                            SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero)
                            User     = $User
                        }
                        $fullTaskList += [PSCustomObject]@{
                            Id       = $upload.Id
                            File     = $detPart
                            SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero)
                            User     = $User
                        }
                        if ($throttleCount -ge $ThrottleLimit) {
                            $totalThrottleCount += $throttleCount
                            if ($Wait) {
                                Watch-GSDriveUpload -Id $taskList.Id -CountUploaded $totalThrottleCount -TotalUploading $totalFiles -Action Retrying
                                $throttleCount = 0
                                $taskList = [System.Collections.ArrayList]@()
                            }
                        }
                    }
                    Watch-GSDriveUpload -Id $fullTaskList.Id -Action Retrying -CountUploaded $totalFiles -TotalUploading $totalFiles
                    $fullStatusList = Get-GSDriveFileUploadStatus -Id $fullTaskList.Id
                    $failedFiles = $fullStatusList | Where-Object {$_.Status -eq "Failed"}
                }
                until (!$failedFiles -or ($totalRetries -ge $RetryCount))
                if ($failedFiles) {
                    Write-Warning "The following files failed to upload:`n`n$($failedFiles | Select-Object Id,Status,Exception,File | Format-List | Out-String)"
                }
                elseif (!$failedFiles) {
                    Write-Verbose "All files uploaded to Google Drive successfully! Total time: $("{0:c}" -f ((Get-Date) - $start) -replace "\..*")"
                }
            }
            [Console]::CursorVisible = $true
        }
    }
}
Export-ModuleMember -Function 'Start-GSDriveFileUpload'

function Update-GSDriveFile {
    <#
    .SYNOPSIS
    Updates the metadata for a Drive file
     
    .DESCRIPTION
    Updates the metadata for a Drive file
     
    .PARAMETER FileId
    The unique Id of the Drive file to Update
     
    .PARAMETER Path
    The path to the local file whose content you would like to upload to Drive.
     
    .PARAMETER Name
    The new name of the Drive file
     
    .PARAMETER Description
    The new description of the Drive file
     
    .PARAMETER AddParents
    The parent Ids to add
     
    .PARAMETER RemoveParents
    The parent Ids to remove
     
    .PARAMETER Projection
    The defined subset of fields to be returned
 
    Available values are:
    * "Minimal"
    * "Standard"
    * "Full"
    * "Access"
     
    .PARAMETER Fields
    The specific fields to returned
     
    .PARAMETER User
    The email or unique Id of the Drive file owner
     
    .EXAMPLE
    Update-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -Name "To-Do Progress"
 
    Updates the Drive file with a new name, "To-Do Progress"
     
    .EXAMPLE
    Update-GSDriveFile -FileId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -Path "C:\Pics\NewPic.png"
 
    Updates the Drive file with the content of the file at that path. In this example, the Drive file is a PNG named "Test.png". This will change the content of the file in Drive to match NewPic.png as well as rename it to "NewPic.png"
    #>

    [cmdletbinding(DefaultParameterSetName = "Depth")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String]
        $FileId,
        [parameter(Mandatory = $false,Position = 1)]
        [ValidateScript({Test-Path $_})]
        [String]
        $Path,
        [parameter(Mandatory = $false)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [String[]]
        $AddParents,
        [parameter(Mandatory = $false)]
        [String[]]
        $RemoveParents,
        [parameter(Mandatory = $false,ParameterSetName = "Depth")]
        [Alias('Depth')]
        [ValidateSet("Minimal","Standard","Full","Access")]
        [String]
        $Projection = "Full",
        [parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [ValidateSet("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","thumbnailLink","thumbnailVersion","trashed","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")]
        [String[]]
        $Fields,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($Projection) {
            $fs = switch ($Projection) {
                Standard {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","owners","parents","properties","version","webContentLink","webViewLink")
                }
                Access {
                    @("createdTime","description","fileExtension","id","lastModifyingUser","modifiedTime","name","ownedByMe","owners","parents","permissionIds","permissions","shared","sharedWithMeTime","sharingUser","viewedByMe","viewedByMeTime","viewersCanCopyContent","writersCanShare")
                }
                Full {
                    @("appProperties","capabilities","contentHints","createdTime","description","explicitlyTrashed","fileExtension","folderColorRgb","fullFileExtension","hasAugmentedPermissions","hasThumbnail","headRevisionId","iconLink","id","imageMediaMetadata","isAppAuthorized","kind","lastModifyingUser","md5Checksum","mimeType","modifiedByMe","modifiedByMeTime","modifiedTime","name","originalFilename","ownedByMe","owners","parents","permissionIds","permissions","properties","quotaBytesUsed","shared","sharedWithMeTime","sharingUser","size","spaces","starred","teamDriveId","thumbnailLink","thumbnailVersion","trashed","trashedTime","trashingUser","version","videoMediaMetadata","viewedByMe","viewedByMeTime","viewersCanCopyContent","webContentLink","webViewLink","writersCanShare")
                }
            }
        }
        elseif ($Fields) {
            $fs = $Fields
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Drive.v3.Data.File'
            if ($Name) {
                $body.Name = [String]$Name
            }
            if ($Description) {
                $body.Description = $Description
            }
            if ($PSBoundParameters.Keys -contains 'Path') {
                $ioFile = Get-Item $Path
                $contentType = Get-MimeType $ioFile
                if ($PSBoundParameters.Keys -notcontains 'Name') {
                    $body.Name = $ioFile.Name
                }
                $stream = New-Object 'System.IO.FileStream' $ioFile.FullName,'Open','Read'
                $request = $service.Files.Update($body,$FileId,$stream,$contentType)
                $request.QuotaUser = $User
                $request.ChunkSize = 512KB
            }
            else {
                $request = $service.Files.Update($body,$FileId)
            }
            $request.SupportsTeamDrives = $true
            if ($fs) {
                $request.Fields = $($fs -join ",")
            }
            if ($AddParents) {
                $request.AddParents = $($AddParents -join ",")
            }
            if ($RemoveParents) {
                $request.RemoveParents = $($RemoveParents -join ",")
            }
            Write-Verbose "Updating file '$FileId' for user '$User'"
            if ($PSBoundParameters.Keys -contains 'Path') {
                $request.Upload() | Out-Null
                $stream.Close()
            }
            else {
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSDriveFile'

function Update-GSTeamDrive {
    <#
    .SYNOPSIS
    Update metatdata for a Team Drive
     
    .DESCRIPTION
    Update metatdata for a Team Drive
     
    .PARAMETER TeamDriveId
    The unique Id of the Team Drive to update
     
    .PARAMETER User
    The user to create the Team Drive for (must have permissions to create Team Drives)
     
    .PARAMETER Name
    The name of the Team Drive
     
    .PARAMETER CanAddChildren
    Whether the current user can add children to folders in this Team Drive
     
    .PARAMETER CanChangeTeamDriveBackground
    Whether the current user can change the background of this Team Drive
     
    .PARAMETER CanComment
    Whether the current user can comment on files in this Team Drive
     
    .PARAMETER CanCopy
    Whether the current user can copy files in this Team Drive
     
    .PARAMETER CanDeleteTeamDrive
    Whether the current user can delete this Team Drive. Attempting to delete the Team Drive may still fail if there are untrashed items inside the Team Drive
     
    .PARAMETER CanDownload
    Whether the current user can download files in this Team Drive
     
    .PARAMETER CanEdit
    Whether the current user can edit files in this Team Drive
     
    .PARAMETER CanListChildren
    Whether the current user can list the children of folders in this Team Drive
     
    .PARAMETER CanManageMembers
    Whether the current user can add members to this Team Drive or remove them or change their role
     
    .PARAMETER CanReadRevisions
    Whether the current user can read the revisions resource of files in this Team Drive
     
    .PARAMETER CanRemoveChildren
    Whether the current user can remove children from folders in this Team Drive
     
    .PARAMETER CanRename
    Whether the current user can rename files or folders in this Team Drive
     
    .PARAMETER CanRenameTeamDrive
    Whether the current user can rename this Team Drive
     
    .PARAMETER CanShare
    Whether the current user can share files or folders in this Team Drive
     
    .EXAMPLE
    Update-GSTeamDrive -TeamDriveId '0AJ8Xjq3FcdCKUk9PVA' -Name "HR Document Repo"
 
    Updated the Team Drive with a new name, "HR Document Repo"
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $TeamDriveId,
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanAddChildren,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanChangeTeamDriveBackground,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanComment,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanCopy,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanDeleteTeamDrive,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanDownload,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanEdit,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanListChildren,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanManageMembers,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanReadRevisions,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanRemoveChildren,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanRename,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanRenameTeamDrive,
        [parameter(Mandatory = $false)]
        [Switch]
        $CanShare
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Drive.v3.DriveService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Drive.v3.Data.TeamDrive'
            $capabilities = New-Object 'Google.Apis.Drive.v3.Data.TeamDrive+CapabilitiesData'
            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    Default {
                        if ($capabilities.PSObject.Properties.Name -contains $key) {
                            $capabilities.$key = $PSBoundParameters[$key]
                        }
                        elseif ($body.PSObject.Properties.Name -contains $key) {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $body.Capabilities = $capabilities
            $request = $service.Teamdrives.Update($body,$TeamDriveId)
            Write-Verbose "Updating Team Drive '$Name' for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSTeamDrive'

function Watch-GSDriveUpload {
    <#
    .SYNOPSIS
    Shows progress in the console of current Drive file uploads
     
    .DESCRIPTION
    Shows progress in the console of current Drive file uploads
     
    .PARAMETER Id
    The upload Id(s) that you would like to watch
     
    .PARAMETER Action
    Whether the action is uploading or retrying. This is mainly for use in Start-GSDriveFileUpload and defaults to 'Uploading'
     
    .PARAMETER CountUploaded
    Current file count being uploaded
     
    .PARAMETER TotalUploading
    Total file count being uploaded
     
    .EXAMPLE
    Watch-GSDriveUpload
 
    Watches the files currently being uploaded from the active session
    #>

    [CmdletBinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Int[]]
        $Id,
        [parameter(Mandatory = $false)]
        [ValidateSet('Uploading','Retrying')]
        [String]
        $Action = "Uploading",
        [parameter(Mandatory = $false)]
        [Int]
        $CountUploaded,
        [parameter(Mandatory = $false)]
        [Int]
        $TotalUploading
    )
    Begin {
        Write-Verbose "Watching Drive File Upload"
    }
    Process {
        do {
            $i = 1
            if ($PSBoundParameters.Keys -contains 'Id') {
                $statusList = Get-GSDriveFileUploadStatus -Verbose:$false
            }
            else {
                $statusList = Get-GSDriveFileUploadStatus -Id @($Id) -Verbose:$false
            }
            if ($statusList) {
                $totalPercent = 0
                $totalSecondsRemaining = 0
                $count = 0
                $statusList | ForEach-Object {
                    $count++
                    $totalPercent += $_.PercentComplete
                    $totalSecondsRemaining += $_.Remaining.TotalSeconds
                }
                $curCount = if ($PSBoundParameters.Keys -contains 'CountUploaded') {
                    $CountUploaded
                }
                else {
                    $count
                }
                $totalCount = if ($PSBoundParameters.Keys -contains 'TotalUploading') {
                    $TotalUploading
                }
                else {
                    $count
                }
                $totalPercent = $totalPercent / $totalCount
                $totalSecondsRemaining = $totalSecondsRemaining / $totalCount
                $parentParams = @{
                    Activity = "[$([Math]::Round($totalPercent,4))%] $Action [$curCount / $totalCount] files to Google Drive"
                    SecondsRemaining = $($statusList.Remaining.TotalSeconds | Sort-Object | Select-Object -Last 1)
                }
                if (!($statusList | Where-Object {$_.Status -ne "Completed"})) {
                    $parentParams['Completed'] = $true
                }
                else {
                    $parentParams['PercentComplete'] = [Math]::Round($totalPercent,4)
                }
                if ($psEditor -or $IsMacOS -or $IsLinux) {
                    Write-InlineProgress @parentParams
                }
                else {
                    $parentParams['Id'] = 1
                    Write-Progress @parentParams
                }
                if (!$psEditor -and !$IsMacOS -and !$IsLinux -and ($statusList.Count -le 5)) {
                    foreach ($status in $statusList) {
                        $i++
                        $statusFmt = if ($status.Status -eq "Completed") {
                            "Completed uploading"
                        }
                        else {
                            $status.Status
                        }
                        $progParams = @{
                            Activity = "[$($status.PercentComplete)%] [ID: $($status.Id)] $($statusFmt) file '$($status.File)' to Google Drive$(if($status.Parents){" (Parents: '$($status.Parents -join "', '")')"})"
                            SecondsRemaining = $status.Remaining.TotalSeconds
                            Id = $i
                            ParentId = 1
                        }
                        if ($_.Status -eq "Completed") {
                            $progParams['Completed'] = $true
                        }
                        else {
                            $progParams['PercentComplete'] = [Math]::Round($status.PercentComplete,4)
                        }
                        Write-Progress @progParams
                    }
                }
                Start-Sleep -Seconds 1
            }
        }
        until (!$statusList -or !($statusList | Where-Object {$_.Status -notin @("Failed","Completed")}))
    }
}
Export-ModuleMember -Function 'Watch-GSDriveUpload'

function Add-GSGmailFilter {
    <#
    .SYNOPSIS
    Adds a new Gmail filter
     
    .DESCRIPTION
    Adds a new Gmail filter
     
    .PARAMETER User
    The email of the user you are adding the filter for
     
    .PARAMETER From
    The sender's display name or email address.
     
    .PARAMETER To
    The recipient's display name or email address. Includes recipients in the "to", "cc", and "bcc" header fields. You can use simply the local part of the email address. For example, "example" and "example@" both match "example@gmail.com". This field is case-insensitive
     
    .PARAMETER Subject
    Case-insensitive phrase found in the message's subject. Trailing and leading whitespace are be trimmed and adjacent spaces are collapsed
     
    .PARAMETER Query
    Only return messages matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser@example.com rfc822msgid: is:unread"
     
    .PARAMETER NegatedQuery
    Only return messages not matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser@example.com rfc822msgid: is:unread"
     
    .PARAMETER HasAttachment
    Whether the message has any attachment
     
    .PARAMETER ExcludeChats
    Whether the response should exclude chats
     
    .PARAMETER AddLabelIDs
    List of labels to add to the message
     
    .PARAMETER RemoveLabelIDs
    List of labels to remove from the message
     
    .PARAMETER Forward
    Email address that the message should be forwarded to
     
    .PARAMETER Size
    The size of the entire RFC822 message in bytes, including all headers and attachments
     
    .PARAMETER SizeComparison
    How the message size in bytes should be in relation to the size field.
 
    Acceptable values are:
    * "larger"
    * "smaller"
    * "unspecified"
     
    .PARAMETER Raw
    If $true, returns the raw response. If not passed or -Raw:$false, response is formatted as a flat object for readability
     
    .EXAMPLE
    Add-GSGmailFilter -To admin@domain.com -ExcludeChats -Forward "admin_directMail@domain.com"
 
    Adds a filter for the AdminEmail user to forward all mail sent directly to the to "admin_directMail@domain.com"
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $From,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $To,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $Subject,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $Query,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $NegatedQuery,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Switch]
        $HasAttachment,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Switch]
        $ExcludeChats,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $AddLabelIDs,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $RemoveLabelIDs,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string]
        $Forward,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [int]
        $Size,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('Larger','Smaller','Unspecified')]
        [string]
        $SizeComparison,
        [parameter(Mandatory = $false)]
        [Switch]
        $Raw
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Gmail.v1.Data.Filter'
            $action = New-Object 'Google.Apis.Gmail.v1.Data.FilterAction'
            $criteria = New-Object 'Google.Apis.Gmail.v1.Data.FilterCriteria'
            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    AddLabelIDs {
                        $action.$key = [String[]]($PSBoundParameters[$key])
                    }
                    RemoveLabelIds {
                        $action.$key = [String[]]($PSBoundParameters[$key])
                    }
                    Forward {
                        $action.$key = $PSBoundParameters[$key]
                    }
                    Default {
                        if ($criteria.PSObject.Properties.Name -contains $key) {
                            $criteria.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $body.Action = $action
            $body.Criteria = $criteria
            $request = $service.Users.Settings.Filters.Create($body,$User)
            Write-Verbose "Creating Filter for user '$User'"
            $response = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            if (!$Raw) {
                $response = $response | Select-Object User,Id,@{N = "From";E = {$_.criteria.from}},@{N = "To";E = {$_.criteria.to}},@{N = "Subject";E = {$_.criteria.subject}},@{N = "Query";E = {$_.criteria.query}},@{N = "NegatedQuery";E = {$_.criteria.negatedQuery}},@{N = "HasAttachment";E = {$_.criteria.hasAttachment}},@{N = "ExcludeChats";E = {$_.criteria.excludeChats}},@{N = "Size";E = {$_.criteria.size}},@{N = "SizeComparison";E = {$_.criteria.sizeComparison}},@{N = "AddLabelIds";E = {$_.action.addLabelIds}},@{N = "RemoveLabelIds";E = {$_.action.removeLabelIds}},@{N = "Forward";E = {$_.action.forward}}
            }
            $response
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSGmailFilter'

function Add-GSGmailForwardingAddress {
    <#
    .SYNOPSIS
    Creates a forwarding address.
     
    .DESCRIPTION
    Creates a forwarding address. If ownership verification is required, a message will be sent to the recipient and the resource's verification status will be set to pending; otherwise, the resource will be created with verification status set to accepted.
     
    .PARAMETER ForwardingAddress
    An email address to which messages can be forwarded.
     
    .PARAMETER User
    The user to create the forwarding addresses for
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Add-GSGmailForwardingAddress "joe@domain.com"
 
    Adds joe@domain.com as a forwarding address for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("Id")]
        [string[]]
        $ForwardingAddress,
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.sharing'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($fwd in $ForwardingAddress) {
                $body = New-Object 'Google.Apis.Gmail.v1.Data.ForwardingAddress' -Property @{
                    ForwardingEmail = $fwd
                }
                $request = $service.Users.Settings.ForwardingAddresses.Create($body,$User)
                Write-Verbose "Creating Forwarding Address '$fwd' for user '$User'"
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSGmailForwardingAddress'

function Get-GSGmailAutoForwardingSettings {
    <#
    .SYNOPSIS
    Gets AutoForwarding settings
     
    .DESCRIPTION
    Gets AutoForwarding settings
     
    .PARAMETER User
    The user to get the AutoForwarding settings for
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSGmailAutoForwardingSettings
 
    Gets the AutoForwarding settings for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $request = $service.Users.Settings.GetAutoForwarding($User)
            Write-Verbose "Getting AutoForwarding settings for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailAutoForwardingSettings'

function Get-GSGmailFilter {
    <#
    .SYNOPSIS
    Gets Gmail filter details
     
    .DESCRIPTION
    Gets Gmail filter details
     
    .PARAMETER FilterId
    The unique Id of the filter you would like to retrieve information for. If excluded, all filters for the user are returned
     
    .PARAMETER User
    The email of the user you are getting the filter information for
     
    .PARAMETER Raw
    If $true, returns the raw response. If not passed or -Raw:$false, response is formatted as a flat object for readability
     
    .EXAMPLE
    Get-GSGmailFilter -User joe
 
    Gets the list of filters for Joe
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("Id")]
        [string[]]
        $FilterId,
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [switch]
        $Raw
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($FilterId) {
                foreach ($fil in $FilterId) {
                    $request = $service.Users.Settings.Filters.Get($User,$fil)
                    Write-Verbose "Getting Filter Id '$fil' for user '$User'"
                    $response = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                    if (!$Raw) {
                        $response = $response | Select-Object User,Id,@{N = "From";E = {$_.criteria.from}},@{N = "To";E = {$_.criteria.to}},@{N = "Subject";E = {$_.criteria.subject}},@{N = "Query";E = {$_.criteria.query}},@{N = "NegatedQuery";E = {$_.criteria.negatedQuery}},@{N = "HasAttachment";E = {$_.criteria.hasAttachment}},@{N = "ExcludeChats";E = {$_.criteria.excludeChats}},@{N = "Size";E = {$_.criteria.size}},@{N = "SizeComparison";E = {$_.criteria.sizeComparison}},@{N = "AddLabelIds";E = {$_.action.addLabelIds}},@{N = "RemoveLabelIds";E = {$_.action.removeLabelIds}},@{N = "Forward";E = {$_.action.forward}}
                    }
                    $response
                }
            }
            else {
                $request = $service.Users.Settings.Filters.List($User)
                Write-Verbose "Getting Filter List for user '$User'"
                $response = $request.Execute() | Select-Object -ExpandProperty Filter | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                if (!$Raw) {
                    $response = $response | Select-Object User,Id,@{N = "From";E = {$_.criteria.from}},@{N = "To";E = {$_.criteria.to}},@{N = "Subject";E = {$_.criteria.subject}},@{N = "Query";E = {$_.criteria.query}},@{N = "NegatedQuery";E = {$_.criteria.negatedQuery}},@{N = "HasAttachment";E = {$_.criteria.hasAttachment}},@{N = "ExcludeChats";E = {$_.criteria.excludeChats}},@{N = "Size";E = {$_.criteria.size}},@{N = "SizeComparison";E = {$_.criteria.sizeComparison}},@{N = "AddLabelIds";E = {$_.action.addLabelIds}},@{N = "RemoveLabelIds";E = {$_.action.removeLabelIds}},@{N = "Forward";E = {$_.action.forward}}
                }
                $response
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailFilter'

function Get-GSGmailForwardingAddress {
    <#
    .SYNOPSIS
    Gets Gmail forwarding address information for the user
     
    .DESCRIPTION
    Gets Gmail forwarding address information for the user
     
    .PARAMETER ForwardingAddress
    The forwarding address you would like to get info for. If excluded, gets the list of forwarding addresses and their info for the user
     
    .PARAMETER User
    The user to get the forwarding addresses for
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSGmailForwardingAddress
 
    Gets the list of forwarding addresses for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("Id")]
        [string[]]
        $ForwardingAddress,
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($ForwardingAddress) {
                foreach ($fwd in $ForwardingAddress) {
                    $request = $service.Users.Settings.ForwardingAddresses.Get($User,$fwd)
                    Write-Verbose "Getting Forwarding Address '$fwd' for user '$User'"
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                }
            }
            else {
                $request = $service.Users.Settings.ForwardingAddresses.List($User)
                Write-Verbose "Getting Forwarding Address List for user '$User'"
                $request.Execute() | Select-Object -ExpandProperty ForwardingAddresses | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailForwardingAddress'

function Get-GSGmailImapSettings {
    <#
    .SYNOPSIS
    Gets IMAP settings
     
    .DESCRIPTION
    Gets IMAP settings
     
    .PARAMETER User
    The user to get the IMAP settings for
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSGmailImapSettings
 
    Gets the IMAP settings for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $request = $service.Users.Settings.GetImap($User)
            Write-Verbose "Getting IMAP settings for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailImapSettings'

function Get-GSGmailLabel {
    <#
    .SYNOPSIS
    Gets Gmail label information for the user
     
    .DESCRIPTION
    Gets Gmail label information for the user
     
    .PARAMETER LabelId
    The unique Id of the label to get information for. If excluded, returns the list of labels for the user
     
    .PARAMETER User
    The user to get label information for
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSGmailLabel
 
    Gets the Gmail labels of the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("Id")]
        [string[]]
        $LabelId,
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($LabelId) {
                foreach ($label in $LabelId) {
                    $request = $service.Users.Labels.Get($User,$label)
                    Write-Verbose "Getting Label Id '$label' for user '$User'"
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                }
            }
            else {
                $request = $service.Users.Labels.List($User)
                Write-Verbose "Getting Label List for user '$User'"
                $request.Execute() | Select-Object -ExpandProperty Labels | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailLabel'

function Get-GSGmailMessage {
    <#
    .SYNOPSIS
    Gets Gmail message details
     
    .DESCRIPTION
    Gets Gmail message details
     
    .PARAMETER User
    The primary email of the user who owns the message
 
    Defaults to the AdminEmail user
     
    .PARAMETER Id
    The Id of the message to retrieve info for
     
    .PARAMETER ParseMessage
    If $true, returns the parsed raw message
     
    .PARAMETER SaveAttachmentsTo
    If the message has attachments, the path to save the attachments to. If excluded, attachments are not saved locally
     
    .PARAMETER Format
    The format of the message metadata to retrieve
 
    Available values are:
    * "Full"
    * "Metadata"
    * "Minimal"
    * "Raw"
 
    Defaults to "Full", but forces -Format as "Raw" if -ParseMessage or -SaveAttachmentsTo are used
     
    .EXAMPLE
    Get-GSGmailMessage -Id 1615f9a6ee36cb5b -ParseMessage
 
    Gets the full message details for the provided Id and parses out the raw MIME message content
    #>

    [cmdletbinding(DefaultParameterSetName = "Format")]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('MessageId')]
        [String[]]
        $Id,
        [parameter(Mandatory = $false,ParameterSetName = "ParseMessage")]
        [switch]
        $ParseMessage,
        [parameter(Mandatory = $false,ParameterSetName = "ParseMessage")]
        [Alias('AttachmentOutputPath','OutFilePath')]
        [ValidateScript({(Get-Item $_).PSIsContainer})]
        [string]
        $SaveAttachmentsTo,
        [parameter(Mandatory = $false,ParameterSetName = "Format")]
        [ValidateSet("Full","Metadata","Minimal","Raw")]
        [string]
        $Format = "Full"
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        if ($ParseMessage) {
            $Format = "Raw"
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($mId in $Id) {
                $request = $service.Users.Messages.Get($User,$mId)
                $request.Format = $Format
                foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne "Id"}) {
                    switch ($key) {
                        Default {
                            if ($request.PSObject.Properties.Name -contains $key) {
                                $request.$key = $PSBoundParameters[$key]
                            }
                        }
                    }
                }
                Write-Verbose "Getting Message Id '$mId' for user '$User'"
                $result = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                if ($ParseMessage) {
                    $parsed = Read-MimeMessage -String $(Convert-Base64 -From WebSafeBase64String -To NormalString -String $result.Raw) | Select-Object @{N = 'User';E = {$User}},@{N = "Id";E = {$result.Id}},@{N = "ThreadId";E = {$result.ThreadId}},@{N = "LabelIds";E = {$result.LabelIds}},@{N = "Snippet";E = {$result.Snippet}},@{N = "HistoryId";E = {$result.HistoryId}},@{N = "InternalDate";E = {$result.InternalDate}},@{N = "InternalDateConverted";E = {Convert-EpochToDate -EpochString $result.internalDate}},@{N = "SizeEstimate";E = {$result.SizeEstimate}},*
                    if ($SaveAttachmentsTo) {
                        $resPath = Resolve-Path $SaveAttachmentsTo
                        $attachments = $parsed.Attachments
                        foreach ($att in $attachments) {
                            $fileName = Join-Path $resPath $att.FileName
                            Write-Verbose "Saving attachment to path '$fileName'"
                            $stream = [System.IO.File]::Create($fileName)
                            $att.ContentObject.DecodeTo($stream)
                            $stream.Close()
                        }
                    }
                    $parsed
                }
                else {
                    $result
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailMessage'

function Get-GSGmailMessageList {
    <#
    .SYNOPSIS
    Gets a list of messages
 
    .DESCRIPTION
    Gets a list of messages
 
    .PARAMETER User
    The primary email of the user to list messages for
 
    Defaults to the AdminEmail user
 
    .PARAMETER Filter
    Only return messages matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser@example.com rfc822msgid: is:unread"
 
    More info on Gmail search operators here: https://support.google.com/mail/answer/7190?hl=en
 
    .PARAMETER LabelIds
    Only return messages with labels that match all of the specified label IDs
 
    .PARAMETER ExcludeChats
    Exclude chats from the message list
 
    .PARAMETER IncludeSpamTrash
    Include messages from SPAM and TRASH in the results
 
    .PARAMETER PageSize
    The page size of the result set
 
    .EXAMPLE
    Get-GSGmailMessageList -Filter "to:me","after:2017/12/25" -ExcludeChats
 
    Gets the list of messages sent directly to the user after 2017/12/25 excluding chats
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [String]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [Alias('Query')]
        [String[]]
        $Filter,
        [parameter(Mandatory = $false)]
        [Alias('LabelId')]
        [String[]]
        $LabelIds,
        [parameter(Mandatory = $false)]
        [switch]
        $ExcludeChats,
        [parameter(Mandatory = $false)]
        [switch]
        $IncludeSpamTrash,
        [parameter(Mandatory = $false)]
        [ValidateRange(1,500)]
        [Int]
        $PageSize = "500"
    )
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                $serviceParams = @{
                    Scope       = 'https://mail.google.com'
                    ServiceType = 'Google.Apis.Gmail.v1.GmailService'
                    User        = $U
                }
                if ($ExcludeChats) {
                    if ($Filter) {
                        $Filter += "-in:chats"
                    }
                    else {
                        $Filter = "-in:chats"
                    }
                }
                $service = New-GoogleService @serviceParams
                $request = $service.Users.Messages.List($U)
                foreach ($key in $PSBoundParameters.Keys) {
                    switch ($key) {
                        Filter {
                            $request.Q = $($Filter -join " ")
                        }
                        LabelIds {
                            $request.LabelIds = [String[]]$LabelIds
                        }
                        Default {
                            if ($request.PSObject.Properties.Name -contains $key) {
                                $request.$key = $PSBoundParameters[$key]
                            }
                        }
                    }
                }
                if ($PageSize) {
                    $request.MaxResults = $PageSize
                }
                if ($Filter) {
                    Write-Verbose "Getting all Messages matching filter '$Filter' for user '$U'"
                }
                else {
                    Write-Verbose "Getting all Messages for user '$U'"
                }
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    if ($result.Messages) {
                        $result.Messages | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'Filter' -Value $Filter -PassThru
                    }
                    if ($result.NextPageToken) {
                        $request.PageToken = $result.NextPageToken
                    }
                    [int]$retrieved = ($i + $result.Messages.Count) - 1
                    Write-Verbose "Retrieved $retrieved Messages..."
                    [int]$i = $i + $result.Messages.Count
                }
                until (!$result.NextPageToken)
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSGmailMessageList'

function Get-GSGmailPopSettings {
    <#
    .SYNOPSIS
    Gets POP settings
     
    .DESCRIPTION
    Gets POP settings
     
    .PARAMETER User
    The user to get the POP settings for
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSGmailPopSettings
 
    Gets the POP settings for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $request = $service.Users.Settings.GetPop($User)
            Write-Verbose "Getting POP settings for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailPopSettings'

function Get-GSGmailProfile {
    <#
    .SYNOPSIS
    Gets Gmail profile for the user
     
    .DESCRIPTION
    Gets Gmail profile for the user
     
    .PARAMETER User
    The user to get profile of
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSGmailProfile
 
    Gets the Gmail profile of the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail
    )
    Process {
        foreach ($U in $User) {
            try {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                $serviceParams = @{
                    Scope       = 'https://mail.google.com'
                    ServiceType = 'Google.Apis.Gmail.v1.GmailService'
                    User        = $U
                }
                $service = New-GoogleService @serviceParams
                $request = $service.Users.GetProfile($U)
                Write-Verbose "Getting Gmail profile for user '$U'"
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailProfile'

function Get-GSGmailSMIMEInfo {
    <#
    .SYNOPSIS
    Gets Gmail S/MIME info
     
    .DESCRIPTION
    Gets Gmail S/MIME info
     
    .PARAMETER SendAsEmail
    The email address that appears in the "From:" header for mail sent using this alias.
     
    .PARAMETER Id
    The immutable ID for the SmimeInfo.
 
    If left blank, returns the list of S/MIME infos for the SendAsEmail and User
     
    .PARAMETER User
    The user's email address
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSGmailSMIMEInfo -SendAsEmail 'joe@otherdomain.com' -User joe@domain.com
 
    Gets the list of S/MIME infos for Joe's SendAsEmail 'joe@otherdomain.com'
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [string]
        $SendAsEmail,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $Id,
        [parameter(Mandatory = $false)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($PSBoundParameters.Keys -contains 'Id') {
                foreach ($I in $Id) {
                    $request = $service.Users.Settings.SendAs.SmimeInfo.Get($User,$SendAsEmail,$I)
                    Write-Verbose "Getting S/MIME Id '$I' of SendAsEmail '$SendAsEmail' for user '$User'"
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                }
            }
            else {
                $request = $service.Users.Settings.SendAs.SmimeInfo.List($User,$SendAsEmail)
                Write-Verbose "Getting list of S/MIME Id's of SendAsEmail '$SendAsEmail' for user '$User'"
                $request.Execute() | Select-Object -ExpandProperty smimeInfo | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailSMIMEInfo'

function Get-GSGmailVacationSettings {
    <#
    .SYNOPSIS
    Gets Vacation settings
     
    .DESCRIPTION
    Gets Vacation settings
     
    .PARAMETER User
    The user to get the Vacation settings for
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Get-GSGmailVacationSettings
 
    Gets the Vacation settings for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $request = $service.Users.Settings.GetVacation($User)
            Write-Verbose "Getting Vacation settings for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGmailVacationSettings'

function New-GSGmailLabel {
    <#
    .SYNOPSIS
    Adds a Gmail label
 
    .DESCRIPTION
    Adds a Gmail label
 
    .PARAMETER Name
    The name of the label to add
 
    .PARAMETER LabelListVisibility
    The visibility of the label in the label list in the Gmail web interface.
 
    Acceptable values are:
    * "labelHide": Do not show the label in the label list.
    * "labelShow": Show the label in the label list. (Default)
    * "labelShowIfUnread": Show the label if there are any unread messages with that label.
 
    .PARAMETER MessageListVisibility
    The visibility of messages with this label in the message list in the Gmail web interface.
 
    Acceptable values are:
    * "hide": Do not show the label in the message list.
    * "show": Show the label in the message list. (Default)
 
    .PARAMETER BackgroundColor
    The background color of the label
 
    Options and their corresponding hex code:
 
        Amethyst = '#8e63ce'
        BananaMania = '#fce8b3'
        Bermuda = '#68dfa9'
        BilobaFlower = '#b694e8'
        Black = '#000000'
        BlueRomance = '#c6f3de'
        BrandyPunch = '#cf8933'
        BurntSienna = '#e66550'
        Cadillac = '#b65775'
        Camelot = '#83334c'
        CeruleanBlue = '#285bac'
        ChathamsBlue = '#1c4587'
        Concrete = '#f3f3f3'
        CornflowerBlue = '#4a86e8'
        LightCornflowerBlue = '#6d9eeb'
        CreamCan = '#f2c960'
        Cupid = '#fbc8d9'
        DeepBlush = '#e07798'
        Desert = '#a46a21'
        DoveGray = '#666666'
        DustyGray = '#999999'
        Eucalyptus = '#2a9c68'
        Flesh = '#ffd6a2'
        FringyFlower = '#b9e4d0'
        Gallery = '#efefef'
        Goldenrod = '#fad165'
        Illusion = '#f7a7c0'
        Jewel = '#1a764d'
        Koromiko = '#ffbc6b'
        LightMountainMeadow = '#16a766'
        LightShamrock = '#43d692'
        LuxorGold = '#aa8831'
        MandysPink = '#f6c5be'
        MediumPurple = '#a479e2'
        Meteorite = '#41236d'
        MoonRaker = '#d0bcf1'
        LightMoonRaker = '#e4d7f5'
        MountainMeadow = '#149e60'
        Oasis = '#fef1d1'
        OceanGreen = '#44b984'
        OldGold = '#d5ae49'
        Perano = '#a4c2f4'
        PersianPink = '#f691b3'
        PigPink = '#fcdee8'
        Pueblo = '#822111'
        RedOrange = '#fb4c2f'
        RoyalBlue = '#3c78d8'
        RoyalPurple = '#653e9b'
        Salem = '#0b804b'
        Salomie = '#fcda83'
        SeaPink = '#efa093'
        Shamrock = '#3dc789'
        Silver = '#cccccc'
        Tabasco = '#ac2b16'
        Tequila = '#ffe6c7'
        Thunderbird = '#cc3a21'
        TropicalBlue = '#c9daf8'
        TulipTree = '#eaa041'
        Tundora = '#434343'
        VistaBlue = '#89d3b2'
        Watercourse = '#076239'
        WaterLeaf = '#a0eac9'
        White = '#ffffff'
        YellowOrange = '#ffad47'
 
    .PARAMETER TextColor
    The text color of the label
 
    Options and their corresponding hex code:
 
        Amethyst = '#8e63ce'
        BananaMania = '#fce8b3'
        Bermuda = '#68dfa9'
        BilobaFlower = '#b694e8'
        Black = '#000000'
        BlueRomance = '#c6f3de'
        BrandyPunch = '#cf8933'
        BurntSienna = '#e66550'
        Cadillac = '#b65775'
        Camelot = '#83334c'
        CeruleanBlue = '#285bac'
        ChathamsBlue = '#1c4587'
        Concrete = '#f3f3f3'
        CornflowerBlue = '#4a86e8'
        LightCornflowerBlue = '#6d9eeb'
        CreamCan = '#f2c960'
        Cupid = '#fbc8d9'
        DeepBlush = '#e07798'
        Desert = '#a46a21'
        DoveGray = '#666666'
        DustyGray = '#999999'
        Eucalyptus = '#2a9c68'
        Flesh = '#ffd6a2'
        FringyFlower = '#b9e4d0'
        Gallery = '#efefef'
        Goldenrod = '#fad165'
        Illusion = '#f7a7c0'
        Jewel = '#1a764d'
        Koromiko = '#ffbc6b'
        LightMountainMeadow = '#16a766'
        LightShamrock = '#43d692'
        LuxorGold = '#aa8831'
        MandysPink = '#f6c5be'
        MediumPurple = '#a479e2'
        Meteorite = '#41236d'
        MoonRaker = '#d0bcf1'
        LightMoonRaker = '#e4d7f5'
        MountainMeadow = '#149e60'
        Oasis = '#fef1d1'
        OceanGreen = '#44b984'
        OldGold = '#d5ae49'
        Perano = '#a4c2f4'
        PersianPink = '#f691b3'
        PigPink = '#fcdee8'
        Pueblo = '#822111'
        RedOrange = '#fb4c2f'
        RoyalBlue = '#3c78d8'
        RoyalPurple = '#653e9b'
        Salem = '#0b804b'
        Salomie = '#fcda83'
        SeaPink = '#efa093'
        Shamrock = '#3dc789'
        Silver = '#cccccc'
        Tabasco = '#ac2b16'
        Tequila = '#ffe6c7'
        Thunderbird = '#cc3a21'
        TropicalBlue = '#c9daf8'
        TulipTree = '#eaa041'
        Tundora = '#434343'
        VistaBlue = '#89d3b2'
        Watercourse = '#076239'
        WaterLeaf = '#a0eac9'
        White = '#ffffff'
        YellowOrange = '#ffad47'
 
    .PARAMETER User
    The primary email of the user to add the label to
 
    Defaults to the AdminEmail user
 
    .EXAMPLE
    New-GSGmailLabel -Name Label1
 
    Adds the label "Label1" to the AdminEmail
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true, Position = 0)]
        [string]
        $Name,
        [parameter(Mandatory = $false)]
        [ValidateSet('labelHide','labelShow','labelShowIfUnread')]
        [string]
        $LabelListVisibility = "labelShow",
        [parameter(Mandatory = $false)]
        [ValidateSet('show','hide')]
        [string]
        $MessageListVisibility = "show",
        [parameter(Mandatory = $false)]
        [ValidateSet('Amethyst','BananaMania','Bermuda','BilobaFlower','Black','BlueRomance','BrandyPunch','BurntSienna','Cadillac','Camelot','CeruleanBlue','ChathamsBlue','Concrete','CornflowerBlue','CreamCan','Cupid','DeepBlush','Desert','DoveGray','DustyGray','Eucalyptus','Flesh','FringyFlower','Gallery','Goldenrod','Illusion','Jewel','Koromiko','LightCornflowerBlue','LightMoonRaker','LightMountainMeadow','LightShamrock','LuxorGold','MandysPink','MediumPurple','Meteorite','MoonRaker','MountainMeadow','Oasis','OceanGreen','OldGold','Perano','PersianPink','PigPink','Pueblo','RedOrange','RoyalBlue','RoyalPurple','Salem','Salomie','SeaPink','Shamrock','Silver','Tabasco','Tequila','Thunderbird','TropicalBlue','TulipTree','Tundora','VistaBlue','Watercourse','WaterLeaf','White','YellowOrange')]
        [string]
        $BackgroundColor,
        [parameter(Mandatory = $false)]
        [ValidateSet('Amethyst','BananaMania','Bermuda','BilobaFlower','Black','BlueRomance','BrandyPunch','BurntSienna','Cadillac','Camelot','CeruleanBlue','ChathamsBlue','Concrete','CornflowerBlue','CreamCan','Cupid','DeepBlush','Desert','DoveGray','DustyGray','Eucalyptus','Flesh','FringyFlower','Gallery','Goldenrod','Illusion','Jewel','Koromiko','LightCornflowerBlue','LightMoonRaker','LightMountainMeadow','LightShamrock','LuxorGold','MandysPink','MediumPurple','Meteorite','MoonRaker','MountainMeadow','Oasis','OceanGreen','OldGold','Perano','PersianPink','PigPink','Pueblo','RedOrange','RoyalBlue','RoyalPurple','Salem','Salomie','SeaPink','Shamrock','Silver','Tabasco','Tequila','Thunderbird','TropicalBlue','TulipTree','Tundora','VistaBlue','Watercourse','WaterLeaf','White','YellowOrange')]
        [string]
        $TextColor,
        [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail", "UserKey", "Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        $colorDict = @{
            Amethyst            = '#8e63ce'
            BananaMania         = '#fce8b3'
            Bermuda             = '#68dfa9'
            BilobaFlower        = '#b694e8'
            Black               = '#000000'
            BlueRomance         = '#c6f3de'
            BrandyPunch         = '#cf8933'
            BurntSienna         = '#e66550'
            Cadillac            = '#b65775'
            Camelot             = '#83334c'
            CeruleanBlue        = '#285bac'
            ChathamsBlue        = '#1c4587'
            Concrete            = '#f3f3f3'
            CornflowerBlue      = '#4a86e8'
            LightCornflowerBlue = '#6d9eeb'
            CreamCan            = '#f2c960'
            Cupid               = '#fbc8d9'
            DeepBlush           = '#e07798'
            Desert              = '#a46a21'
            DoveGray            = '#666666'
            DustyGray           = '#999999'
            Eucalyptus          = '#2a9c68'
            Flesh               = '#ffd6a2'
            FringyFlower        = '#b9e4d0'
            Gallery             = '#efefef'
            Goldenrod           = '#fad165'
            Illusion            = '#f7a7c0'
            Jewel               = '#1a764d'
            Koromiko            = '#ffbc6b'
            LightMountainMeadow = '#16a766'
            LightShamrock       = '#43d692'
            LuxorGold           = '#aa8831'
            MandysPink          = '#f6c5be'
            MediumPurple        = '#a479e2'
            Meteorite           = '#41236d'
            MoonRaker           = '#d0bcf1'
            LightMoonRaker      = '#e4d7f5'
            MountainMeadow      = '#149e60'
            Oasis               = '#fef1d1'
            OceanGreen          = '#44b984'
            OldGold             = '#d5ae49'
            Perano              = '#a4c2f4'
            PersianPink         = '#f691b3'
            PigPink             = '#fcdee8'
            Pueblo              = '#822111'
            RedOrange           = '#fb4c2f'
            RoyalBlue           = '#3c78d8'
            RoyalPurple         = '#653e9b'
            Salem               = '#0b804b'
            Salomie             = '#fcda83'
            SeaPink             = '#efa093'
            Shamrock            = '#3dc789'
            Silver              = '#cccccc'
            Tabasco             = '#ac2b16'
            Tequila             = '#ffe6c7'
            Thunderbird         = '#cc3a21'
            TropicalBlue        = '#c9daf8'
            TulipTree           = '#eaa041'
            Tundora             = '#434343'
            VistaBlue           = '#89d3b2'
            Watercourse         = '#076239'
            WaterLeaf           = '#a0eac9'
            White               = '#ffffff'
            YellowOrange        = '#ffad47'
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }

    Process {
        $body = New-Object 'Google.Apis.Gmail.v1.Data.Label'
        foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
            $body.$prop = $PSBoundParameters[$prop]
        }
        if ($PSBoundParameters.Keys -contains 'BackgroundColor' -or $PSBoundParameters.Keys -contains 'TextColor') {
            $color = New-Object 'Google.Apis.Gmail.v1.Data.LabelColor'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$color.PSObject.Properties.Name -contains $_}) {
                $color.$prop = $colorDict[$PSBoundParameters[$prop]]
            }
            $body.Color = $color
        }
        try {
            Write-Verbose "Creating Label '$Name' for user '$User'"
            $request = $service.Users.Labels.Create($body, $User)
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}

Export-ModuleMember -Function 'New-GSGmailLabel'

function New-GSGmailSMIMEInfo {
    <#
    .SYNOPSIS
    Adds Gmail S/MIME info
     
    .DESCRIPTION
    Adds Gmail S/MIME info
     
    .PARAMETER SendAsEmail
    The email address that appears in the "From:" header for mail sent using this alias.
     
    .PARAMETER Pkcs12
    PKCS#12 format containing a single private/public key pair and certificate chain. This format is only accepted from client for creating a new SmimeInfo and is never returned, because the private key is not intended to be exported. PKCS#12 may be encrypted, in which case encryptedKeyPassword should be set appropriately.
     
    .PARAMETER EncryptedKeyPassword
    Encrypted key password, when key is encrypted.
 
    .PARAMETER IsDefault
    Whether this SmimeInfo is the default one for this user's send-as address.
     
    .PARAMETER User
    The user's email address
     
    .EXAMPLE
    New-GSGmailSMIMEInfo -SendAsEmail 'joe@otherdomain.com' -Pkcs12 .\MyCert.pfx -User joe@domain.com
 
    Creates a specified S/MIME for Joe's SendAsEmail 'joe@otherdomain.com' using the provided PKCS12 certificate
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [string]
        $SendAsEmail,
        [parameter(Mandatory = $true)]
        [ValidateScript({
            if (!(Test-Path $_)) {
                throw "Please enter a valid file path."
            }
            elseif ($_ -notlike "*.pfx" -and $_ -notlike "*.p12") {
                throw "Pkcs12 must be a .pfx or .p12 file"
            }
            else {
                $true
            }
        })]
        [string]
        $Pkcs12,
        [parameter(Mandatory = $false)]
        [SecureString]
        $EncryptedKeyPassword,
        [parameter(Mandatory = $false)]
        [Switch]
        $IsDefault,
        [parameter(Mandatory = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Gmail.v1.Data.SmimeInfo'
            foreach ($key in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($key) {
                    EncryptedKeyPassword {
                        $body.$key = (New-Object PSCredential "user",$PSBoundParameters[$key]).GetNetworkCredential().Password
                    }
                    Pkcs12 {
                        $p12String = Convert-Base64 -From NormalString -To WebSafeBase64String -String "$([System.IO.File]::ReadAllText((Resolve-Path $PSBoundParameters[$key]).Path))"
                        $body.$key = $p12String
                    }
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            Write-Verbose "Adding new S/MIME of SendAsEmail '$SendAsEmail' for user '$User' using Certificate '$Pkcs12'"
            $request = $service.Users.Settings.SendAs.SmimeInfo.Insert($body,$User,$SendAsEmail)
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSGmailSMIMEInfo'

function Remove-GSGmailFilter {
    <#
    .SYNOPSIS
    Removes a Gmail filter
     
    .DESCRIPTION
    Removes a Gmail filter
     
    .PARAMETER User
    The primary email of the user to remove the filter from
 
    Defaults to the AdminEmail user
     
    .PARAMETER FilterId
    The unique Id of the filter to remove
     
    .PARAMETER Raw
    If $true, returns the raw response. If not passed or -Raw:$false, response is formatted as a flat object for readability
     
    .EXAMPLE
    Remove-GSGmailFilter -FilterId ANe1Bmj5l3089jd3k1eQbY90g9rXswjS03LVOw
 
    Removes the Filter from the AdminEmail user after confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("Id")]
        [string[]]
        $FilterId,
        [parameter(Mandatory = $false)]
        [switch]
        $Raw
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($fil in $FilterId) {
                if ($PSCmdlet.ShouldProcess("Deleting Filter Id '$fil' from user '$User'")) {
                    Write-Verbose "Deleting Filter Id '$fil' from user '$User'"
                    $request = $service.Users.Settings.Filters.Delete($User,$fil)
                    $request.Execute()
                    Write-Verbose "Filter Id '$fil' deleted successfully from user '$User'"
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSGmailFilter'

function Remove-GSGmailLabel {
    <#
    .SYNOPSIS
    Removes a Gmail label
 
    .DESCRIPTION
    Removes a Gmail label
 
    .PARAMETER LabelId
    The unique Id of the label to remove
 
    .PARAMETER User
    The primary email of the user to remove the label from
 
    Defaults to the AdminEmail user
 
    .EXAMPLE
    Remove-GSGmailLabel -LabelId ANe1Bmj5l3089jd3k1eQbY90g9rXswjS03LVOw
 
    Removes the Label from the AdminEmail user after confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias("Id")]
        [string[]]
        $LabelId,
        [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail", "UserKey", "Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($label in $LabelId) {
            try {
                if ($PSCmdlet.ShouldProcess("Deleting Label Id '$label' from user '$User'")) {
                    Write-Verbose "Deleting Label Id '$label' from user '$User'"
                    $request = $service.Users.Labels.Delete($User, $label)
                    $request.Execute()
                    Write-Verbose "Label Id '$label' deleted successfully from user '$User'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSGmailLabel'

function Remove-GSGmailMessage {
    <#
    .SYNOPSIS
    Removes a Gmail message from the user
 
    .DESCRIPTION
    Removes a Gmail message from the user
 
    .PARAMETER Id
    The Id of the message to remove
 
    .PARAMETER Filter
    The Gmail query to pull the list of messages to remove instead of passing the MessageId directly
 
    .PARAMETER MaxToModify
    The maximum amount of emails you would like to remove. Use this with the `Filter` parameter as a safeguard.
 
    .PARAMETER Method
    The method used to delete the message
 
    Available values are:
    * "Trash": moves the message to the TRASH label (Default - preferred method, as this is recoverable)
    * "Delete": permanently deletes the message (NON-RECOVERABLE!)
 
    Default value is 'Trash'
 
    .PARAMETER User
    The primary email of the user to remove the message from
 
    Defaults to the AdminEmail user
 
    .EXAMPLE
    Remove-GSGmailMessage -User joe -Id 161622d7b76b7e1e,1616227c34d435f2
 
    Moves the 2 message Id's from Joe's inbox into their TRASH after confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High",DefaultParameterSetName = "MessageId")]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "MessageId")]
        [Alias('MessageId')]
        [String[]]
        $Id,
        [parameter(Mandatory = $true, ParameterSetName = "Filter")]
        [Alias('Query')]
        [string]
        $Filter,
        [parameter(Mandatory = $false,ParameterSetName = "Filter")]
        [int]
        $MaxToModify,
        [parameter(Mandatory = $false)]
        [ValidateSet('Trash','Delete')]
        [String]
        $Method = 'Trash',
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Process {
        if ($MyInvocation.InvocationName -eq 'Move-GSGmailMessageToTrash') {
            $Method = 'Trash'
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $msgId = switch ($PSCmdlet.ParameterSetName) {
            MessageId {
                $Id
            }
            Filter {
                (Get-GSGmailMessageList -Filter $Filter -User $User).Id
            }
        }
        if ($PSBoundParameters.Keys -contains 'MaxToModify' -and $msgId.Count -gt $MaxToModify) {
            Write-Error "MaxToModify is set to $MaxToModify but total modifications are $($msgId.Count). No action taken."
        }
        else {
            $service = New-GoogleService @serviceParams
            try {
                foreach ($mId in $msgId) {
                    $request = switch ($Method) {
                        Trash {
                            $service.Users.Messages.Trash($User,$mId)
                            $message = "moved to TRASH"
                        }
                        Delete {
                            $service.Users.Messages.Delete($User,$mId)
                            $message = "deleted"
                        }
                    }
                    if ($PSCmdlet.ShouldProcess("Removing Message Id '$mId' for user '$User'")) {
                        Write-Verbose "Removing Message Id '$mId' for user '$User'"
                        $res = $request.Execute()
                        if ($res) {
                            $res | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
                        }
                        Write-Verbose "Message ID '$mId' successfully $message for user '$User'"
                    }
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Remove-GSGmailMessage'

function Remove-GSGmailSMIMEInfo {
    <#
    .SYNOPSIS
    Removes Gmail S/MIME info
     
    .DESCRIPTION
    Removes Gmail S/MIME info
     
    .PARAMETER SendAsEmail
    The email address that appears in the "From:" header for mail sent using this alias.
     
    .PARAMETER Id
    The immutable ID for the SmimeInfo
     
    .PARAMETER User
    The user's email address
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    Remove-GSGmailSMIMEInfo -SendAsEmail 'joe@otherdomain.com' -Id 1008396210820120578939 -User joe@domain.com
 
    Removes the specified S/MIME info for Joe's SendAsEmail 'joe@otherdomain.com'.
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [string]
        $SendAsEmail,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $Id,
        [parameter(Mandatory = $false)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($I in $Id) {
            try {
                if ($PSCmdlet.ShouldProcess("Removing S/MIME Id '$I' of SendAsEmail '$SendAsEmail' for user '$User'")) {
                    $request = $service.Users.Settings.SendAs.SmimeInfo.Delete($User,$SendAsEmail,$I)
                    $request.Execute()
                    Write-Verbose "Successfully removed S/MIME Id '$I' of SendAsEmail '$SendAsEmail' for user '$User'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSGmailSMIMEInfo'

function Restore-GSGmailMessage {
    <#
    .SYNOPSIS
    Restores a trashed message to the inbox
     
    .DESCRIPTION
    Restores a trashed message to the inbox
     
    .PARAMETER User
    The primary email of the user to restore the message for
 
    Defaults to the AdminEmail user
     
    .PARAMETER Id
    The Id of the message to restore
     
    .EXAMPLE
    Restore-GSGmailMessage -User joe -Id 161622d7b76b7e1e,1616227c34d435f2
 
    Restores the 2 message Id's from Joe's TRASH back to their inbox
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('MessageID')]
        [String[]]
        $Id
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($mId in $Id) {
                $request = $service.Users.Messages.Untrash($User,$mId)
                Write-Verbose "Removing Message Id '$mId' from TRASH for user '$User'"
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Restore-GSGmailMessage'

function Send-GmailMessage {
    <#
    .SYNOPSIS
    Sends a Gmail message
     
    .DESCRIPTION
    Sends a Gmail message. Designed for parity with Send-GmailMessage
     
    .PARAMETER From
    The primary email of the user that is sending the message. This MUST be a user account owned by the customer, as the Gmail Service must be built under this user's context and will fail if a group or alias is passed instead
 
    Defaults to the AdminEmail user
     
    .PARAMETER Subject
    The subject of the email
     
    .PARAMETER Body
    The email body. Supports HTML when used in conjunction with the -BodyAsHtml parameter
     
    .PARAMETER To
    The To recipient(s) of the email
     
    .PARAMETER CC
    The Cc recipient(s) of the email
     
    .PARAMETER BCC
    The Bcc recipient(s) of the email
     
    .PARAMETER Attachments
    The attachment(s) of the email
     
    .PARAMETER BodyAsHtml
    If passed, renders the HTML content of the body on send
     
    .EXAMPLE
    Send-GmailMessage -From Joe -To john.doe@domain.com -Subject "New Pricing Models" -Body $body -BodyAsHtml -Attachments 'C:\Reports\PricingModel_2018.xlsx'
 
    Sends a message from Joe to john.doe@domain.com with HTML body and an Excel spreadsheet attached
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String]
        $From = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $true)]
        [string]
        $Subject,
        [parameter(Mandatory = $false)]
        [string]
        $Body,
        [parameter(Mandatory = $false)]
        [string[]]
        $To,
        [parameter(Mandatory = $false)]
        [string[]]
        $CC,
        [parameter(Mandatory = $false)]
        [string[]]
        $BCC,
        [parameter(Mandatory = $false)]
        [ValidateScript( {Test-Path $_})]
        [string[]]
        $Attachments,
        [parameter(Mandatory = $false)]
        [switch]
        $BodyAsHtml
    )
    Process {
        $User = $From -replace ".*<","" -replace ">",""
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
            $From = $User
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
            $From = $User
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
        $messageParams = @{
            From                     = $From
            Subject                  = $Subject
            ReturnConstructedMessage = $true
        }
        if ($To) {
            $messageParams.Add("To",@($To))
        }
        if ($Body) {
            $messageParams.Add("Body",$Body)
        }
        if ($CC) {
            $messageParams.Add("CC",@($CC))
        }
        if ($BCC) {
            $messageParams.Add("BCC",@($BCC))
        }
        if ($Attachments) {
            $messageParams.Add("Attachment",@($Attachments)) 
        }
        if ($BodyAsHtml) {
            $messageParams.Add("BodyAsHtml",$true)
        }
        $raw = New-MimeMessage @messageParams | Convert-Base64 -From NormalString -To WebSafeBase64String
        try {
            $bodySend = New-Object 'Google.Apis.Gmail.v1.Data.Message' -Property @{
                Raw = $raw
            }
            $request = $service.Users.Messages.Send($bodySend,$User)
            Write-Verbose "Sending Message '$Subject' from user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Send-GmailMessage'

function Update-GSGmailAutoForwardingSettings {
    <#
    .SYNOPSIS
    Updates the auto-forwarding setting for the specified account. A verified forwarding address must be specified when auto-forwarding is enabled.
     
    .DESCRIPTION
    Updates the auto-forwarding setting for the specified account. A verified forwarding address must be specified when auto-forwarding is enabled.
     
    .PARAMETER User
    The user to update the AutoForwarding settings for
     
    .PARAMETER Disposition
    The state that a message should be left in after it has been forwarded.
 
    Acceptable values are:
    * "archive": Archive the message.
    * "dispositionUnspecified": Unspecified disposition.
    * "leaveInInbox": Leave the message in the INBOX.
    * "markRead": Leave the message in the INBOX and mark it as read.
    * "trash": Move the message to the TRASH.
     
    .PARAMETER EmailAddress
    Email address to which all incoming messages are forwarded. This email address must be a verified member of the forwarding addresses.
     
    .PARAMETER Enabled
    Whether all incoming mail is automatically forwarded to another address.
     
    .EXAMPLE
    Update-GSGmailAutoForwardingSettings -User me -Disposition leaveInInbox -EmailAddress joe@domain.com -Enabled
 
    Enables auto forwarding of all mail for the AdminEmail user. Forwarded mail will be left in their inbox.
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User,
        [parameter(Mandatory = $false)]
        [ValidateSet('archive','dispositionUnspecified','leaveInInbox','markRead','trash')]
        [string]
        $Disposition,
        [parameter(Mandatory = $false)]
        [string]
        $EmailAddress,
        [parameter(Mandatory = $false)]
        [switch]
        $Enabled
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.sharing'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Gmail.v1.Data.AutoForwarding'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                $body.$prop = $PSBoundParameters[$prop]
            }
            $request = $service.Users.Settings.UpdateAutoForwarding($body,$User)
            Write-Verbose "Updating AutoForwarding settings for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSGmailAutoForwardingSettings'

function Update-GSGmailImapSettings {
    <#
    .SYNOPSIS
    Updates IMAP settings
     
    .DESCRIPTION
    Updates IMAP settings
     
    .PARAMETER User
    The user to update the IMAP settings for
     
    .PARAMETER AutoExpunge
    If this value is true, Gmail will immediately expunge a message when it is marked as deleted in IMAP. Otherwise, Gmail will wait for an update from the client before expunging messages marked as deleted.
     
    .PARAMETER Enabled
    Whether IMAP is enabled for the account.
     
    .PARAMETER ExpungeBehavior
    The action that will be executed on a message when it is marked as deleted and expunged from the last visible IMAP folder.
 
    Acceptable values are:
    * "archive": Archive messages marked as deleted.
    * "deleteForever": Immediately and permanently delete messages marked as deleted. The expunged messages cannot be recovered.
    * "expungeBehaviorUnspecified": Unspecified behavior.
    * "trash": Move messages marked as deleted to the trash.
     
    .PARAMETER MaxFolderSize
    An optional limit on the number of messages that an IMAP folder may contain. Legal values are 0, 1000, 2000, 5000 or 10000. A value of zero is interpreted to mean that there is no limit.
     
    .EXAMPLE
    Update-GSGmailImapSettings -Enabled:$false -User me
 
    Disables IMAP for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User,
        [parameter(Mandatory = $false)]
        [switch]
        $AutoExpunge,
        [parameter(Mandatory = $false)]
        [switch]
        $Enabled,
        [parameter(Mandatory = $false)]
        [ValidateSet('archive','deleteForever','expungeBehaviorUnspecified','trash')]
        [string]
        $ExpungeBehavior,
        [parameter(Mandatory = $false)]
        [ValidateSet(0,1000,2000,5000,10000)]
        [int]
        $MaxFolderSize
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Gmail.v1.Data.ImapSettings'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                $body.$prop = $PSBoundParameters[$prop]
            }
            $request = $service.Users.Settings.UpdateImap($body,$User)
            Write-Verbose "Updating IMAP settings for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSGmailImapSettings'

function Update-GSGmailLabel {
    <#
    .SYNOPSIS
    Updates Gmail label information for the specified labelid
 
    .DESCRIPTION
    Updates Gmail label information for the specified labelid
 
    .PARAMETER LabelId
    The unique Id of the label to update
 
    .PARAMETER LabelListVisibility
    The visibility of the label in the label list in the Gmail web interface.
 
    Acceptable values are:
    * "labelHide": Do not show the label in the label list.
    * "labelShow": Show the label in the label list. (Default)
    * "labelShowIfUnread": Show the label if there are any unread messages with that label.
 
    .PARAMETER MessageListVisibility
    The visibility of messages with this label in the message list in the Gmail web interface.
 
    Acceptable values are:
    * "hide": Do not show the label in the message list.
    * "show": Show the label in the message list. (Default)
 
    .PARAMETER Name
    The display name of the label
 
    .PARAMETER BackgroundColor
    The background color of the label
 
    Options and their corresponding hex code:
 
        Amethyst = '#8e63ce'
        BananaMania = '#fce8b3'
        Bermuda = '#68dfa9'
        BilobaFlower = '#b694e8'
        Black = '#000000'
        BlueRomance = '#c6f3de'
        BrandyPunch = '#cf8933'
        BurntSienna = '#e66550'
        Cadillac = '#b65775'
        Camelot = '#83334c'
        CeruleanBlue = '#285bac'
        ChathamsBlue = '#1c4587'
        Concrete = '#f3f3f3'
        CornflowerBlue = '#4a86e8'
        LightCornflowerBlue = '#6d9eeb'
        CreamCan = '#f2c960'
        Cupid = '#fbc8d9'
        DeepBlush = '#e07798'
        Desert = '#a46a21'
        DoveGray = '#666666'
        DustyGray = '#999999'
        Eucalyptus = '#2a9c68'
        Flesh = '#ffd6a2'
        FringyFlower = '#b9e4d0'
        Gallery = '#efefef'
        Goldenrod = '#fad165'
        Illusion = '#f7a7c0'
        Jewel = '#1a764d'
        Koromiko = '#ffbc6b'
        LightMountainMeadow = '#16a766'
        LightShamrock = '#43d692'
        LuxorGold = '#aa8831'
        MandysPink = '#f6c5be'
        MediumPurple = '#a479e2'
        Meteorite = '#41236d'
        MoonRaker = '#d0bcf1'
        LightMoonRaker = '#e4d7f5'
        MountainMeadow = '#149e60'
        Oasis = '#fef1d1'
        OceanGreen = '#44b984'
        OldGold = '#d5ae49'
        Perano = '#a4c2f4'
        PersianPink = '#f691b3'
        PigPink = '#fcdee8'
        Pueblo = '#822111'
        RedOrange = '#fb4c2f'
        RoyalBlue = '#3c78d8'
        RoyalPurple = '#653e9b'
        Salem = '#0b804b'
        Salomie = '#fcda83'
        SeaPink = '#efa093'
        Shamrock = '#3dc789'
        Silver = '#cccccc'
        Tabasco = '#ac2b16'
        Tequila = '#ffe6c7'
        Thunderbird = '#cc3a21'
        TropicalBlue = '#c9daf8'
        TulipTree = '#eaa041'
        Tundora = '#434343'
        VistaBlue = '#89d3b2'
        Watercourse = '#076239'
        WaterLeaf = '#a0eac9'
        White = '#ffffff'
        YellowOrange = '#ffad47'
 
    .PARAMETER TextColor
    The text color of the label
 
    Options and their corresponding hex code:
 
        Amethyst = '#8e63ce'
        BananaMania = '#fce8b3'
        Bermuda = '#68dfa9'
        BilobaFlower = '#b694e8'
        Black = '#000000'
        BlueRomance = '#c6f3de'
        BrandyPunch = '#cf8933'
        BurntSienna = '#e66550'
        Cadillac = '#b65775'
        Camelot = '#83334c'
        CeruleanBlue = '#285bac'
        ChathamsBlue = '#1c4587'
        Concrete = '#f3f3f3'
        CornflowerBlue = '#4a86e8'
        LightCornflowerBlue = '#6d9eeb'
        CreamCan = '#f2c960'
        Cupid = '#fbc8d9'
        DeepBlush = '#e07798'
        Desert = '#a46a21'
        DoveGray = '#666666'
        DustyGray = '#999999'
        Eucalyptus = '#2a9c68'
        Flesh = '#ffd6a2'
        FringyFlower = '#b9e4d0'
        Gallery = '#efefef'
        Goldenrod = '#fad165'
        Illusion = '#f7a7c0'
        Jewel = '#1a764d'
        Koromiko = '#ffbc6b'
        LightMountainMeadow = '#16a766'
        LightShamrock = '#43d692'
        LuxorGold = '#aa8831'
        MandysPink = '#f6c5be'
        MediumPurple = '#a479e2'
        Meteorite = '#41236d'
        MoonRaker = '#d0bcf1'
        LightMoonRaker = '#e4d7f5'
        MountainMeadow = '#149e60'
        Oasis = '#fef1d1'
        OceanGreen = '#44b984'
        OldGold = '#d5ae49'
        Perano = '#a4c2f4'
        PersianPink = '#f691b3'
        PigPink = '#fcdee8'
        Pueblo = '#822111'
        RedOrange = '#fb4c2f'
        RoyalBlue = '#3c78d8'
        RoyalPurple = '#653e9b'
        Salem = '#0b804b'
        Salomie = '#fcda83'
        SeaPink = '#efa093'
        Shamrock = '#3dc789'
        Silver = '#cccccc'
        Tabasco = '#ac2b16'
        Tequila = '#ffe6c7'
        Thunderbird = '#cc3a21'
        TropicalBlue = '#c9daf8'
        TulipTree = '#eaa041'
        Tundora = '#434343'
        VistaBlue = '#89d3b2'
        Watercourse = '#076239'
        WaterLeaf = '#a0eac9'
        White = '#ffffff'
        YellowOrange = '#ffad47'
 
    .PARAMETER User
    The user to update label information for
 
    Defaults to the AdminEmail user
 
    .EXAMPLE
    Update-GSGmailLabel -User user@domain.com -LabelId Label_79 -BackgroundColor Black -TextColor Bermuda
 
    Updates the specified Gmail label with new background and text colors
 
    .EXAMPLE
    Get-GSGmailLabel | Where-Object {$_.LabelListVisibility -eq 'labelShowIfUnread'} | Update-GSGmailLabel -LabelListVisibility labelShow -BackgroundColor Bermuda -TextColor Tundora
 
    Updates all labels with LabelListVisibility of 'labelShowIfUnread' with new background and text colors and sets all of them to always show
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias("Id")]
        [string[]]
        $LabelId,
        [parameter(Mandatory = $false, Position = 1)]
        [ValidateSet("labelHide","labelShow","labelShowIfUnread")]
        [string]
        $LabelListVisibility,
        [parameter(Mandatory = $false, Position = 2)]
        [ValidateSet("hide","show")]
        [string]
        $MessageListVisibility,
        [parameter(Mandatory = $false, Position = 3)]
        [string]
        $Name,
        [parameter(Mandatory = $false)]
        [ValidateSet('Amethyst','BananaMania','Bermuda','BilobaFlower','Black','BlueRomance','BrandyPunch','BurntSienna','Cadillac','Camelot','CeruleanBlue','ChathamsBlue','Concrete','CornflowerBlue','CreamCan','Cupid','DeepBlush','Desert','DoveGray','DustyGray','Eucalyptus','Flesh','FringyFlower','Gallery','Goldenrod','Illusion','Jewel','Koromiko','LightCornflowerBlue','LightMoonRaker','LightMountainMeadow','LightShamrock','LuxorGold','MandysPink','MediumPurple','Meteorite','MoonRaker','MountainMeadow','Oasis','OceanGreen','OldGold','Perano','PersianPink','PigPink','Pueblo','RedOrange','RoyalBlue','RoyalPurple','Salem','Salomie','SeaPink','Shamrock','Silver','Tabasco','Tequila','Thunderbird','TropicalBlue','TulipTree','Tundora','VistaBlue','Watercourse','WaterLeaf','White','YellowOrange')]
        [string]
        $BackgroundColor,
        [parameter(Mandatory = $false)]
        [ValidateSet('Amethyst','BananaMania','Bermuda','BilobaFlower','Black','BlueRomance','BrandyPunch','BurntSienna','Cadillac','Camelot','CeruleanBlue','ChathamsBlue','Concrete','CornflowerBlue','CreamCan','Cupid','DeepBlush','Desert','DoveGray','DustyGray','Eucalyptus','Flesh','FringyFlower','Gallery','Goldenrod','Illusion','Jewel','Koromiko','LightCornflowerBlue','LightMoonRaker','LightMountainMeadow','LightShamrock','LuxorGold','MandysPink','MediumPurple','Meteorite','MoonRaker','MountainMeadow','Oasis','OceanGreen','OldGold','Perano','PersianPink','PigPink','Pueblo','RedOrange','RoyalBlue','RoyalPurple','Salem','Salomie','SeaPink','Shamrock','Silver','Tabasco','Tequila','Thunderbird','TropicalBlue','TulipTree','Tundora','VistaBlue','Watercourse','WaterLeaf','White','YellowOrange')]
        [string]
        $TextColor,
        [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail", "UserKey", "Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        $colorDict = @{
            Amethyst            = '#8e63ce'
            BananaMania         = '#fce8b3'
            Bermuda             = '#68dfa9'
            BilobaFlower        = '#b694e8'
            Black               = '#000000'
            BlueRomance         = '#c6f3de'
            BrandyPunch         = '#cf8933'
            BurntSienna         = '#e66550'
            Cadillac            = '#b65775'
            Camelot             = '#83334c'
            CeruleanBlue        = '#285bac'
            ChathamsBlue        = '#1c4587'
            Concrete            = '#f3f3f3'
            CornflowerBlue      = '#4a86e8'
            LightCornflowerBlue = '#6d9eeb'
            CreamCan            = '#f2c960'
            Cupid               = '#fbc8d9'
            DeepBlush           = '#e07798'
            Desert              = '#a46a21'
            DoveGray            = '#666666'
            DustyGray           = '#999999'
            Eucalyptus          = '#2a9c68'
            Flesh               = '#ffd6a2'
            FringyFlower        = '#b9e4d0'
            Gallery             = '#efefef'
            Goldenrod           = '#fad165'
            Illusion            = '#f7a7c0'
            Jewel               = '#1a764d'
            Koromiko            = '#ffbc6b'
            LightMountainMeadow = '#16a766'
            LightShamrock       = '#43d692'
            LuxorGold           = '#aa8831'
            MandysPink          = '#f6c5be'
            MediumPurple        = '#a479e2'
            Meteorite           = '#41236d'
            MoonRaker           = '#d0bcf1'
            LightMoonRaker      = '#e4d7f5'
            MountainMeadow      = '#149e60'
            Oasis               = '#fef1d1'
            OceanGreen          = '#44b984'
            OldGold             = '#d5ae49'
            Perano              = '#a4c2f4'
            PersianPink         = '#f691b3'
            PigPink             = '#fcdee8'
            Pueblo              = '#822111'
            RedOrange           = '#fb4c2f'
            RoyalBlue           = '#3c78d8'
            RoyalPurple         = '#653e9b'
            Salem               = '#0b804b'
            Salomie             = '#fcda83'
            SeaPink             = '#efa093'
            Shamrock            = '#3dc789'
            Silver              = '#cccccc'
            Tabasco             = '#ac2b16'
            Tequila             = '#ffe6c7'
            Thunderbird         = '#cc3a21'
            TropicalBlue        = '#c9daf8'
            TulipTree           = '#eaa041'
            Tundora             = '#434343'
            VistaBlue           = '#89d3b2'
            Watercourse         = '#076239'
            WaterLeaf           = '#a0eac9'
            White               = '#ffffff'
            YellowOrange        = '#ffad47'
        }
    }
    Process {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
        $body = New-Object 'Google.Apis.Gmail.v1.Data.Label'
        foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
            $body.$prop = $PSBoundParameters[$prop]
        }
        if ($PSBoundParameters.Keys -contains 'BackgroundColor' -or $PSBoundParameters.Keys -contains 'TextColor') {
            $color = New-Object 'Google.Apis.Gmail.v1.Data.LabelColor'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$color.PSObject.Properties.Name -contains $_}) {
                $color.$prop = $colorDict[$PSBoundParameters[$prop]]
            }
            $body.Color = $color
        }
        foreach ($label in $LabelId) {
            try {
                Write-Verbose "Updating Label Id '$label' for user '$User'"
                $request = $service.Users.Labels.Patch($body, $User, $label)
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Update-GSGmailLabel'

function Update-GSGmailMessageLabels {
    <#
    .SYNOPSIS
    Updates Gmail label information for the specified message
 
    .DESCRIPTION
    Updates Gmail label information for the specified message
 
    .PARAMETER MessageId
    The unique Id of the message to update.
 
    .PARAMETER Filter
    The Gmail query to pull the list of messages to update instead of passing the MessageId directly.
 
    .PARAMETER MaxToModify
    The maximum amount of emails you would like to remove. Use this with the `Filter` parameter as a safeguard.
 
    .PARAMETER AddLabel
    The label(s) to add to the message. This supports either the unique LabelId or the Display Name for the label
 
    .PARAMETER RemoveLabel
    The label(s) to remove from the message. This supports either the unique LabelId or the Display Name for the label
 
    .PARAMETER User
    The user to update message labels for
 
    Defaults to the AdminEmail user
 
    .EXAMPLE
    Set-GSGmailLabel -user user@domain.com -LabelId Label_798170282134616520 -
 
    Gets the Gmail labels of the AdminEmail user
    #>

    [cmdletbinding(DefaultParameterSetName = "MessageId")]
    Param
    (
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "MessageId")]
        [Alias("Id")]
        [string[]]
        $MessageId,
        [parameter(Mandatory = $true, ParameterSetName = "Filter")]
        [Alias('Query')]
        [string]
        $Filter,
        [parameter(Mandatory = $false,ParameterSetName = "Filter")]
        [int]
        $MaxToModify,
        [parameter(Mandatory = $false)]
        [string[]]
        $AddLabel,
        [parameter(Mandatory = $false)]
        [string[]]
        $RemoveLabel,
        [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail", "UserKey", "Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User = $Script:PSGSuite.AdminEmail
    )
    Process {
        if ($PSBoundParameters.Keys -notcontains 'AddLabel' -and $PSBoundParameters.Keys -notcontains 'RemoveLabel') {
            throw "You must specify a value for either AddLabel or RemoveLabel!"
        }
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://mail.google.com'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $msgId = switch ($PSCmdlet.ParameterSetName) {
            MessageId {
                $MessageId
            }
            Filter {
                (Get-GSGmailMessageList -Filter $Filter -User $User).Id
            }
        }
        if ($PSBoundParameters.Keys -contains 'MaxToModify' -and $msgId.Count -gt $MaxToModify) {
            Write-Error "MaxToModify is set to $MaxToModify but total modifications are $($msgId.Count). No action taken."
        }
        else {
            $service = New-GoogleService @serviceParams
            $userLabels = @{}
            Get-GSGmailLabel -User $User -Verbose:$false | ForEach-Object {
                $userLabels[$_.Name] = $_.Id
            }
            $body = New-Object 'Google.Apis.Gmail.v1.Data.ModifyMessageRequest'
            if ($PSBoundParameters.Keys -contains 'AddLabel') {
                $addLs = New-Object 'System.Collections.Generic.List[System.String]'
                foreach ($label in $AddLabel) {
                    try {
                        $addLs.Add($userLabels[$label])
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
                $body.AddLabelIds = $addLs
            }
            if ($PSBoundParameters.Keys -contains 'RemoveLabel') {
                $remLs = New-Object 'System.Collections.Generic.List[System.String]'
                foreach ($label in $RemoveLabel) {
                    try {
                        $remLs.Add($userLabels[$label])
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
                $body.RemoveLabelIds = $remLs
            }
            foreach ($message in $msgId) {
                try {
                    $request = $service.Users.Messages.Modify($body, $User, $message)
                    Write-Verbose "Updating Labels on Message '$message' for user '$User'"
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Update-GSGmailMessageLabels'

function Update-GSGmailPopSettings {
    <#
    .SYNOPSIS
    Updates POP settings
     
    .DESCRIPTION
    Updates POP settings
     
    .PARAMETER User
    The user to update the POP settings for
     
    .PARAMETER AccessWindow
    The range of messages which are accessible via POP.
 
    Acceptable values are:
    * "accessWindowUnspecified": Unspecified range.
    * "allMail": Indicates that all unfetched messages are accessible via POP.
    * "disabled": Indicates that no messages are accessible via POP.
    * "fromNowOn": Indicates that unfetched messages received after some past point in time are accessible via POP.
     
    .PARAMETER Disposition
    The action that will be executed on a message after it has been fetched via POP.
 
    Acceptable values are:
    * "archive": Archive the message.
    * "dispositionUnspecified": Unspecified disposition.
    * "leaveInInbox": Leave the message in the INBOX.
    * "markRead": Leave the message in the INBOX and mark it as read.
    * "trash": Move the message to the TRASH.
     
    .EXAMPLE
    Update-GSGmailPopSettings -User me -AccessWindow allMail
 
    Sets the POP AccessWindow to 'allMail' for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User,
        [parameter(Mandatory = $false)]
        [ValidateSet('accessWindowUnspecified','allMail','disabled','fromNowOn')]
        [string]
        $AccessWindow,
        [parameter(Mandatory = $false)]
        [ValidateSet('archive','dispositionUnspecified','leaveInInbox','markRead','trash')]
        [string]
        $Disposition
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Gmail.v1.Data.PopSettings'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                $body.$prop = $PSBoundParameters[$prop]
            }
            $request = $service.Users.Settings.UpdatePop($body,$User)
            Write-Verbose "Updating POP settings for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSGmailPopSettings'

function Update-GSGmailVacationSettings {
    <#
    .SYNOPSIS
    Updates vacation responder settings for the specified account.
     
    .DESCRIPTION
    Updates vacation responder settings for the specified account.
     
    .PARAMETER User
    The user to update the VacationSettings settings for
     
    .PARAMETER EnableAutoReply
    Flag that controls whether Gmail automatically replies to messages.
     
    .PARAMETER EndTime
    An optional end time for sending auto-replies. When this is specified, Gmail will automatically reply only to messages that it receives before the end time. If both startTime and endTime are specified, startTime must precede endTime.
     
    .PARAMETER ResponseBodyHtml
    Response body in HTML format. Gmail will sanitize the HTML before storing it.
     
    .PARAMETER ResponseBodyPlainText
    Response body in plain text format.
     
    .PARAMETER ResponseSubject
    Optional text to prepend to the subject line in vacation responses. In order to enable auto-replies, either the response subject or the response body must be nonempty.
     
    .PARAMETER RestrictToContacts
    Flag that determines whether responses are sent to recipients who are not in the user's list of contacts.
     
    .PARAMETER RestrictToDomain
    Flag that determines whether responses are sent to recipients who are outside of the user's domain. This feature is only available for G Suite users.
     
    .PARAMETER StartTime
    An optional start time for sending auto-replies. When this is specified, Gmail will automatically reply only to messages that it receives after the start time. If both startTime and endTime are specified, startTime must precede endTime.
     
    .EXAMPLE
    Update-GSGmailVacationSettings -User me -ResponseBodyHtml "I'm on vacation and will reply when I'm back in the office. Thanks!" -RestrictToDomain -EndTime (Get-Date).AddDays(7) -StartTime (Get-Date) -EnableAutoReply
 
    Enables the vacation auto-reply for the AdminEmail user. Auto-replies will be sent to other users in the same domain only. The vacation response is enabled for 7 days from the time that the command is sent.
     
    .EXAMPLE
    Update-GSGmailVacationSettings -User me -EnableAutoReply:$false
 
    Disables the vacaction auto-response for the AdminEmail user immediately.
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string]
        $User,
        [parameter(Mandatory = $false)]
        [switch]
        $EnableAutoReply,
        [parameter(Mandatory = $false)]
        [datetime]
        $EndTime,
        [parameter(Mandatory = $false)]
        [string]
        $ResponseBodyHtml,
        [parameter(Mandatory = $false)]
        [string]
        $ResponseBodyPlainText,
        [parameter(Mandatory = $false)]
        [string]
        $ResponseSubject,
        [parameter(Mandatory = $false)]
        [switch]
        $RestrictToContacts,
        [parameter(Mandatory = $false)]
        [switch]
        $RestrictToDomain,
        [parameter(Mandatory = $false)]
        [datetime]
        $StartTime
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/gmail.settings.basic'
            ServiceType = 'Google.Apis.Gmail.v1.GmailService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Gmail.v1.Data.VacationSettings'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    StartTime {
                        $epochMs = (Convert-DateToEpoch $StartTime)
                        $body.$prop = [long]$epochMs
                    }
                    EndTime {
                        $epochMs = (Convert-DateToEpoch $EndTime)
                        $body.$prop = [long]$epochMs
                    }
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            $request = $service.Users.Settings.UpdateVacation($body,$User)
            Write-Verbose "Updating Vacation settings for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSGmailVacationSettings'

function Add-GSGroupMember {
    <#
    .SYNOPSIS
    Adds a list of emails to a target group
     
    .DESCRIPTION
    Adds a list of emails to a target group. Designed for parity with Add-ADGroupMember
     
    .PARAMETER Identity
    The email or GroupID of the target group to add members to
     
    .PARAMETER Member
    The list of user and/or group emails that you would like to add to the target group
     
    .PARAMETER Role
    The role that you would like to add the members as
     
    Defaults to "MEMBER"
     
    .EXAMPLE
    Add-GSGroupMember "admins@domain.com" -Member "joe-admin@domain.com","sally.admin@domain.com"
 
    Adds 2 users to the group "admins@domain.com"
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('GroupEmail','Group','Email')]
        [String]
        $Identity,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)]
        [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Member,
        [parameter(Mandatory = $false)]
        [ValidateSet("MEMBER","MANAGER","OWNER")]
        [String]
        $Role = "MEMBER"
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($Identity -notlike "*@*.*") {
                $Identity = "$($Identity)@$($Script:PSGSuite.Domain)"
            }
            $groupObj = Get-GSGroup -Group $Identity -Verbose:$false
            try {
                foreach ($U in $Member) {
                    if ($U -notlike "*@*.*") {
                        $U = "$($U)@$($Script:PSGSuite.Domain)"
                    }
                    Write-Verbose "Adding '$U' as a $Role of group '$Identity'"
                    $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Member'
                    $body.Email = $U
                    $body.Role = $Role
                    $request = $service.Members.Insert($body,$groupObj.Id)
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $Identity -PassThru
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSGroupMember'

function Add-GSPrincipalGroupMembership {
    <#
    .SYNOPSIS
    Adds the target email to a list of groups
     
    .DESCRIPTION
    Adds the target email to a list of groups. Designed for parity with Add-ADPrincipalGroupMembership
     
    .PARAMETER Identity
    The user or group email that you would like to add to the list of groups
     
    .PARAMETER MemberOf
    The list of groups to add the target email to
     
    .PARAMETER Role
    The role that you would like to add the members as
     
    Defaults to "MEMBER"
     
    .EXAMPLE
    Add-GSPrincipalGroupMembership "joe@domain.com" -MemberOf "admins@domain.com","users@domain.com"
 
    Adds the email "joe@domain.com" to the admins@ and users@ groups
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail","User","Email","UserEmail")]
        [String]
        $Identity,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)]
        [Alias('GroupEmail','Group')]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $MemberOf,
        [parameter(Mandatory = $false)]
        [ValidateSet("MEMBER","MANAGER","OWNER")]
        [String]
        $Role = "MEMBER"
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($Identity -notlike "*@*.*") {
                $Identity = "$($Identity)@$($Script:PSGSuite.Domain)"
            }
            try {
                foreach ($U in $MemberOf) {
                    $groupObj = Get-GSGroup -Group $U -Verbose:$false
                    if ($U -notlike "*@*.*") {
                        $U = "$($U)@$($Script:PSGSuite.Domain)"
                    }
                    Write-Verbose "Adding '$Identity' as a $Role of group '$U'"
                    $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Member'
                    $body.Email = $Identity
                    $body.Role = $Role
                    $request = $service.Members.Insert($body,$groupObj.Id)
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $U -PassThru
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSPrincipalGroupMembership'

function Get-GSGroup {
    <#
    .SYNOPSIS
    Gets the specified group's information. Returns the full group list if -Group is excluded
 
    .DESCRIPTION
    Gets the specified group's information. Returns the full group list if -Group is excluded. Designed for parity with Get-ADGroup (although Google's API is unable to 'Filter' for groups)
 
    .PARAMETER Group
    The list of groups you would like to retrieve info for. If excluded, returns the group list instead
 
    .PARAMETER Filter
    Query string search. Complete documentation is at https://developers.google.com/admin-sdk/directory/v1/guides/search-groups
 
    .PARAMETER Where_IsAMember
    Include a user email here to get the list of groups that user is a member of
 
    .PARAMETER Domain
    The domain name. Use this field to get fields from only one domain. To return groups for all domains you own, exclude this parameter
 
    .PARAMETER Fields
    The fields to return in the response
 
    .PARAMETER PageSize
    Page size of the result set
 
    Defaults to 200
 
    .EXAMPLE
    Get-GSGroup -Where_IsAMember "joe@domain.com"
 
    Gets the list of groups that joe@domain.com is a member of
 
    .EXAMPLE
    Get-GSGroup -Domain mysubdomain.org
 
    Gets the list of groups only for the 'mysubdomain.org' domain.
 
    .EXAMPLE
    Get-GSGroup -Filter "email:support*"
 
    Gets all the groups with emails beginning with 'support'
 
    .EXAMPLE
    Get-GSGroup -Filter "name -eq 'IT HelpDesk'"
 
    Gets the IT HelpDesk group by name using PowerShell syntax. PowerShell syntax is supported as a best effort, please refer to the Group Search documentation from Google for exact syntax.
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")]
        [Alias("Email")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Group,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Alias('Query')]
        [string]
        $Filter,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [String]
        $Where_IsAMember,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [string]
        $Domain,
        [parameter(Mandatory = $false,ParameterSetName = "Get")]
        [String[]]
        $Fields,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateRange(1,200)]
        [Alias("MaxResults")]
        [Int]
        $PageSize = "200"
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($G in $Group) {
                    try {
                        if ($G -notlike "*@*.*") {
                            $G = "$($G)@$($Script:PSGSuite.Domain)"
                        }
                        Write-Verbose "Getting group '$G'"
                        $request = $service.Groups.Get($G)
                        if ($Fields) {
                            $request.Fields = "$($Fields -join ",")"
                        }
                        $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $G -PassThru
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                $verbString = "Getting all G Suite Groups"
                try {
                    $request = $service.Groups.List()
                    if ($PSBoundParameters.Keys -contains 'Where_IsAMember') {
                        if ($Where_IsAMember -ceq "me") {
                            $Where_IsAMember = $Script:PSGSuite.AdminEmail
                        }
                        elseif ($Where_IsAMember -notlike "*@*.*") {
                            $Where_IsAMember = "$($Where_IsAMember)@$($Script:PSGSuite.Domain)"
                        }
                        $verbString += " where '$Where_IsAMember' is a member"
                        $request.UserKey = $Where_IsAMember
                    }
                    if ($PSBoundParameters.Keys -contains 'Filter') {
                        if ($Filter -eq '*') {
                            $Filter = ""
                        }
                        else {
                            $Filter = "$($Filter -join " ")"
                        }
                        $Filter = $Filter -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False"
                        if (-not [String]::IsNullOrEmpty($Filter.Trim())) {
                            $verbString += " matching query '$($Filter.Trim())'"
                            $request.Query = $Filter.Trim()
                        }
                    }
                    if ($PSBoundParameters.Keys -contains 'Domain') {
                        $verbString += " for domain '$Domain'"
                        $request.Domain = $Domain
                    }
                    elseif ( -not [String]::IsNullOrEmpty($Script:PSGSuite.CustomerID)) {
                        $verbString += " for customer '$($Script:PSGSuite.CustomerID)'"
                        $request.Customer = $Script:PSGSuite.CustomerID
                    }
                    else {
                        $verbString += " for customer 'my_customer'"
                        $request.Customer = "my_customer"
                    }
                    if ($PageSize) {
                        $request.MaxResults = $PageSize
                    }
                    Write-Verbose $verbString
                    [int]$i = 1
                    do {
                        $result = $request.Execute()
                        if ($null -ne $result.GroupsValue) {
                            $result.GroupsValue | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Email} -PassThru -Force
                        }
                        $request.PageToken = $result.NextPageToken
                        [int]$retrieved = ($i + $result.GroupsValue.Count) - 1
                        Write-Verbose "Retrieved $retrieved groups..."
                        [int]$i = $i + $result.GroupsValue.Count
                    }
                    until (!$result.NextPageToken)
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSGroup'

function Get-GSGroupAlias {
    <#
    .SYNOPSIS
    Gets the specified G SUite Group's aliases
     
    .DESCRIPTION
    Gets the specified G SUite Group's aliases
     
    .PARAMETER Group
    The primary email or ID of the group who you are trying to get aliases for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
     
    .EXAMPLE
    Get-GSGroupAlias -Group hr
 
    Gets the list of aliases for the group hr@domain.com
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("Email")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Group
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($G in $Group) {
            try {
                if ($G -notlike "*@*.*") {
                    $G = "$($G)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Getting Alias list for Group '$G'"
                $request = $service.Groups.Aliases.List($G)
                $request.Execute() | Select-Object -ExpandProperty AliasesValue
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGroupAlias'

function Get-GSGroupMember {
    <#
    .SYNOPSIS
    Gets the group member list of a target group
 
    .DESCRIPTION
    Gets the group member list of a target group. Designed for parity with Get-ADGroupMember
 
    .PARAMETER Identity
    The email or GroupID of the target group
 
    .PARAMETER Member
    If specified, returns only the information for this member of the target group
 
    .PARAMETER Roles
    If specified, returns only the members of the specified role(s)
 
    .PARAMETER PageSize
    Page size of the result set
 
    .EXAMPLE
    Get-GSGroupMember "admins@domain.com" -Roles Owner,Manager
 
    Returns the list of owners and managers of the group "admins@domain.com"
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('GroupEmail','Group','Email')]
        [String[]]
        $Identity,
        [parameter(Mandatory = $false,Position = 1,ParameterSetName = "Get")]
        [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Member,
        [parameter(Mandatory=$false,ParameterSetName = "List")]
        [ValidateSet("Owner","Manager","Member")]
        [String[]]
        $Roles,
        [parameter(Mandatory=$false,ParameterSetName = "List")]
        [ValidateRange(1,200)]
        [Int]
        $PageSize="200"
    )
    Begin {
        if ($PSCmdlet.ParameterSetName -eq 'Get') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($I in $Identity) {
                    try {
                        if ($I -notlike "*@*.*") {
                            $I = "$($I)@$($Script:PSGSuite.Domain)"
                        }
                        foreach ($G in $Member) {
                            if ($G -notlike "*@*.*") {
                                $G = "$($G)@$($Script:PSGSuite.Domain)"
                            }
                            Write-Verbose "Getting member '$G' of group '$I'"
                            $request = $service.Members.Get($I,$G)
                            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $I -PassThru
                        }
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                Get-GSGroupMemberListPrivate @PSBoundParameters
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSGroupMember'

function Get-GSGroupSettings {
    <#
    .SYNOPSIS
    Gets a group's settings
     
    .DESCRIPTION
    Gets a group's settings
     
    .PARAMETER Identity
    The email of the group
 
    If only the email name-part is passed, the full email will be contstructed using the Domain from the active config
     
    .EXAMPLE
    Get-GSGroupSettings admins
 
    Gets the group settings for admins@domain.com
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('GroupEmail','Group','Email')]
        [String[]]
        $Identity
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/apps.groups.settings'
            ServiceType = 'Google.Apis.Groupssettings.v1.GroupssettingsService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($G in $Identity) {
                if ($G -notlike "*@*.*") {
                    $G = "$($G)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Getting settings for group '$G'"
                $request = $service.Groups.Get($G)
                $request.Alt = "Json"
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $G -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSGroupSettings'

function New-GSGroup {
    <#
    .SYNOPSIS
    Creates a new Google Group
     
    .DESCRIPTION
    Creates a new Google Group
     
    .PARAMETER Email
    The desired email of the new group. If the group already exists, a GoogleApiException will be thrown. You can exclude the '@domain.com' to insert the Domain in the config
     
    .PARAMETER Name
    The name of the new group
     
    .PARAMETER Description
    The description of the new group
     
    .EXAMPLE
    New-GSGroup -Email appdev -Name "Application Developers" -Description "App Dev team members"
 
    Creates a new group named "Application Developers" with the email "appdev@domain.com" and description "App Dev team members"
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [String]
        $Email,
        [parameter(Mandatory = $true)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [String]
        $Description
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($Email -notlike "*@*.*") {
                $Email = "$($Email)@$($Script:PSGSuite.Domain)"
            }
            Write-Verbose "Creating group '$Email'"
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Group'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    Email {
                        if ($PSBoundParameters[$prop] -notlike "*@*.*") {
                            $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)"
                        }
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            $request = $service.Groups.Insert($body)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSGroup'

function New-GSGroupAlias {
    <#
    .SYNOPSIS
    Creates a new alias for a G Suite group
     
    .DESCRIPTION
    Creates a new alias for a G Suite group
     
    .PARAMETER Group
    The group to create the alias for
     
    .PARAMETER Alias
    The alias or list of aliases to create for the group
     
    .EXAMPLE
    New-GSGroupAlias -Group humanresources@domain.com -Alias 'hr@domain.com','hrhelp@domain.com'
 
    Creates 2 new aliases for group Human Resources as 'hr@domain.com' and 'hrhelp@domain.com'
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Group,
        [parameter(Mandatory = $true,Position = 1)]
        [String[]]
        $Alias
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($A in $Alias) {
            try {
                if ($Group -notlike "*@*.*") {
                    $Group = "$($Group)@$($Script:PSGSuite.Domain)"
                }
                if ($A -notlike "*@*.*") {
                    $A = "$($A)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Creating alias '$A' for Group '$Group'"
                $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Alias'
                $body.AliasValue = $A
                $request = $service.Groups.Aliases.Insert($body,$Group)
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSGroupAlias'

function Remove-GSGroup {
    <#
    .SYNOPSIS
    Removes a group
     
    .DESCRIPTION
    Removes a group
     
    .PARAMETER Identity
    The email or unique Id of the group to removed
     
    .EXAMPLE
    Remove-GSGroup 'test_group' -Confirm:$false
 
    Removes the group 'test_group@domain.com' without asking for confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('GroupEmail','Group','Email')]
        [String[]]
        $Identity
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($G in $Identity) {
                if ($G -notlike "*@*.*") {
                    $G = "$($G)@$($Script:PSGSuite.Domain)"
                }
                if ($PSCmdlet.ShouldProcess("Removing group '$G'")) {
                    Write-Verbose "Removing group '$G'"
                    $request = $service.Groups.Delete($G)
                    $request.Execute()
                    Write-Verbose "Group '$G' has been successfully removed"
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSGroup'

function Remove-GSGroupAlias {
    <#
    .SYNOPSIS
    Removes an alias from a G Suite group
     
    .DESCRIPTION
    Removes an alias from a G Suite group
     
    .PARAMETER Group
    The group to remove the alias from
     
    .PARAMETER Alias
    The alias or list of aliases to remove from the group
     
    .EXAMPLE
    Remove-GSGroupAlias -Group humanresources@domain.com -Alias 'hr@domain.com','hrhelp@domain.com'
 
    Removes 2 aliases for group Human Resources: 'hr@domain.com' and 'hrhelp@domain.com'
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Group,
        [parameter(Mandatory = $true,Position = 1)]
        [String[]]
        $Alias
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($A in $Alias) {
            try {
                if ($Group -notlike "*@*.*") {
                    $Group = "$($Group)@$($Script:PSGSuite.Domain)"
                }
                if ($A -notlike "*@*.*") {
                    $A = "$($A)@$($Script:PSGSuite.Domain)"
                }
                if ($PSCmdlet.ShouldProcess("Removing alias '$A' from Group '$Group'")) {
                    Write-Verbose "Removing alias '$A' from Group '$Group'"
                    $request = $service.Groups.Aliases.Delete($Group,$A)
                    $request.Execute()
                    Write-Verbose "Alias '$A' has been successfully deleted from Group '$Group'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSGroupAlias'

function Remove-GSGroupMember {
    <#
    .SYNOPSIS
    Removes members from a group
     
    .DESCRIPTION
    Removes members from a group
     
    .PARAMETER Identity
    The email or unique Id of the group to remove members from
     
    .PARAMETER Member
    The member or array of members to remove from the target group
     
    .EXAMPLE
    Remove-GSGroupMember -Identity admins -Member joe.smith,mark.taylor -Confirm:$false
 
    Removes members Joe Smith and Mark Taylor from the group admins@domain.com and skips asking for confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('GroupEmail','Group','Email')]
        [String]
        $Identity,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)]
        [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Member
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        if ($Identity -notlike "*@*.*") {
            $Identity = "$($Identity)@$($Script:PSGSuite.Domain)"
        }
        foreach ($G in $Member) {
            try {
                if ($G -notlike "*@*.*") {
                    $G = "$($G)@$($Script:PSGSuite.Domain)"
                }
                if ($PSCmdlet.ShouldProcess("Removing member '$G' from group '$Identity'")) {
                    Write-Verbose "Removing member '$G' from group '$Identity'"
                    $request = $service.Members.Delete($Identity,$G)
                    $request.Execute()
                    Write-Verbose "Member '$G' has been successfully removed"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSGroupMember'

function Remove-GSPrincipalGroupMembership {
    <#
    .SYNOPSIS
    Removes the target member from a group or list of groups
     
    .DESCRIPTION
    Removes the target member from a group or list of groups
     
    .PARAMETER Identity
    The email or unique Id of the member you would like to remove from the group(s)
     
    .PARAMETER MemberOf
    The group(s) to remove the member from
     
    .EXAMPLE
    Remove-GSPrincipalGroupMembership -Identity 'joe.smith' -MemberOf admins,test_pool
 
    Removes Joe Smith from the groups admins@domain.com and test_pool@domain.com
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail","User","UserEmail")]
        [String]
        $Identity,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,Position = 1)]
        [Alias('GroupEmail','Group','Email')]
        [String[]]
        $MemberOf
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.group'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        if ($Identity -notlike "*@*.*") {
            $Identity = "$($Identity)@$($Script:PSGSuite.Domain)"
        }
        foreach ($G in $MemberOf) {
            try {
                if ($G -notlike "*@*.*") {
                    $G = "$($G)@$($Script:PSGSuite.Domain)"
                }
                if ($PSCmdlet.ShouldProcess("Removing member '$Identity' from group '$G'")) {
                    Write-Verbose "Removing member '$Identity' from group '$G'"
                    $request = $service.Members.Delete($G,$Identity)
                    $request.Execute()
                    Write-Verbose "Member '$G' has been successfully removed"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSPrincipalGroupMembership'

function Set-GSGroupSettings {
    <#
    .SYNOPSIS
    Hard-sets the settings for a group
     
    .DESCRIPTION
    Hard-sets the settings for a group
     
    .PARAMETER Identity
    The primary email or unique Id of the group to set
     
    .PARAMETER Name
    The new name of the group
     
    .PARAMETER Description
    The new description of the group
     
    .PARAMETER ArchiveOnly
    If the group is archive only
     
    .PARAMETER AllowExternalMembers
    Are external members allowed to join the group
     
    .PARAMETER AllowGoogleCommunication
    Is google allowed to contact admins
     
    .PARAMETER AllowWebPosting
    If posting from web is allowed
     
    .PARAMETER CustomFooterText
    Custom footer text
     
    .PARAMETER CustomReplyToAddress
    Default email to which reply to any message should go
     
    .PARAMETER DefaultMessageDenyNotificationText
    Default message deny notification message
     
    .PARAMETER Email
    Email id of the group
     
    .PARAMETER IncludeCustomFooter
    Whether to include custom footer
     
    .PARAMETER IncludeInGlobalAddressList
    If this groups should be included in global address list or not
     
    .PARAMETER IsArchived
    If the contents of the group are archived
     
    .PARAMETER MaxMessageBytes
    Maximum message size allowed
     
    .PARAMETER MembersCanPostAsTheGroup
    Can members post using the group email address
     
    .PARAMETER MessageDisplayFont
    Default message display font
     
    Available values are:
    * "DEFAULT_FONT"
    * "FIXED_WIDTH_FONT"
     
    .PARAMETER MessageModerationLevel
    Moderation level for messages
     
    Available values are:
    * "MODERATE_ALL_MESSAGES"
    * "MODERATE_NON_MEMBERS"
    * "MODERATE_NEW_MEMBERS"
    * "MODERATE_NONE"
     
    .PARAMETER ReplyTo
    Who should the default reply to a message go to
     
    Available values are:
    * "REPLY_TO_CUSTOM"
    * "REPLY_TO_SENDER"
    * "REPLY_TO_LIST"
    * "REPLY_TO_OWNER"
    * "REPLY_TO_IGNORE"
    * "REPLY_TO_MANAGERS"
     
    .PARAMETER SendMessageDenyNotification
    Should the member be notified if his message is denied by owner
     
    .PARAMETER ShowInGroupDirectory
    Is the group listed in groups directory
     
    .PARAMETER SpamModerationLevel
    Moderation level for messages detected as spam
     
    Available values are:
    * "ALLOW"
    * "MODERATE"
    * "SILENTLY_MODERATE"
    * "REJECT"
     
    .PARAMETER WhoCanAdd
    Permissions to add members
     
    Available values are:
    * "ALL_MANAGERS_CAN_ADD"
    * "ALL_MEMBERS_CAN_ADD"
    * "NONE_CAN_ADD"
     
    .PARAMETER WhoCanContactOwner
    Permission to contact owner of the group via web UI
     
    Available values are:
    * "ANYONE_CAN_CONTACT"
    * "ALL_IN_DOMAIN_CAN_CONTACT"
    * "ALL_MEMBERS_CAN_CONTACT"
    * "ALL_MANAGERS_CAN_CONTACT"
     
    .PARAMETER WhoCanInvite
    Permissions to invite members.
    Available values are:
    * "ALL_MEMBERS_CAN_INVITE"
    * "ALL_MANAGERS_CAN_INVITE"
    * "NONE_CAN_INVITE"
     
    .PARAMETER WhoCanJoin
    Permissions to join the group.
    Available values are:
    * "ANYONE_CAN_JOIN"
    * "ALL_IN_DOMAIN_CAN_JOIN"
    * "INVITED_CAN_JOIN"
    * "CAN_REQUEST_TO_JOIN"
     
    .PARAMETER WhoCanLeaveGroup
    Permission to leave the group.
 
    Available values are:
    * "ALL_MANAGERS_CAN_LEAVE"
    * "ALL_MEMBERS_CAN_LEAVE"
    * "NONE_CAN_LEAVE"
     
    .PARAMETER WhoCanPostMessage
    Permissions to post messages to the group.
     
    Available values are:
    * "NONE_CAN_POST"
    * "ALL_MANAGERS_CAN_POST"
    * "ALL_MEMBERS_CAN_POST"
    * "ALL_OWNERS_CAN_POST"
    * "ALL_IN_DOMAIN_CAN_POST"
    * "ANYONE_CAN_POST"
     
    .PARAMETER WhoCanViewGroup
    Permissions to view group.
     
    Available values are:
    * "ANYONE_CAN_VIEW"
    * "ALL_IN_DOMAIN_CAN_VIEW"
    * "ALL_MEMBERS_CAN_VIEW"
    * "ALL_MANAGERS_CAN_VIEW"
     
    .PARAMETER WhoCanViewMembership
    Permissions to view membership.
     
    Available values are:
    * "ALL_IN_DOMAIN_CAN_VIEW"
    * "ALL_MEMBERS_CAN_VIEW"
    * "ALL_MANAGERS_CAN_VIEW"
     
    .EXAMPLE
    Set-GSGroupSettings admins,hr-notifications -AllowExternalMembers:$false -WhoCanPostMessage ALL_OWNERS_CAN_POST
 
    Sets the group settings for both admins@domain.com and hr-notifications@domain.com to deny external members and limit posting to only group owners
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('GroupEmail','Group')]
        [String[]]
        $Identity,
        [parameter(Mandatory = $false)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [Switch]
        $ArchiveOnly,
        [parameter(Mandatory = $false)]
        [Switch]
        $AllowExternalMembers,
        [parameter(Mandatory = $false)]
        [Switch]
        $AllowGoogleCommunication,
        [parameter(Mandatory = $false)]
        [Switch]
        $AllowWebPosting,
        [parameter(Mandatory = $false)]
        [ValidateScript( {$_.length -le 1000})]
        [String]
        $CustomFooterText,
        [parameter(Mandatory = $false)]
        [String]
        $CustomReplyToAddress,
        [parameter(Mandatory = $false)]
        [Alias('MessageDenyNotificationText')]
        [ValidateScript( {$_.length -le 10000})]
        [String]
        $DefaultMessageDenyNotificationText,
        [parameter(Mandatory = $false)]
        [String]
        $Email,
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeCustomFooter,
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeInGlobalAddressList,
        [parameter(Mandatory = $false)]
        [Switch]
        $IsArchived,
        [parameter(Mandatory = $false)]
        [int]
        $MaxMessageBytes,
        [parameter(Mandatory = $false)]
        [Switch]
        $MembersCanPostAsTheGroup,
        [parameter(Mandatory = $false)]
        [ValidateSet("DEFAULT_FONT","FIXED_WIDTH_FONT")]
        [String]
        $MessageDisplayFont,
        [parameter(Mandatory = $false)]
        [ValidateSet("MODERATE_ALL_MESSAGES","MODERATE_NEW_MEMBERS","MODERATE_NONE","MODERATE_NON_MEMBERS")]
        [String]
        $MessageModerationLevel,
        [parameter(Mandatory = $false)]
        [ValidateSet("REPLY_TO_CUSTOM","REPLY_TO_IGNORE","REPLY_TO_LIST","REPLY_TO_MANAGERS","REPLY_TO_OWNER","REPLY_TO_SENDER")]
        [String]
        $ReplyTo,
        [parameter(Mandatory = $false)]
        [Switch]
        $SendMessageDenyNotification,
        [parameter(Mandatory = $false)]
        [Switch]
        $ShowInGroupDirectory,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALLOW","MODERATE","SILENTLY_MODERATE","REJECT")]
        [String]
        $SpamModerationLevel,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_MEMBERS_CAN_ADD","ALL_MANAGERS_CAN_ADD","NONE_CAN_ADD")]
        [String]
        $WhoCanAdd,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_CONTACT","ALL_MANAGERS_CAN_CONTACT","ALL_MEMBERS_CAN_CONTACT","ANYONE_CAN_CONTACT")]
        [String]
        $WhoCanContactOwner,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_MANAGERS_CAN_INVITE","ALL_MEMBERS_CAN_INVITE","NONE_CAN_INVITE")]
        [String]
        $WhoCanInvite,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_JOIN","ANYONE_CAN_JOIN","CAN_REQUEST_TO_JOIN","INVITED_CAN_JOIN")]
        [String]
        $WhoCanJoin,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_MANAGERS_CAN_LEAVE","ALL_MEMBERS_CAN_LEAVE","NONE_CAN_LEAVE")]
        [String]
        $WhoCanLeaveGroup,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_POST","ALL_MANAGERS_CAN_POST","ALL_MEMBERS_CAN_POST","ANYONE_CAN_POST","NONE_CAN_POST")]
        [String]
        $WhoCanPostMessage,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_VIEW","ALL_MANAGERS_CAN_VIEW","ALL_MEMBERS_CAN_VIEW","ANYONE_CAN_VIEW")]
        [String]
        $WhoCanViewGroup,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_VIEW","ALL_MANAGERS_CAN_VIEW","ALL_MEMBERS_CAN_VIEW")]
        [String]
        $WhoCanViewMembership
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/apps.groups.settings'
            ServiceType = 'Google.Apis.Groupssettings.v1.GroupssettingsService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($G in $Identity) {
                if ($G -notlike "*@*.*") {
                    $G = "$($G)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Updating settings for group '$G'"
                $body = New-Object 'Google.Apis.Groupssettings.v1.Data.Groups'
                foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                    switch ($prop) {
                        MaxMessageBytes {
                            $body.$prop = $PSBoundParameters[$prop]
                        }
                        Default {
                            $body.$prop = if ($PSBoundParameters[$prop].ToString() -in @("True","False")) {
                                $($PSBoundParameters[$prop]).ToString().ToLower()
                            }
                            else {
                                $PSBoundParameters[$prop]
                            }
                        }
                    }
                }
                $request = $service.Groups.Update($body,$G)
                $request.Alt = "Json"
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $G -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Set-GSGroupSettings'

function Update-GSGroupSettings {
    <#
    .SYNOPSIS
    Updates the settings for a group while retaining excluded, already existing settings
     
    .DESCRIPTION
    Updates the settings for a group while retaining excluded, already existing settings
     
    .PARAMETER Identity
    The primary email or unique Id of the group to update
     
    .PARAMETER Name
    The new name of the group
     
    .PARAMETER Description
    The new description of the group
     
    .PARAMETER ArchiveOnly
    If the group is archive only
     
    .PARAMETER AllowExternalMembers
    Are external members allowed to join the group
     
    .PARAMETER AllowGoogleCommunication
    Is google allowed to contact admins
     
    .PARAMETER AllowWebPosting
    If posting from web is allowed
     
    .PARAMETER CustomFooterText
    Custom footer text
     
    .PARAMETER CustomReplyToAddress
    Default email to which reply to any message should go
     
    .PARAMETER DefaultMessageDenyNotificationText
    Default message deny notification message
     
    .PARAMETER Email
    Email id of the group
     
    .PARAMETER IncludeCustomFooter
    Whether to include custom footer
     
    .PARAMETER IncludeInGlobalAddressList
    If this groups should be included in global address list or not
     
    .PARAMETER IsArchived
    If the contents of the group are archived
     
    .PARAMETER MaxMessageBytes
    Maximum message size allowed
     
    .PARAMETER MembersCanPostAsTheGroup
    Can members post using the group email address
     
    .PARAMETER MessageDisplayFont
    Default message display font
     
    Available values are:
    * "DEFAULT_FONT"
    * "FIXED_WIDTH_FONT"
     
    .PARAMETER MessageModerationLevel
    Moderation level for messages
     
    Available values are:
    * "MODERATE_ALL_MESSAGES"
    * "MODERATE_NON_MEMBERS"
    * "MODERATE_NEW_MEMBERS"
    * "MODERATE_NONE"
     
    .PARAMETER ReplyTo
    Who should the default reply to a message go to
     
    Available values are:
    * "REPLY_TO_CUSTOM"
    * "REPLY_TO_SENDER"
    * "REPLY_TO_LIST"
    * "REPLY_TO_OWNER"
    * "REPLY_TO_IGNORE"
    * "REPLY_TO_MANAGERS"
     
    .PARAMETER SendMessageDenyNotification
    Should the member be notified if his message is denied by owner
     
    .PARAMETER ShowInGroupDirectory
    Is the group listed in groups directory
     
    .PARAMETER SpamModerationLevel
    Moderation level for messages detected as spam
     
    Available values are:
    * "ALLOW"
    * "MODERATE"
    * "SILENTLY_MODERATE"
    * "REJECT"
     
    .PARAMETER WhoCanAdd
    Permissions to add members
     
    Available values are:
    * "ALL_MANAGERS_CAN_ADD"
    * "ALL_MEMBERS_CAN_ADD"
    * "NONE_CAN_ADD"
     
    .PARAMETER WhoCanContactOwner
    Permission to contact owner of the group via web UI
     
    Available values are:
    * "ANYONE_CAN_CONTACT"
    * "ALL_IN_DOMAIN_CAN_CONTACT"
    * "ALL_MEMBERS_CAN_CONTACT"
    * "ALL_MANAGERS_CAN_CONTACT"
     
    .PARAMETER WhoCanInvite
    Permissions to invite members.
    Available values are:
    * "ALL_MEMBERS_CAN_INVITE"
    * "ALL_MANAGERS_CAN_INVITE"
    * "NONE_CAN_INVITE"
     
    .PARAMETER WhoCanJoin
    Permissions to join the group.
    Available values are:
    * "ANYONE_CAN_JOIN"
    * "ALL_IN_DOMAIN_CAN_JOIN"
    * "INVITED_CAN_JOIN"
    * "CAN_REQUEST_TO_JOIN"
     
    .PARAMETER WhoCanLeaveGroup
    Permission to leave the group.
 
    Available values are:
    * "ALL_MANAGERS_CAN_LEAVE"
    * "ALL_MEMBERS_CAN_LEAVE"
    * "NONE_CAN_LEAVE"
     
    .PARAMETER WhoCanPostMessage
    Permissions to post messages to the group.
     
    Available values are:
    * "NONE_CAN_POST"
    * "ALL_MANAGERS_CAN_POST"
    * "ALL_MEMBERS_CAN_POST"
    * "ALL_OWNERS_CAN_POST"
    * "ALL_IN_DOMAIN_CAN_POST"
    * "ANYONE_CAN_POST"
     
    .PARAMETER WhoCanViewGroup
    Permissions to view group.
     
    Available values are:
    * "ANYONE_CAN_VIEW"
    * "ALL_IN_DOMAIN_CAN_VIEW"
    * "ALL_MEMBERS_CAN_VIEW"
    * "ALL_MANAGERS_CAN_VIEW"
     
    .PARAMETER WhoCanViewMembership
    Permissions to view membership.
     
    Available values are:
    * "ALL_IN_DOMAIN_CAN_VIEW"
    * "ALL_MEMBERS_CAN_VIEW"
    * "ALL_MANAGERS_CAN_VIEW"
     
    .EXAMPLE
    Updates-GSGroupSettings admins,hr-notifications -AllowExternalMembers:$false -WhoCanPostMessage ALL_OWNERS_CAN_POST
 
    Updates the group settings for both admins@domain.com and hr-notifications@domain.com to deny external members and limit posting to only group owners
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('GroupEmail','Group')]
        [String[]]
        $Identity,
        [parameter(Mandatory = $false)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [Switch]
        $ArchiveOnly,
        [parameter(Mandatory = $false)]
        [Switch]
        $AllowExternalMembers,
        [parameter(Mandatory = $false)]
        [Switch]
        $AllowGoogleCommunication,
        [parameter(Mandatory = $false)]
        [Switch]
        $AllowWebPosting,
        [parameter(Mandatory = $false)]
        [ValidateScript( {$_.length -le 1000})]
        [String]
        $CustomFooterText,
        [parameter(Mandatory = $false)]
        [String]
        $CustomReplyToAddress,
        [parameter(Mandatory = $false)]
        [Alias('MessageDenyNotificationText')]
        [ValidateScript( {$_.length -le 10000})]
        [String]
        $DefaultMessageDenyNotificationText,
        [parameter(Mandatory = $false)]
        [String]
        $Email,
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeCustomFooter,
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeInGlobalAddressList,
        [parameter(Mandatory = $false)]
        [Switch]
        $IsArchived,
        [parameter(Mandatory = $false)]
        [int]
        $MaxMessageBytes,
        [parameter(Mandatory = $false)]
        [Switch]
        $MembersCanPostAsTheGroup,
        [parameter(Mandatory = $false)]
        [ValidateSet("DEFAULT_FONT","FIXED_WIDTH_FONT")]
        [String]
        $MessageDisplayFont,
        [parameter(Mandatory = $false)]
        [ValidateSet("MODERATE_ALL_MESSAGES","MODERATE_NEW_MEMBERS","MODERATE_NONE","MODERATE_NON_MEMBERS")]
        [String]
        $MessageModerationLevel,
        [parameter(Mandatory = $false)]
        [ValidateSet("REPLY_TO_CUSTOM","REPLY_TO_IGNORE","REPLY_TO_LIST","REPLY_TO_MANAGERS","REPLY_TO_OWNER","REPLY_TO_SENDER")]
        [String]
        $ReplyTo,
        [parameter(Mandatory = $false)]
        [Switch]
        $SendMessageDenyNotification,
        [parameter(Mandatory = $false)]
        [Switch]
        $ShowInGroupDirectory,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALLOW","MODERATE","SILENTLY_MODERATE","REJECT")]
        [String]
        $SpamModerationLevel,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_MEMBERS_CAN_ADD","ALL_MANAGERS_CAN_ADD","NONE_CAN_ADD")]
        [String]
        $WhoCanAdd,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_CONTACT","ALL_MANAGERS_CAN_CONTACT","ALL_MEMBERS_CAN_CONTACT","ANYONE_CAN_CONTACT")]
        [String]
        $WhoCanContactOwner,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_MANAGERS_CAN_INVITE","ALL_MEMBERS_CAN_INVITE","NONE_CAN_INVITE")]
        [String]
        $WhoCanInvite,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_JOIN","ANYONE_CAN_JOIN","CAN_REQUEST_TO_JOIN","INVITED_CAN_JOIN")]
        [String]
        $WhoCanJoin,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_MANAGERS_CAN_LEAVE","ALL_MEMBERS_CAN_LEAVE","NONE_CAN_LEAVE")]
        [String]
        $WhoCanLeaveGroup,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_POST","ALL_MANAGERS_CAN_POST","ALL_MEMBERS_CAN_POST","ANYONE_CAN_POST","NONE_CAN_POST")]
        [String]
        $WhoCanPostMessage,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_VIEW","ALL_MANAGERS_CAN_VIEW","ALL_MEMBERS_CAN_VIEW","ANYONE_CAN_VIEW")]
        [String]
        $WhoCanViewGroup,
        [parameter(Mandatory = $false)]
        [ValidateSet("ALL_IN_DOMAIN_CAN_VIEW","ALL_MANAGERS_CAN_VIEW","ALL_MEMBERS_CAN_VIEW")]
        [String]
        $WhoCanViewMembership
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/apps.groups.settings'
            ServiceType = 'Google.Apis.Groupssettings.v1.GroupssettingsService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($G in $Identity) {
                if ($G -notlike "*@*.*") {
                    $G = "$($G)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Updating settings for group '$G'"
                $body = New-Object 'Google.Apis.Groupssettings.v1.Data.Groups'
                foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                    switch ($prop) {
                        MaxMessageBytes {
                            $body.$prop = $PSBoundParameters[$prop]
                        }
                        Default {
                            $body.$prop = if ($PSBoundParameters[$prop].ToString() -in @("True","False")) {
                                $($PSBoundParameters[$prop]).ToString().ToLower()
                            }
                            else {
                                $PSBoundParameters[$prop]
                            }
                        }
                    }
                }
                $request = $service.Groups.Patch($body,$G)
                $request.Alt = "Json"
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Group' -Value $G -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSGroupSettings'

function Add-GSChatButton {
    <#
    .SYNOPSIS
    Creates a Chat Button widget to include in a section
 
    .DESCRIPTION
    Creates a Chat Button widget to include in a section
 
    .PARAMETER Text
    The Text for a Text Button
 
    .PARAMETER Icon
    The icon for the Image Button
 
    Available values are:
    * AIRPLANE
    * BOOKMARK
    * BUS
    * CAR
    * CLOCK
    * CONFIRMATION_NUMBER_ICON
    * DOLLAR
    * DESCRIPTION
    * EMAIL
    * EVENT_PERFORMER
    * EVENT_SEAT
    * FLIGHT_ARRIVAL
    * FLIGHT_DEPARTURE
    * HOTEL
    * HOTEL_ROOM_TYPE
    * INVITE
    * MAP_PIN
    * MEMBERSHIP
    * MULTIPLE_PEOPLE
    * OFFER
    * PERSON
    * PHONE
    * RESTAURANT_ICON
    * SHOPPING_CART
    * STAR
    * STORE
    * TICKET
    * TRAIN
    * VIDEO_CAMERA
    * VIDEO_PLAY
 
    .PARAMETER IconUrl
    The Url of the icon for the Image Button
 
    .PARAMETER OnClick
    The OnClick event that triggers when a user clicks the KeyValue
 
    You must use the function `Add-GSChatOnClick` to create OnClicks, otherwise this will throw a terminating error.
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    [CmdletBinding(DefaultParameterSetName = "Text")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Text")]
        [String]
        $Text,
        [parameter(Mandatory = $true,ParameterSetName = "Icon")]
        [ValidateSet('AIRPLANE','BOOKMARK','BUS','CAR','CLOCK','CONFIRMATION_NUMBER_ICON','DOLLAR','DESCRIPTION','EMAIL','EVENT_PERFORMER','EVENT_SEAT','FLIGHT_ARRIVAL','FLIGHT_DEPARTURE','HOTEL','HOTEL_ROOM_TYPE','INVITE','MAP_PIN','MEMBERSHIP','MULTIPLE_PEOPLE','OFFER','PERSON','PHONE','RESTAURANT_ICON','SHOPPING_CART','STAR','STORE','TICKET','TRAIN','VIDEO_CAMERA','VIDEO_PLAY')]
        [String]
        $Icon,
        [parameter(Mandatory = $true,ParameterSetName = "IconUrl")]
        [String]
        $IconUrl,
        [parameter(Mandatory = $false)]
        [ValidateScript( {
            $allowedTypes = "PSGSuite.Chat.Message.Card.OnClick"
            if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                $true
            }
            else {
                throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")."
            }
        })]
        [Object]
        $OnClick,
        [parameter(Mandatory = $false,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment
    )
    Begin {
        $widgetObject = @{
            Webhook = @{
                buttons = @()
            }
            SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.WidgetMarkup' -Property @{
                Buttons = (New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Button]')
            })
        }
        $widgetStack = @()
        switch ($PSCmdlet.ParameterSetName) {
            Text {
                $widgetObject['Webhook']['buttons'] += @{
                    textButton = @{
                        text = $Text
                        onClick = $OnClick['Webhook']
                    }
                }
                $widgetObject['SDK'].Buttons.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.Button' -Property @{
                    TextButton = (New-Object 'Google.Apis.HangoutsChat.v1.Data.TextButton' -Property @{
                        Text = $Text
                        OnClick = $OnClick['SDK']
                    })
                })) | Out-Null
            }
            Icon {
                $widgetObject['Webhook']['buttons'] += @{
                    imageButton = @{
                        icon = $Icon
                        onClick = $OnClick['Webhook']
                    }
                }
                $widgetObject['SDK'].Buttons.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.Button' -Property @{
                    ImageButton = (New-Object 'Google.Apis.HangoutsChat.v1.Data.ImageButton' -Property @{
                        Icon = $Icon
                        OnClick = $OnClick['SDK']
                    })
                })) | Out-Null
            }
            IconUrl {
                $widgetObject['Webhook']['buttons'] += @{
                    imageButton = @{
                        iconUrl = $IconUrl
                        onClick = $OnClick['Webhook']
                    }
                }
                $widgetObject['SDK'].Buttons.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.Button' -Property @{
                    ImageButton = (New-Object 'Google.Apis.HangoutsChat.v1.Data.ImageButton' -Property @{
                        IconUrl = $IconUrl
                        OnClick = $OnClick['SDK']
                    })
                })) | Out-Null
            }
        }
    }
    Process {
        if ($MessageSegment) {
            foreach ($segment in $MessageSegment) {
                if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) {
                    $widgetStack += $segment
                }
                else {
                    $segment
                }
            }
        }
    }
    End {
        [void]$widgetObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section.Button')
        if($widgetStack) {
            $newWidgetStack = @()
            for ($i = 0;$i -lt $widgetStack.Count;$i++) {
                if ($i -eq ($widgetStack.Count -1) -and ($widgetStack[$i].PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.Section.Button')) {
                    $widgetStack[$i]['Webhook']['buttons'] += $widgetObject['Webhook']['buttons'][0]
                    $widgetStack[$i]['SDK'].Buttons.Add($widgetObject['SDK'].Buttons[0]) | Out-Null
                    $newWidgetStack += $widgetStack[$i]
                }
                elseif ($i -eq ($widgetStack.Count -1)) {
                    $newWidgetStack += $widgetStack[$i]
                    $newWidgetStack += $widgetObject
                }
                else {
                    $newWidgetStack += $widgetStack[$i]
                }
            }
            $newWidgetStack
        }
        else {
            $widgetObject
        }
    }
}
Export-ModuleMember -Function 'Add-GSChatButton'

function Add-GSChatCard {
    <#
    .SYNOPSIS
    Creates a Chat Message Card
 
    .DESCRIPTION
    Creates a Chat Message Card
 
    .PARAMETER HeaderTitle
    The header title of the card
 
    .PARAMETER HeaderSubtitle
    The header subtitle of the card
 
    .PARAMETER HeaderImageStyle
    The header image style of the card
 
    Available values are:
    * IMAGE
    * AVATAR
 
    .PARAMETER HeaderImageUrl
    The header image URL of the card
 
    .PARAMETER CardActions
    The cardActions of the card.
 
    You must use the function `New-GSChatCardAction` to create cardActions, otherwise this will throw a terminating error.
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    If section widgets are passed directly to this function, a new section without a SectionHeader will be created and the widgets will be added to it
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [String]
        $HeaderTitle,
        [parameter(Mandatory = $false)]
        [String]
        $HeaderSubtitle,
        [parameter(Mandatory = $false)]
        [ValidateSet('IMAGE','AVATAR')]
        [String]
        $HeaderImageStyle,
        [parameter(Mandatory = $false)]
        [String]
        $HeaderImageUrl,
        [parameter(Mandatory = $false)]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card.CardAction"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $CardActions,
        [parameter(Mandatory = $false,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript( {
            $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment
    )
    Begin {
        $cardObject = @{
            Webhook = @{}
            SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.Card')
        }
        $addlSectionWidgets = @()
        foreach ($key in $PSBoundParameters.Keys) {
            switch ($key) {
                HeaderTitle {
                    if (!$cardObject['Webhook']['header']) {
                        $cardObject['Webhook']['header'] = @{}
                    }
                    $cardObject['Webhook']['header']['title'] = $PSBoundParameters[$key]
                    if (!$cardObject['SDK'].Header) {
                        $cardObject['SDK'].Header = New-Object 'Google.Apis.HangoutsChat.v1.Data.CardHeader'
                    }
                    $cardObject['SDK'].Header.Title = $PSBoundParameters[$key]
                }
                HeaderSubtitle {
                    if (!$cardObject['Webhook']['header']) {
                        $cardObject['Webhook']['header'] = @{}
                    }
                    $cardObject['Webhook']['header']['subtitle'] = $PSBoundParameters[$key]
                    if (!$cardObject['SDK'].Header) {
                        $cardObject['SDK'].Header = New-Object 'Google.Apis.HangoutsChat.v1.Data.CardHeader'
                    }
                    $cardObject['SDK'].Header.Subtitle = $PSBoundParameters[$key]
                }
                HeaderImageStyle {
                    if (!$cardObject['Webhook']['header']) {
                        $cardObject['Webhook']['header'] = @{}
                    }
                    $cardObject['Webhook']['header']['imageStyle'] = $PSBoundParameters[$key]
                    if (!$cardObject['SDK'].Header) {
                        $cardObject['SDK'].Header = New-Object 'Google.Apis.HangoutsChat.v1.Data.CardHeader'
                    }
                    $cardObject['SDK'].Header.ImageStyle = $PSBoundParameters[$key]
                }
                HeaderImageUrl {
                    if (!$cardObject['Webhook']['header']) {
                        $cardObject['Webhook']['header'] = @{}
                    }
                    $cardObject['Webhook']['header']['imageUrl'] = $PSBoundParameters[$key]
                    if (!$cardObject['SDK'].Header) {
                        $cardObject['SDK'].Header = New-Object 'Google.Apis.HangoutsChat.v1.Data.CardHeader'
                    }
                    $cardObject['SDK'].Header.ImageUrl = $PSBoundParameters[$key]
                }
                CardActions {
                    if (!$cardObject['Webhook']['cardActions']) {
                        $cardObject['Webhook']['cardActions'] = @()
                    }
                    foreach ($cardAction in $CardActions) {
                        $cardObject['Webhook']['cardActions'] += $cardAction['Webhook']
                    }
                    if (!$cardObject['SDK'].CardActions) {
                        $cardObject['SDK'].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]'
                    }
                    foreach ($cardAction in $CardActions) {
                        $cardObject['SDK'].CardActions.Add($cardAction['SDK']) | Out-Null
                    }
                }
            }
        }
    }
    Process {
        foreach ($segment in $MessageSegment) {
            if ($segment.PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card') {
                $segment
            }
            elseif ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) {
                $addlSectionWidgets += $segment
            }
            elseif ($segment.PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.CardAction') {
                if (!$cardObject['Webhook']['cardActions']) {
                    $cardObject['Webhook']['cardActions'] = @()
                }
                $cardObject['Webhook']['cardActions'] += $segment['Webhook']
                if (!$cardObject['SDK'].CardActions) {
                    $cardObject['SDK'].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]'
                }
                $cardObject['SDK'].CardActions.Add($segment['SDK']) | Out-Null
            }
            elseif ($segment.PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.Section') {
                if (!$cardObject['Webhook']['sections']) {
                    $cardObject['Webhook']['sections'] = @()
                }
                $cardObject['Webhook']['sections'] += $segment['Webhook']
                if (!$cardObject['SDK'].Sections) {
                    $cardObject['SDK'].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]'
                }
                $cardObject['SDK'].Sections.Add($segment['SDK']) | Out-Null
            }
        }
    }
    End {
        if($addlSectionWidgets) {
            $newWidgetStack = @()
            for ($i = 0;$i -lt $addlSectionWidgets.Count;$i++) {
                if ($newWidgetStack -and ($addlSectionWidgets[$i].PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.Section.Button') -and ($newWidgetStack[-1].PSTypeNames[0] -eq 'PSGSuite.Chat.Message.Card.Section.Button')) {
                    $newWidgetStack[-1]['Webhook']['buttons'] += $addlSectionWidgets[$i]['Webhook']['buttons'][0]
                    $newWidgetStack[-1]['SDK'].Buttons.Add($addlSectionWidgets[$i]['SDK'].Buttons[0]) | Out-Null
                }
                else {
                    $newWidgetStack += $addlSectionWidgets[$i]
                }
            }
            $addlSection = $newWidgetStack | Add-GSChatCardSection
            if (!$cardObject['Webhook']['sections']) {
                $cardObject['Webhook']['sections'] = @()
            }
            $cardObject['Webhook']['sections'] += $addlSection['Webhook']
            if (!$cardObject['SDK'].Sections) {
                $cardObject['SDK'].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]'
            }
            $cardObject['SDK'].Sections.Add($addlSection['SDK']) | Out-Null
        }
        [void]$cardObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card')
        return $cardObject
    }
}
Export-ModuleMember -Function 'Add-GSChatCard'

function Add-GSChatCardAction {
    <#
    .SYNOPSIS
    Creates a Chat CardAction
 
    .DESCRIPTION
    Creates a Chat CardAction
 
    .PARAMETER ActionLabel
    The label used to be displayed in the action menu item.
 
    .PARAMETER OnClick
    The OnClick event that triggers when a user clicks the CardAction
 
    You must use the function `Add-GSChatOnClick` to create OnClicks, otherwise this will throw a terminating error.
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $ActionLabel,
        [parameter(Mandatory = $true,Position = 1)]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card.OnClick"
            if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                $true
            }
            else {
                throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")."
            }
        })]
        [Object]
        $OnClick,
        [parameter(Mandatory = $false,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment
    )
    Begin {
        $cardActionObject = @{
            Webhook = @{
                actionLabel = $ActionLabel
                onClick = $OnClick['Webhook']
            }
            SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.CardAction' -Property @{
                ActionLabel = $ActionLabel
                OnClick = $OnClick['SDK']
            })
        }
        $widgetStack = @()
    }
    Process {
        foreach ($segment in $MessageSegment) {
            if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) {
                $widgetStack += $segment
            }
            else {
                $segment
            }
        }
    }
    End {
        [void]$cardActionObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.CardAction')
        if($widgetStack) {
            $widgetStack += $cardActionObject
            $widgetStack
        }
        else {
            $cardActionObject
        }
    }
}
Export-ModuleMember -Function 'Add-GSChatCardAction'

function Add-GSChatCardSection {
    <#
    .SYNOPSIS
    Creates a Chat Message Card Section
 
    .DESCRIPTION
    Creates a Chat Message Card Section
 
    .PARAMETER SectionHeader
    The header title of the section
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [String]
        $SectionHeader,
        [parameter(Mandatory = $true,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment
    )
    Begin {
        $sectionObject = @{
            Webhook = @{
                widgets = @()
            }
            SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.Section' -Property @{
                Widgets = (New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.WidgetMarkup]')
            })
        }
        if ($PSBoundParameters.Keys -contains 'SectionHeader') {
            $sectionObject['Webhook']['header'] = $SectionHeader
            $sectionObject['SDK'].Header = $SectionHeader
        }
    }
    Process {
        foreach ($segment in $MessageSegment) {
            if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) {
                $sectionObject['Webhook']['widgets'] += $segment['Webhook']
                $sectionObject['SDK'].Widgets.Add($segment['SDK'])
            }
            else {
                $segment
            }
            
        }
    }
    End {
        [void]$sectionObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section')
        return $sectionObject
    }
}
Export-ModuleMember -Function 'Add-GSChatCardSection'

function Add-GSChatImage {
    <#
    .SYNOPSIS
    Creates a Chat Image widget to include in a section
 
    .DESCRIPTION
    Creates a Chat Image widget to include in a section
 
    .PARAMETER ImageUrl
    The Url of the Image
 
    .PARAMETER AspectRatio
    The AspectRatio of the Image
 
    .PARAMETER LinkImage
    If $true, automatically creates the OnClick event for the image to open the image URL
 
    .PARAMETER OnClick
    The OnClick event that triggers when a user clicks the KeyValue
 
    You must use the function `Add-GSChatOnClick` to create OnClicks, otherwise this will throw a terminating error.
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    [CmdletBinding(DefaultParameterSetName = "LinkImage")]
    Param
    (
        [parameter(Mandatory = $true)]
        [String]
        $ImageUrl,
        [parameter(Mandatory = $false)]
        [Double]
        $AspectRatio,
        [parameter(Mandatory = $false,ParameterSetName = "LinkImage")]
        [Switch]
        $LinkImage,
        [parameter(Mandatory = $false,ParameterSetName = "OnClick")]
        [ValidateScript( {
            $allowedTypes = "PSGSuite.Chat.Message.Card.OnClick"
            if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                $true
            }
            else {
                throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")."
            }
        })]
        [Object]
        $OnClick,
        [parameter(Mandatory = $false,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment
    )
    Begin {
        $widgetObject = @{
            Webhook = @{
                image = @{}
            }
            SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.WidgetMarkup' -Property @{
                Image = (New-Object 'Google.Apis.HangoutsChat.v1.Data.Image')
            })
        }
        $widgetStack = @()
        foreach ($key in $PSBoundParameters.Keys) {
            switch ($key) {
                ImageUrl {
                    $widgetObject['Webhook']['image']['imageUrl'] = $PSBoundParameters[$key]
                    $widgetObject['SDK'].Image.ImageUrl = $PSBoundParameters[$key]
                }
                AspectRatio {
                    $widgetObject['Webhook']['image']['aspectRatio'] = $PSBoundParameters[$key]
                    $widgetObject['SDK'].Image.AspectRatio = $PSBoundParameters[$key]
                }
                OnClick {
                    $widgetObject['Webhook']['image']['onClick'] = $PSBoundParameters[$key]['Webhook']
                    $widgetObject['SDK'].Image.OnClick = $PSBoundParameters[$key]['SDK']
                }
            }
        }
        if ($LinkImage) {
            $newOnClick = Add-GSChatOnClick -Url $ImageUrl
            $widgetObject['Webhook']['image']['onClick'] = $newOnClick['Webhook']
            $widgetObject['SDK'].Image.OnClick = $newOnClick['SDK']
        }
    }
    Process {
        foreach ($segment in $MessageSegment) {
            if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) {
                $widgetStack += $segment
            }
            else {
                $segment
            }
        }
    }
    End {
        [void]$widgetObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section.Image')
        if ($widgetStack) {
            $widgetStack += $widgetObject
            $widgetStack
        }
        else {
            $widgetObject
        }
    }
}
Export-ModuleMember -Function 'Add-GSChatImage'

function Add-GSChatKeyValue {
    <#
    .SYNOPSIS
    Creates a Chat KeyValue widget to include in a section
 
    .DESCRIPTION
    Creates a Chat KeyValue widget to include in a section
 
    .PARAMETER TopLabel
    The TopLabel for the KeyValue
 
    .PARAMETER Content
    The Content for the KeyValue
 
    .PARAMETER BottomLabel
    The BottomLabel for the KeyValue
 
    .PARAMETER Icon
    The icon to display next to the KeyValue
 
    Available values are:
    * AIRPLANE
    * BOOKMARK
    * BUS
    * CAR
    * CLOCK
    * CONFIRMATION_NUMBER_ICON
    * DOLLAR
    * DESCRIPTION
    * EMAIL
    * EVENT_PERFORMER
    * EVENT_SEAT
    * FLIGHT_ARRIVAL
    * FLIGHT_DEPARTURE
    * HOTEL
    * HOTEL_ROOM_TYPE
    * INVITE
    * MAP_PIN
    * MEMBERSHIP
    * MULTIPLE_PEOPLE
    * OFFER
    * PERSON
    * PHONE
    * RESTAURANT_ICON
    * SHOPPING_CART
    * STAR
    * STORE
    * TICKET
    * TRAIN
    * VIDEO_CAMERA
    * VIDEO_PLAY
 
    .PARAMETER IconUrl
    The Url of the icon to display next to the KeyValue
 
    .PARAMETER ContentMultiline
    Whether the content of the KeyValue is multiline or not
 
    .PARAMETER OnClick
    The OnClick event that triggers when a user clicks the KeyValue
 
    You must use the function `Add-GSChatOnClick` to create OnClicks, otherwise this will throw a terminating error.
 
    .PARAMETER Button
    A button to add to the KeyValue
 
    You must use the function `Add-GSChatButton` to create Buttons, otherwise this will throw a terminating error.
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    [CmdletBinding(DefaultParameterSetName = "Icon")]
    Param
    (
        [parameter(Mandatory = $false)]
        [String]
        $TopLabel,
        [parameter(Mandatory = $false)]
        [String]
        $Content,
        [parameter(Mandatory = $false)]
        [String]
        $BottomLabel,
        [parameter(Mandatory = $false,ParameterSetName = "Icon")]
        [ValidateSet('AIRPLANE','BOOKMARK','BUS','CAR','CLOCK','CONFIRMATION_NUMBER_ICON','DOLLAR','DESCRIPTION','EMAIL','EVENT_PERFORMER','EVENT_SEAT','FLIGHT_ARRIVAL','FLIGHT_DEPARTURE','HOTEL','HOTEL_ROOM_TYPE','INVITE','MAP_PIN','MEMBERSHIP','MULTIPLE_PEOPLE','OFFER','PERSON','PHONE','RESTAURANT_ICON','SHOPPING_CART','STAR','STORE','TICKET','TRAIN','VIDEO_CAMERA','VIDEO_PLAY')]
        [String]
        $Icon,
        [parameter(Mandatory = $false,ParameterSetName = "IconUrl")]
        [String]
        $IconUrl,
        [parameter(Mandatory = $false)]
        [Switch]
        $ContentMultiline,
        [parameter(Mandatory = $false)]
        [ValidateScript( {
            $allowedTypes = "PSGSuite.Chat.Message.Card.OnClick"
            if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                $true
            }
            else {
                throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")."
            }
        })]
        [Object]
        $OnClick,
        [parameter(Mandatory = $false)]
        [ValidateScript( {
            $allowedTypes = "PSGSuite.Chat.Message.Card.Section.Button"
            if ([string]$($_.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                $true
            }
            else {
                throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($_.PSTypeNames -join ", ")."
            }
        })]
        [Object]
        $Button,
        [parameter(Mandatory = $false,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment
    )
    Begin {
        $widgetObject = @{
            Webhook = @{
                keyValue = @{}
            }
            SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.WidgetMarkup' -Property @{
                KeyValue = (New-Object 'Google.Apis.HangoutsChat.v1.Data.KeyValue')
            })
        }
        $widgetStack = @()
        foreach ($key in $PSBoundParameters.Keys) {
            switch ($key) {
                TopLabel {
                    $widgetObject['Webhook']['keyValue']['topLabel'] = $PSBoundParameters[$key]
                    $widgetObject['SDK'].KeyValue.TopLabel = $PSBoundParameters[$key]
                }
                Content {
                    $widgetObject['Webhook']['keyValue']['content'] = $PSBoundParameters[$key]
                    $widgetObject['SDK'].KeyValue.Content = $PSBoundParameters[$key]
                }
                BottomLabel {
                    $widgetObject['Webhook']['keyValue']['bottomLabel'] = $PSBoundParameters[$key]
                    $widgetObject['SDK'].KeyValue.BottomLabel = $PSBoundParameters[$key]
                }
                Icon {
                    $widgetObject['Webhook']['keyValue']['icon'] = $PSBoundParameters[$key]
                    $widgetObject['SDK'].KeyValue.Icon = $PSBoundParameters[$key]
                }
                IconUrl {
                    $widgetObject['Webhook']['keyValue']['iconUrl'] = $PSBoundParameters[$key]
                    $widgetObject['SDK'].KeyValue.IconUrl = $PSBoundParameters[$key]
                }
                ContentMultiline {
                    $widgetObject['Webhook']['keyValue']['contentMultiline'] = $PSBoundParameters[$key]
                    $widgetObject['SDK'].KeyValue.ContentMultiline = $PSBoundParameters[$key]
                }
                OnClick {
                    $widgetObject['Webhook']['keyValue']['onClick'] = $PSBoundParameters[$key]['Webhook']
                    $widgetObject['SDK'].KeyValue.OnClick = $PSBoundParameters[$key]['SDK']
                }
                Button {
                    $widgetObject['Webhook']['keyValue']['button'] = $PSBoundParameters[$key]['Webhook']
                    $widgetObject['SDK'].KeyValue.Button = $PSBoundParameters[$key]['SDK']
                }
            }
        }
    }
    Process {
        foreach ($segment in $MessageSegment) {
            if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) {
                $widgetStack += $segment
            }
            else {
                $segment
            }
        }
    }
    End {
        [void]$widgetObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section.KeyValue')
        if ($widgetStack) {
            $widgetStack += $widgetObject
            $widgetStack
        }
        else {
            $widgetObject
        }
    }
}
Export-ModuleMember -Function 'Add-GSChatKeyValue'

function Add-GSChatOnClick {
    <#
    .SYNOPSIS
    Creates a Chat OnClick action to include in a widget
 
    .DESCRIPTION
    Creates a Chat OnClick action to include in a widget
 
    .PARAMETER Url
    The Url to open for an OpenLink action on click
 
    .PARAMETER ActionMethodName
    Apps Script function to invoke when the containing element is clicked/activated.
 
    .PARAMETER ActionParameters
    A hashtable containing key/value pairs of parameters to pass to the ActionMethod
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    [CmdletBinding(DefaultParameterSetName = "OpenLink")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "OpenLink")]
        [String]
        $Url,
        [parameter(Mandatory = $true,ParameterSetName = "Action")]
        [String]
        $ActionMethodName,
        [parameter(Mandatory = $false,ParameterSetName = "Action")]
        [Hashtable[]]
        $ActionParameters
    )
    Begin {
        $onClickObject = @{
            Webhook = @{}
            SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.OnClick')
        }
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            OpenLink {
                $onClickObject['Webhook']['openLink'] = @{
                    url = $Url
                }
                $onClickObject['SDK'].OpenLink = (New-Object 'Google.Apis.HangoutsChat.v1.Data.OpenLink' -Property @{
                    Url = $Url
                })
            }
            Action {
                $onClickObject['Webhook']['action'] = @{
                    actionMethodName = $ActionMethodName
                }
                $onClickObject['SDK'].Action = (New-Object 'Google.Apis.HangoutsChat.v1.Data.FormAction' -Property @{
                    ActionMethodName = $ActionMethodName
                })
                if ($PSBoundParameters.Keys -contains 'ActionParameters') {
                    $onClickObject['Webhook']['action']['parameters'] = @()
                    $onClickObject['SDK'].Action.Parameters = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.ActionParameter]'
                    foreach ($dict in $ActionParameters) {
                        if ($dict.Keys.Count -eq 2 -and $dict.Keys -contains 'key' -and $dict.Keys -contains 'value') {
                            $onClickObject['Webhook']['action']['parameters'] += ([PSCustomObject]@{
                                key = "$($dict['key'])"
                                value = "$($dict['value'])"
                            })
                            $onClickObject['SDK'].Action.Parameters.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionParameter' -Property @{
                                Key = $dict['key']
                                Value = $dict['value']
                            })) | Out-Null
                        }
                        else {
                            foreach ($key in $dict.Keys) {
                                $onClickObject['Webhook']['action']['parameters'] += ([PSCustomObject]@{
                                    key = "$key"
                                    value = "$($dict[$key])"
                                })
                                $onClickObject['SDK'].Action.Parameters.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionParameter' -Property @{
                                    Key = $key
                                    Value = $dict[$key]
                                })) | Out-Null
                            }
                        }
                    }
                }
            }
        }
    }
    End {
        [void]$onClickObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.OnClick')
        $onClickObject
    }
}
Export-ModuleMember -Function 'Add-GSChatOnClick'

function Add-GSChatTextParagraph {
    <#
    .SYNOPSIS
    Creates a Chat TextParagraph widget to include in a section
 
    .DESCRIPTION
    Creates a Chat TextParagraph widget to include in a section
 
    .PARAMETER Text
    The text for the textParagraph
 
    .PARAMETER MessageSegment
    Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values.
 
    .EXAMPLE
    Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports)
 
    Sends a simple Chat message using the JobReports webhook
 
    .EXAMPLE
    Add-GSChatTextParagraph -Text "Guys...","We <b>NEED</b> to <i>stop</i> spending money on <b>crap</b>!" |
    Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR |
    Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE |
    Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage |
    Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 |
    Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 |
    Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 |
    Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 |
    Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card |
    Add-GSChatTextParagraph -Text "This message sent by <b>PSGSuite</b> via WebHook!" |
    Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 |
    Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom
 
    This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom:
        1. Add a TextParagraph widget
        2. Add a KeyValue with an icon
        3. Add another KeyValue with a different icon
        4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter
        5. Add a new section to encapsulate the widgets sent through the pipeline before it
        6. Add a TextButton that opens the PSGSuite GitHub repo when clicked
        7. Add another TextButton that opens Google Admin Console when clicked
        8. Wrap the 2 buttons in a new Section to divide the content
        9. Wrap all widgets and sections in the pipeline so far in a Card
        10. Add a new TextParagraph as a footer to the message
        11. Wrap that TextParagraph in a new section
        12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script.
 
    .EXAMPLE
    Get-Service | Select-Object -First 5 | ForEach-Object {
        Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET
    } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports
 
    This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook
    #>

    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String[]]
        $Text,
        [parameter(Mandatory = $false,ValueFromPipeline = $true)]
        [Alias('InputObject')]
        [ValidateScript({
            $allowedTypes = "PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue"
            foreach ($item in $_) {
                if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") {
                    $true
                }
                else {
                    throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")."
                }
            }
        })]
        [Object[]]
        $MessageSegment
    )
    Begin {
        $widgetObject = @{
            Webhook = @{
                textParagraph = @{
                    text = ($Text -join "`n")
                }
            }
            SDK = (New-Object 'Google.Apis.HangoutsChat.v1.Data.WidgetMarkup' -Property @{
                TextParagraph = (New-Object 'Google.Apis.HangoutsChat.v1.Data.TextParagraph' -Property @{
                    Text = ($Text -join "`n")
                })
            })
        }
        $widgetStack = @()
    }
    Process {
        foreach ($segment in $MessageSegment) {
            if ($segment.PSTypeNames[0] -in @("PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue")) {
                $widgetStack += $segment
            }
            else {
                $segment
            }
        }
    }
    End {
        [void]$widgetObject.PSObject.TypeNames.Insert(0,'PSGSuite.Chat.Message.Card.Section.TextParagraph')
        if ($widgetStack) {
            $widgetStack += $widgetObject
            $widgetStack
        }
        else {
            $widgetObject
        }
    }
}
Export-ModuleMember -Function 'Add-GSChatTextParagraph'

function Add-GSEventAttendee {
    <#
    .SYNOPSIS
    Adds an event attendee to a calendar event
     
    .DESCRIPTION
    Adds an event attendee to a calendar event
     
    .PARAMETER Email
    The email address of the attendee
     
    .PARAMETER AdditionalGuests
    How many additional guests, if any
     
    .PARAMETER Comment
    Attendee comment
     
    .PARAMETER DisplayName
    The attendee's name, if available
     
    .PARAMETER Optional
    Whether this is an optional attendee
     
    .PARAMETER Organizer
    Whether the attendee is the organizer of the event
     
    .PARAMETER Resource
    Whether the attendee is a resource
     
    .PARAMETER ResponseStatus
    The attendee's response status.
     
    Possible values are:
    * "NeedsAction": The attendee has not responded to the invitation.
    * "Declined": The attendee has declined the invitation.
    * "Tentative": The attendee has tentatively accepted the invitation.
    * "Accepted": The attendee has accepted the invitation
     
    .PARAMETER InputObject
    Used for pipeline input of an existing UserAddress object to strip the extra attributes and prevent errors
    #>

    [CmdletBinding(DefaultParameterSetName = "InputObject")]
    Param
    (
        [Parameter(Mandatory = $true,ParameterSetName = "Fields")]
        [String]
        $Email,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [Int]
        $AdditionalGuests,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [String]
        $Comment,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [String]
        $DisplayName,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [Switch]
        $Optional,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [Switch]
        $Organizer,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [Switch]
        $Resource,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [ValidateSet('NeedsAction','Declined','Tentative','Accepted')]
        [String]
        $ResponseStatus,
        [Parameter(Mandatory = $false,ValueFromPipeline = $true,ParameterSetName = "InputObject")]
        [Google.Apis.Calendar.v3.Data.EventAttendee[]]
        $InputObject
    )
    Begin {
        $propsToWatch = @(
            'AdditionalGuests'
            'Comment'
            'DisplayName'
            'Email'
            'Optional'
            'Organizer'
            'Resource'
            'ResponseStatus'
        )
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Fields {
                    Write-Verbose "Adding event attendee '$Email'"
                    $obj = New-Object 'Google.Apis.Calendar.v3.Data.EventAttendee'
                    foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) {
                        $obj.$prop = $PSBoundParameters[$prop]
                    }
                    $obj
                }
                InputObject {
                    foreach ($iObj in $InputObject) {
                        $obj = New-Object 'Google.Apis.Calendar.v3.Data.EventAttendee'
                        foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) {
                            $obj.$prop = $iObj.$prop
                        }
                        $obj
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSEventAttendee'

function Add-GSUserAddress {
    <#
    .SYNOPSIS
    Builds a UserAddress object to use when creating or updating a User
 
    .DESCRIPTION
    Builds a UserAddress object to use when creating or updating a User
 
    .PARAMETER Country
    Country
 
    .PARAMETER CountryCode
    The country code. Uses the ISO 3166-1 standard: http://www.iso.org/iso/iso-3166-1_decoding_table
 
    .PARAMETER CustomType
    If the address type is custom, this property contains the custom value
 
    .PARAMETER ExtendedAddress
    For extended addresses, such as an address that includes a sub-region
 
    .PARAMETER Formatted
    A full and unstructured postal address. This is not synced with the structured address fields
 
    .PARAMETER Locality
    The town or city of the address
 
    .PARAMETER PoBox
    The post office box, if present
 
    .PARAMETER PostalCode
    The ZIP or postal code, if applicable
 
    .PARAMETER Primary
    If this is the user's primary address. The addresses list may contain only one primary address
 
    .PARAMETER Region
    The abbreviated province or state
 
    .PARAMETER SourceIsStructured
    Indicates if the user-supplied address was formatted. Formatted addresses are not currently supported
 
    .PARAMETER StreetAddress
    The street address, such as 1600 Amphitheatre Parkway. Whitespace within the string is ignored; however, newlines are significant
 
    .PARAMETER Type
        The address type.
 
    Acceptable values are:
    * "Custom"
    * "Home"
    * "Other"
    * "Work"
 
    .PARAMETER InputObject
    Used for pipeline input of an existing UserAddress object to strip the extra attributes and prevent errors
 
    .EXAMPLE
    $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary
 
    $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary
 
    $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2
 
    $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com
 
    New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email
 
    Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object.
    #>

    [CmdletBinding(DefaultParameterSetName = "InputObject")]
    Param
    (
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Country,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $CountryCode,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $CustomType,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $ExtendedAddress,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Formatted,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Alias('Town', 'City')]
        [String]
        $Locality,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $PoBox,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $PostalCode,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Switch]
        $Primary,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Alias('State', 'Province')]
        [String]
        $Region,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Switch]
        $SourceIsStructured,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $StreetAddress,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [ValidateSet('Custom', 'Home', 'Other', 'Work')]
        [String]
        $Type,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]]
        $InputObject
    )
    Begin {
        $propsToWatch = @(
            'Country'
            'CountryCode'
            'CustomType'
            'ExtendedAddress'
            'Formatted'
            'Locality'
            'PoBox'
            'PostalCode'
            'Primary'
            'Region'
            'SourceIsStructured'
            'StreetAddress'
            'Type'
        )
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Fields {
                    $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserAddress'
                    foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) {
                        $obj.$prop = $PSBoundParameters[$prop]
                    }
                    $obj
                }
                InputObject {
                    foreach ($iObj in $InputObject) {
                        $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserAddress'
                        foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) {
                            $obj.$prop = $iObj.$prop
                        }
                        $obj
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSUserAddress'

function Add-GSUserEmail {
    <#
    .SYNOPSIS
    Builds a Email object to use when creating or updating a User
 
    .DESCRIPTION
    Builds a Email object to use when creating or updating a User
 
    .PARAMETER Address
    The user's email address. Also serves as the email ID. This value can be the user's primary email address or an alias.
 
    .PARAMETER CustomType
    If the value of type is custom, this property contains the custom type.
 
    .PARAMETER Primary
    Indicates if this is the user's primary email. Only one entry can be marked as primary.
 
    .PARAMETER Type
    The type of the email account.
 
    Acceptable values are:
    * "custom"
    * "home"
    * "other"
    * "work"
 
    .PARAMETER InputObject
    Used for pipeline input of an existing Email object to strip the extra attributes and prevent errors
 
    .EXAMPLE
    $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary
 
    $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary
 
    $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2
 
    $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com
 
    New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email
 
    Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object.
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Address,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $CustomType,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Switch]
        $Primary,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [ValidateSet('custom', 'home', 'other', 'work')]
        [String]
        $Type,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]]
        $InputObject
    )
    Begin {
        $propsToWatch = @(
            'Address'
            'CustomType'
            'Type'
        )
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Fields {
                    $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserEmail'
                    foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) {
                        $obj.$prop = $PSBoundParameters[$prop]
                    }
                    $obj
                }
                InputObject {
                    foreach ($iObj in $InputObject) {
                        $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserEmail'
                        foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) {
                            $obj.$prop = $iObj.$prop
                        }
                        $obj
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSUserEmail'

function Add-GSUserExternalId {
    <#
    .SYNOPSIS
    Builds a UserExternalId object to use when creating or updating a User
 
    .DESCRIPTION
    Builds a UserExternalId object to use when creating or updating a User
 
    .PARAMETER CustomType
    If the external ID type is custom, this property holds the custom type
 
    .PARAMETER Type
    The type of the ID.
 
    Acceptable values are:
    * "account"
    * "custom"
    * "customer"
    * "login_id"
    * "network"
    * "organization": For example, Employee ID.
 
    .PARAMETER Value
    The value of the ID
 
    .PARAMETER InputObject
    Used for pipeline input of an existing UserExternalId object to strip the extra attributes and prevent errors
 
    .EXAMPLE
    $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary
 
    $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary
 
    $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2
 
    $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com
 
    New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email
 
    Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object.
    #>

    [CmdletBinding(DefaultParameterSetName = "InputObject")]
    Param
    (
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $CustomType,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [ValidateSet('account', 'custom', 'customer', 'login_id', 'network', 'organization')]
        [String]
        $Type,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Alias('ExternalId')]
        [String]
        $Value,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]]
        $InputObject
    )
    Begin {
        $propsToWatch = @(
            'CustomType'
            'Type'
            'Value'
        )
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Fields {
                    $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId'
                    foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) {
                        $obj.$prop = $PSBoundParameters[$prop]
                    }
                    $obj
                }
                InputObject {
                    foreach ($iObj in $InputObject) {
                        $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId'
                        foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) {
                            $obj.$prop = $iObj.$prop
                        }
                        $obj
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSUserExternalId'

function Add-GSUserOrganization {
    <#
    .SYNOPSIS
    Builds a Organization object to use when creating or updating a User
 
    .DESCRIPTION
    Builds a Organization object to use when creating or updating a User
 
    .PARAMETER CustomType
    If the external ID type is custom, this property holds the custom type
 
    .PARAMETER Type
    The type of the organization.
 
    If using a CustomType
 
    .PARAMETER Value
    The value of the ID
 
    .PARAMETER InputObject
    Used for pipeline input of an existing UserExternalId object to strip the extra attributes and prevent errors
 
    .EXAMPLE
    $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary
 
    $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary
 
    $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2
 
    $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com
 
    New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email
 
    Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object.
    #>

    [CmdletBinding(DefaultParameterSetName = "InputObject")]
    Param
    (
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $CostCenter,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $CustomType,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Department,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Description,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Domain,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Int]
        $FullTimeEquivalent,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Location,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Name,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Switch]
        $Primary,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Symbol,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Title,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $Type,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]]
        $InputObject
    )
    Begin {
        $propsToWatch = @(
            'CostCenter'
            'CustomType'
            'Department'
            'Description'
            'Domain'
            'FullTimeEquivalent'
            'Location'
            'Name'
            'Primary'
            'Symbol'
            'Title'
            'Type'
        )
        if ($PSBoundParameters.Keys -contains 'CustomType') {
            $PSBoundParameters['Type'] = 'CUSTOM'
        }
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Fields {
                    $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization'
                    foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) {
                        $obj.$prop = $PSBoundParameters[$prop]
                    }
                    $obj
                }
                InputObject {
                    foreach ($iObj in $InputObject) {
                        $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization'
                        foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) {
                            $obj.$prop = $iObj.$prop
                        }
                        $obj
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSUserOrganization'

function Add-GSUserPhone {
    <#
    .SYNOPSIS
    Builds a UserPhone object to use when creating or updating a User
 
    .DESCRIPTION
    Builds a UserPhone object to use when creating or updating a User
 
    .PARAMETER CustomType
    If the value of type is custom, this property contains the custom type
 
    .PARAMETER Primary
    Indicates if this is the user's primary phone number. A user may only have one primary phone number
 
    .PARAMETER Type
    The type of phone number.
 
    Acceptable values are:
    * "assistant"
    * "callback"
    * "car"
    * "company_main"
    * "custom"
    * "grand_central"
    * "home"
    * "home_fax"
    * "isdn"
    * "main"
    * "mobile"
    * "other"
    * "other_fax"
    * "pager"
    * "radio"
    * "telex"
    * "tty_tdd"
    * "work"
    * "work_fax"
    * "work_mobile"
    * "work_pager"
 
    .PARAMETER Value
    A human-readable phone number. It may be in any telephone number format
 
    .PARAMETER InputObject
    Used for pipeline input of an existing UserPhone object to strip the extra attributes and prevent errors
 
    .EXAMPLE
    $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary
 
    $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary
 
    $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2
 
    $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com
 
    New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email
 
    Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object.
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [String]
        $CustomType,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Switch]
        $Primary,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [ValidateSet('assistant', 'callback', 'car', 'company_main', 'custom', 'grand_central', 'home', 'home_fax', 'isdn', 'main', 'mobile', 'other', 'other_fax', 'pager', 'radio', 'telex', 'tty_tdd', 'work', 'work_fax', 'work_mobile', 'work_pager')]
        [String]
        $Type,
        [Parameter(Mandatory = $false, ParameterSetName = "Fields")]
        [Alias('Phone')]
        [String]
        $Value,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ParameterSetName = "InputObject")]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]]
        $InputObject
    )
    Begin {
        $propsToWatch = @(
            'CustomType'
            'Type'
            'Value'
        )
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Fields {
                    $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhone'
                    foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) {
                        $obj.$prop = $PSBoundParameters[$prop]
                    }
                    $obj
                }
                InputObject {
                    foreach ($iObj in $InputObject) {
                        $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhone'
                        foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) {
                            $obj.$prop = $iObj.$prop
                        }
                        $obj
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSUserPhone'

function Add-GSUserSchemaField {
    <#
    .SYNOPSIS
    Builds a UserPhone object to use when creating or updating a Schema
     
    .DESCRIPTION
    Builds a UserPhone object to use when creating or updating a Schema
     
    .PARAMETER FieldName
    The name of the field
     
    .PARAMETER FieldType
    The type of the field.
 
 
    * Acceptable values are:
    * "BOOL": Boolean values.
    * "DATE": Dates in ISO-8601 format: http://www.w3.org/TR/NOTE-datetime
    * "DOUBLE": Double-precision floating-point values.
    * "EMAIL": Email addresses.
    * "INT64": 64-bit integer values.
    * "PHONE": Phone numbers.
    * "STRING": String values.
     
    .PARAMETER Indexed
    Switch specifying whether the field is indexed or not. Default: true
     
    .PARAMETER MultiValued
    A switch specifying whether this is a multi-valued field or not. Default: false
     
    .PARAMETER ReadAccessType
    Parameter description
     
    .PARAMETER InputObject
    Parameter description
     
    .EXAMPLE
    New-GSUserSchema -SchemaName "SDK" -Fields (Add-GSUserSchemaField -FieldName "string" -FieldType STRING -ReadAccessType ADMINS_AND_SELF),(Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF)
 
    This command will create a schema named "SDK" with two fields, "string" and "date", readable by ADMINS_AND_SELF
    #>

    [CmdletBinding(DefaultParameterSetName = "InputObject")]
    Param
    (
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [String]
        $FieldName,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [ValidateSet("BOOL","DATE","DOUBLE","EMAIL","INT64","PHONE","STRING")]
        [String]
        $FieldType = "STRING",
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [Switch]
        $Indexed,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [Switch]
        $MultiValued,
        [Parameter(Mandatory = $false,ParameterSetName = "Fields")]
        [ValidateSet("ADMINS_AND_SELF","ALL_DOMAIN_USERS")]
        [String]
        $ReadAccessType = "ADMINS_AND_SELF",
        [Parameter(Mandatory = $false,ValueFromPipeline = $true,ParameterSetName = "InputObject")]
        [Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec[]]
        $InputObject
    )
    Begin {
        $propsToWatch = @(
            'FieldName'
            'FieldType'
            'Indexed'
            'MultiValued'
            'ReadAccessType'
        )
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Fields {
                    $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec'
                    foreach ($prop in $PSBoundParameters.Keys | Where-Object {$obj.PSObject.Properties.Name -contains $_}) {
                        $obj.$prop = $PSBoundParameters[$prop]
                    }
                    $obj
                }
                InputObject {
                    foreach ($iObj in $InputObject) {
                        $obj = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec'
                        foreach ($prop in $iObj.PSObject.Properties.Name | Where-Object {$obj.PSObject.Properties.Name -contains $_ -and $propsToWatch -contains $_}) {
                            $obj.$prop = $iObj.$prop
                        }
                        $obj
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Add-GSUserSchemaField'

function Block-CoreCLREncryptionWarning {
    <#
    .SYNOPSIS
    Blocks CoreCLR encryption warning from reappearing (specific to PSGSuite)
     
    .DESCRIPTION
    Blocks CoreCLR encryption warning from reappearing (specific to PSGSuite) by creating a blank txt file in the path: ~\.scrthq
     
    .EXAMPLE
    Block-CoreCLREncryptionWarning
 
    Creates the breadcrumb file to let PSGSuite know that you've acknowledged the CoreCLR encryption warning and it does not need to be displayed every time the module is imported
    #>

    New-Item -Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt") -ItemType File -Force | Out-Null
}
Export-ModuleMember -Function 'Block-CoreCLREncryptionWarning'

function Compare-ModuleVersion {
    [CmdletBinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [String[]]
        $ModuleName = 'PSGSuite'
    )
    Begin {
        $results = New-Object System.Collections.ArrayList
    }
    Process {
        foreach ($module in $ModuleName) {
            Write-Verbose "Comparing module versions for module '$module'"
            $result = [PSCustomObject][Ordered]@{
                ModuleName       = $module
                InstalledVersion = $null
                GalleryVersion   = $null
                UpdateAvailable  = $null
            }
            if ($InstalledVersion = ((Get-Module -Name $module -ListAvailable).Version | Sort-Object)[-1]) {
                $result.InstalledVersion = $InstalledVersion
                $uri = [Uri]"https://www.powershellgallery.com/api/v2/Packages?`$filter=Id eq '$($module)' and IsLatestVersion"
                if ($GalleryVersion = [System.Version]((Invoke-RestMethod -Uri $uri -Verbose:$false).properties.Version)) {
                    $result.GalleryVersion = $GalleryVersion
                    $result.UpdateAvailable = if ($InstalledVersion -ge $GalleryVersion) {
                        $false
                    }
                    else {
                        $true
                    }
                }
            }
            else {
                Write-Warning "Module '$module' was not found on this machine; unable to compare module versions."
            }
            [void]$results.Add($result)
        }
    }
    End {
        return $results
    }
}
Export-ModuleMember -Function 'Compare-ModuleVersion'

function Unblock-CoreCLREncryptionWarning {
    <#
    .SYNOPSIS
    Unblocks CoreCLR encryption warning to ensure it appears if applicable (specific to PSGSuite)
     
    .DESCRIPTION
    Unblocks CoreCLR encryption warning to ensure it appears if applicable (specific to PSGSuite) by removing the following file if it exists: ~\.scrthq\BlockCoreCLREncryptionWarning.txt
     
    .EXAMPLE
    Unblock-CoreCLREncryptionWarning
 
    Removes the breadcrumb file to let PSGSuite know that you want to receive the CoreCLR encryption warning if applicable
    #>

    if (Test-Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt")) {
        Remove-Item -Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt") -Force
    }
}
Export-ModuleMember -Function 'Unblock-CoreCLREncryptionWarning'

function Get-GSUserLicense {
    <#
    .SYNOPSIS
    Gets the G Suite license information for a user or list of users
     
    .DESCRIPTION
    Gets the G Suite license information for a user or list of users
     
    .PARAMETER User
    The primary email or unique Id of the user to retrieve license information for
     
    .PARAMETER License
    The license SKU to retrieve information for. If excluded, searches all license SKUs
     
    .PARAMETER ProductID
    The product Id to list licenses for
     
    .PARAMETER PageSize
    The page size of the result set
     
    .EXAMPLE
    Get-GSUserLicense
 
    Gets the full list of licenses for the customer
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User,
        [parameter(Mandatory = $false)]
        [Alias("SkuId")]
        [ValidateSet("G-Suite-Enterprise","Google-Apps-Unlimited","Google-Apps-For-Business","Google-Apps-For-Postini","Google-Apps-Lite","Google-Drive-storage-20GB","Google-Drive-storage-50GB","Google-Drive-storage-200GB","Google-Drive-storage-400GB","Google-Drive-storage-1TB","Google-Drive-storage-2TB","Google-Drive-storage-4TB","Google-Drive-storage-8TB","Google-Drive-storage-16TB","Google-Vault","Google-Vault-Former-Employee","1010020020")]
        [string]
        $License,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateSet("Google-Apps","Google-Drive-storage","Google-Vault")]
        [string[]]
        $ProductID = @("Google-Apps","Google-Drive-storage","Google-Vault"),
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Alias("MaxResults")]
        [ValidateRange(1,1000)]
        [Int]
        $PageSize = "1000"
    )
    Begin {
        if ($PSCmdlet.ParameterSetName -eq 'Get') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/apps.licensing'
                ServiceType = 'Google.Apis.Licensing.v1.LicensingService'
            }
            $service = New-GoogleService @serviceParams
            $productHash = @{
                '1010020020'                   = 'Google-Apps'
                'G-Suite-Enterprise'           = 'Google-Apps'
                'Google-Apps-Unlimited'        = 'Google-Apps'
                'Google-Apps-For-Business'     = 'Google-Apps'
                'Google-Apps-For-Postini'      = 'Google-Apps'
                'Google-Apps-Lite'             = 'Google-Apps'
                'Google-Vault'                 = 'Google-Vault'
                'Google-Vault-Former-Employee' = 'Google-Vault'
                'Google-Drive-storage-20GB'    = 'Google-Drive-storage'
                'Google-Drive-storage-50GB'    = 'Google-Drive-storage'
                'Google-Drive-storage-200GB'   = 'Google-Drive-storage'
                'Google-Drive-storage-400GB'   = 'Google-Drive-storage'
                'Google-Drive-storage-1TB'     = 'Google-Drive-storage'
                'Google-Drive-storage-2TB'     = 'Google-Drive-storage'
                'Google-Drive-storage-4TB'     = 'Google-Drive-storage'
                'Google-Drive-storage-8TB'     = 'Google-Drive-storage'
                'Google-Drive-storage-16TB'    = 'Google-Drive-storage'
            }
        }
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Get {
                    foreach ($U in $User) {
                        if ($U -ceq 'me') {
                            $U = $Script:PSGSuite.AdminEmail
                        }
                        elseif ($U -notlike "*@*.*") {
                            $U = "$($U)@$($Script:PSGSuite.Domain)"
                        }
                        if ($License) {
                            Write-Verbose "Getting License SKU '$License' for User '$U'"
                            if ($License -eq "G-Suite-Enterprise") {
                                $License = "1010020020"
                            }
                            $request = $service.LicenseAssignments.Get($productHash[$License],$License,$U)
                            $request.Execute()
                        }
                        else {
                            foreach ($license in (@("G-Suite-Enterprise","Google-Apps-Unlimited","Google-Apps-For-Business","Google-Apps-For-Postini","Google-Apps-Lite","Google-Drive-storage-20GB","Google-Drive-storage-50GB","Google-Drive-storage-200GB","Google-Drive-storage-400GB","Google-Drive-storage-1TB","Google-Drive-storage-2TB","Google-Drive-storage-4TB","Google-Drive-storage-8TB","Google-Drive-storage-16TB","Google-Vault","Google-Vault-Former-Employee") | Sort-Object)) {
                                Write-Verbose "Getting License SKU '$License' for User '$U'"
                                if ($License -eq "G-Suite-Enterprise") {
                                    $License = "1010020020"
                                }
                                try {
                                    $request = $service.LicenseAssignments.Get($productHash[$License],$License,$U)
                                    $response = $request.Execute()
                                }
                                catch { 
                                }
                                if ($response) {
                                    break
                                }
                            }
                            if (!$response) {
                                Write-Error "No license found for $U!"
                            }
                            else {
                                return $response
                            }
                        }
                    }
                }
                List {
                    Get-GSUserLicenseListPrivate @PSBoundParameters
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSUserLicense'

function Remove-GSUserLicense {
    <#
    .SYNOPSIS
    Removes a license assignment from a user
     
    .DESCRIPTION
    Removes a license assignment from a user. Useful for restoring a user from a Vault-Former-Employee to an auto-assigned G Suite Business license by removing the Vault-Former-Employee license, for example.
     
    .PARAMETER User
    The user's current primary email address
     
    .PARAMETER License
    The license SKU to remove from the user
     
    .EXAMPLE
    Remove-GSUserLicense -User joe -License Google-Vault-Former-Employee
 
    Removes the Vault-Former-Employee license from Joe
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $User,
        [parameter(Mandatory = $true,Position = 1)]
        [Alias("SkuId")]
        [ValidateSet("G-Suite-Enterprise","Google-Apps-Unlimited","Google-Apps-For-Business","Google-Apps-For-Postini","Google-Apps-Lite","Google-Drive-storage-20GB","Google-Drive-storage-50GB","Google-Drive-storage-200GB","Google-Drive-storage-400GB","Google-Drive-storage-1TB","Google-Drive-storage-2TB","Google-Drive-storage-4TB","Google-Drive-storage-8TB","Google-Drive-storage-16TB","Google-Vault","Google-Vault-Former-Employee","1010020020")]
        [string]
        $License
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/apps.licensing'
            ServiceType = 'Google.Apis.Licensing.v1.LicensingService'
        }
        $service = New-GoogleService @serviceParams
        $productHash = @{
            '1010020020'                   = 'Google-Apps'
            'G-Suite-Enterprise'           = 'Google-Apps'
            'Google-Apps-Unlimited'        = 'Google-Apps'
            'Google-Apps-For-Business'     = 'Google-Apps'
            'Google-Apps-For-Postini'      = 'Google-Apps'
            'Google-Apps-Lite'             = 'Google-Apps'
            'Google-Vault'                 = 'Google-Vault'
            'Google-Vault-Former-Employee' = 'Google-Vault'
            'Google-Drive-storage-20GB'    = 'Google-Drive-storage'
            'Google-Drive-storage-50GB'    = 'Google-Drive-storage'
            'Google-Drive-storage-200GB'   = 'Google-Drive-storage'
            'Google-Drive-storage-400GB'   = 'Google-Drive-storage'
            'Google-Drive-storage-1TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-2TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-4TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-8TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-16TB'    = 'Google-Drive-storage'
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                if ($PSCmdlet.ShouldProcess("Revoking license '$License' from user '$U'")) {
                    Write-Verbose "Revoking license '$License' from user '$U'"
                    if ($License -eq "G-Suite-Enterprise") {
                        $License = "1010020020"
                    }
                    $request = $service.LicenseAssignments.Delete($productHash[$License],$License,$U)
                    $request.Execute()
                    Write-Verbose "License revoked for user '$U'"
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSUserLicense'

function Set-GSUserLicense {
    <#
    .SYNOPSIS
    Sets the license for a user
     
    .DESCRIPTION
    Sets the license for a user
     
    .PARAMETER User
    The user's current primary email address
     
    .PARAMETER License
    The license SKU to set for the user
     
    .EXAMPLE
    Set-GSUserLicense -User joe -License Google-Apps-For-Business
 
    Sets Joe to a Google-Apps-For-Business license
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User,
        [parameter(Mandatory = $true,Position = 1)]
        [Alias("SkuId")]
        [ValidateSet("G-Suite-Enterprise","Google-Apps-Unlimited","Google-Apps-For-Business","Google-Apps-For-Postini","Google-Apps-Lite","Google-Drive-storage-20GB","Google-Drive-storage-50GB","Google-Drive-storage-200GB","Google-Drive-storage-400GB","Google-Drive-storage-1TB","Google-Drive-storage-2TB","Google-Drive-storage-4TB","Google-Drive-storage-8TB","Google-Drive-storage-16TB","Google-Vault","Google-Vault-Former-Employee","1010020020")]
        [string]
        $License
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/apps.licensing'
            ServiceType = 'Google.Apis.Licensing.v1.LicensingService'
        }
        $service = New-GoogleService @serviceParams
        $productHash = @{
            '1010020020'                   = 'Google-Apps'
            'G-Suite-Enterprise'           = 'Google-Apps'
            'Google-Apps-Unlimited'        = 'Google-Apps'
            'Google-Apps-For-Business'     = 'Google-Apps'
            'Google-Apps-For-Postini'      = 'Google-Apps'
            'Google-Apps-Lite'             = 'Google-Apps'
            'Google-Vault'                 = 'Google-Vault'
            'Google-Vault-Former-Employee' = 'Google-Vault'
            'Google-Drive-storage-20GB'    = 'Google-Drive-storage'
            'Google-Drive-storage-50GB'    = 'Google-Drive-storage'
            'Google-Drive-storage-200GB'   = 'Google-Drive-storage'
            'Google-Drive-storage-400GB'   = 'Google-Drive-storage'
            'Google-Drive-storage-1TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-2TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-4TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-8TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-16TB'    = 'Google-Drive-storage'
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Setting license for $U to $License"
                if ($License -eq "G-Suite-Enterprise") {
                    $License = "1010020020"
                }
                $body = New-Object 'Google.Apis.Licensing.v1.Data.LicenseAssignmentInsert' -Property @{
                    UserId = $U
                }
                $request = $service.LicenseAssignments.Insert($body,$productHash[$License],$License)
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Set-GSUserLicense'

function Update-GSUserLicense {
    <#
    .SYNOPSIS
    Reassign a user's product SKU with a different SKU in the same product
     
    .DESCRIPTION
    Reassign a user's product SKU with a different SKU in the same product
     
    .PARAMETER User
    The user's current primary email address
     
    .PARAMETER License
    The license SKU that you would like to reassign the user to
     
    .EXAMPLE
    Update-GSUserLicense -User joe -License G-Suite-Enterprise
 
    Updates Joe to a G-Suite-Enterprise license
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User,
        [parameter(Mandatory = $false)]
        [Alias("SkuId")]
        [ValidateSet("G-Suite-Enterprise","Google-Apps-Unlimited","Google-Apps-For-Business","Google-Apps-For-Postini","Google-Apps-Lite","Google-Drive-storage-20GB","Google-Drive-storage-50GB","Google-Drive-storage-200GB","Google-Drive-storage-400GB","Google-Drive-storage-1TB","Google-Drive-storage-2TB","Google-Drive-storage-4TB","Google-Drive-storage-8TB","Google-Drive-storage-16TB","Google-Vault","Google-Vault-Former-Employee","1010020020")]
        [string]
        $License
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/apps.licensing'
            ServiceType = 'Google.Apis.Licensing.v1.LicensingService'
        }
        $service = New-GoogleService @serviceParams
        $productHash = @{
            '1010020020'                   = 'Google-Apps'
            'G-Suite-Enterprise'           = 'Google-Apps'
            'Google-Apps-Unlimited'        = 'Google-Apps'
            'Google-Apps-For-Business'     = 'Google-Apps'
            'Google-Apps-For-Postini'      = 'Google-Apps'
            'Google-Apps-Lite'             = 'Google-Apps'
            'Google-Vault'                 = 'Google-Vault'
            'Google-Vault-Former-Employee' = 'Google-Vault'
            'Google-Drive-storage-20GB'    = 'Google-Drive-storage'
            'Google-Drive-storage-50GB'    = 'Google-Drive-storage'
            'Google-Drive-storage-200GB'   = 'Google-Drive-storage'
            'Google-Drive-storage-400GB'   = 'Google-Drive-storage'
            'Google-Drive-storage-1TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-2TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-4TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-8TB'     = 'Google-Drive-storage'
            'Google-Drive-storage-16TB'    = 'Google-Drive-storage'
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Setting license for $U to $License"
                if ($License -eq "G-Suite-Enterprise") {
                    $License = "1010020020"
                }
                $body = Get-GSUserLicense -User $U
                $request = $service.LicenseAssignments.Update($body,$productHash[$License],$License,$U)
                $request.Execute()
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSUserLicense'

function Get-GSOrganizationalUnit {
    <#
    .SYNOPSIS
    Gets Organizational Unit information
     
    .DESCRIPTION
    Gets Organizational Unit information
     
    .PARAMETER SearchBase
    The OrgUnitPath you would like to search for. This can be the single OrgUnit to return or the top level of which to return children of
     
    .PARAMETER SearchScope
    The depth at which to return the list of OrgUnits children
 
    Available values are:
    * "Base": only return the OrgUnit specified in the SearchBase
    * "Subtree": return the full list of OrgUnits underneath the specified SearchBase
    * "OneLevel": return the SearchBase and the OrgUnit's directly underneath it
    * "All": same as Subtree
    * "Children": same as OneLevel
 
    Defaults to 'All'
     
    .EXAMPLE
    Get-GSOrganizationalUnit -SearchBase "/" -SearchScope Base
 
    Gets the top level Organizational Unit information
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [Alias('OrgUnitPath','BaseOrgUnitPath')]
        [String]
        $SearchBase,
        [parameter(Mandatory = $false)]
        [Alias('Type')]
        [ValidateSet('Base','Subtree','OneLevel','All','Children')]
        [String]
        $SearchScope = 'All'
    )
    Begin {
        if ($PSBoundParameters.Keys -contains 'SearchBase' -and $SearchBase -ne "/" -and $SearchScope -eq 'Base') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.orgunit'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        try {
            if ($PSBoundParameters.Keys -contains 'SearchBase' -and $SearchBase -ne "/" -and $SearchScope -eq 'Base') {
                foreach ($O in $SearchBase) {
                    Write-Verbose "Getting Organizational Unit '$O'"
                    $O = $O.TrimStart('/')
                    $request = $service.Orgunits.Get($Script:PSGSuite.CustomerId,([Google.Apis.Util.Repeatable[String]]::new([String[]]$O)))
                    $request.Execute()
                }
            }
            elseif ($SearchBase -eq "/" -and $SearchScope -eq 'Base') {
                $topId = Get-GSOrganizationalUnitListPrivate -SearchBase "/" -Type Children -Verbose:$false | Where-Object {$_.ParentOrgUnitPath -eq "/"} | Select-Object -ExpandProperty ParentOrgUnitId -Unique
                Get-GSOrganizationalUnit -OrgUnitPath $topId -SearchScope Base
            }
            else {
                Get-GSOrganizationalUnitListPrivate @PSBoundParameters
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSOrganizationalUnit'

function New-GSOrganizationalUnit {
    <#
    .SYNOPSIS
    Creates a new OrgUnit
     
    .DESCRIPTION
    Creates a new Organizational Unit
     
    .PARAMETER Name
    The name of the new OrgUnit
     
    .PARAMETER ParentOrgUnitPath
    The path of the parent OrgUnit
 
    Defaults to "/" (the root OrgUnit)
 
    .PARAMETER ParentOrgUnitId
    The unique ID of the parent organizational unit.
     
    .PARAMETER Description
    Description of the organizational unit.
     
    .PARAMETER BlockInheritance
    Determines if a sub-organizational unit can inherit the settings of the parent organization. The default value is false, meaning a sub-organizational unit inherits the settings of the nearest parent organizational unit. For more information on inheritance and users in an organization structure, see the administration help center: http://support.google.com/a/bin/answer.py?hl=en&answer=182442&topic=1227584&ctx=topic
     
    .EXAMPLE
    New-GSOrganizationalUnit -Name "Test Org" -ParentOrgUnitPath "/Testing" -Description "This is a test OrgUnit"
     
    Creates a new OrgUnit named "Test Org" underneath the existing org unit path "/Testing" with the description "This is a test OrgUnit"
    #>

    [cmdletbinding(DefaultParameterSetName = 'ParentOrgUnitPath')]
    Param
    (
        [parameter(Mandatory = $true)]
        [String]
        $Name,
        [parameter(Mandatory = $false,ParameterSetName = 'ParentOrgUnitPath')]
        [string]
        $ParentOrgUnitPath,
        [parameter(Mandatory = $false,ParameterSetName = 'ParentOrgUnitId')]
        [string]
        $ParentOrgUnitId,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [Switch]
        $BlockInheritance
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.orgunit'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        if ($PSBoundParameters.Keys -notcontains 'ParentOrgUnitPath' -and $PSCmdlet.ParameterSetName -eq 'ParentOrgUnitPath') {
            $PSBoundParameters['ParentOrgUnitPath'] = '/'
        }
    }
    Process {
        try {
            Write-Verbose "Creating OrgUnit '$Name'"
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.OrgUnit'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            $request = $service.Orgunits.Insert($body,$Script:PSGSuite.CustomerId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSOrganizationalUnit'

function Remove-GSOrganizationalUnit {
    <#
    .SYNOPSIS
    Removes an OrgUnit
     
    .DESCRIPTION
    Removes an Organization Unit
     
    .PARAMETER OrgUnitPath
    The path of the OrgUnit you would like to Remove-GSOrganizationalUnit
     
    .EXAMPLE
    Remove-GSOrganizationalUnit -OrgUnitPath "/Testing"
 
    Removes the OrgUnit "/Testing" on confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String[]]
        $OrgUnitPath
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.orgunit'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($O in $OrgUnitPath) {
                if ($PSCmdlet.ShouldProcess("Deleting OrgUnit at Path '$O'")) {
                    Write-Verbose "Deleting OrgUnit at Path '$O'"
                    $O = $O.TrimStart('/')
                    $request = $service.Orgunits.Delete($Script:PSGSuite.CustomerId,([Google.Apis.Util.Repeatable[String]]::new([String[]]$O)))
                    $request.Execute()
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSOrganizationalUnit'

function Update-GSOrganizationalUnit {
    <#
    .SYNOPSIS
    Updates an OrgUnit
     
    .DESCRIPTION
    Updates an Organizational Unit
     
    .PARAMETER OrgUnitID
    The unique Id of the OrgUnit to update
     
    .PARAMETER OrgUnitPath
    The path of the OrgUnit to update
     
    .PARAMETER Name
    The new name for the OrgUnit
     
    .PARAMETER ParentOrgUnitId
    The new Parent ID for the OrgUnit
     
    .PARAMETER ParentOrgUnitPath
    The path of the new Parent for the OrgUnit
     
    .PARAMETER Description
    The new description for the OrgUnit
     
    .PARAMETER BlockInheritance
    Determines if a sub-organizational unit can inherit the settings of the parent organization. The default value is false, meaning a sub-organizational unit inherits the settings of the nearest parent organizational unit. For more information on inheritance and users in an organization structure, see the administration help center: http://support.google.com/a/bin/answer.py?hl=en&answer=182442&topic=1227584&ctx=topic
     
    .EXAMPLE
    Update-GSOrganizationalUnit -OrgUnitPath "/Testing" -Name "Testing More" -Description "Doing some more testing"
 
    Updates the OrgUnit '/Testing' with a new name "Testing More" and new description "Doing some more testing"
    #>

    [cmdletbinding(DefaultParameterSetName = "Id")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Id")]
        [ValidateNotNullOrEmpty()]
        [String]
        $OrgUnitID,
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Path")]
        [ValidateNotNullOrEmpty()]
        [String]
        $OrgUnitPath,
        [parameter(Mandatory = $false)]
        [String]
        $Name,
        [parameter(Mandatory = $false)]
        [string]
        $ParentOrgUnitId,
        [parameter(Mandatory = $false)]
        [string]
        $ParentOrgUnitPath,
        [parameter(Mandatory = $false)]
        [String]
        $Description,
        [parameter(Mandatory = $false)]
        [Switch]
        $BlockInheritance
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.orgunit'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = switch ($PSCmdlet.ParameterSetName) {
                Path {
                    Get-GSOrganizationalUnit -OrgUnitPath $OrgUnitPath -Verbose:$false
                }
                Id {
                    Get-GSOrganizationalUnit -OrgUnitPath $OrgUnitID -Verbose:$false
                }
            }
            if ($ParentOrgUnitPath) {
                $body.ParentOrgUnitId = $(Get-GSOrganizationalUnit -OrgUnitPath $ParentOrgUnitPath -Verbose:$false | Select-Object -ExpandProperty OrgUnitID)
            }
            elseif ($ParentOrgUnitId) {
                $body.ParentOrgUnitPath = $(Get-GSOrganizationalUnit -OrgUnitPath $ParentOrgUnitId -Verbose:$false | Select-Object -ExpandProperty OrgUnitPath)
            }
            Write-Verbose "Updating OrgUnit '$OrgUnitPath'"
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_ -and $_ -ne 'OrgUnitPath'}) {
                $body.$prop = $PSBoundParameters[$prop]
            }
            $trimPath = $body.OrgUnitPath.TrimStart('/')
            $request = $service.Orgunits.Patch($body,$Script:PSGSuite.CustomerId,([Google.Apis.Util.Repeatable[String]]::new([String[]]$trimPath)))
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSOrganizationalUnit'

function Get-GSActivityReport {
    <#
    .SYNOPSIS
    Retrieves a list of activities
     
    .DESCRIPTION
    Retrieves a list of activities
     
    .PARAMETER UserKey
    Represents the profile id or the user email for which the data should be filtered. When 'all' is specified as the userKey, it returns usageReports for all users
     
    .PARAMETER ApplicationName
    Application name for which the events are to be retrieved.
     
    Available values are:
    * "Admin": The Admin console application's activity reports return account information about different types of administrator activity events.
    * "Calendar": The G Suite Calendar application's activity reports return information about various Calendar activity events.
    * "Drive": The Google Drive application's activity reports return information about various Google Drive activity events. The Drive activity report is only available for G Suite Business customers.
    * "Groups": The Google Groups application's activity reports return information about various Groups activity events.
    * "GPlus": The Google+ application's activity reports return information about various Google+ activity events.
    * "Login": The G Suite Login application's activity reports return account information about different types of Login activity events.
    * "Mobile": The G Suite Mobile Audit activity report return information about different types of Mobile Audit activity events.
    * "Rules": The G Suite Rules activity report return information about different types of Rules activity events.
    * "Token": The G Suite Token application's activity reports return account information about different types of Token activity events.
 
    Defaults to "Admin"
     
    .PARAMETER EventName
    The name of the event being queried
     
    .PARAMETER ActorIpAddress
    IP Address of host where the event was performed. Supports both IPv4 and IPv6 addresses
     
    .PARAMETER EndTime
    Return events which occurred at or before this time
     
    .PARAMETER Filters
    Event parameters in the form [parameter1 name][operator][parameter1 value]
     
    .PARAMETER PageSize
    Number of activity records to be shown in each page
     
    .EXAMPLE
    Get-GSActivityReport -StartTime (Get-Date).AddDays(-30)
 
    Gets the admin activity report for the last 30 days
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0)]
        [ValidateNotNullOrEmpty()]
        [String]
        $UserKey = 'all',
        [parameter(Mandatory = $false,Position = 1)]
        [ValidateSet("Admin","Calendar","Drive","Groups","GPlus","Login","Mobile","Rules","Token")]
        [String]
        $ApplicationName = "Admin",
        [parameter(Mandatory = $false,Position = 2)]
        [String]
        $EventName,
        [parameter(Mandatory = $false)]
        [DateTime]
        $StartTime,
        [parameter(Mandatory = $false)]
        [DateTime]
        $EndTime,
        [parameter(Mandatory = $false)]
        [String]
        $ActorIpAddress,
        [parameter(Mandatory = $false)]
        [String[]]
        $Filters,
        [parameter(Mandatory = $false)]
        [ValidateRange(1,1000)]
        [Alias("MaxResults")]
        [Int]
        $PageSize = "1000"
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.reports.audit.readonly'
            ServiceType = 'Google.Apis.Admin.Reports.reports_v1.ReportsService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($UserKey -notlike "*@*.*" -and $UserKey -cne 'all') {
                $UserKey = "$($UserKey)@$($Script:PSGSuite.Domain)"
            }
            Write-Verbose "Getting $ApplicationName Activity report"
            $request = $service.Activities.List($UserKey,($ApplicationName.ToLower()))
            $request.MaxResults = $PageSize
            foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -notin @('UserKey','ApplicationName')}) {
                switch ($key) {
                    StartTime {
                        $request.$key = $StartTime.ToString('o')
                    }
                    EndTime {
                        $request.$key = $EndTime.ToString('o')
                    }
                    Filters {
                        $request.$key = $PSBoundParameters[$key] -join ","
                    }
                    Default {
                        if ($request.PSObject.Properties.Name -contains $key) {
                            $request.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $response = @()
            [int]$i = 1
            do {
                $result = $request.Execute()
                $response += $result.Items
                $request.PageToken = $result.NextPageToken
                [int]$retrieved = ($i + $result.Items.Count) - 1
                Write-Verbose "Retrieved $retrieved events..."
                [int]$i = $i + $result.Items.Count
            }
            until (!$result.NextPageToken)
            return $response
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSActivityReport'

function Get-GSUsageReport {
    <#
    .SYNOPSIS
    Retrieves a list of activities
     
    .DESCRIPTION
    Retrieves a list of activities
     
    .PARAMETER Date
    Represents the date for which the data is to be fetched
     
    .PARAMETER UserKey
    Represents the profile id or the user email for which the data should be filtered
     
    .PARAMETER EntityType
    Type of object. Should be one of - gplus_communities
     
    .PARAMETER EntityKey
    Represents the key of object for which the data should be filtered
     
    .PARAMETER Filters
    Represents the set of filters including parameter operator value
     
    .PARAMETER Parameters
    Represents the application name, parameter name pairs to fetch in csv as app_name1:param_name1
     
    .PARAMETER PageSize
    Maximum number of results to return. Maximum allowed is 1000
     
    .EXAMPLE
    Get-GSUsageReport -Date (Get-Date).AddDays(-30)
 
    Gets the Customer Usage report from 30 days prior
    #>

    [cmdletbinding(DefaultParameterSetName = "Customer")]
    Param
    (
        [parameter(Mandatory = $true,ParameterSetName = "Customer")]
        [parameter(Mandatory = $true,ParameterSetName = "Entity")]
        [parameter(Mandatory = $true,ParameterSetName = "User")]
        [DateTime]
        $Date,
        [parameter(Mandatory = $true,ParameterSetName = "User")]
        [ValidateNotNullOrEmpty()]
        [String]
        $UserKey,
        [parameter(Mandatory = $true,ParameterSetName = "Entity")]
        [ValidateNotNullOrEmpty()]
        [String]
        $EntityType,
        [parameter(Mandatory = $true,ParameterSetName = "Entity")]
        [ValidateNotNullOrEmpty()]
        [String]
        $EntityKey,
        [parameter(Mandatory = $false,ParameterSetName = "Entity")]
        [parameter(Mandatory = $false,ParameterSetName = "User")]
        [String[]]
        $Filters,
        [parameter(Mandatory = $false)]
        [String[]]
        $Parameters,
        [parameter(Mandatory = $false,ParameterSetName = "Entity")]
        [parameter(Mandatory = $false,ParameterSetName = "User")]
        [ValidateRange(1,1000)]
        [Alias("MaxResults")]
        [Int]
        $PageSize = "1000"
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.reports.usage.readonly'
            ServiceType = 'Google.Apis.Admin.Reports.reports_v1.ReportsService'
        }
        $service = New-GoogleService @serviceParams
        $props = @()
        $props += (@{N = "Date";E = {$Date.ToString('yyyy-MM-dd')}})
    }
    Process {
        try {
            Write-Verbose "Getting $($PSCmdlet.ParameterSetName) Usage report for $($Date.ToString('yyyy-MM-dd'))"
            switch ($PSCmdlet.ParameterSetName) {
                Customer {
                    $request = $service.CustomerUsageReports.Get(($Date.ToString('yyyy-MM-dd')))
                }
                Entity {
                    $request = $service.EntityUsageReports.Get($EntityType,$EntityKey,($Date.ToString('yyyy-MM-dd')))
                    $request.MaxResults = $PageSize
                    $props += (@{N = "EntityType";E = {$EntityType}})
                    $props += (@{N = "EntityKey";E = {$EntityKey}})
                }
                User {
                    if ($UserKey -ceq 'me') {
                        $UserKey = $Script:PSGSuite.AdminEmail
                    }
                    elseif ($UserKey -notlike "*@*.*") {
                        $UserKey = "$($UserKey)@$($Script:PSGSuite.Domain)"
                    }
                    $request = $service.UserUsageReport.Get($UserKey,($Date.ToString('yyyy-MM-dd')))
                    $request.MaxResults = $PageSize
                    $props += (@{N = "UserKey";E = {$UserKey}})
                }
            }
            $props += '*'
            foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -notin @('Date','UserKey','EntityKey','EntityType')}) {
                switch ($key) {
                    Filters {
                        $request.$key = $PSBoundParameters[$key] -join ","
                    }
                    Parameters {
                        $request.$key = $PSBoundParameters[$key] -join ","
                    }
                    Default {
                        if ($request.PSObject.Properties.Name -contains $key) {
                            $request.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $response = @()
            $warnings = @()
            [int]$i = 1
            do {
                $result = $request.Execute()
                $response += $result.UsageReportsValue.Parameters | Select-Object $props
                $warnings += $result.Warnings
                $request.PageToken = $result.NextPageToken
                [int]$retrieved = ($i + $result.UsageReportsValue.Parameters.Count) - 1
                Write-Verbose "Retrieved $retrieved report parameters..."
                [int]$i = $i + $result.UsageReportsValue.Parameters.Count
            }
            until (!$result.NextPageToken)
            if ($warnings) {
                $warnings | ForEach-Object {
                    Write-Warning "[$($_.Code)] $($_.Message)"
                }
            }
            return $response
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSUsageReport'

function Get-GSResource {
    <#
    .SYNOPSIS
    Gets Calendar Resources (Calendars, Buildings & Features supported)
     
    .DESCRIPTION
    Gets Calendar Resources (Calendars, Buildings & Features supported)
     
    .PARAMETER Id
    If Id is provided, gets the Resource by Id
     
    .PARAMETER Resource
    The Resource Type to List
 
    Available values are:
    * "Calendars": resource calendars (legacy and new - i.e. conference rooms)
    * "Buildings": new Building Resources (i.e. "Building A" or "North Campus")
    * "Features": new Feature Resources (i.e. "Video Conferencing" or "Projector")
     
    .PARAMETER Filter
    String query used to filter results. Should be of the form "field operator value" where field can be any of supported fields and operators can be any of supported operations. Operators include '=' for exact match and ':' for prefix match or HAS match where applicable. For prefix match, the value should always be followed by a *. Supported fields include generatedResourceName, name, buildingId, featureInstances.feature.name. For example buildingId=US-NYC-9TH AND featureInstances.feature.name:Phone.
 
    PowerShell filter syntax here is supported as "best effort". Please use Google's filter operators and syntax to ensure best results
     
    .PARAMETER OrderBy
    Field(s) to sort results by in either ascending or descending order. Supported fields include resourceId, resourceName, capacity, buildingId, and floorName.
     
    .PARAMETER PageSize
    Page size of the result set
     
    .EXAMPLE
    Get-GSResource -Resource Buildings
 
    Gets the full list of Buildings Resources
    #>

    [CmdletBinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")]
        [String[]]
        $Id,
        [parameter(Mandatory = $false,Position = 1)]
        [ValidateSet('Calendars','Buildings','Features')]
        [String]
        $Resource = 'Calendars',
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Alias('Query')]
        [String[]]
        $Filter,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [String[]]
        $OrderBy,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateRange(1,500)]
        [Alias("MaxResults")]
        [Int]
        $PageSize = "500"
    )
    Begin {
        if ($PSCmdlet.ParameterSetName -eq 'Get') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.resource.calendar'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($I in $Id) {
                    try {
                        Write-Verbose "Getting Resource $Resource Id '$I'"
                        $request = $service.Resources.$Resource.Get($(if($Script:PSGSuite.CustomerID){$Script:PSGSuite.CustomerID}else{'my_customer'}),$I)
                        $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Id' -Value $I -PassThru | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $Resource -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Id} -PassThru -Force
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                Get-GSResourceListPrivate @PSBoundParameters
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSResource'

function New-GSResource {
    <#
    .SYNOPSIS
    Creates a new Calendar Resource
     
    .DESCRIPTION
    Creates a new Calendar Resource. Supports Resource types 'Calendars','Buildings' & 'Features'
     
    .PARAMETER Name
    The name of the new Resource
     
    .PARAMETER Id
    The unique ID for the calendar resource.
     
    .PARAMETER BuildingId
    Unique ID for the building a resource is located in.
     
    .PARAMETER Description
    Description of the resource, visible only to admins.
     
    .PARAMETER Capacity
    Capacity of a resource, number of seats in a room.
     
    .PARAMETER FloorName
    Name of the floor a resource is located on (Calendars Resource type)
     
    .PARAMETER FloorNames
    The names of the floors in the building (Buildings Resource type)
     
    .PARAMETER FloorSection
    Name of the section within a floor a resource is located in.
     
    .PARAMETER Category
    The category of the calendar resource. Either CONFERENCE_ROOM or OTHER. Legacy data is set to CATEGORY_UNKNOWN.
 
    Acceptable values are:
    * "CATEGORY_UNKNOWN"
    * "CONFERENCE_ROOM"
    * "OTHER"
 
    Defaults to 'CATEGORY_UNKNOWN' if creating a Calendar Resource
     
    .PARAMETER ResourceType
    The type of the calendar resource, intended for non-room resources.
     
    .PARAMETER UserVisibleDescription
    Description of the resource, visible to users and admins.
     
    .PARAMETER Resource
    The resource type you would like to create
 
    Available values are:
    * "Calendars": create a Resource Calendar or legacy resource type
    * "Buildings": create a Resource Building
    * "Features": create a Resource Feature
     
    .EXAMPLE
    New-GSResource -Name "Training Room" -Id "Train01" -Capacity 75 -Category 'CONFERENCE_ROOM' -ResourceType "Conference Room" -Description "Training room for new hires - has 1 LAN port per station" -UserVisibleDescription "Training room for new hires"
 
    Creates a new training room Resource Calendar that will be bookable on Google Calendar
    #>

    [CmdletBinding(DefaultParameterSetName = 'Features')]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $Name,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [Alias('ResourceId')]
        [String]
        $Id,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [parameter(Mandatory = $false,ParameterSetName = 'Buildings')]
        [String]
        $BuildingId,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [parameter(Mandatory = $false,ParameterSetName = 'Buildings')]
        [String]
        $Description,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [Int]
        $Capacity,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $FloorName,
        [parameter(Mandatory = $false,ParameterSetName = 'Buildings')]
        [String[]]
        $FloorNames,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $FloorSection,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [ValidateSet('CATEGORY_UNKNOWN','CONFERENCE_ROOM','OTHER')]
        [String]
        $Category,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $ResourceType,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $UserVisibleDescription,
        [parameter(Mandatory = $false)]
        [ValidateSet('Calendars','Buildings','Features')]
        [String]
        $Resource
    )
    Begin {
        $resType = if ($MyInvocation.InvocationName -eq 'New-GSCalendarResource') {
            'Calendars'
        }
        elseif ($Resource) {
            $Resource
        }
        else {
            $PSCmdlet.ParameterSetName
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.resource.calendar'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        if ($PSBoundParameters -notcontains 'Category' -and $PSCmdlet.ParameterSetName -eq 'Calendars') {
            $PSBoundParameters['Category'] = 'CATEGORY_UNKNOWN'
        }
    }
    Process {
        try {
            Write-Verbose "Creating Resource $resType '$Name'"
            $body = New-Object "$(switch ($resType) {
                Calendars {
                    'Google.Apis.Admin.Directory.directory_v1.Data.CalendarResource'
                }
                Buildings {
                    'Google.Apis.Admin.Directory.directory_v1.Data.Building'
                }
                Features {
                    'Google.Apis.Admin.Directory.directory_v1.Data.Feature'
                }
            })"

            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    Category {
                        $body.ResourceCategory = $PSBoundParameters[$key]
                    }
                    Id {
                        if ($resType -eq 'Calendars') {
                            $body.ResourceId = $PSBoundParameters[$key]
                        }
                        else {
                            $body.$key = $PSBoundParameters[$key]
                        }
                        
                    }
                    Name {
                        if ($resType -eq 'Calendars') {
                            $body.ResourceName = $PSBoundParameters[$key]
                        }
                        elseif ($resType -eq 'Buildings') {
                            $body.BuildingName = $PSBoundParameters[$key]
                        }
                        else {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                    Description {
                        if ($resType -eq 'Calendars') {
                            $body.ResourceDescription = $PSBoundParameters[$key]
                        }
                        else {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                    Default {
                        if ($body.PSObject.Properties.Name -contains $key) {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $request = $service.Resources.$resType.Insert($body,$(if ($Script:PSGSuite.CustomerID) {
                        $Script:PSGSuite.CustomerID
                    }
                    else {
                        'my_customer'
                    }))
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $resType -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSResource'

function Remove-GSResource {
    <#
    .SYNOPSIS
    Removes a resource
     
    .DESCRIPTION
    Removes a resource
     
    .PARAMETER ResourceId
    The Resource Id of the Resource *Calendar* you would like to remove
     
    .PARAMETER BuildingId
    The Building Id of the Resource *Building* you would like to remove
     
    .PARAMETER FeatureKey
    The Feature Key of the Resource *Feature* you would like to remove
     
    .EXAMPLE
    Remove-GSResource -ResourceId Train01
 
    Removes the Resource Calendar 'Train01' after confirmation
    #>

    [CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact = "High",DefaultParameterSetName = 'Calendars')]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Calendars')]
        [Alias('CalendarResourceId')]
        [String[]]
        $ResourceId,
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Buildings')]
        [String[]]
        $BuildingId,
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Features')]
        [Alias('Name')]
        [String[]]
        $FeatureKey
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.resource.calendar'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                Calendars {
                    $Resource = 'Calendars'
                    $list = $ResourceId
                }
                Buildings {
                    $Resource = 'Buildings'
                    $list = $BuildingId
                }
                Features {
                    $Resource = 'Features'
                    $list = $FeatureKey
                }
            }
            foreach ($I in $list) {
                if ($PSCmdlet.ShouldProcess("Deleting Resource $Resource Id '$I'")) {
                    Write-Verbose "Deleting Resource $Resource Id '$I'"
                    $request = $service.Resources.$Resource.Delete($(if($Script:PSGSuite.CustomerID){$Script:PSGSuite.CustomerID}else{'my_customer'}),$I)
                    $request.Execute()
                    Write-Verbose "Resource $Resource Id '$I' has been successfully deleted"
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSResource'

function Update-GSResource {
    <#
    .SYNOPSIS
    Updates a Calendar Resource
     
    .DESCRIPTION
    Updates a Calendar Resource
     
    .PARAMETER ResourceId
    The unique Id of the Resource Calendar that you would like to update
     
    .PARAMETER BuildingId
    If updating a Resource Building, the unique Id of the building you would like to update
 
    If updating a Resource Calendar, the new Building Id for the resource
     
    .PARAMETER FeatureKey
    The unique key of the Feature you would like to update
     
    .PARAMETER Name
    The new name of the resource
     
    .PARAMETER Id
    The unique ID for the calendar resource.
     
    .PARAMETER Description
    Description of the resource, visible only to admins.
     
    .PARAMETER Capacity
    Capacity of a resource, number of seats in a room.
     
    .PARAMETER FloorName
    Name of the floor a resource is located on (Calendars Resource type)
     
    .PARAMETER FloorNames
    The names of the floors in the building (Buildings Resource type)
     
    .PARAMETER FloorSection
    Name of the section within a floor a resource is located in.
     
    .PARAMETER Category
    The new category of the calendar resource. Either CONFERENCE_ROOM or OTHER. Legacy data is set to CATEGORY_UNKNOWN.
 
    Acceptable values are:
    * "CATEGORY_UNKNOWN"
    * "CONFERENCE_ROOM"
    * "OTHER"
 
    Defaults to 'CATEGORY_UNKNOWN' if creating a Calendar Resource
     
    .PARAMETER ResourceType
    The type of the calendar resource, intended for non-room resources.
     
    .PARAMETER UserVisibleDescription
    Description of the resource, visible to users and admins.
     
    .PARAMETER Resource
    The resource type you would like to create
 
    Available values are:
    * "Calendars": create a Resource Calendar or legacy resource type
    * "Buildings": create a Resource Building
    * "Features": create a Resource Feature
     
    .EXAMPLE
    Update-GSResource -ResourceId Train01 -Id TrainingRoom01
 
    Updates the resource Id 'Train01' to the new Id 'TrainingRoom01'
    #>

    [CmdletBinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Calendars')]
        [Alias('CalendarResourceId')]
        [String]
        $ResourceId,
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Buildings')]
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $BuildingId,
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Features')]
        [String]
        $FeatureKey,
        [parameter(Mandatory = $false,Position = 0)]
        [String]
        $Name,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $Id,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [parameter(Mandatory = $false,ParameterSetName = 'Buildings')]
        [String]
        $Description,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [Int]
        $Capacity,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $FloorName,
        [parameter(Mandatory = $false,ParameterSetName = 'Buildings')]
        [String[]]
        $FloorNames,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $FloorSection,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [ValidateSet('CATEGORY_UNKNOWN','CONFERENCE_ROOM','OTHER')]
        [String]
        $Category,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $ResourceType,
        [parameter(Mandatory = $false,ParameterSetName = 'Calendars')]
        [String]
        $UserVisibleDescription,
        [parameter(Mandatory = $false)]
        [ValidateSet('Calendars','Buildings','Features')]
        [String]
        $Resource
    )
    Begin {
        $resType = if ($MyInvocation.InvocationName -eq 'Update-GSCalendarResource') {
            'Calendars'
        }
        elseif ($Resource) {
            $Resource
        }
        else {
            $PSCmdlet.ParameterSetName
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.resource.calendar'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        if ($PSBoundParameters -notcontains 'Category' -and $PSCmdlet.ParameterSetName -eq 'Calendars') {
            $PSBoundParameters['Category'] = 'CATEGORY_UNKNOWN'
        }
    }
    Process {
        try {
            Write-Verbose "Creating Resource $resType '$Name'"
            $body = New-Object "$(switch ($resType) {
                Calendars {
                    'Google.Apis.Admin.Directory.directory_v1.Data.CalendarResource'
                }
                Buildings {
                    'Google.Apis.Admin.Directory.directory_v1.Data.Building'
                }
                Features {
                    'Google.Apis.Admin.Directory.directory_v1.Data.Feature'
                }
            })"

            foreach ($key in $PSBoundParameters.Keys) {
                switch ($key) {
                    Category {
                        $body.ResourceCategory = $PSBoundParameters[$key]
                    }
                    Id {
                        if ($resType -eq 'Calendars') {
                            $body.ResourceId = $PSBoundParameters[$key]
                        }
                        else {
                            $body.$key = $PSBoundParameters[$key]
                        }
                        
                    }
                    Name {
                        if ($resType -eq 'Calendars') {
                            $body.ResourceName = $PSBoundParameters[$key]
                        }
                        elseif ($resType -eq 'Buildings') {
                            $body.BuildingName = $PSBoundParameters[$key]
                        }
                        else {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                    Description {
                        if ($resType -eq 'Calendars') {
                            $body.ResourceDescription = $PSBoundParameters[$key]
                        }
                        else {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                    Default {
                        if ($body.PSObject.Properties.Name -contains $key) {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $resId = switch ($PSCmdlet.ParameterSetName) {
                Calendars {
                    $ResourceId
                }
                Buildings {
                    $BuildingId
                }
                Features {
                    $FeatureKey
                }
            }
            $request = $service.Resources.$resType.Patch($body,$(if ($Script:PSGSuite.CustomerID) {
                        $Script:PSGSuite.CustomerID
                    }
                    else {
                        'my_customer'
                    }),$resId)
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'Resource' -Value $resType -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSResource'

function Get-GSAdminRoleAssignment {
    <#
    .SYNOPSIS
    Gets a specific Admin Role Assignments or the list of Admin Role Assignments for a given role
     
    .DESCRIPTION
    Gets a specific Admin Role Assignments or the list of Admin Role Assignments for a given role
     
    .PARAMETER RoleAssignmentId
    The RoleAssignmentId(s) you would like to retrieve info for.
 
    If left blank, returns the full list of Role Assignments
     
    .PARAMETER UserKey
    The UserKey(s) you would like to retrieve Role Assignments for. This can be a user's email or their unique UserId
 
    If left blank, returns the full list of Role Assignments
     
    .PARAMETER RoleId
    The RoleId(s) you would like to retrieve Role Assignments for.
 
    If left blank, returns the full list of Role Assignments
     
    .PARAMETER PageSize
    Page size of the result set
     
    .EXAMPLE
    Get-GSAdminRoleAssignment
 
    Gets the list of Admin Role Assignments
     
    .EXAMPLE
    Get-GSAdminRoleAssignment -RoleId 9191482342768644,9191482342768642
 
    Gets the Admin Role Assignments matching the provided RoleIds
    #>

    [cmdletbinding(DefaultParameterSetName = "ListUserKey")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Get")]
        [String[]]
        $RoleAssignmentId,
        [parameter(Mandatory = $false,ParameterSetName = "ListUserKey")]
        [string[]]
        $UserKey,
        [parameter(Mandatory = $false,ParameterSetName = "ListRoleId")]
        [string[]]
        $RoleId,
        [parameter(Mandatory = $false,ParameterSetName = "ListUserKey")]
        [parameter(Mandatory = $false,ParameterSetName = "ListRoleId")]
        [ValidateRange(1,100)]
        [Alias("MaxResults")]
        [Int]
        $PageSize
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        $customerId = if ($Script:PSGSuite.CustomerID) {
            $Script:PSGSuite.CustomerID
        }
        else {
            'my_customer'
        }
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($Role in $RoleAssignmentId) {
                    try {
                        Write-Verbose "Getting Admin Role Assignment '$Role'"
                        $request = $service.RoleAssignments.Get($customerId,$Role)
                        $request.Execute()
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            Default {
                [int]$i = 1
                Write-Verbose "Getting Admin Role Assignment List"
                $baseRequest = $service.RoleAssignments.List($customerId)
                if ($PSBoundParameters.Keys -contains 'PageSize') {
                    $baseRequest.MaxResults = $PSBoundParameters['PageSize']
                }
                if ($PSBoundParameters.Keys -contains 'RoleId' -or $PSBoundParameters.Keys -contains 'UserKey') {
                    switch ($PSBoundParameters.Keys) {
                        RoleId {
                            foreach ($Role in $RoleId) {
                                try {
                                    $request = $baseRequest
                                    $request.RoleId = $Role
                                    do {
                                        $result = $request.Execute()
                                        $result.Items | Add-Member -MemberType NoteProperty -Name 'Filter' -Value ([PSCustomObject]@{RoleId = $Role}) -PassThru
                                        $request.PageToken = $result.NextPageToken
                                        [int]$retrieved = ($i + $result.Items.Count) - 1
                                        Write-Verbose "Retrieved $retrieved role assignments..."
                                        [int]$i = $i + $result.Items.Count
                                    }
                                    until (!$result.NextPageToken)
                                }
                                catch {
                                    if ($ErrorActionPreference -eq 'Stop') {
                                        $PSCmdlet.ThrowTerminatingError($_)
                                    }
                                    else {
                                        Write-Error $_
                                    }
                                }
                            }
                        }
                        UserKey {
                            foreach ($User in $UserKey) {
                                try {
                                    $request = $baseRequest
                                    $uKey = try {
                                        [int64]$User
                                    }
                                    catch {
                                        if ($User -ceq 'me') {
                                            $User = $Script:PSGSuite.AdminEmail
                                        }
                                        elseif ($User -notlike "*@*.*") {
                                            $User = "$($User)@$($Script:PSGSuite.Domain)"
                                        }
                                        (Get-GSUser -User $User -Verbose:$false).Id
                                    }
                                    $request.UserKey = $uKey
                                    do {
                                        $result = $request.Execute()
                                        $result.Items | Add-Member -MemberType NoteProperty -Name 'Filter' -Value ([PSCustomObject]@{UserKey = $User}) -PassThru 
                                        $request.PageToken = $result.NextPageToken
                                        [int]$retrieved = ($i + $result.Items.Count) - 1
                                        Write-Verbose "Retrieved $retrieved role assignments..."
                                        [int]$i = $i + $result.Items.Count
                                    }
                                    until (!$result.NextPageToken)
                                }
                                catch {
                                    if ($ErrorActionPreference -eq 'Stop') {
                                        $PSCmdlet.ThrowTerminatingError($_)
                                    }
                                    else {
                                        Write-Error $_
                                    }
                                }
                            }
                        }
                    }
                }
                else {
                    try {
                        $request = $baseRequest
                        do {
                            $result = $request.Execute()
                            $result.Items
                            $request.PageToken = $result.NextPageToken
                            [int]$retrieved = ($i + $result.Items.Count) - 1
                            Write-Verbose "Retrieved $retrieved role assignments..."
                            [int]$i = $i + $result.Items.Count
                        }
                        until (!$result.NextPageToken)
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSAdminRoleAssignment'

function New-GSAdminRoleAssignment {
    <#
    .SYNOPSIS
    Creates a new Admin Role Assignment
 
    .DESCRIPTION
    Creates a new Admin Role Assignment
 
    .PARAMETER AssignedTo
    The unique ID of the user this role is assigned to.
 
    .PARAMETER RoleId
    The ID of the role that is assigned.
 
    .PARAMETER OrgUnitId
    If the role is restricted to an organization unit, this contains the ID for the organization unit the exercise of this role is restricted to.
 
    .PARAMETER ScopeType
    The scope in which this role is assigned.
 
    Acceptable values are:
    * "CUSTOMER"
    * "ORG_UNIT"
 
    .EXAMPLE
    New-GSAdminRoleAssignment -AssignedTo jsmith -RoleId 9191482342768644
 
    Assign a new role to a given user.
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true, Position = 0)]
        [String[]]
        $AssignedTo,
        [parameter(Mandatory = $true)]
        [Int64]
        $RoleId,
        [parameter(Mandatory = $false)]
        [String]
        $OrgUnitId,
        [parameter(Mandatory = $false)]
        [ValidateSet('CUSTOMER', 'ORG_UNIT')]
        [String]
        $ScopeType = 'CUSTOMER'
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.rolemanagement'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams

        $customerId = if ($Script:PSGSuite.CustomerID) {
            $Script:PSGSuite.CustomerID
        }
        else {
            'my_customer'
        }
    }
    Process {
        foreach ($Assigned in $AssignedTo) {
            try {
                $uKey = try {
                    [int64]$Assigned
                }
                catch {
                    if ($Assigned -ceq 'me') {
                        $Assigned = $Script:PSGSuite.AdminEmail
                    }
                    elseif ($Assigned -notlike "*@*.*") {
                        $Assigned = "$($Assigned)@$($Script:PSGSuite.Domain)"
                    }
                    (Get-GSUser -User $Assigned -Verbose:$false).Id
                }
                $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.RoleAssignment'
                $body.ScopeType = $ScopeType
                foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                    switch ($prop) {
                        AssignedTo {
                            $body.AssignedTo = $uKey
                        }
                        Default {
                            $body.$prop = $PSBoundParameters[$prop]
                        }
                    }
                }
                Write-Verbose "Creating Admin Role Assignment for user '$Assigned' for Role Id '$RoleId'"
                $request = $service.RoleAssignments.Insert($body, $customerId)
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSAdminRoleAssignment'

function Remove-GSAdminRoleAssignment {
    <#
    .SYNOPSIS
    Removes a specific Admin Role Assignment or the list of Admin Role Assignments
     
    .DESCRIPTION
    Removes a specific Admin Role Assignment or the list of Admin Role Assignments
     
    .PARAMETER RoleAssignmentId
    The RoleAssignmentId(s) you would like to remove
     
    .EXAMPLE
    Remove-GSAdminRoleAssignment -RoleAssignmentId 9191482342768644,9191482342768642
 
    Removes the role assignments matching the provided Ids
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $RoleAssignmentId
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.rolemanagement'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        $customerId = if ($Script:PSGSuite.CustomerID) {
            $Script:PSGSuite.CustomerID
        }
        else {
            'my_customer'
        }
    }
    Process {
        foreach ($Role in $RoleAssignmentId) {
            try {
                if ($PSCmdlet.ShouldProcess("Deleting Role Assignment Id '$Role'")) {
                    Write-Verbose "Deleting Role Assignment Id '$Role'"
                    $request = $service.RoleAssignments.Delete($customerId,$Role)
                    $request.Execute()
                    Write-Verbose "Role Assignment Id '$Role' has been successfully deleted"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSAdminRoleAssignment'

function Get-GSAdminRole {
    <#
    .SYNOPSIS
    Gets a specific Admin Role or the list of Admin Roles
     
    .DESCRIPTION
    Gets a specific Admin Role or the list of Admin Roles
     
    .PARAMETER RoleId
    The RoleId(s) you would like to retrieve info for.
 
    If left blank, returns the full list of Roles
     
    .PARAMETER PageSize
    Page size of the result set
     
    .EXAMPLE
    Get-GSAdminRole
 
    Gets the list of Admin Roles
     
    .EXAMPLE
    Get-GSAdminRole -RoleId 9191482342768644,9191482342768642
 
    Gets the admin roles matching the provided Ids
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Get")]
        [int64[]]
        $RoleId,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateRange(1,100)]
        [Alias("MaxResults")]
        [Int]
        $PageSize
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.rolemanagement'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        $customerId = if ($Script:PSGSuite.CustomerID) {
            $Script:PSGSuite.CustomerID
        }
        else {
            'my_customer'
        }
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($Role in $RoleId) {
                    try {
                        Write-Verbose "Getting Admin Role '$Role'"
                        $request = $service.Roles.Get($customerId,$Role)
                        $request.Execute()
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                try {
                    Write-Verbose "Getting Admin Role List"
                    $request = $service.Roles.List($customerId)
                    if ($PSBoundParameters.Keys -contains 'PageSize') {
                        $request.MaxResults = $PSBoundParameters['PageSize']
                    }
                    [int]$i = 1
                    do {
                        $result = $request.Execute()
                        $result.Items
                        $request.PageToken = $result.NextPageToken
                        [int]$retrieved = ($i + $result.Items.Count) - 1
                        Write-Verbose "Retrieved $retrieved roles..."
                        [int]$i = $i + $result.Items.Count
                    }
                    until (!$result.NextPageToken)
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSAdminRole'

function New-GSAdminRole {
    <#
    .SYNOPSIS
    Creates a new Admin Role
     
    .DESCRIPTION
    Creates a new Admin Role
     
    .PARAMETER RoleName
    The name of the new role
     
    .PARAMETER RolePrivileges
    The set of privileges that are granted to this role.
 
    .PARAMETER RoleDescription
    A short description of the role.
     
    .EXAMPLE
    Get-GSAdminRole
 
    Gets the list of Admin Roles
     
    .EXAMPLE
    Get-GSAdminRole -RoleId '9191482342768644','9191482342768642'
 
    Gets the admin roles matching the provided Ids
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $RoleName,
        [parameter(Mandatory = $true,Position = 1,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Google.Apis.Admin.Directory.directory_v1.Data.Role+RolePrivilegesData[]]
        $RolePrivileges,
        [parameter(Mandatory = $false)]
        [String]
        $RoleDescription
    )
    Begin {
        if ($PSCmdlet.ParameterSetName -eq 'Get') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.rolemanagement'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
        $customerId = if ($Script:PSGSuite.CustomerID) {
            $Script:PSGSuite.CustomerID
        }
        else {
            'my_customer'
        }
        $privArray = New-Object 'System.Collections.Generic.List[Google.Apis.Admin.Directory.directory_v1.Data.Role+RolePrivilegesData]'
    }
    Process {
        foreach ($Privilege in $RolePrivileges) {
            $privArray.Add($Privilege)
        }
    }
    End {
        try {
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Role'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    RolePrivileges {
                        $body.RolePrivileges = $privArray
                    }
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            Write-Verbose "Creating Admin Role '$RoleName' with the following privileges:`n`t- $($privArray.PrivilegeName -join "`n`t- ")"
            $request = $service.Roles.Insert($body,$customerId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSAdminRole'

function Remove-GSAdminRole {
    <#
    .SYNOPSIS
    Removes a specific Admin Role or a list of Admin Roles
     
    .DESCRIPTION
    Removes a specific Admin Role or a list of Admin Roles
     
    .PARAMETER RoleId
    The RoleId(s) you would like to remove
     
    .EXAMPLE
    Remove-GSAdminRole -RoleId 9191482342768644,9191482342768642
 
    Removes the admin roles matching the provided Ids
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [int64[]]
        $RoleId
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.rolemanagement'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        $customerId = if ($Script:PSGSuite.CustomerID) {
            $Script:PSGSuite.CustomerID
        }
        else {
            'my_customer'
        }
    }
    Process {
        foreach ($Role in $RoleId) {
            try {
                if ($PSCmdlet.ShouldProcess("Deleting Role Id '$Role'")) {
                    Write-Verbose "Deleting Role Id '$Role'"
                    $request = $service.Roles.Delete($customerId,$Role)
                    $request.Execute()
                    Write-Verbose "Role Id '$Role' has been successfully deleted"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSAdminRole'

function Update-GSAdminRole {
    <#
    .SYNOPSIS
    Update an Admin Role
     
    .DESCRIPTION
    Update an Admin Role
 
    .PARAMETER RoleId
    The Id of the role to update
     
    .PARAMETER RoleName
    The name of the role
     
    .PARAMETER RolePrivileges
    The set of privileges that are granted to this role.
 
    .PARAMETER RoleDescription
    A short description of the role.
     
    .EXAMPLE
    Update-GSAdminRole -RoleId 9191482342768644 -RoleName 'Help_Desk_Level2' -RoleDescription 'Help Desk Level 2'
 
    Updates the specified Admin Role with a new name and description
     
    .EXAMPLE
    Get-GSAdminRole | Where-Object {$_.RoleDescription -like "*Help*Desk*"} | Update-GSAdminRole -RoleId 9191482342768644 -RoleName 'Help_Desk_Level2' -RoleDescription 'Help Desk Level 2'
 
    Updates the specified Admin Role's RolePrivileges to match every other Admin Role with Help Desk in the description. Useful for basing a new role off another to add additional permissions on there
    #>

    [cmdletbinding()]
    Param
    (
        
        [parameter(Mandatory = $true,Position = 0)]
        [int64]
        $RoleId,
        [parameter(Mandatory = $false)]
        [String]
        $RoleName,
        [parameter(Mandatory = $false,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Google.Apis.Admin.Directory.directory_v1.Data.Role+RolePrivilegesData[]]
        $RolePrivileges,
        [parameter(Mandatory = $false)]
        [String]
        $RoleDescription
    )
    Begin {
        if ($PSCmdlet.ParameterSetName -eq 'Get') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.rolemanagement'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
        $customerId = if ($Script:PSGSuite.CustomerID) {
            $Script:PSGSuite.CustomerID
        }
        else {
            'my_customer'
        }
        $privArray = New-Object 'System.Collections.Generic.List[Google.Apis.Admin.Directory.directory_v1.Data.Role+RolePrivilegesData]'
    }
    Process {
        foreach ($Privilege in $RolePrivileges) {
            $privArray.Add($Privilege)
        }
    }
    End {
        try {
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Role'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$_ -ne 'RoleId' -and $body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    RolePrivileges {
                        $body.RolePrivileges = $privArray
                    }
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            Write-Verbose "Updating Admin Role '$RoleId'"
            $request = $service.Roles.Insert($body,$customerId,$RoleId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSAdminRole'

function Get-GSUserSchema {
    <#
    .SYNOPSIS
    Gets custom user schema info
     
    .DESCRIPTION
    Gets custom user schema info
     
    .PARAMETER SchemaId
    The Id or Name of the user schema you would like to return info for. If excluded, gets the full list of user schemas
     
    .EXAMPLE
    Get-GSUserSchema
 
    Gets the list of custom user schemas
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Schema')]
        [String[]]
        $SchemaId
    )
    Begin {
        if ($PSBoundParameters.Keys -contains 'SchemaId') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.userschema'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        try {
            if ($PSBoundParameters.Keys -contains 'SchemaId') {
                foreach ($S in $SchemaId) {
                    Write-Verbose "Getting schema Id '$S'"
                    $request = $service.Schemas.Get($Script:PSGSuite.CustomerId,$S)
                    $request.Execute()
                }
            }
            else {
                Get-GSUserSchemaListPrivate @PSBoundParameters
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSUserSchema'

function New-GSUserSchema {
    <#
    .SYNOPSIS
    Creates a new user schema
     
    .DESCRIPTION
    Creates a new user schema
     
    .PARAMETER SchemaName
    The name of the schema to create
     
    .PARAMETER Fields
    New schema fields to set
     
    Expects SchemaFieldSpec objects. You can create these with the helper function Add-GSUserSchemaField, i.e.: Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF
     
    .EXAMPLE
    New-GSUserSchema -SchemaName "SDK" -Fields (Add-GSUserSchemaField -FieldName "string" -FieldType STRING -ReadAccessType ADMINS_AND_SELF),(Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF)
 
    This command will create a schema named "SDK" with two fields, "string" and "date", readable by ADMINS_AND_SELF
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [String]
        $SchemaName,
        [parameter(Mandatory = $true)]
        [Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec[]]
        $Fields
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.userschema'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Creating schema '$SchemaName'"
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Schema'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            $request = $service.Schemas.Insert($body,$Script:PSGSuite.CustomerId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSUserSchema'

function Remove-GSUserSchema {
    <#
    .SYNOPSIS
    Removes a custom user schema
     
    .DESCRIPTION
    Removes a custom user schema
     
    .PARAMETER SchemaId
    The SchemaId or SchemaName to remove. If excluded, all Custom User Schemas for the customer will be removed
    .EXAMPLE
    Remove-GSUserSchema 2SV
 
    Removes the custom user schema named '2SV' after confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Schema','SchemaKey','SchemaName')]
        [String[]]
        $SchemaId
    )
    Begin {
        if ($PSBoundParameters.Keys -contains 'SchemaName') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.user.userschema'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        try {
            if ($PSBoundParameters.Keys -contains 'SchemaId') {
                foreach ($S in $SchemaId) {
                    if ($PSCmdlet.ShouldProcess("Deleting User Schema '$S'")) {
                        Write-Verbose "Deleting User Schema '$S'"
                        $request = $service.Schemas.Delete($Script:PSGSuite.CustomerID,$S)
                        $request.Execute()
                        Write-Verbose "User Schema '$S' has been successfully deleted"
                    }
                }
            }
            else {
                if ($PSCmdlet.ShouldProcess("Deleting ALL User Schemas")) {
                    Write-Verbose "Deleting ALL User Schemas"
                    Get-GSUserSchema -Verbose:$false | ForEach-Object {
                        $request = $service.Schemas.Delete($Script:PSGSuite.CustomerID,$_.SchemaId)
                        $request.Execute()
                        Write-Verbose "User Schema '$($_.SchemaId)' has been successfully deleted"
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSUserSchema'

function Set-GSUserSchema {
    <#
    .SYNOPSIS
    Hard-sets a schema's configuration
     
    .DESCRIPTION
    Hard-sets a schema's configuration
     
    .PARAMETER SchemaId
    The unique Id of the schema to set
     
    .PARAMETER SchemaName
    The new schema name
     
    .PARAMETER Fields
    New schema fields to set
 
    Expects SchemaFieldSpec objects. You can create these with the helper function Add-GSUserSchemaField, i.e.: Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF
     
    .EXAMPLE
    Set-GSUserSchema -SchemaId "9804800jfl08917304j" -SchemaName "SDK_2" -Fields (Add-GSUserSchemaField -FieldName "string2" -FieldType STRING -ReadAccessType ADMINS_AND_SELF)
 
    This command will set the schema Id '9804800jfl08917304j' with the name "SDK_2" and one field "string2" readable by ADMINS_AND_SELF
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Schema')]
        [String]
        $SchemaId,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [String]
        $SchemaName,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec[]]
        $Fields
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.userschema'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Setting schema '$SchemaId'"
            $schemaObj = Get-GSUserSchema -Schema $SchemaId -Verbose:$false
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Schema'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                $body.$prop = $PSBoundParameters[$prop]
            }
            $request = $service.Schemas.Update($body,$Script:PSGSuite.CustomerId,$schemaObj.SchemaId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Set-GSUserSchema'

function Update-GSUserSchema {
    <#
    .SYNOPSIS
    Updates/patches a schema's configuration
     
    .DESCRIPTION
    Updates/patches a schema's configuration
     
    .PARAMETER SchemaId
    The unique Id of the schema to update
     
    .PARAMETER SchemaName
    The new schema name
     
    .PARAMETER Fields
    New schema fields to set
 
    Expects SchemaFieldSpec objects. You can create these with the helper function Add-GSUserSchemaField, i.e.: Add-GSUserSchemaField -FieldName "date" -FieldType DATE -ReadAccessType ADMINS_AND_SELF
     
    .EXAMPLE
    Update-GSUserSchema -SchemaId "9804800jfl08917304j" -SchemaName "SDK_2" -Fields (Add-GSUserSchemaField -FieldName "string2" -FieldType STRING -ReadAccessType ADMINS_AND_SELF)
 
    This command will update the schema Id '9804800jfl08917304j' with the name "SDK_2" and add one field "string2" readable by ADMINS_AND_SELF
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Schema')]
        [String]
        $SchemaId,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [String]
        $SchemaName,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.SchemaFieldSpec[]]
        $Fields
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.userschema'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Updating schema '$SchemaId'"
            $schemaObj = Get-GSUserSchema -Schema $SchemaId -Verbose:$false
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Schema'
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                $body.$prop = $PSBoundParameters[$prop]
            }
            $request = $service.Schemas.Patch($body,$Script:PSGSuite.CustomerId,$schemaObj.SchemaId)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSUserSchema'

function Get-GSMobileDevice {
    <#
    .SYNOPSIS
    Gets the list of Mobile Devices registered for the user's account
     
    .DESCRIPTION
    Gets the list of Mobile Devices registered for the user's account
     
    .PARAMETER User
    The user that you would like to retrieve the Mobile Device list for. If no user is specified, it will list all of the Mobile Devices of the CustomerID
     
    .PARAMETER Filter
    Search string in the format given at: http://support.google.com/a/bin/answer.py?hl=en&answer=1408863#search
     
    .PARAMETER Projection
    Restrict information returned to a set of selected fields.
 
    Acceptable values are:
    * "BASIC": Includes only the basic metadata fields (e.g., deviceId, model, status, type, and status)
    * "FULL": Includes all metadata fields
 
    Defauls to "FULL"
     
    .PARAMETER PageSize
    Page size of the result set
     
    .PARAMETER OrderBy
    Device property to use for sorting results.
 
    Acceptable values are:
    * "deviceId": The serial number for a Google Sync mobile device. For Android devices, this is a software generated unique identifier.
    * "email": The device owner's email address.
    * "lastSync": Last policy settings sync date time of the device.
    * "model": The mobile device's model.
    * "name": The device owner's user name.
    * "os": The device's operating system.
    * "status": The device status.
    * "type": Type of the device.
     
    .PARAMETER SortOrder
    Whether to return results in ascending or descending order. Must be used with the OrderBy parameter.
 
    Acceptable values are:
    * "ASCENDING": Ascending order.
    * "DESCENDING": Descending order.
     
    .EXAMPLE
    Get-GSMobileDevice
 
    Gets the Mobile Device list for the AdminEmail
    #>

    [cmdletbinding(DefaultParameterSetName = "User")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "User")]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User,
        [parameter(Mandatory = $false,ParameterSetName = "Query",Position = 0)]
        [Alias('Query')]
        [String]
        $Filter,
        [parameter(Mandatory = $false)]
        [ValidateSet("BASIC","FULL")]
        [String]
        $Projection = "FULL",
        [parameter(Mandatory = $false)]
        [ValidateRange(1,1000)]
        [Int]
        $PageSize = "1000",
        [parameter(Mandatory = $false)]
        [ValidateSet("deviceId","email","lastSync","model","name","os","status","type")]
        [String]
        $OrderBy,
        [parameter(Mandatory = $false)]
        [ValidateSet("Ascending","Descending")]
        [String]
        $SortOrder
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.device.mobile'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $request = $service.Mobiledevices.List($Script:PSGSuite.CustomerID)
            switch ($PSCmdlet.ParameterSetName) {
                User {
                    if ($User) {
                        foreach ($U in $User) {
                            if ($U -ceq 'me') {
                                $U = $Script:PSGSuite.AdminEmail
                            }
                            elseif ($U -notlike "*@*.*") {
                                $U = "$($U)@$($Script:PSGSuite.Domain)"
                            }
                            $Filter = "email:`"$U`""
                            $request.Query = $Filter
                            Write-Verbose "Getting Mobile Device list for User '$U'"
                            $response = @()
                            [int]$i = 1
                            do {
                                $result = $request.Execute()
                                $response += $result.Mobiledevices
                                if ($result.NextPageToken) {
                                    $request.PageToken = $result.NextPageToken
                                }
                                [int]$retrieved = ($i + $result.Mobiledevices.Count) - 1
                                Write-Verbose "Retrieved $retrieved Mobile Devices..."
                                [int]$i = $i + $result.Mobiledevices.Count
                            }
                            until (!$result.NextPageToken)
                            return $response
                        }
                    }
                    else {
                        Write-Verbose "Getting Mobile Device list for customer '$($script:PSGSuite.CustomerID)'"
                        $response = @()
                        [int]$i = 1
                        do {
                            $result = $request.Execute()
                            $response += $result.Mobiledevices
                            if ($result.NextPageToken) {
                                $request.PageToken = $result.NextPageToken
                            }
                            [int]$retrieved = ($i + $result.Mobiledevices.Count) - 1
                            Write-Verbose "Retrieved $retrieved Mobile Devices..."
                            [int]$i = $i + $result.Mobiledevices.Count
                        }
                        until (!$result.NextPageToken)
                        return $response
                    }
                }
                Query {
                    $request.Query = $Filter
                    Write-Verbose "Getting Mobile Device list for filter '$Filter'"
                    $response = @()
                    [int]$i = 1
                    do {
                        $result = $request.Execute()
                        $response += $result.Mobiledevices
                        if ($result.NextPageToken) {
                            $request.PageToken = $result.NextPageToken
                        }
                        [int]$retrieved = ($i + $result.Mobiledevices.Count) - 1
                        Write-Verbose "Retrieved $retrieved Mobile Devices..."
                        [int]$i = $i + $result.Mobiledevices.Count
                    }
                    until (!$result.NextPageToken)
                    return $response
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSMobileDevice'

function Get-GSUserASP {
    <#
    .SYNOPSIS
    Gets Application Specific Passwords for a user
     
    .DESCRIPTION
    Gets Application Specific Passwords for a user
     
    .PARAMETER User
    The primary email or UserID of the user who you are trying to get info for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
     
    .PARAMETER CodeId
    The ID of the ASP you would like info for. If excluded, returns the full list of ASP's for the user
     
    .EXAMPLE
    Get-GSUserASP
 
    Gets the list of Application Specific Passwords for the user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,Position = 1,ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $CodeId
    )
    Begin {
        if ($PSBoundParameters.Keys -contains 'CodeId') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                if ($PSBoundParameters.Keys -contains 'CodeId') {
                    Write-Verbose "Getting ASP '$CodeId' for User '$U'"
                    $request = $service.Asps.Get($U,$CodeId)
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
                }
                else {
                    $PSBoundParameters['User'] = $U
                    Get-GSUserASPListPrivate @PSBoundParameters
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSUserASP'

function Get-GSUserToken {
    <#
    .SYNOPSIS
    Gets security tokens for a user
     
    .DESCRIPTION
    Gets security tokens for a user
     
    .PARAMETER User
    The primary email or UserID of the user who you are trying to get info for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
     
    .PARAMETER ClientId
    The Id of the client you are trying to get token info for. If excluded, gets the full list of tokens for the user
     
    .EXAMPLE
    Get-GSUserToken -ClientId "Google Chrome"
 
    Gets the token info for "Google Chrome" for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,Position = 1,ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ClientId
    )
    Begin {
        if ($PSBoundParameters.Keys -contains 'ClientId') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                if ($PSBoundParameters.Keys -contains 'ClientId') {
                    Write-Verbose "Getting Token '$ClientId' for User '$U'"
                    $request = $service.Tokens.Get($U,$ClientId)
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
                }
                else {
                    $PSBoundParameters['User'] = $U
                    Get-GSUserTokenListPrivate @PSBoundParameters
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSUserToken'

function Get-GSUserVerificationCodes {
    <#
    .SYNOPSIS
    Gets the 2-Step Verification Codes for the user
     
    .DESCRIPTION
    Gets the 2-Step Verification Codes for the user
     
    .PARAMETER User
    The primary email or UserID of the user who you are trying to get info for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
     
    .EXAMPLE
    Get-GSUserVerificationCodes
 
    Gets the Verification Codes for AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Getting Verification Code list for User '$U'"
                $request = $service.VerificationCodes.List($U)
                $request.Execute() | Select-Object -ExpandProperty Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSUserVerificationCodes'

function New-GSUserVerificationCodes {
    <#
    .SYNOPSIS
    Generates new verification codes for the user
     
    .DESCRIPTION
    Generates new verification codes for the user
     
    .PARAMETER User
    The primary email or UserID of the user who you are trying to get info for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config
     
    .EXAMPLE
    New-GSUserVerificationCodes -User me
 
    Generates new verification codes for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Generating new verification codes for user '$U'"
                $request = $service.VerificationCodes.Generate($U)
                $request.Execute()
                Write-Verbose "New verification codes successfully generated for user '$U'"
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSUserVerificationCodes'

function Remove-GSMobileDevice {
    <#
    .SYNOPSIS
    Removes a mobile device from Device Management
     
    .DESCRIPTION
    Removes a mobile device from Device Management
     
    .PARAMETER ResourceID
    The unique Id of the mobile device you would like to remove
     
    .EXAMPLE
    Remove-GSMobileDevice -ResourceId 'AFiQxQ8Qgd-rouSmcd2UnuvhYV__WXdacTgJhPEA1QoQJrK1hYbKJXm-8JFlhZOjBF4aVbhleS2FVQk5lI069K2GULpteTlLVpKLJFSLSL'
     
    Removes the mobile device with the specified Id
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $ResourceId
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.device.mobile'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($R in $DeviceId) {
                if ($PSCmdlet.ShouldProcess("Removing Mobile Device '$R'")) {
                    Write-Verbose "Removing Mobile Device '$R'"
                    $request = $service.Mobiledevices.Delete($Script:PSGSuite.CustomerID,$R)
                    $request.Execute()
                    Write-Verbose "Mobile Device '$R' has been successfully removed"
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSMobileDevice'

function Remove-GSUserASP {
    <#
    .SYNOPSIS
    Removes an Application Specific Password for a user
     
    .DESCRIPTION
    Removes an Application Specific Password for a user
     
    .PARAMETER User
    The user to remove ASPs ValueFromPipeline
     
    .PARAMETER CodeId
    The ASP Code Id to remove. If excluded, all ASPs for the user will be removed
     
    .EXAMPLE
    Remove-GSUserASP -User joe
 
    Removes *ALL* ASPs from joe@domain.com's account after confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $CodeId
    )
    Begin {
        if ($PSBoundParameters.Keys -contains 'CodeId') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                if ($PSBoundParameters.Keys -contains 'CodeId') {
                    foreach ($C in $CodeId) {
                        if ($PSCmdlet.ShouldProcess("Deleting ASP CodeId '$C' for user '$U'")) {
                            Write-Verbose "Deleting ASP CodeId '$C' for user '$U'"
                            $request = $service.Asps.Delete($U,$C)
                            $request.Execute()
                            Write-Verbose "ASP CodeId '$C' has been successfully deleted for user '$U'"
                        }
                    }
                }
                else {
                    if ($PSCmdlet.ShouldProcess("Deleting ALL ASPs for user '$U'")) {
                        Write-Verbose "Deleting ALL ASPs for user '$U'"
                        Get-GSUserASP -User $U -Verbose:$false | ForEach-Object {
                            $request = $service.Asps.Delete($U,$_.CodeId)
                            $request.Execute()
                            Write-Verbose "ASP CodeId '$($_.CodeId)' has been successfully deleted for user '$U'"
                        }
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSUserASP'

function Remove-GSUserToken {
    <#
    .SYNOPSIS
    Removes a security token from a user
     
    .DESCRIPTION
    Removes a security token from a user
     
    .PARAMETER User
    The user to remove the security token from
     
    .PARAMETER ClientID
    The client Id of the security token. If excluded, all security tokens for the user are removed
     
    .EXAMPLE
    An example
     
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $ClientID
    )
    Begin {
        if ($PSBoundParameters.Keys -contains 'ClientID') {
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
                ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                if ($PSBoundParameters.Keys -contains 'ClientID') {
                    foreach ($C in $ClientID) {
                        if ($PSCmdlet.ShouldProcess("Deleting Token ClientID '$C' for user '$U'")) {
                            Write-Verbose "Deleting Token ClientID '$C' for user '$U'"
                            $request = $service.Tokens.Delete($U,$C)
                            $request.Execute()
                            Write-Verbose "Token ClientID '$C' has been successfully deleted for user '$U'"
                        }
                    }
                }
                else {
                    if ($PSCmdlet.ShouldProcess("Deleting ALL tokens for user '$U'")) {
                        Write-Verbose "Deleting ALL tokens for user '$U'"
                        Get-GSUserToken -User $U -Verbose:$false | ForEach-Object {
                            $request = $service.Tokens.Delete($U,$_.ClientID)
                            $request.Execute()
                            Write-Verbose "Token ClientID '$($_.ClientID)' has been successfully deleted for user '$U'"
                        }
                    }
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSUserToken'

function Revoke-GSUserVerificationCodes {
    <#
    .SYNOPSIS
    Revokes/invalidates Verification Codes for the user
     
    .DESCRIPTION
    Revokes/invalidates Verification Codes for the user
     
    .PARAMETER User
    The user to revoke verification codes from
     
    .EXAMPLE
    Revoke-GSUserVerificationCodes -User me -Confirm:$false
 
    Invalidates the verification codes for the AdminEmail user, skipping confirmation
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user.security'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Invalidating verification codes for user '$U'"
                $request = $service.VerificationCodes.Invalidate($U)
                $request.Execute()
                Write-Verbose "Verification codes successfully invalidated for user '$U'"
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Revoke-GSUserVerificationCodes'

function Clear-GSSheet {
    <#
    .SYNOPSIS
    Clears a Sheet
     
    .DESCRIPTION
    Clears a Sheet
     
    .PARAMETER SpreadsheetId
    The unique Id of the SpreadSheet
     
    .PARAMETER SheetName
    The name of the Sheet (tab) to clear
     
    .PARAMETER Range
    The specific range to clear. If excluded, clears the entire Sheet
     
    .PARAMETER User
    The primary email of the user who has Edit rights to the target Range/Sheet
     
    .PARAMETER Raw
    If $true, return the raw response, otherwise, return a flattened response for readability
     
    .EXAMPLE
    Clear-GSSheet -SpreadsheetId '1ZVdewVhy-VtVLyGL1lk2kgvySIF_bCfJA6ggn7obGh2U' -SheetName 2017
 
    Clears the Sheet '2017' located on the SpreadSheet Id provided
    #>

    [cmdletbinding()]
    Param
    (      
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $SpreadsheetId,
        [parameter(Mandatory = $false)]
        [String]
        $SheetName,
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Alias('SpecifyRange')]
        [string]
        $Range,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [switch]
        $Raw
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Sheets.v4.SheetsService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($SheetName) {
                if ($Range -like "'*'!*") {
                    throw "SpecifyRange formatting error! When using the SheetName parameter, please exclude the SheetName when formatting the SpecifyRange value (i.e. 'A1:Z1000')"
                }
                elseif ($Range) {
                    $Range = "'$($SheetName)'!$Range"
                }
                else {
                    $Range = "$SheetName"
                }
            }
            $body = New-Object 'Google.Apis.Sheets.v4.Data.ClearValuesRequest'
            $request = $service.Spreadsheets.Values.Clear($body,$SpreadsheetId,$Range)
            Write-Verbose "Clearing range '$Range' on Sheet '$SpreadsheetId' for user '$User'"
            $response = $request.Execute()
            if (!$Raw) {
                $response = $response | Select-Object @{N = "Title";E = {$_.properties.title}},@{N = "MaxRows";E = {[int]($_.sheets.properties.gridProperties.rowCount | Sort-Object | Select-Object -Last 1)}},@{N = "MaxColumns";E = {[int]($_.sheets.properties.gridProperties.columnCount | Sort-Object | Select-Object -Last 1)}},*
            }
            $response | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Clear-GSSheet'

function Copy-GSSheet {
    <#
    .SYNOPSIS
    Copies a Sheet from one SpreadSheet to another
     
    .DESCRIPTION
    Copies a Sheet from one SpreadSheet to another
     
    .PARAMETER SourceSpreadsheetId
    The unique Id of the SpreadSheet to copy the Sheet from
     
    .PARAMETER SourceSheetId
    The Id of the Sheet to copy
     
    .PARAMETER DestinationSpreadsheetId
    The target SpreadSheet to copy the Sheet to
     
    .PARAMETER NewSheetTitle
    The new title for the new SpreadhSheet to create if not copying to a Destination Sheet
     
    .PARAMETER User
    The primary email of the user who has at least Edit rights to both the Source SpreadSheet and Destination SpreadSheet
     
    .PARAMETER Raw
    If $true, return the raw response, otherwise, return a flattened response for readability
     
    .EXAMPLE
    Copy-GSSheet -SourceSpreadsheetId '1ZVdewVhy-VtVLyGLhClkj8234ljk_fJA6ggn7obGh2U' -SourceSheetId 2017 -NewSheetTitle '2017 Archive'
 
    Copies the Sheet '2017' from the SourceSpreadsheet provided onto a new SpreadSheet named '2017 Archive'
    #>

    [cmdletbinding(DefaultParameterSetName = "CreateNewSheet")]
    Param
    (      
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $SourceSpreadsheetId,
        [parameter(Mandatory = $true,Position = 1)]
        [String]
        $SourceSheetId,
        [parameter(Mandatory = $true,Position = 2,ParameterSetName = "UseExisting")]
        [String]
        $DestinationSpreadsheetId,
        [parameter(Mandatory = $false,ParameterSetName = "CreateNewSheet")]
        [Alias('SheetTitle')]
        [String]
        $NewSheetTitle,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [switch]
        $Raw
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Sheets.v4.SheetsService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($PSCmdlet.ParameterSetName -eq "CreateNewSheet") {
                if ($NewSheetTitle) {
                    Write-Verbose "Creating new spreadsheet titled: $NewSheetTitle"
                }
                else {
                    Write-Verbose "Creating new untitled spreadsheet"
                }
                $DestinationSpreadsheetId = New-GSSheet -Title $NewSheetTitle -User $User -Verbose:$false | Select-Object -ExpandProperty SpreadsheetId
                Write-Verbose "New spreadsheet ID: $DestinationSpreadsheetId"
            }
            $body = New-Object 'Google.Apis.Sheets.v4.Data.CopySheetToAnotherSpreadsheetRequest' -Property @{
                DestinationSpreadsheetId = $DestinationSpreadsheetId
            }
            Write-Verbose "Copying Sheet '$SourceSheetId' from Spreadsheet '$SourceSpreadsheetId' to Spreadsheet '$DestinationSpreadsheetId' for user '$User'"
            $request = $service.Spreadsheets.Sheets.CopyTo($body,$SourceSpreadsheetId,$SourceSheetId)
            $response = $request.Execute()
            if (!$Raw) {
                $response = $response | Select-Object @{N = "Title";E = {$_.properties.title}},@{N = "MaxRows";E = {[int]($_.sheets.properties.gridProperties.rowCount | Sort-Object | Select-Object -Last 1)}},@{N = "MaxColumns";E = {[int]($_.sheets.properties.gridProperties.columnCount | Sort-Object | Select-Object -Last 1)}},*
            }
            $response | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Copy-GSSheet'

function Export-GSSheet {
    <#
    .SYNOPSIS
    Updates a Sheet's values
     
    .DESCRIPTION
    Updates a Sheet's values. Accepts either an Array of objects/strings/ints or a single value
     
    .PARAMETER SpreadsheetId
    The unique Id of the SpreadSheet to update if updating an existing Sheet
     
    .PARAMETER NewSheetTitle
    The title of the new SpreadSheet to be created
     
    .PARAMETER Array
    Array of objects/strings/ints to add to the SpreadSheet
     
    .PARAMETER Value
    A single value to update 1 cell with. Useful if you are tracking the last time updated in a specific cell during a job that updates Sheets
     
    .PARAMETER SheetName
    The name of the Sheet to add the data to. If excluded, defaults to Sheet Id '0'. If a new SpreadSheet is being created, this is set to 'Sheet1' to prevent error
     
    .PARAMETER Style
    The table style you would like to export the data as
 
    Available values are:
    * "Standard": headers are on Row 1, table rows are added as subsequent rows (Default)
    * "Horizontal": headers are on Column A, table rows are added as subsequent columns
     
    .PARAMETER Range
    The specific range to add the value(s) to. If using the -Value parameter, set this to the specific cell you would like to set the value of
     
    .PARAMETER Append
    If $true, skips adding headers to the Sheet
     
    .PARAMETER User
    The primary email of the user that had at least Edit rights to the target Sheet
 
    Defaults to the AdminEmail user
     
    .PARAMETER ValueInputOption
    How the input data should be interpreted
 
    Available values are:
    * "INPUT_VALUE_OPTION_UNSPECIFIED"
    * "RAW"
    * "USER_ENTERED"
     
    .PARAMETER IncludeValuesInResponse
    Determines if the update response should include the values of the cells that were updated. By default, responses do not include the updated values
     
    .PARAMETER Launch
    If $true, opens the new SpreadSheet Url in your default browser
     
    .EXAMPLE
    $array | Export-GSSheet -NewSheetTitle "Finance Workbook" -Launch
 
 
    #>

    [cmdletbinding(DefaultParameterSetName = "CreateNewSheetArray")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "UseExistingArray")]
        [parameter(Mandatory = $true,Position = 0,ParameterSetName = "UseExistingValue")]
        [String]
        $SpreadsheetId,
        [parameter(Mandatory = $false,Position = 0,ParameterSetName = "CreateNewSheetArray")]
        [parameter(Mandatory = $false,Position = 0,ParameterSetName = "CreateNewSheetValue")]
        [String]
        $NewSheetTitle,
        [parameter(Mandatory = $true,Position = 1,ValueFromPipeline = $true,ParameterSetName = "UseExistingArray")]
        [parameter(Mandatory = $true,Position = 1,ValueFromPipeline = $true,ParameterSetName = "CreateNewSheetArray")]
        [object[]]
        $Array,
        [parameter(Mandatory = $true,Position = 1,ParameterSetName = "UseExistingValue")]
        [parameter(Mandatory = $true,Position = 1,ParameterSetName = "CreateNewSheetValue")]
        [string]
        $Value,
        [parameter(Mandatory = $false)]
        [String]
        $SheetName,
        [parameter(Mandatory = $false,ParameterSetName = "UseExistingArray")]
        [parameter(Mandatory = $false,ParameterSetName = "CreateNewSheetArray")]
        [ValidateSet('Standard','Horizontal')]
        [String]
        $Style = "Standard",
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Alias('SpecifyRange')]
        [string]
        $Range,
        [parameter(Mandatory = $false)]
        [switch]
        $Append,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [ValidateSet("INPUT_VALUE_OPTION_UNSPECIFIED","RAW","USER_ENTERED")]
        [string]
        $ValueInputOption = "RAW",
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeValuesInResponse,
        [parameter(Mandatory = $false)]
        [Alias('Open')]
        [Switch]
        $Launch
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Sheets.v4.SheetsService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
        $values = New-Object 'System.Collections.Generic.List[System.Collections.Generic.IList[Object]]'
    }
    Process {
        try {
            if ($Value) {
                $finalArray = $([pscustomobject]@{Value = "$Value"})
                $Append = $true
            }
            else {
                if (!$contentType) {
                    $contentType = $Array[0].PSObject.TypeNames[0]
                }
                $finalArray = @()
                if ($contentType -eq 'System.String' -or $contentType -like "System.Int*") {
                    $Append = $true
                    foreach ($item in $Array) {
                        $finalArray += $([pscustomobject]@{Value = $item})
                    }
                }
                else {
                    foreach ($item in $Array) {
                        $finalArray += $item
                    }
                }
            }
            if (!$Append) {
                $propArray = New-Object 'System.Collections.Generic.List[Object]'
                $finalArray[0].PSObject.Properties.Name | ForEach-Object {
                    $propArray.Add($_)
                }
                $values.Add([System.Collections.Generic.IList[Object]]$propArray)
                $Append = $true
            }
            foreach ($object in $finalArray) {
                $valueArray = New-Object 'System.Collections.Generic.List[Object]'
                $object.PSobject.Properties.Value | ForEach-Object {
                    $valueArray.Add($_)
                }
                $values.Add([System.Collections.Generic.IList[Object]]$valueArray)
            }
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    End {
        try {
            if ($PSCmdlet.ParameterSetName -like "CreateNewSheet*") {
                if ($NewSheetTitle) {
                    Write-Verbose "Creating new spreadsheet titled: $NewSheetTitle"
                }
                else {
                    Write-Verbose "Creating new untitled spreadsheet"
                }
                $sheet = New-GSSheet -Title $NewSheetTitle -User $User -Verbose:$false
                $SpreadsheetId = $sheet.SpreadsheetId
                $SpreadsheetUrl = $sheet.SpreadsheetUrl
                $SheetName = 'Sheet1'
                Write-Verbose "New spreadsheet ID: $SpreadsheetId"
            }
            else {
                $sheet = Get-GSSheetInfo -SpreadsheetId $SpreadsheetId -User $User -Verbose:$false
                $SpreadsheetUrl = $sheet.SpreadsheetUrl
            }
            if ($SheetName) {
                if ($Range -like "'*'!*") {
                    throw "SpecifyRange formatting error! When using the SheetName parameter, please exclude the SheetName when formatting the SpecifyRange value (i.e. 'A1:Z1000')"
                }
                elseif ($Range) {
                    $Range = "'$($SheetName)'!$Range"
                }
                else {
                    $Range = "$SheetName"
                }
            }
            $bodyData = (New-Object 'Google.Apis.Sheets.v4.Data.ValueRange' -Property @{
                Range = $Range
                MajorDimension = "$(if($Style -eq 'Horizontal'){'COLUMNS'}else{'ROWS'})"
                Values = [System.Collections.Generic.IList[System.Collections.Generic.IList[Object]]]$values
            })
            $body = New-Object 'Google.Apis.Sheets.v4.Data.BatchUpdateValuesRequest'
            $body.ValueInputOption = $ValueInputOption
            $body.IncludeValuesInResponse = $IncludeValuesInResponse
            $body.Data = [Google.Apis.Sheets.v4.Data.ValueRange[]]$bodyData
            $request = $service.Spreadsheets.Values.BatchUpdate($body,$SpreadsheetId)
            Write-Verbose "Updating Range '$Range' on Spreadsheet '$SpreadsheetId' for user '$User'"
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru | Add-Member -MemberType NoteProperty -Name 'SpreadsheetUrl' -Value $SpreadsheetUrl -PassThru
            if ($Launch) {
                Write-Verbose "Launching new spreadsheet at $SpreadsheetUrl"
                Start-Process $SpreadsheetUrl
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Export-GSSheet'

function Get-GSSheetInfo {
    <#
    .SYNOPSIS
    Gets metadata about a SpreadSheet
     
    .DESCRIPTION
    Gets metadata about a SpreadSheet
     
    .PARAMETER SpreadsheetId
    The unique Id of the SpreadSheet to retrieve info for
     
    .PARAMETER User
    The owner of the SpreadSheet
     
    .PARAMETER SheetName
    The name of the Sheet to retrieve info for
     
    .PARAMETER Range
    The specific range of the Sheet to retrieve info for
     
    .PARAMETER IncludeGridData
    Whether or not to include Grid Data in the response
     
    .PARAMETER Fields
    The fields to return in the response
 
    Available values are:
    * "NamedRanges"
    * "Properties"
    * "Sheets"
    * "SpreadsheetId"
     
    .PARAMETER Raw
    If $true, return the raw response, otherwise, return a flattened response for readability
     
    .EXAMPLE
    Get-GSSheetInfo -SpreadsheetId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976'
 
    Gets the info for the SpreadSheet provided
    #>

    [cmdletbinding()]
    Param
    (      
        [parameter(Mandatory = $true)]
        [String]
        $SpreadsheetId,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [String]
        $SheetName,
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Alias('SpecifyRange')]
        [string]
        $Range,
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeGridData,
        [parameter(Mandatory = $false)]
        [ValidateSet("namedRanges","properties","sheets","spreadsheetId")]
        [string[]]
        $Fields,
        [parameter(Mandatory = $false)]
        [switch]
        $Raw
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Sheets.v4.SheetsService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($SheetName) {
                if ($Range -like "'*'!*") {
                    throw "SpecifyRange formatting error! When using the SheetName parameter, please exclude the SheetName when formatting the SpecifyRange value (i.e. 'A1:Z1000')"
                }
                elseif ($Range) {
                    $Range = "'$($SheetName)'!$Range"
                }
                else {
                    $Range = "$SheetName"
                }
            }
            $request = $service.Spreadsheets.Get($SpreadsheetId)
            if ($Range) {
                $request.Ranges = [Google.Apis.Util.Repeatable[String]]::new([String[]]$Range)
            }
            if ($Fields) {
                $request.Fields = "$($Fields -join ",")"
            }
            elseif ($PSBoundParameters.Keys -contains 'IncludeGridData') {
                $request.IncludeGridData = $IncludeGridData
            }
            else {
                $request.IncludeGridData = $true
            }
            Write-Verbose "Getting Spreadsheet Id '$SpreadsheetId' for user '$User'"
            $response = $request.Execute()
            if (!$Raw) {
                $response = $response | Select-Object @{N = "Title";E = {$_.properties.title}},@{N = "MaxRows";E = {[int]($_.sheets.properties.gridProperties.rowCount | Sort-Object | Select-Object -Last 1)}},@{N = "MaxColumns";E = {[int]($_.sheets.properties.gridProperties.columnCount | Sort-Object | Select-Object -Last 1)}},*
            }
            $response | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru

        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSSheetInfo'

function Import-GSSheet {
    <#
    .SYNOPSIS
    Imports data from a Sheet as if it was a CSV
     
    .DESCRIPTION
    Imports data from a Sheet as if it was a CSV
     
    .PARAMETER SpreadsheetId
    The unique Id of the SpreadSheet to import data from
     
    .PARAMETER SheetName
    The name of the Sheet to import data from
     
    .PARAMETER User
    The owner of the SpreadSheet
     
    .PARAMETER Range
    The specific range to import data from
     
    .PARAMETER RowStart
    The starting row of data. Useful if the headers for your table are not in Row 1 of the Sheet
     
    .PARAMETER Headers
    Allows you to define the headers for the rows on the sheet, in case there is no header row
     
    .PARAMETER DateTimeRenderOption
    How to render the DateTime cells
 
    Available values are:
    * "FORMATTED_STRING" (Default)
    * "SERIAL_NUMBER"
     
    .PARAMETER ValueRenderOption
    How to render the value cells and formula cells
 
    Available values are:
    * "FORMATTED_VALUE" (Default)
    * "UNFORMATTED_VALUE"
    * "FORMULA"
     
    .PARAMETER MajorDimension
    The major dimension that results should use.
 
    For example, if the spreadsheet data is: A1=1,B1=2,A2=3,B2=4, then requesting range=A1:B2,majorDimension=ROWS will return [[1,2],[3,4]], whereas requesting range=A1:B2,majorDimension=COLUMNS will return [[1,3],[2,4]].
 
    Available values are:
    * "ROWS" (Default)
    * "COLUMNS"
    * "DIMENSION_UNSPECIFIED"
     
    .PARAMETER As
    Whether to return the result set as an array of PSObjects or an array of DataRows
 
    Available values are:
    * "PSObject" (Default)
    * "DataRow"
     
    .PARAMETER Raw
    If $true, return the raw response, otherwise, return a flattened response for readability
     
    .EXAMPLE
    Import-GSSheet -SpreadsheetId '1rhsAYTOB_vrpvfwImPmWy0TcVa2sgmQa_9u976' -SheetName Sheet1 -RowStart 2 -Range 'B:C'
 
    Imports columns B-C as an Array of PSObjects, skipping the first row and treating Row 2 as the header row. Objects in the array will be what's contained in range 'B3:C' after that
    #>

    [cmdletbinding(DefaultParameterSetName = "Import")]
    Param
    (      
        [parameter(Mandatory = $true)]
        [String]
        $SpreadsheetId,
        [parameter(Mandatory = $false)]
        [String]
        $SheetName,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Alias('SpecifyRange')]
        [string]
        $Range,
        [parameter(Mandatory = $false,ParameterSetName = "Import")]
        [int]
        $RowStart = 1,
        [parameter(Mandatory = $false,ParameterSetName = "Import")]
        [string[]]
        $Headers,
        [parameter(Mandatory = $false)]
        [ValidateSet("FORMATTED_STRING","SERIAL_NUMBER")]
        [string]
        $DateTimeRenderOption = "FORMATTED_STRING",
        [parameter(Mandatory = $false)]
        [ValidateSet("FORMATTED_VALUE","UNFORMATTED_VALUE","FORMULA")]
        [string]
        $ValueRenderOption = "FORMATTED_VALUE",
        [parameter(Mandatory = $false)]
        [ValidateSet("ROWS","COLUMNS","DIMENSION_UNSPECIFIED")]
        [string]
        $MajorDimension = "ROWS",
        [Parameter(Mandatory = $false,ParameterSetName = "Import")]
        [ValidateSet("DataRow","PSObject")]
        [string]
        $As = "PSObject",
        [parameter(Mandatory = $false,ParameterSetName = "Raw")]
        [switch]
        $Raw
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Sheets.v4.SheetsService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            if ($SheetName) {
                if ($Range -like "'*'!*") {
                    throw "SpecifyRange formatting error! When using the SheetName parameter, please exclude the SheetName when formatting the SpecifyRange value (i.e. 'A1:Z1000')"
                }
                elseif ($Range) {
                    $Range = "'$($SheetName)'!$Range"
                }
                else {
                    $Range = "$SheetName"
                }
            }
            $request = $service.Spreadsheets.Values.BatchGet($SpreadsheetId)
            $request.Ranges = [Google.Apis.Util.Repeatable[String]]::new([String[]]$Range)
            $request.DateTimeRenderOption = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+GetRequest+DateTimeRenderOptionEnum]::$($DateTimeRenderOption -replace "_","")
            $request.ValueRenderOption = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+GetRequest+ValueRenderOptionEnum]::$($ValueRenderOption -replace "_","")
            $request.MajorDimension = [Google.Apis.Sheets.v4.SpreadsheetsResource+ValuesResource+GetRequest+MajorDimensionEnum]::$($MajorDimension -replace "_","")
            if ($MajorDimension -ne "ROWS" -and !$Raw) {
                $Raw = $true
                Write-Warning "Setting -Raw to True -- Parsing requires the MajorDimension to be set to ROWS (default value)"
            }
            Write-Verbose "Importing Range '$Range' from Spreadsheet '$SpreadsheetId' for user '$User'"
            $response = $request.Execute()
            if (!$Raw) {
                $i = 0
                $datatable = New-Object System.Data.Datatable
                if ($Headers) {
                    foreach ($col in $Headers) {
                        [void]$datatable.Columns.Add("$col")
                    }
                    $i++
                }
                $(if ($RowStart) {
                        $response.valueRanges.values | Select-Object -Skip $([int]$RowStart - 1)
                    }
                    else {
                        $response.valueRanges.values
                    }) | ForEach-Object {
                    if ($i -eq 0) {
                        foreach ($col in $_) {
                            [void]$datatable.Columns.Add("$col")
                        }
                    }
                    else {
                        [void]$datatable.Rows.Add([String[]]$_)
                    }
                    $i++
                }
                switch ($As) {
                    DataRow {
                        Write-Verbose "Created DataTable with $($i - 1) DataRows"
                        $datatable
                    }
                    PSObject {
                        Write-Verbose "Created PSObject array with $($i - 1) objects"
                        foreach ($row in $datatable) {
                            $obj = [Ordered]@{}
                            $props = $row.Table.Columns.ColumnName
                            foreach ($prop in $props) {
                                $obj[$prop] = $row.$prop
                            }
                            [PSCustomObject]$obj
                        }
                    }
                }
            }
            else {
                $response | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }

        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Import-GSSheet'

function New-GSSheet {
    <#
    .SYNOPSIS
    Creates a new SpreadSheet
     
    .DESCRIPTION
    Creates a new SpreadSheet
     
    .PARAMETER Title
    The name of the new SpreadSheet
     
    .PARAMETER User
    The user to create the Sheet for
     
    .PARAMETER Launch
    If $true, opens the new SpreadSheet Url in your default browser
     
    .EXAMPLE
    New-GSSheet -Title "Finance Workbook" -Launch
 
    Creates a new SpreadSheet titled "Finance Workbook" and opens it in the browser on creation
    #>

    [cmdletbinding()]
    Param
    (      
        [parameter(Mandatory = $false)]
        [Alias('SheetTitle')]
        [String]
        $Title,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias('Owner','PrimaryEmail','UserKey','Mail')]
        [string]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [Alias('Open')]
        [Switch]
        $Launch
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/drive'
            ServiceType = 'Google.Apis.Sheets.v4.SheetsService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            $body = New-Object 'Google.Apis.Sheets.v4.Data.Spreadsheet'
            $body.Properties = New-Object 'Google.Apis.Sheets.v4.Data.SpreadsheetProperties' -Property @{
                Title = $Title
            }
            if (!$Title) {
                $Title = "Untitled spreadsheet"
            }
            Write-Verbose "Creating Spreadsheet '$Title' for user '$User'"
            $request = $service.Spreadsheets.Create($body)
            $response = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            if ($Launch) {
                Write-Verbose "Launching new spreadsheet at $($response.SpreadsheetUrl)"
                Start-Process $response.SpreadsheetUrl
            }
            $response
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSSheet'

function Clear-GSTasklist {
    <#
    .SYNOPSIS
    Clears all completed tasks from the specified task list. The affected tasks will be marked as 'hidden' and no longer be returned by default when retrieving all tasks for a task list
     
    .DESCRIPTION
    Clears all completed tasks from the specified task list. The affected tasks will be marked as 'hidden' and no longer be returned by default when retrieving all tasks for a task list
     
    .PARAMETER Tasklist
    The unique Id of the Tasklist to clear
     
    .PARAMETER User
    The User who owns the Tasklist.
 
    Defaults to the AdminUser's email.
     
    .EXAMPLE
    Clear-GSTasklist -Tasklist 'MTA3NjIwMjA1NTEzOTk0MjQ0OTk6NTMyNDY5NDk1NDM5MzMxO' -Confirm:$false
 
    Clears the specified Tasklist owned by the AdminEmail user and skips the confirmation check
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String[]]
        $Tasklist,
        [parameter(Mandatory = $false,Position = 1)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($list in $Tasklist) {
            try {
                if ($PSCmdlet.ShouldProcess("Clearing Tasklist '$list' for user '$User'")) {
                    $request = $service.Tasks.Clear($list)
                    $request.Execute()
                    Write-Verbose "Successfully cleared Tasklist '$list' for user '$User'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Clear-GSTasklist'

function Get-GSTask {
    <#
    .SYNOPSIS
    Gets a specific Task or the list of Tasks
     
    .DESCRIPTION
    Gets a specific Task or the list of Tasks
     
    .PARAMETER User
    The User who owns the Task.
 
    Defaults to the AdminUser's email.
     
    .PARAMETER Task
    The unique Id of the Task.
 
    If left blank, returns the list of Tasks on the Tasklist
     
    .PARAMETER Tasklist
    The unique Id of the Tasklist the Task is on.
     
    .PARAMETER PageSize
    Page size of the result set
     
    .EXAMPLE
    Get-GSTasklist
 
    Gets the list of Tasklists owned by the AdminEmail user
     
    .EXAMPLE
    Get-GSTasklist -Tasklist MTUzNTU0MDYscM0NjKDMTIyNjQ6MDow -User john@domain.com
 
    Gets the Tasklist matching the provided Id owned by John
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")]
        [Alias('Id')]
        [String[]]
        $Task,
        [parameter(Mandatory = $true,Position = 1)]
        [String]
        $Tasklist,
        [parameter(Mandatory = $false)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [DateTime]
        $CompletedMax,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [DateTime]
        $CompletedMin,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [DateTime]
        $DueMax,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [DateTime]
        $DueMin,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [DateTime]
        $UpdatedMin,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Switch]
        $ShowCompleted,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Switch]
        $ShowDeleted,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Switch]
        $ShowHidden,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateRange(1,100)]
        [Alias("MaxResults")]
        [Int]
        $PageSize
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks.readonly'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($T in $Task) {
                    try {
                        Write-Verbose "Getting Task '$T' from Tasklist '$Tasklist' for user '$User'"
                        $request = $service.Tasks.Get($Tasklist,$T)
                        $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                try {
                    Write-Verbose "Getting all Tasks from Tasklist '$Tasklist' for user '$User'"
                    $request = $service.Tasks.List($Tasklist)
                    foreach ($key in $PSBoundParameters.Keys | Where-Object {$request.PSObject.Properties.Name -contains $_}) {
                        switch ($key) {
                            Tasklist {}
                            {$_ -in @('CompletedMax','CompletedMin','DueMax','DueMin','UpdatedMin')} {
                                $request.$key = ($PSBoundParameters[$key]).ToString('o')
                            }
                            default {
                                if ($request.PSObject.Properties.Name -contains $key) {
                                    $request.$key = $PSBoundParameters[$key]
                                }
                            }
                        }
                    }
                    if ($PSBoundParameters.Keys -contains 'PageSize') {
                        $request.MaxResults = $PSBoundParameters['PageSize']
                    }
                    [int]$i = 1
                    do {
                        $result = $request.Execute()
                        $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                        $request.PageToken = $result.NextPageToken
                        [int]$retrieved = ($i + $result.Items.Count) - 1
                        Write-Verbose "Retrieved $retrieved Tasks..."
                        [int]$i = $i + $result.Items.Count
                    }
                    until (!$result.NextPageToken)
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSTask'

function Get-GSTasklist {
    <#
    .SYNOPSIS
    Gets a specific Tasklist or the list of Tasklists
     
    .DESCRIPTION
    Gets a specific Tasklist or the list of Tasklists
     
    .PARAMETER User
    The User who owns the Tasklist.
 
    Defaults to the AdminUser's email.
     
    .PARAMETER Tasklist
    The unique Id of the Tasklist.
 
    If left blank, gets the full list of Tasklists
     
    .PARAMETER PageSize
    Page size of the result set
     
    .EXAMPLE
    Get-GSTasklist
 
    Gets the list of Tasklists owned by the AdminEmail user
     
    .EXAMPLE
    Get-GSTasklist -Tasklist MTUzNTU0MDYscM0NjKDMTIyNjQ6MDow -User john@domain.com
 
    Gets the Tasklist matching the provided Id owned by John
    #>

    [cmdletbinding(DefaultParameterSetName = "List")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")]
        [Alias('Id')]
        [String[]]
        $Tasklist,
        [parameter(Mandatory = $false,Position = 1)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateRange(1,100)]
        [Alias("MaxResults")]
        [Int]
        $PageSize
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks.readonly'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        switch ($PSCmdlet.ParameterSetName) {
            Get {
                foreach ($list in $Tasklist) {
                    try {
                        Write-Verbose "Getting Tasklist '$list' for user '$User'"
                        $request = $service.Tasklists.Get($list)
                        $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                    }
                    catch {
                        if ($ErrorActionPreference -eq 'Stop') {
                            $PSCmdlet.ThrowTerminatingError($_)
                        }
                        else {
                            Write-Error $_
                        }
                    }
                }
            }
            List {
                try {
                    Write-Verbose "Getting all Tasklists for user '$User'"
                    $request = $service.Tasklists.List()
                    if ($PSBoundParameters.Keys -contains 'PageSize') {
                        $request.MaxResults = $PSBoundParameters['PageSize']
                    }
                    [int]$i = 1
                    do {
                        $result = $request.Execute()
                        $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                        $request.PageToken = $result.NextPageToken
                        [int]$retrieved = ($i + $result.Items.Count) - 1
                        Write-Verbose "Retrieved $retrieved Tasklists..."
                        [int]$i = $i + $result.Items.Count
                    }
                    until (!$result.NextPageToken)
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSTasklist'

function Move-GSTask {
    <#
    .SYNOPSIS
    Moves the specified task to another position in the task list. This can include putting it as a child task under a new parent and/or move it to a different position among its sibling tasks.
     
    .DESCRIPTION
    Moves the specified task to another position in the task list. This can include putting it as a child task under a new parent and/or move it to a different position among its sibling tasks.
     
    .PARAMETER Tasklist
    The unique Id of the Tasklist where the Task currently resides
     
    .PARAMETER Task
    The unique Id of the Task to move
 
    .PARAMETER Parent
    Parent task identifier. If the task is created at the top level, this parameter is omitted.
 
    .PARAMETER Previous
    Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.
     
    .PARAMETER User
    The User who owns the Tasklist.
 
    Defaults to the AdminUser's email.
     
    .EXAMPLE
    Clear-GSTasklist -Tasklist 'MTA3NjIwMjA1NTEzOTk0MjQ0OTk6NTMyNDY5NDk1NDM5MzMxO' -Confirm:$false
 
    Clears the specified Tasklist owned by the AdminEmail user and skips the confirmation check
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $Tasklist,
        [parameter(Mandatory = $true,Position = 1,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String[]]
        $Task,
        [parameter(Mandatory = $false)]
        [String]
        $Parent,
        [parameter(Mandatory = $false)]
        [String]
        $Previous,
        [parameter(Mandatory = $false,Position = 1)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($T in $Task) {
            try {
                Write-Verbose "Moving Task '$T' for user '$User'"
                $request = $service.Tasks.Move($Tasklist,$T)
                foreach ($key in $PSBoundParameters.Keys | Where-Object {$request.PSObject.Properties.Name -contains $_}) {
                    switch ($key) {
                        {$_ -in @('Parent','Previous')} {
                            $request.$key = $PSBoundParameters[$key]
                        }
                    }
                }
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Move-GSTask'

function New-GSTask {
    <#
    .SYNOPSIS
    Creates a new Task
     
    .DESCRIPTION
    Creates a new Task
     
    .PARAMETER Title
    The title of the new Task
     
    .PARAMETER Tasklist
    The Id of the Tasklist to create the new Task on
     
    .PARAMETER Completed
    The DateTime of the task completion
     
    .PARAMETER Due
    The DateTime of the task due date
     
    .PARAMETER Notes
    Notes describing the task
     
    .PARAMETER Status
    Status of the task. This is either "needsAction" or "completed".
 
    .PARAMETER Parent
    Parent task identifier. If the task is created at the top level, this parameter is omitted.
 
    .PARAMETER Previous
    Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.
     
    .PARAMETER User
    The User to create the Task for.
 
    Defaults to the AdminUser's email.
     
    .EXAMPLE
    New-GSTask -Title 'Sweep kitchen','Mow lawn' -Tasklist MTA3NjIwMjA1NTEzOTk0MjQ0OTk6ODEzNTI1MjE3ODk0MTY2MDow
 
    Creates 2 new Tasks titled 'Sweep kitchen' and 'Mow lawn' for the AdminEmail user on the specified Tasklist Id
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [String[]]
        $Title,
        [parameter(Mandatory = $true)]
        [String]
        $Tasklist,
        [parameter(Mandatory = $false)]
        [DateTime]
        $Completed,
        [parameter(Mandatory = $false)]
        [DateTime]
        $Due,
        [parameter(Mandatory = $false)]
        [String]
        $Notes,
        [parameter(Mandatory = $false)]
        [ValidateSet('needsAction','completed')]
        [String]
        $Status,
        [parameter(Mandatory = $false)]
        [String]
        $Parent,
        [parameter(Mandatory = $false)]
        [String]
        $Previous,
        [parameter(Mandatory = $false)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($T in $Title) {
            try {
                Write-Verbose "Creating Task '$T' on Tasklist '$Tasklist' for user '$User'"
                $body = New-Object 'Google.Apis.Tasks.v1.Data.Task'
                foreach ($key in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                    switch ($key) {
                        Parent {}
                        default {
                            if ($body.PSObject.Properties.Name -contains $key) {
                                $body.$key = $PSBoundParameters[$key]
                            }
                        }
                    }
                }
                $request = $service.Tasks.Insert($body,$Tasklist)
                foreach ($key in $PSBoundParameters.Keys | Where-Object {$request.PSObject.Properties.Name -contains $_}) {
                    switch ($key) {
                        Tasklist {}
                        default {
                            if ($request.PSObject.Properties.Name -contains $key) {
                                $request.$key = $PSBoundParameters[$key]
                            }
                        }
                    }
                }
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSTask'

function New-GSTasklist {
    <#
    .SYNOPSIS
    Creates a new Tasklist
     
    .DESCRIPTION
    Creates a new Tasklist
     
    .PARAMETER User
    The User to create the Tasklist for.
 
    Defaults to the AdminUser's email.
     
    .PARAMETER Title
    The title of the new Tasklist
     
    .EXAMPLE
    New-GSTasklist -Title 'Chores','Projects'
 
    Creates 2 new Tasklists titled 'Chores' and 'Projects' for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String[]]
        $Title,
        [parameter(Mandatory = $false,Position = 1)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($list in $Title) {
            try {
                Write-Verbose "Creating Tasklist '$list' for user '$User'"
                $body = New-Object 'Google.Apis.Tasks.v1.Data.TaskList' -Property @{
                    Title = $list
                }
                $request = $service.Tasklists.Insert($body)
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSTasklist'

function Remove-GSTask {
    <#
    .SYNOPSIS
    Deletes the authenticated user's specified task
     
    .DESCRIPTION
    Deletes the authenticated user's specified task
     
    .PARAMETER Task
    The unique Id of the Task to delete
     
    .PARAMETER Tasklist
    The unique Id of the Tasklist where the Task is
     
    .PARAMETER User
    The User who owns the Tasklist.
 
    Defaults to the AdminUser's email.
     
    .EXAMPLE
    Remove-GSTask -Task 'MTA3NjIwMjA1NTEzOTk0MjQ0OTk6MDow' -Tasklist 'MTA3NjIwMjA1NTEzOTk0MjQ0OTk6NTMyNDY5NDk1NDM5MzMxO' -Confirm:$false
 
    Remove the specified Task owned by the AdminEmail user and skips the confirmation check
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String[]]
        $Task,
        [parameter(Mandatory = $true,Position = 1)]
        [String]
        $Tasklist,
        [parameter(Mandatory = $false,Position = 1)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($T in $Task) {
            try {
                if ($PSCmdlet.ShouldProcess("Removing Task '$T' from Tasklist '$Tasklist' for user '$User'")) {
                    $request = $service.Tasks.Delete($Tasklist,$T)
                    $request.Execute()
                    Write-Verbose "Successfully removed Task '$T' from Tasklist '$Tasklist' for user '$User'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSTask'

function Remove-GSTasklist {
    <#
    .SYNOPSIS
    Deletes the authenticated user's specified task list
     
    .DESCRIPTION
    Deletes the authenticated user's specified task list
     
    .PARAMETER Tasklist
    The unique Id of the Tasklist to remove
     
    .PARAMETER User
    The User who owns the Tasklist.
 
    Defaults to the AdminUser's email.
     
    .EXAMPLE
    Remove-GSTasklist -Tasklist 'MTA3NjIwMjA1NTEzOTk0MjQ0OTk6NTMyNDY5NDk1NDM5MzMxO' -Confirm:$false
 
    Remove the specified Tasklist owned by the AdminEmail user and skips the confirmation check
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String[]]
        $Tasklist,
        [parameter(Mandatory = $false,Position = 1)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($list in $Tasklist) {
            try {
                if ($PSCmdlet.ShouldProcess("Removing Tasklist '$list' for user '$User'")) {
                    $request = $service.Tasklists.Delete($list)
                    $request.Execute()
                    Write-Verbose "Successfully removed Tasklist '$list' for user '$User'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSTasklist'

function Update-GSTask {
    <#
    .SYNOPSIS
    Updates a Task
     
    .DESCRIPTION
    Updates a Task
     
    .PARAMETER Tasklist
    The Id of the Tasklist the Task is on
     
    .PARAMETER Task
    The Id of the Task
     
    .PARAMETER Title
    The title of the Task
     
    .PARAMETER Completed
    The DateTime of the task completion
     
    .PARAMETER Due
    The DateTime of the task due date
     
    .PARAMETER Notes
    Notes describing the task
     
    .PARAMETER Status
    Status of the task. This is either "needsAction" or "completed".
 
    .PARAMETER Parent
    Parent task identifier. If the task is created at the top level, this parameter is omitted.
 
    .PARAMETER Previous
    Previous sibling task identifier. If the task is created at the first position among its siblings, this parameter is omitted.
     
    .PARAMETER User
    The User who owns the Task
 
    Defaults to the AdminUser's email.
     
    .EXAMPLE
    Update-GSTask -Title 'Return Ben Crawford's call -Tasklist MTA3NjIwMjA1NTEzOTk0MjQ0OTk6ODEzNTI1MjE3ODk0MTY2MDow -Task 'MTA3NjIwMjA1NTEzOTk0MjQ0OTk6MDo4MjM4NDQ2MDA0MzIxMDEx' -Status completed
 
    Updates the specified Task's title and marks it as completed
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [String]
        $Tasklist,
        [parameter(Mandatory = $true)]
        [String]
        $Task,
        [parameter(Mandatory = $false)]
        [String[]]
        $Title,
        [parameter(Mandatory = $false)]
        [DateTime]
        $Completed,
        [parameter(Mandatory = $false)]
        [DateTime]
        $Due,
        [parameter(Mandatory = $false)]
        [String]
        $Notes,
        [parameter(Mandatory = $false)]
        [ValidateSet('needsAction','completed')]
        [String]
        $Status,
        [parameter(Mandatory = $false)]
        [String]
        $Parent,
        [parameter(Mandatory = $false)]
        [String]
        $Previous,
        [parameter(Mandatory = $false)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Updating Task '$Task' on Tasklist '$Tasklist' for user '$User'"
            $body = New-Object 'Google.Apis.Tasks.v1.Data.Task'
            foreach ($key in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($key) {
                    Parent {}
                    default {
                        if ($body.PSObject.Properties.Name -contains $key) {
                            $body.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $request = $service.Tasks.Update($body,$Tasklist,$Task)
            foreach ($key in $PSBoundParameters.Keys | Where-Object {$request.PSObject.Properties.Name -contains $_}) {
                switch ($key) {
                    {$_ -in @('Tasklist','Task')} {}
                    default {
                        if ($request.PSObject.Properties.Name -contains $key) {
                            $request.$key = $PSBoundParameters[$key]
                        }
                    }
                }
            }
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSTask'

function Update-GSTasklist {
    <#
    .SYNOPSIS
    Updates a Tasklist title
     
    .DESCRIPTION
    Updates a Tasklist title
     
    .PARAMETER Tasklist
    The unique Id of the Tasklist to update
     
    .PARAMETER Title
    The new title of the Tasklist
     
    .PARAMETER User
    The User who owns the Tasklist.
 
    Defaults to the AdminUser's email.
     
    .EXAMPLE
    Update-GSTasklist -Tasklist 'MTA3NjIwMjA1NTEzOTk0MjQ0OTk6NTMyNDY5NDk1NDM5MzMxOTow' -Title 'Hi-Pri Callbacks'
 
    Updates the specified TaskList with the new title 'Hi-Pri Callbacks'
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0)]
        [String]
        $Tasklist,
        [parameter(Mandatory = $true,Position = 1)]
        [String]
        $Title,
        [parameter(Mandatory = $false)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($User)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/tasks'
            ServiceType = 'Google.Apis.Tasks.v1.TasksService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Updating Tasklist '$list' to Title '$Title' for user '$User'"
            $body = New-Object 'Google.Apis.Tasks.v1.Data.TaskList' -Property @{
                Title = $Title
            }
            $request = $service.Tasklists.Patch($body,$Tasklist)
            $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSTasklist'

function Get-GSShortUrl {
    <#
    .SYNOPSIS
    Gets information about a user's Short Url's created at https://goo.gl/
     
    .DESCRIPTION
    Gets information about a user's Short Url's created at https://goo.gl/
     
    .PARAMETER ShortUrl
    The Short Url to return information for. If excluded, returns the list of the user's Short Url's
     
    .PARAMETER User
    The primary email of the user you would like to retrieve Short Url information for
 
    Defaults to the AdminEmail user
     
    .PARAMETER Projection
        Additional information to return.
 
    Acceptable values are:
    * "ANALYTICS_CLICKS" - Returns short URL click counts.
    * "FULL" - Returns short URL click counts.
     
    .EXAMPLE
    Get-GSShortUrl
 
    Gets the Short Url list of the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [String[]]
        $ShortUrl,
        [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory=$false)]
        [ValidateSet("Full","Analytics_Clicks")]
        [string]
        $Projection = "Full"
    )
    Begin {
        if ($ShortUrl) {
            if ($User -ceq 'me') {
                $User = $Script:PSGSuite.AdminEmail
            }
            elseif ($User -notlike "*@*.*") {
                $User = "$($U)@$($Script:PSGSuite.Domain)"
            }
            $serviceParams = @{
                Scope       = 'https://www.googleapis.com/auth/urlshortener'
                ServiceType = 'Google.Apis.Urlshortener.v1.UrlshortenerService'
                User        = $User
            }
            $service = New-GoogleService @serviceParams
        }
    }
    Process {
        try {
            if ($ShortUrl) {
                foreach ($S in $ShortUrl) {
                    Write-Verbose "Getting short Url '$S'"
                    $request = $service.Url.Get($S)
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
                }
            }
            else {
                Get-GSShortUrlListPrivate @PSBoundParameters
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSShortUrl'

function New-GSShortUrl {
    <#
    .SYNOPSIS
    Creates a new Short Url
     
    .DESCRIPTION
    Creates a new Short Url
     
    .PARAMETER LongUrl
    The full Url to shorten
     
    .PARAMETER User
    The user to create the Short Url for
 
    Defaults to the AdminEmail user
     
    .EXAMPLE
    New-GSShortUrl "http://ferrell.io"
 
    Creates a new Short Url pointing at http://ferrell.io/
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [String[]]
        $LongUrl,
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        if ($User -ceq 'me') {
            $User = $Script:PSGSuite.AdminEmail
        }
        elseif ($User -notlike "*@*.*") {
            $User = "$($U)@$($Script:PSGSuite.Domain)"
        }
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/urlshortener'
            ServiceType = 'Google.Apis.Urlshortener.v1.UrlshortenerService'
            User        = $User
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($L in $LongUrl) {
                Write-Verbose "Creating short Url for '$L'"
                $body = New-Object 'Google.Apis.Urlshortener.v1.Data.Url' -Property @{
                    LongUrl = $L
                }
                $request = $service.Url.Insert($body)
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSShortUrl'

function Get-GSUser {
    <#
    .SYNOPSIS
    Gets the specified G SUite User or a list of Users
 
    .DESCRIPTION
    Gets the specified G SUite User. Designed for parity with Get-ADUser as much as possible
 
    .PARAMETER User
    The primary email or UserID of the user who you are trying to get info for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
 
    .PARAMETER Filter
    Query string for searching user fields
 
    For more information on constructing user queries, see: https://developers.google.com/admin-sdk/directory/v1/guides/search-users
 
    PowerShell filter syntax here is supported as "best effort". Please use Google's filter operators and syntax to ensure best results
 
    .PARAMETER Domain
    The specific domain you would like to list users for. Useful for customers with multiple domains.
 
    .PARAMETER SearchBase
    The organizational unit path that you would like to list users from
 
    .PARAMETER SearchScope
    The depth at which to return the list of users
 
    Available values are:
    * "Base": only return the users specified in the SearchBase
    * "Subtree": return the full list of users underneath the specified SearchBase
    * "OneLevel": return the SearchBase and the Users directly underneath it
 
    .PARAMETER ShowDeleted
    Returns deleted users
 
    .PARAMETER Projection
    What subset of fields to fetch for this user
 
    Acceptable values are:
    * "Basic": Do not include any custom fields for the user
    * "Custom": Include custom fields from schemas requested in customFieldMask
    * "Full": Include all fields associated with this user (default for this module)
 
    .PARAMETER CustomFieldMask
    A comma-separated list of schema names. All fields from these schemas are fetched. This should only be set when using '-Projection Custom'
 
    .PARAMETER ViewType
    Whether to fetch the administrator-only or domain-wide public view of the user. For more information, see Retrieve a user as a non-administrator
 
    Acceptable values are:
    * "Admin_View": Results include both administrator-only and domain-public fields for the user. (default)
    * "Domain_Public": Results only include fields for the user that are publicly visible to other users in the domain.
 
    .PARAMETER Fields
    The specific fields to fetch for this user
 
    .PARAMETER PageSize
    Page size of the result set
 
    .PARAMETER OrderBy
    Property to use for sorting results.
 
    Acceptable values are:
    * "Email": Primary email of the user.
    * "FamilyName": User's family name.
    * "GivenName": User's given name.
 
    .PARAMETER SortOrder
    Whether to return results in ascending or descending order.
 
    Acceptable values are:
    * "Ascending": Ascending order.
    * "Descending": Descending order.
 
    .EXAMPLE
    Get-GSUser
 
    Gets the user info for the AdminEmail on the config
 
    .EXAMPLE
    Get-GSUser -Filter *
 
    Gets the list of users
 
    .EXAMPLE
    Get-GSUser -Filter "IsAdmin -eq '$true'"
 
    Gets the list of SuperAdmin users
 
    .EXAMPLE
    Get-GSUser -Filter "IsEnrolledIn2Sv -eq '$false'" -SearchBase /Contractors -SearchScope Subtree
 
    Gets the list of users not currently enrolled in 2-Step Verification from the Contractors OrgUnit or any OrgUnits underneath it
    #>

    [cmdletbinding(DefaultParameterSetName = "Get")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")]
        [Alias("PrimaryEmail","UserKey","Mail","Email","Id")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Alias("Query")]
        [String[]]
        $Filter,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [String]
        $Domain,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Alias("OrgUnitPath")]
        [String]
        $SearchBase,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateSet("Base","OneLevel","Subtree")]
        [String]
        $SearchScope = "Subtree",
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [Switch]
        $ShowDeleted,
        [parameter(Mandatory = $false)]
        [ValidateSet("Basic","Custom","Full")]
        [string]
        $Projection = "Full",
        [parameter(Mandatory = $false)]
        [String]
        $CustomFieldMask,
        [parameter(Mandatory = $false)]
        [ValidateSet("Admin_View","Domain_Public")]
        [String]
        $ViewType = "Admin_View",
        [parameter(Mandatory = $false)]
        [String[]]
        $Fields,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateRange(1,500)]
        [Alias("MaxResults")]
        [Int]
        $PageSize = "500",
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateSet("Email","GivenName","FamilyName")]
        [String]
        $OrderBy,
        [parameter(Mandatory = $false,ParameterSetName = "List")]
        [ValidateSet("Ascending","Descending")]
        [String]
        $SortOrder
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user.readonly'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        if ($MyInvocation.InvocationName -ne 'Get-GSUserList' -and $PSCmdlet.ParameterSetName -eq 'Get') {
            foreach ($U in $User) {
                try {
                    if ( -not ($U -as [decimal])) {
                        if ($U -ceq 'me') {
                            $U = $Script:PSGSuite.AdminEmail
                        }
                        elseif ($U -notlike "*@*.*") {
                            $U = "$($U)@$($Script:PSGSuite.Domain)"
                        }
                    }
                    Write-Verbose "Getting User '$U'"
                    $request = $service.Users.Get($U)
                    $request.Projection = $Projection
                    $request.ViewType = ($ViewType -replace '_','')
                    if ($CustomFieldMask) {
                        $request.CustomFieldMask = $CustomFieldMask
                    }
                    if ($Fields) {
                        $request.Fields = "$($Fields -join ",")"
                    }
                    $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -Force -PassThru  | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.PrimaryEmail} -PassThru -Force
                }
                catch {
                    if ($ErrorActionPreference -eq 'Stop') {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    else {
                        Write-Error $_
                    }
                }
            }
        }
        else {
            try {
                $request = $service.Users.List()
                $request.Projection = $Projection
                if ($PSBoundParameters.Keys -contains 'Domain') {
                    $verbScope = "domain '$($PSBoundParameters['Domain'])'"
                    $request.Domain = $PSBoundParameters['Domain']
                }
                elseif ($Script:PSGSuite.Preference) {
                    switch ($Script:PSGSuite.Preference) {
                        Domain {
                            $verbScope = "domain '$($Script:PSGSuite.Domain)'"
                            $request.Domain = $Script:PSGSuite.Domain
                        }
                        CustomerID {
                            $verbScope = "customer '$($Script:PSGSuite.CustomerID)'"
                            $request.Customer = "$($Script:PSGSuite.CustomerID)"
                        }
                    }
                }
                else {
                    $verbScope = "customer 'my_customer'"
                    $request.Customer = "my_customer"
                }
                if ($PageSize) {
                    $request.MaxResults = $PageSize
                }
                foreach ($prop in $PSBoundParameters.Keys | Where-Object {$_ -in @('OrderBy','SortOrder','CustomFieldMask','ShowDeleted','ViewType')}) {
                    $request.$prop = $PSBoundParameters[$prop]
                }
                if (![String]::IsNullOrEmpty($Filter) -or $SearchBase) {
                    if ($Filter -eq '*') {
                        $Filter = ""
                    }
                    else {
                        $Filter = "$($Filter -join " ")"
                    }
                    if ($SearchBase) {
                        $Filter += " OrgUnitPath='$SearchBase'"
                    }
                    $Filter = $Filter -replace " -eq ","=" -replace " -like ",":" -replace " -match ",":" -replace " -contains ",":" -creplace "'True'","True" -creplace "'False'","False"
                    $request.Query = $Filter.Trim()
                    if ([String]::IsNullOrEmpty($Filter.Trim())) {
                        Write-Verbose "Getting all Users for $verbScope"
                    }
                    else {
                        Write-Verbose "Getting Users for $verbScope matching filter: `"$($Filter.Trim())`""
                    }
                }
                else {
                    Write-Verbose "Getting all Users for $verbScope"
                }
                $response = New-Object System.Collections.ArrayList
                [int]$i = 1
                do {
                    $result = $request.Execute()
                    if ($result.UsersValue) {
                        $result.UsersValue | ForEach-Object {
                            $_ | Add-Member -MemberType NoteProperty -Name 'User' -Value $_.PrimaryEmail -Force -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.PrimaryEmail} -Force
                            [void]$response.Add($_)
                        }
                    }
                    $request.PageToken = $result.NextPageToken
                    [int]$retrieved = ($i + $result.UsersValue.Count) - 1
                    Write-Verbose "Retrieved $retrieved users..."
                    [int]$i = $i + $result.UsersValue.Count
                }
                until (!$result.NextPageToken)
                if ($SearchScope -ne "Subtree") {
                    if (!$SearchBase) {
                        $SearchBase = "/"
                    }
                    $response = switch ($SearchScope) {
                        Base {
                            $response | Where-Object {$_.OrgUnitPath -eq $SearchBase}
                        }
                        OneLevel {
                            $maxDepth = ($SearchBase -split "/" | Where-Object {$_}).Count + 1
                            $children = $response | Select-Object -ExpandProperty OrgUnitPath -Unique | ForEach-Object {
                                if (($_ -split "/" | Where-Object {$_}).Count -le $maxDepth) {
                                    $_
                                }
                            }
                            $response | Where-Object {$_.OrgUnitPath -in $children}
                        }
                    }
                    Write-Verbose "Total users in SearchScope: $($response.Count)"
                }
                return $response
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}

Export-ModuleMember -Function 'Get-GSUser'

function Get-GSUserAlias {
    <#
    .SYNOPSIS
    Gets the specified G SUite User's aliases
     
    .DESCRIPTION
    Gets the specified G SUite User's aliases
     
    .PARAMETER User
    The primary email or UserID of the user who you are trying to get aliases for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
     
    .EXAMPLE
    Get-GSUserAlias
 
    Gets the list of aliases for the AdminEmail user
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user.readonly'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($U in $User) {
            try {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Getting Alias list for User '$U'"
                $request = $service.Users.Aliases.List($U)
                $request.Execute() | Select-Object -ExpandProperty AliasesValue
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSUserAlias'

function Get-GSUserPhoto {
    <#
    .SYNOPSIS
    Gets the photo data for the specified user
 
    .DESCRIPTION
    Gets the photo data for the specified user
 
    .PARAMETER User
    The primary email or UserID of the user who you are trying to get info for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    Defaults to the AdminEmail in the config
 
    .PARAMETER OutFilePath
    The directory path that you would like to save the photos to. If excluded, this will return the photo information
 
    .PARAMETER OutFileFormat
    The format that you would like to save the photo as.
 
    Available values are:
    * "PNG": saves the photo in .png format
    * "JPG": saves the photo in .jpg format
    * "Base64": saves the photo as a .txt file containing standard (non-WebSafe) Base64 content.
 
    Defaults to PNG
 
    .EXAMPLE
    Get-GSUserPhoto -OutFilePath .
 
    Saves the Google user photo of the AdminEmail in the current working directory as a .png image
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = "Get")]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User = $Script:PSGSuite.AdminEmail,
        [parameter(Mandatory = $false)]
        [ValidateScript({(Get-Item $_).PSIsContainer})]
        [String]
        $OutFilePath,
        [parameter(Mandatory = $false)]
        [ValidateSet('Base64','PNG','JPG')]
        [String]
        $OutFileFormat = 'PNG'
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user.readonly'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            foreach ($U in $User) {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Getting photo for User '$U'"
                $request = $service.Users.Photos.Get($U)
                $res = $request.Execute()
                $base64 = $res.PhotoData | Convert-Base64 -From WebSafeBase64String -To Base64String
                $bytes = [Convert]::FromBase64String($base64)
                if ($OutFilePath) {
                    $fileBaseName = "$($U -replace '@.*','')"
                    switch ($OutFileFormat) {
                        JPG {
                            $filePath = Join-Path $OutFilePath "$($fileBaseName).jpg"
                            Write-Verbose "Saving photo at '$filePath'"
                            [System.IO.File]::WriteAllBytes($filePath, $bytes)
                        }
                        PNG {
                            $filePath = Join-Path $OutFilePath "$($fileBaseName).png"
                            Write-Verbose "Saving photo at '$filePath'"
                            [System.IO.File]::WriteAllBytes($filePath, $bytes)
                        }
                        Base64 {
                            $filePath = Join-Path $OutFilePath "$($fileBaseName).txt"
                            Write-Verbose "Saving Base64 photo content at '$filePath'"
                            [System.IO.File]::WriteAllText($filePath,$base64)
                        }
                    }
                }
                else {
                    $res | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'PhotoBytes' -Value $bytes -PassThru | Add-Member -MemberType NoteProperty -Name 'PhotoBase64' -Value $base64 -PassThru
                }
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Get-GSUserPhoto'

function New-GSUser {
    <#
    .SYNOPSIS
    Creates a new G Suite user
 
    .DESCRIPTION
    Creates a new G Suite user
 
    .PARAMETER PrimaryEmail
    The primary email for the user. If a user with the desired email already exists, a GoogleApiException will be thrown
 
    .PARAMETER GivenName
    The given (first) name of the user
 
    .PARAMETER FamilyName
    The family (last) name of the user
 
    .PARAMETER FullName
    The full name of the user, if different from "$FirstName $LastName"
 
    .PARAMETER Password
    The password for the user. Requires a SecureString
 
    .PARAMETER ChangePasswordAtNextLogin
    If set, user will need to change their password on their first login
 
    .PARAMETER OrgUnitPath
    The OrgUnitPath to create the user in
 
    .PARAMETER Suspended
    If set, user will be created in a suspended state
 
    .PARAMETER Addresses
    The address objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]' object type. You can create objects of this type easily by using the function 'Add-GSUserAddress'
 
    .PARAMETER Emails
    The email objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]' object type. You can create objects of this type easily by using the function 'Add-GSUserEmail'
 
    .PARAMETER ExternalIds
    The externalId objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]' object type. You can create objects of this type easily by using the function 'Add-GSUserExternalId'
 
    .PARAMETER Organizations
    The organization objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]' object type. You can create objects of this type easily by using the function 'Add-GSUserOrganization'
 
    .PARAMETER Phones
    The phone objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhone[]' object type. You can create objects of this type easily by using the function 'Add-GSUserPhone'
 
    .PARAMETER IncludeInGlobalAddressList
    Indicates if the user's profile is visible in the G Suite global address list when the contact sharing feature is enabled for the domain. For more information about excluding user profiles, see the administration help center: http://support.google.com/a/bin/answer.py?answer=1285988
 
    .PARAMETER IpWhitelisted
    If true, the user's IP address is white listed: http://support.google.com/a/bin/answer.py?answer=60752
 
    .PARAMETER CustomSchemas
    Custom user attribute values to add to the user's account.
 
    The Custom Schema and it's fields **MUST** exist prior to updating these values for a user otherwise it will return an error.
 
    This parameter only accepts a hashtable where the keys are Schema Names and the value for each key is another hashtable, i.e.:
 
        Update-GSUser -User john.smith@domain.com -CustomSchemas @{
            schemaName1 = @{
                fieldName1 = $fieldValue1
                fieldName2 = $fieldValue2
            }
            schemaName2 = @{
                fieldName3 = $fieldValue3
            }
        }
 
    If you need to CLEAR a custom schema value, simply pass $null as the value(s) for the fieldName in the hashtable, i.e.:
 
        Update-GSUser -User john.smith@domain.com -CustomSchemas @{
            schemaName1 = @{
                fieldName1 = $null
                fieldName2 = $null
            }
            schemaName2 = @{
                fieldName3 = $null
            }
        }
 
    .EXAMPLE
    $address = Add-GSUserAddress -Country USA -Locality Dallas -PostalCode 75000 Region TX -StreetAddress '123 South St' -Type Work -Primary
 
    $phone = Add-GSUserPhone -Type Work -Value "(800) 873-0923" -Primary
 
    $extId = Add-GSUserExternalId -Type Login_Id -Value jsmith2
 
    $email = Add-GSUserEmail -Type work -Address jsmith@contoso.com
 
    New-GSUser -PrimaryEmail john.smith@domain.com -GivenName John -FamilyName Smith -Password (ConvertTo-SecureString -String 'Password123' -AsPlainText -Force) -ChangePasswordAtNextLogin -OrgUnitPath "/Users/New Hires" -IncludeInGlobalAddressList -Addresses $address -Phones $phone -ExternalIds $extId -Emails $email
 
    Creates a user named John Smith and adds their work address, work phone, login_id and alternate non gsuite work email to the user object.
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true, Position = 0)]
        [String]
        $PrimaryEmail,
        [parameter(Mandatory = $true)]
        [String]
        $GivenName,
        [parameter(Mandatory = $true)]
        [String]
        $FamilyName,
        [parameter(Mandatory = $false)]
        [String]
        $FullName,
        [parameter(Mandatory = $true)]
        [SecureString]
        $Password,
        [parameter(Mandatory = $false)]
        [Switch]
        $ChangePasswordAtNextLogin,
        [parameter(Mandatory = $false)]
        [String]
        $OrgUnitPath,
        [parameter(Mandatory = $false)]
        [Switch]
        $Suspended,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]]
        $Addresses,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]]
        $Emails,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]]
        $ExternalIds,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]]
        $Organizations,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserPhone[]]
        $Phones,
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeInGlobalAddressList,
        [parameter(Mandatory = $false)]
        [Switch]
        $IpWhitelisted,
        [parameter(Mandatory = $false)]
        [ValidateScript( {
                $hash = $_
                foreach ($schemaName in $hash.Keys) {
                    if ($hash[$schemaName].GetType().Name -ne 'Hashtable') {
                        throw "The CustomSchemas parameter only accepts a hashtable where the value of the top-level keys must also be a hashtable. The key '$schemaName' has a value of type '$($hash[$schemaName].GetType().Name)'"
                        $valid = $false
                    }
                    else {
                        $valid = $true
                    }
                }
                $valid
            })]
        [Hashtable]
        $CustomSchemas
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        try {
            Write-Verbose "Creating user '$PrimaryEmail'"
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.User'
            $name = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserName' -Property @{
                GivenName  = $GivenName
                FamilyName = $FamilyName
            }
            if ($PSBoundParameters.Keys -contains 'FullName') {
                $name.FullName = $FullName
            }
            else {
                $name.FullName = "$GivenName $FamilyName"
            }
            $body.Name = $name
            foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_}) {
                switch ($prop) {
                    PrimaryEmail {
                        if ($PSBoundParameters[$prop] -notlike "*@*.*") {
                            $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)"
                        }
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                    Password {
                        $body.Password = (New-Object PSCredential "user", $Password).GetNetworkCredential().Password
                    }
                    Emails {
                        $emailList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserEmail]'
                        foreach ($email in $Emails) {
                            $emailList.Add($email)
                        }
                        $body.Emails = $emailList
                    }
                    ExternalIds {
                        $extIdList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId]'
                        foreach ($extId in $ExternalIds) {
                            $extIdList.Add($extId)
                        }
                        $body.ExternalIds = $extIdList
                    }
                    Organizations {
                        $orgList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization]'
                        foreach ($organization in $Organizations) {
                            $orgList.Add($organization)
                        }
                        $body.Organizations = $orgList
                    }
                    Phones {
                        $phoneList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserPhone]'
                        foreach ($phone in $Phones) {
                            $phoneList.Add($phone)
                        }
                        $body.Phones = $phoneList
                    }
                    CustomSchemas {
                        $schemaDict = New-Object 'System.Collections.Generic.Dictionary`2[[System.String],[System.Collections.Generic.IDictionary`2[[System.String],[System.Object]]]]'
                        foreach ($schemaName in $CustomSchemas.Keys) {
                            $fieldDict = New-Object 'System.Collections.Generic.Dictionary`2[[System.String],[System.Object]]'
                            $schemaFields = $CustomSchemas[$schemaName]
                            $schemaFields.Keys | ForEach-Object {
                                $fieldDict.Add($_, $schemaFields[$_])
                            }
                            $schemaDict.Add($schemaName, $fieldDict)
                        }
                        $body.CustomSchemas = $schemaDict
                    }
                    Default {
                        $body.$prop = $PSBoundParameters[$prop]
                    }
                }
            }
            $request = $service.Users.Insert($body)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSUser'

function New-GSUserAlias {
    <#
    .SYNOPSIS
    Creates a new alias for a G Suite user
     
    .DESCRIPTION
    Creates a new alias for a G Suite user
     
    .PARAMETER User
    The user to create the alias for
     
    .PARAMETER Alias
    The alias or list of aliases to create for the user
     
    .EXAMPLE
    New-GSUserAlias -User john.smith@domain.com -Alias 'jsmith@domain.com','johns@domain.com'
 
    Creates 2 new aliases for user John Smith as 'jsmith@domain.com' and 'johns@domain.com'
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User,
        [parameter(Mandatory = $true,Position = 1)]
        [String[]]
        $Alias
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($A in $Alias) {
            try {
                if ($User -ceq 'me') {
                    $User = $Script:PSGSuite.AdminEmail
                }
                elseif ($User -notlike "*@*.*") {
                    $User = "$($User)@$($Script:PSGSuite.Domain)"
                }
                if ($A -notlike "*@*.*") {
                    $A = "$($A)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Creating alias '$A' for user '$User'"
                $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.Alias'
                $body.AliasValue = $A
                $request = $service.Users.Aliases.Insert($body,$User)
                $request.Execute()
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'New-GSUserAlias'

function Remove-GSUser {
    <#
    .SYNOPSIS
    Removes a user
     
    .DESCRIPTION
    Removes a user
     
    .PARAMETER User
    The primary email or unique Id of the user to Remove-GSUser
     
    .EXAMPLE
    Remove-GSUser joe -Confirm:$false
 
    Removes the user 'joe@domain.com', skipping confirmation
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($U in $User) {
            try {
                if ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                if ($PSCmdlet.ShouldProcess("Deleting user '$U'")) {
                    Write-Verbose "Deleting user '$U'"
                    $request = $service.Users.Delete($U)
                    $request.Execute()
                    Write-Verbose "User '$U' has been successfully deleted"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSUser'

function Remove-GSUserAlias {
    <#
    .SYNOPSIS
    Removes an alias from a G Suite user
     
    .DESCRIPTION
    Removes an alias from a G Suite user
     
    .PARAMETER User
    The user to remove the alias from
     
    .PARAMETER Alias
    The alias or list of aliases to remove from the user
     
    .EXAMPLE
    Remove-GSUserAlias -User john.smith@domain.com -Alias 'jsmith@domain.com','johns@domain.com'
 
    Removes 2 aliases from user John Smith: 'jsmith@domain.com' and 'johns@domain.com'
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail","Email")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User,
        [parameter(Mandatory = $true,Position = 1)]
        [String[]]
        $Alias
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($A in $Alias) {
            try {
                if ($User -ceq 'me') {
                    $User = $Script:PSGSuite.AdminEmail
                }
                elseif ($User -notlike "*@*.*") {
                    $User = "$($User)@$($Script:PSGSuite.Domain)"
                }
                if ($A -notlike "*@*.*") {
                    $A = "$($A)@$($Script:PSGSuite.Domain)"
                }
                if ($PSCmdlet.ShouldProcess("Removing alias '$A' from user '$User'")) {
                    Write-Verbose "Removing alias '$A' from user '$User'"
                    $request = $service.Users.Aliases.Delete($User,$A)
                    $request.Execute()
                    Write-Verbose "Alias '$A' has been successfully deleted from user '$User'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSUserAlias'

function Remove-GSUserPhoto {
    <#
    .SYNOPSIS
    Removes the photo for the specified user
 
    .DESCRIPTION
    Removes the photo for the specified user
 
    .PARAMETER User
    The primary email or UserID of the user who you are trying to remove the photo for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    .EXAMPLE
    Remove-GSUserPhoto -User me
 
    Removes the Google user photo of the AdminEmail user
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($U in $User) {
            try {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                if ($PSCmdlet.ShouldProcess("Removing the photo for User '$U'")) {
                    Write-Verbose "Removing the photo for User '$U'"
                    $request = $service.Users.Photos.Delete($U)
                    $request.Execute()
                    Write-Verbose "Successfully removed the photo for user '$U'"
                }
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Remove-GSUserPhoto'

function Restore-GSUser {
    <#
    .SYNOPSIS
    Restores a deleted user
     
    .DESCRIPTION
    Restores a deleted user
     
    .PARAMETER User
    The email address of the user to restore
     
    .PARAMETER Id
    The unique Id of the user to restore
     
    .PARAMETER OrgUnitPath
    The OrgUnitPath to restore the user to
 
    Defaults to the root OrgUnit "/"
     
    .PARAMETER RecentOnly
    If multiple users with the email address are found in deleted users, this forces restoration of the most recently deleted user. If not passed and multiple deleted users are found with the specified email address, you will be prompted to choose which you'd like to restore based on deletion time
     
    .EXAMPLE
    Restore-GSUser -User john.smith@domain.com -OrgUnitPath "/Users/Rehires" -Confirm:$false
 
    Restores user John Smith to the OrgUnitPath "/Users/Rehires", skipping confirmation. If multiple accounts with the email "john.smith@domain.com" are found, the user is presented with a dialog to choose which account to restore based on deletion time
    #>

    [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact = "Medium",DefaultParameterSetName = 'User')]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'User')]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User,
        [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true,ParameterSetName = 'Id')]
        [Int]
        $Id,
        [parameter(Mandatory = $false,Position = 1)]
        [String]
        $OrgUnitPath = "/",
        [parameter(Mandatory = $false)]
        [Switch]
        $RecentOnly
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        if (!$User) {
            $User =  ($Id | ForEach-Object {"$_"})
            $GetId = $false
        }
    }
    Process {
        try {
            foreach ($U in $User) {
                $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserUndelete'
                $body.OrgUnitPath = $OrgUnitPath
                if (!$Id) {
                    if ($PSCmdlet.ShouldProcess("Undeleting user '$U'")) {
                        if ($U -notlike "*@*.*") {
                            $U = "$($U)@$($Script:PSGSuite.Domain)"
                        }
                        $delUsers = Get-GSUser -Filter "email=$U" -ShowDeleted -Verbose:$false | Where-Object {$_.PrimaryEmail -eq $U} | Sort-Object DeletionTime -Descending
                        $userId = if ($delUsers.Count -gt 1 -and !$RecentOnly) {
                            $i = 0
                            $options = @()
                            $idHash = @{}
                            $delUsers | ForEach-Object {
                                $i++
                                $optText = "$($i): $($_.DeletionTime.ToString())"
                                $options += @{"&$($optText)" = "User '$($_.PrimaryEmail)' deleted on $($_.DeletionTime.ToLongDateString()) at $($_.DeletionTime.ToLongTimeString())"}
                                $idHash[$optText] = $_.Id
                            }
                            $choice = Read-Prompt -Options $options -Title "`n** Choose Which User To Undelete **`n" -Message "There are $($delUsers.Count) deleted users with the email address '$U'. Please enter the number for the user you would like to undelete based on the time the account was deleted`n"
                            $idHash[$choice]
                        }
                        else {
                            $delUsers[0].Id
                        }
                    }
                }
                else {
                    $userId = $U
                }
                Write-Verbose "Undeleting User Id '$userId' [$U]"
                $request = $service.Users.Undelete($body,$userId)
                $request.Execute()
                Write-Verbose "User '$U' has been successfully undeleted"
            }
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Restore-GSUser'

function Sync-GSUserCache {
    <#
    .SYNOPSIS
    Syncs your GS Users to a hashtable contained in the global scoped variable $global:GSUserCache for fast lookups in scripts.
 
    .DESCRIPTION
    Syncs your GS Users to a hashtable contained in the global scoped variable $global:GSUserCache for fast lookups in scripts.
 
    .PARAMETER Filter
    The filter to use with Get-GSUser to populate your UserCache with.
 
    Defaults to * (all users).
 
    If you'd like to limit to just Active (not suspended) users, use the following filter:
 
        "IsSuspended -eq '$false'"
 
    .PARAMETER Keys
    The user properties to use as keys in the Cache hash.
 
    Available values are:
    * PrimaryEmail
    * Id
    * Alias
 
    Defaults to all 3.
 
    .PARAMETER PassThru
    If $true, returns the hashtable as output
 
    .EXAMPLE
    Sync-GSUserCache -Filter 'IsSuspended=False'
 
    Fills the $global:GSUserCache hashtable with all active users using the default Keys.
    #>

    [CmdLetBinding()]
    Param (
        [parameter(Mandatory = $false, Position = 0)]
        [String[]]
        $Filter = @('*'),
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('PrimaryEmail','Id','Alias')]
        [String[]]
        $Keys = @('PrimaryEmail','Id','Alias'),
        [parameter(Mandatory = $false)]
        [Switch]
        $PassThru
    )
    Begin {
        $global:GSUserCache = @{}
    }
    Process {
        Write-Verbose "Syncing users to `$global:GSUserCache"
        Get-GSUser -Filter $Filter | ForEach-Object {
            if ($Keys -contains 'Id') {
                $global:GSUserCache[$_.Id] = $_
            }
            if ($Keys -contains 'PrimaryEmail') {
                $global:GSUserCache[$_.PrimaryEmail] = $_
            }
            if ($Keys -contains 'Alias') {
                foreach ($email in $_.Emails.Address) {
                    if (-not ($global:GSUserCache.ContainsKey($email))) {
                        $global:GSUserCache[$email] = $_
                    }
                }
            }
        }
    }
    End {
        if ($PassThru) {
            return $global:GSUserCache
        }
    }
}

Export-ModuleMember -Function 'Sync-GSUserCache'

function Update-GSUser {
    <#
    .SYNOPSIS
    Updates a user
 
    .DESCRIPTION
    Updates a user
 
    .PARAMETER User
    The primary email or unique Id of the user to update
 
    .PARAMETER PrimaryEmail
    The new primary email for the user. The previous primary email will become an alias automatically
 
    .PARAMETER GivenName
    The new given (first) name for the user
 
    .PARAMETER FamilyName
    The new family (last) name for the user
 
    .PARAMETER FullName
    The new full name for the user
 
    .PARAMETER Password
    The new password for the user as a SecureString
 
    .PARAMETER ChangePasswordAtNextLogin
    If set, user will need to change their password on their next login
 
    .PARAMETER OrgUnitPath
    The new OrgUnitPath for the user
 
    .PARAMETER Suspended
    If set to $true or passed as a bare switch (-Suspended), user will be suspended. If set to $false, user will be unsuspended. If excluded, user's suspension status will remain as-is
 
    .PARAMETER Addresses
    The address objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]' object type. You can create objects of this type easily by using the function 'Add-GSUserAddress'
 
    .PARAMETER Emails
    The email objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]' object type. You can create objects of this type easily by using the function 'Add-GSUserEmail'
 
    .PARAMETER ExternalIds
    The externalId objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]' object type. You can create objects of this type easily by using the function 'Add-GSUserExternalId'
 
    .PARAMETER Organizations
    The organization objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]' object type. You can create objects of this type easily by using the function 'Add-GSUserOrganization'
 
    .PARAMETER Phones
    The phone objects of the user
 
    This parameter expects a 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhone[]' object type. You can create objects of this type easily by using the function 'Add-GSUserPhone'
 
    .PARAMETER IncludeInGlobalAddressList
    Indicates if the user's profile is visible in the G Suite global address list when the contact sharing feature is enabled for the domain. For more information about excluding user profiles, see the administration help center: http://support.google.com/a/bin/answer.py?answer=1285988
 
    .PARAMETER IpWhitelisted
    If true, the user's IP address is white listed: http://support.google.com/a/bin/answer.py?answer=60752
 
    .PARAMETER IsAdmin
    If true, the user will be made a SuperAdmin. If $false, the user will have SuperAdmin privileges revoked.
 
    Requires confirmation.
 
    .PARAMETER CustomSchemas
    Custom user attribute values to add to the user's account. This parameter only accepts a hashtable where the keys are Schema Names and the value for each key is another hashtable, i.e.:
 
        Update-GSUser -User john.smith@domain.com -CustomSchemas @{
            schemaName1 = @{
                fieldName1 = $fieldValue1
                fieldName2 = $fieldValue2
            }
            schemaName2 = @{
                fieldName3 = $fieldValue3
            }
        }
 
    If you need to CLEAR a custom schema value, simply pass $null as the value(s) for the fieldName in the hashtable, i.e.:
 
        Update-GSUser -User john.smith@domain.com -CustomSchemas @{
            schemaName1 = @{
                fieldName1 = $null
                fieldName2 = $null
            }
            schemaName2 = @{
                fieldName3 = $null
            }
        }
 
    The Custom Schema and it's fields **MUST** exist prior to updating these values for a user otherwise it will return an error.
 
    .EXAMPLE
    Update-GSUser -User john.smith@domain.com -PrimaryEmail johnathan.smith@domain.com -GivenName Johnathan -Suspended:$false
 
    Updates user john.smith@domain.com with a new primary email of "johnathan.smith@domain.com", sets their Given Name to "Johnathan" and unsuspends them. Their previous primary email "john.smith@domain.com" will become an alias on their account automatically
    #>

    [cmdletbinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
    Param
    (
        [parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias("Id", "UserKey", "Mail")]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $User,
        [parameter(Mandatory = $false)]
        [String]
        $PrimaryEmail,
        [parameter(Mandatory = $false)]
        [String]
        $GivenName,
        [parameter(Mandatory = $false)]
        [String]
        $FamilyName,
        [parameter(Mandatory = $false)]
        [String]
        $FullName,
        [parameter(Mandatory = $false)]
        [SecureString]
        $Password,
        [parameter(Mandatory = $false)]
        [Switch]
        $ChangePasswordAtNextLogin,
        [parameter(Mandatory = $false)]
        [String]
        $OrgUnitPath,
        [parameter(Mandatory = $false)]
        [Switch]
        $Suspended,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserAddress[]]
        $Addresses,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserEmail[]]
        $Emails,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId[]]
        $ExternalIds,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization[]]
        $Organizations,
        [parameter(Mandatory = $false)]
        [Google.Apis.Admin.Directory.directory_v1.Data.UserPhone[]]
        $Phones,
        [parameter(Mandatory = $false)]
        [Switch]
        $IncludeInGlobalAddressList,
        [parameter(Mandatory = $false)]
        [Switch]
        $IpWhitelisted,
        [parameter(Mandatory = $false)]
        [Switch]
        $IsAdmin,
        [parameter(Mandatory = $false)]
        [ValidateScript( {
                $hash = $_
                foreach ($schemaName in $hash.Keys) {
                    if ($hash[$schemaName].GetType().Name -ne 'Hashtable') {
                        throw "The CustomSchemas parameter only accepts a hashtable where the value of the top-level keys must also be a hashtable. The key '$schemaName' has a value of type '$($hash[$schemaName].GetType().Name)'"
                        $valid = $false
                    }
                    else {
                        $valid = $true
                    }
                }
                $valid
            })]
        [Hashtable]
        $CustomSchemas
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
    }
    Process {
        foreach ($U in $User) {
            try {
                if ($U -ceq 'me') {
                    $U = $Script:PSGSuite.AdminEmail
                }
                elseif ($U -notlike "*@*.*") {
                    $U = "$($U)@$($Script:PSGSuite.Domain)"
                }
                Write-Verbose "Updating user '$U'"
                $userObj = Get-GSUser $U -Verbose:$false
                $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.User'
                $name = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserName'
                $nameUpdated = $false
                foreach ($prop in $PSBoundParameters.Keys | Where-Object {$body.PSObject.Properties.Name -contains $_ -or $name.PSObject.Properties.Name -contains $_}) {
                    switch ($prop) {
                        PrimaryEmail {
                            if ($PSBoundParameters[$prop] -notlike "*@*.*") {
                                $PSBoundParameters[$prop] = "$($PSBoundParameters[$prop])@$($Script:PSGSuite.Domain)"
                            }
                            $body.$prop = $PSBoundParameters[$prop]
                        }
                        GivenName {
                            $name.$prop = $PSBoundParameters[$prop]
                            $nameUpdated = $true
                        }
                        FamilyName {
                            $name.$prop = $PSBoundParameters[$prop]
                            $nameUpdated = $true
                        }
                        FullName {
                            $name.$prop = $PSBoundParameters[$prop]
                            $nameUpdated = $true
                        }
                        Password {
                            $body.Password = (New-Object PSCredential "user", $Password).GetNetworkCredential().Password
                        }
                        CustomSchemas {
                            $schemaDict = New-Object 'System.Collections.Generic.Dictionary`2[[System.String],[System.Collections.Generic.IDictionary`2[[System.String],[System.Object]]]]'
                            foreach ($schemaName in $CustomSchemas.Keys) {
                                $fieldDict = New-Object 'System.Collections.Generic.Dictionary`2[[System.String],[System.Object]]'
                                $schemaFields = $CustomSchemas[$schemaName]
                                $schemaFields.Keys | ForEach-Object {
                                    $fieldDict.Add($_, $schemaFields[$_])
                                }
                                $schemaDict.Add($schemaName, $fieldDict)
                            }
                            $body.CustomSchemas = $schemaDict
                        }
                        Emails {
                            $emailList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserEmail]'
                            foreach ($email in $Emails) {
                                $emailList.Add($email)
                            }
                            $body.Emails = $emailList
                        }
                        ExternalIds {
                            $extIdList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserExternalId]'
                            foreach ($extId in $ExternalIds) {
                                $extIdList.Add($extId)
                            }
                            $body.ExternalIds = $extIdList
                        }
                        Organizations {
                            $orgList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserOrganization]'
                            foreach ($organization in $Organizations) {
                                $orgList.Add($organization)
                            }
                            $body.Organizations = $orgList
                        }
                        Phones {
                            $phoneList = New-Object 'System.Collections.Generic.List`1[Google.Apis.Admin.Directory.directory_v1.Data.UserPhone]'
                            foreach ($phone in $Phones) {
                                $phoneList.Add($phone)
                            }
                            $body.Phones = $phoneList
                        }
                        IsAdmin {
                            if ($userObj.IsAdmin -eq $PSBoundParameters[$prop]) {
                                Write-Verbose "User '$U' already has IsAdmin set to '$($userObj.IsAdmin)'"
                            }
                            else {
                                if ($PSCmdlet.ShouldProcess("Updating user '$U' to IsAdmin '$($PSBoundParameters[$prop])'")) {
                                    Write-Verbose "Updating user '$U' to IsAdmin '$($PSBoundParameters[$prop])'"
                                    $adminBody = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserMakeAdmin' -Property @{
                                        Status = $PSBoundParameters[$prop]
                                    }
                                    $request = $service.Users.MakeAdmin($adminBody, $userObj.Id)
                                    $request.Execute()
                                }
                            }
                        }
                        Default {
                            $body.$prop = $PSBoundParameters[$prop]
                        }
                    }
                }
                if ($nameUpdated) {
                    $body.Name = $name
                }
                $request = $service.Users.Update($body, $userObj.Id)
                $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru
            }
            catch {
                if ($ErrorActionPreference -eq 'Stop') {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
                else {
                    Write-Error $_
                }
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSUser'

function Update-GSUserPhoto {
    <#
    .SYNOPSIS
    Updates the photo for the specified user
 
    .DESCRIPTION
    Updates the photo for the specified user
 
    .PARAMETER User
    The primary email or UserID of the user who you are trying to update the photo for. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config.
 
    .PARAMETER Path
    The path of the photo that you would like to update the user with.
 
    .EXAMPLE
    Update-GSUserPhoto -User me -Path .\myphoto.png
 
    Updates the Google user photo of the AdminEmail with the image at the specified path
    #>

    [cmdletbinding()]
    Param
    (
        [parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)]
        [Alias("PrimaryEmail","UserKey","Mail")]
        [ValidateNotNullOrEmpty()]
        [String]
        $User,
        [parameter(Mandatory = $true,Position = 1)]
        [ValidateScript({Test-Path $_})]
        [String]
        $Path
    )
    Begin {
        $serviceParams = @{
            Scope       = 'https://www.googleapis.com/auth/admin.directory.user'
            ServiceType = 'Google.Apis.Admin.Directory.directory_v1.DirectoryService'
        }
        $service = New-GoogleService @serviceParams
        $Path = (Resolve-Path $Path).Path
    }
    Process {
        try {
            if ($User -ceq 'me') {
                $User = $Script:PSGSuite.AdminEmail
            }
            elseif ($User -notlike "*@*.*") {
                $User = "$($User)@$($Script:PSGSuite.Domain)"
            }
            $mimeType = Get-MimeType -File $Path
            $photoData = [System.Convert]::ToBase64String((Get-Content $Path -Encoding Byte)) | Convert-Base64 -From Base64String -To WebSafeBase64String
            $body = New-Object 'Google.Apis.Admin.Directory.directory_v1.Data.UserPhoto' -Property @{
                PhotoData = $photoData
                MimeType = $mimeType
            }
            Write-Verbose "Updating the photo for User '$User' with file '$Path'"
            $request = $service.Users.Photos.Update($body,$User)
            $request.Execute()
        }
        catch {
            if ($ErrorActionPreference -eq 'Stop') {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            else {
                Write-Error $_
            }
        }
    }
}
Export-ModuleMember -Function 'Update-GSUserPhoto'


Import-GoogleSDK

if ($global:PSGSuiteKey -and $MyInvocation.BoundParameters['Debug']) {
    $prevDebugPref = $DebugPreference
    $DebugPreference = "Continue"
    Write-Debug "`$global:PSGSuiteKey is set to a $($global:PSGSuiteKey.Count * 8)-bit key!"
    $DebugPreference = $prevDebugPref
}

$aliasHash = @{
    'Get-GSCalendarResourceList'        = 'Get-GSResourceList'
    'Switch-PSGSuiteDomain'             = 'Switch-PSGSuiteConfig'
    'Get-GSUserSchemaInfo'              = 'Get-GSUserSchema'
    'Get-GSUserLicenseInfo'             = 'Get-GSUserLicense'
    'Get-GSGmailMessageInfo'            = 'Get-GSGmailMessage'
    'New-GSCalendarResource'            = 'New-GSResource'
    'Update-GSCalendarResource'         = 'Update-GSResource'
    'Get-GSShortURLInfo'                = 'Get-GSShortURL'
    'Move-GSGmailMessageToTrash'        = 'Remove-GSGmailMessage'
    'Remove-GSGmailMessageFromTrash'    = 'Restore-GSGmailMessage'
    'Get-GSGmailFilterList'             = 'Get-GSGmailFilter'
    'Get-GSGmailLabelList'              = 'Get-GSGmailLabel'
    'Get-GSDriveFileInfo'               = 'Get-GSDriveFile'
    'Get-GSTeamDrivesList'              = 'Get-GSTeamDrive'
    'Add-GSDriveFilePermissions'        = 'Add-GSDrivePermission'
    'Get-GSDriveFilePermissionsList'    = 'Get-GSDrivePermission'
    'Get-GSGroupList'                   = 'Get-GSGroup'
    'Get-GSGroupMemberList'             = 'Get-GSGroupMember'
    'Get-GSOrgUnitList'                 = 'Get-GSOrganizationalUnit'
    'Get-GSOU'                          = 'Get-GSOrganizationalUnit'
    'Get-GSOrganizationalUnitList'      = 'Get-GSOrganizationalUnit'
    'Get-GSOrgUnit'                     = 'Get-GSOrganizationalUnit'
    'Get-GSMobileDeviceList'            = 'Get-GSMobileDevice'
    'Get-GSDataTransferApplicationList' = 'Get-GSDataTransferApplication'
    'Get-GSResourceList'                = 'Get-GSResource'
    'Get-GSUserASPList'                 = 'Get-GSUserASP'
    'Get-GSUserList'                    = 'Get-GSUser'
    'Get-GSUserSchemaList'              = 'Get-GSUserSchema'
    'Get-GSUserTokenList'               = 'Get-GSUserToken'
    'Get-GSUserLicenseList'             = 'Get-GSUserLicense'
    'Update-GSSheetValue'               = 'Export-GSSheet'
    'Export-PSGSuiteConfiguration'      = 'Set-PSGSuiteConfig'
    'Import-PSGSuiteConfiguration'      = 'Get-PSGSuiteConfig'
    'Set-PSGSuiteDefaultDomain'         = 'Switch-PSGSuiteConfig'
    'Get-GSGmailDelegates'              = 'Get-GSGmailDelegate'
}

foreach ($key in $aliasHash.Keys) {
    try {
        New-Alias -Name $key -Value $aliasHash[$key] -Force
    }
    catch {
        Write-Error "[ALIAS: $($key)] $($_.Exception.Message.ToString())"
    }
}

Export-ModuleMember -Alias '*'

if (!(Test-Path (Join-Path "~" ".scrthq"))) {
    New-Item -Path (Join-Path "~" ".scrthq") -ItemType Directory -Force | Out-Null
}

if ($PSVersionTable.ContainsKey('PSEdition') -and $PSVersionTable.PSEdition -eq 'Core' -and !$Global:PSGSuiteKey -and !$IsWindows) {
    if (!(Test-Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt"))) {
        Write-Warning "CoreCLR does not support DPAPI encryption! Setting a basic AES key to prevent errors. Please create a unique key as soon as possible as this will only obfuscate secrets from plain text in the Configuration, the key is not secure as is. If you would like to prevent this message from displaying in the future, run the following command:

Block-CoreCLREncryptionWarning
"

    }
    $Global:PSGSuiteKey = [Byte[]]@(1..16)
    $ConfigScope = "User"
}

if ($Global:PSGSuiteKey -is [System.Security.SecureString]) {
    $Method = "SecureString"
    if (!$ConfigScope) {
        $ConfigScope = "Machine"
    }
}
elseif ($Global:PSGSuiteKey -is [System.Byte[]]) {
    $Method = "AES Key"
    if (!$ConfigScope) {
        $ConfigScope = "Machine"
    }
}
else {
    $Method = "DPAPI"
    $ConfigScope = "User"
}

Add-MetadataConverter -Converters @{
    [SecureString] = {
        $encParams = @{}
        if ($Global:PSGSuiteKey -is [System.Byte[]]) {
            $encParams["Key"] = $Global:PSGSuiteKey
        }
        elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) {
            $encParams["SecureKey"] = $Global:PSGSuiteKey
        }
        'Secure "{0}"' -f (ConvertFrom-SecureString $_ @encParams)
    }
    "Secure" = {
        param([string]$String)
        $encParams = @{}
        if ($Global:PSGSuiteKey -is [System.Byte[]]) {
            $encParams["Key"] = $Global:PSGSuiteKey
        }
        elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) {
            $encParams["SecureKey"] = $Global:PSGSuiteKey
        }
        ConvertTo-SecureString $String @encParams
    }
}
try {
    $confParams = @{
        Scope = $ConfigScope
    }
    if ($ConfigName) {
        $confParams["ConfigName"] = $ConfigName
        $Script:ConfigName = $ConfigName
    }
    try {
        Get-PSGSuiteConfig @confParams -ErrorAction Stop
    }
    catch {
        if (Test-Path "$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml") {
            Get-PSGSuiteConfig -Path "$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml" -ErrorAction Stop
            Write-Warning "No Configuration.psd1 found at scope '$ConfigScope'; falling back to legacy XML. If you would like to convert your legacy XML to the newer Configuration.psd1, run the following command:

Get-PSGSuiteConfig -Path '$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml' -PassThru | Set-PSGSuiteConfig
"

        }
        else {
            Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved."
        }
    }
}
catch {
    Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved."
}