UMN-Google.psm1

###############
# Module for interacting with Google API
# More details found at https://developers.google.com/sheets/ and https://developers.google.com/drive/
#
###############

###
# Copyright 2017 University of Minnesota, Office of Information Technology

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with Foobar. If not, see <http://www.gnu.org/licenses/>.
###

#region Dependancies

function ConvertTo-Base64URL
{
    <#
        .Synopsis
            convert text or byte array to URL friendly Base64

        .DESCRIPTION
            Used for preparing the JWT token to a proper format.
        
        .PARAMETER bytes
            The bytes to be converted

        .PARAMETER text
            The text to be converted

        .EXAMPLE
            ConvertTo-Base64URL -text $headerJSON

        .EXAMPLE
            ConvertTo-Base64URL -Bytes $rsa.SignData($toSign,"SHA256")
    #>

    param
    (
        [Parameter(ParameterSetName='Bytes')]
        [System.Byte[]]$Bytes,
        
        [Parameter(ParameterSetName='String')]
        [string]$text
    )

    if($Bytes){$base = $Bytes}
    else{$base =  [System.Text.Encoding]::UTF8.GetBytes($text)}
    $base64Url = [System.Convert]::ToBase64String($base)
    $base64Url = $base64Url.Split('=')[0]
    $base64Url = $base64Url.Replace('+', '-')
    $base64Url = $base64Url.Replace('/', '_')
    $base64Url
}

#endregion

#region oAuth 2.0

function Get-GOAuthTokenService
{
    <#
        .Synopsis
            Get google auth 2.0 token for a service account

        .DESCRIPTION
            This is used in server-server OAuth token generation
        
        .PARAMETER certPath
            Local or network path to .p12 used to sign the JWT token

        .PARAMETER certPswd
            Password to access the private key in the .p12

        .PARAMETER iss
            This is the Google Service account address

        .PARAMATER scope
            The API scopes to be included in the request. Space delimited, "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive"
                
        .EXAMPLE
            Get-GOAuthTokenService -scope "https://www.googleapis.com/auth/spreadsheets" -certPath "C:\users\$env:username\Desktop\googleSheets.p12" -certPswd 'notasecret' -iss "serviceAccount@googleProjectName.iam.gserviceaccount.com"

    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$certPath,

        [Parameter(Mandatory)]
        [string]$certPswd,

        [Parameter(Mandatory)]
        [string]$iss,
        
        [Parameter(Mandatory)]
        [string]$scope
    )

    Begin
    {
        # build JWT header
        $headerJSON = [Ordered]@{
            alg = "RS256"
            typ = "JWT"
        } | ConvertTo-Json -Compress
        $headerBase64 = ConvertTo-Base64URL -text $headerJSON
    }
    Process
    {        
        # Build claims for JWT
        $now = (Get-Date).ToUniversalTime()
        $iat = [Math]::Floor([decimal](Get-Date($now) -UFormat "%s"))
        $exp = [Math]::Floor([decimal](Get-Date($now.AddMinutes(59)) -UFormat "%s")) 
        $aud = "https://www.googleapis.com/oauth2/v4/token"
        $claimsJSON = [Ordered]@{
            iss = $iss
            scope = $scope
            aud = $aud
            exp = $exp
            iat = $iat
        } | ConvertTo-Json -Compress

        $claimsBase64 = ConvertTo-Base64URL -text $claimsJSON

        ################# Create JWT
        # Prep JWT certificate signing
        $googleCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, $certPswd,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable ) 
        $rsaPrivate = $googleCert.PrivateKey 
        $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider 
        $null = $rsa.ImportParameters($rsaPrivate.ExportParameters($true))
        
        # Signature is our base64urlencoded header and claims, delimited by a period.
        $toSign = [System.Text.Encoding]::UTF8.GetBytes($headerBase64 + "." + $claimsBase64)
        $signature = ConvertTo-Base64URL -Bytes $rsa.SignData($toSign,"SHA256") ## this needs to be converted back to regular text
        
        # Build request
        $jwt = $headerBase64 + "." + $claimsBase64 + "." + $signature
        $fields = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion='+$jwt

        # Fetch token
        $response = Invoke-RestMethod -Uri "https://www.googleapis.com/oauth2/v4/token" -Method Post -Body $fields -ContentType "application/x-www-form-urlencoded"

    }
    End
    {
        return $response.access_token
    }
}

function Get-GOAuthTokenUser
{
    <#
        .Synopsis
            Get Valid OAuth Token.
        
        .DESCRIPTION
            The access token is good for an hour, the refresh token is mostly permanent and can be used to get a new access token without having to reauthenticate.
        
        .PARAMETER appKey
            The google project App Key

        .PARAMETER appSecret
            The google project application secret

        .PARAMETER projectID
            The google project ID

        .PARAMETER redirectUri
            An https project redirect. Can be anything as long as https

        .PARAMETER refreshToken
            A refresh token if refreshing

        .PARAMATER scope
            The API scopes to be included in the request. Space delimited, "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive"
        
        .EXAMPLE
            Get-GOAuthTokenUser -appKey $appKey -appSecret $appSecret -projectID $projectID -redirectUri $redirectUri -scope $scope
                
        .EXAMPLE
            Get-GOAuthTokenUser -appKey $appKey -appSecret $appSecret -projectID $projectID -redirectUri $redirectUri -scope $scope -refreshToken $refreshToken
            
        .NOTES
            Requires GUI with Internet Explorer to get first token.
    #>

    [CmdletBinding()]
    [OutputType([array])]
    Param
    (
        [Parameter(Mandatory)]
        [string]$appKey,

        [Parameter(Mandatory)]
        [string]$appSecret,
        
        [Parameter(Mandatory)]
        [string]$projectID,
        
        [Parameter(Mandatory)]
        [string]$redirectUri,

        [string]$refreshToken,

        [Parameter(Mandatory)]
        [string]$scope

    )

    Begin
    {
        $requestUri = "https://accounts.google.com/o/oauth2/token"
    }
    Process
    {

        if(!($refreshToken))
        { 
            ### Get the authorization code - IE Popup and user interaction section
            $auth_string = "https://accounts.google.com/o/oauth2/auth?scope=$scope&response_type=code&redirect_uri=$redirectUri&client_id=$appKey&access_type=offline&approval_prompt=force"
            $ie = New-Object -comObject InternetExplorer.Application
            $ie.visible = $true
            $null = $ie.navigate($auth_string)

            #Wait for user interaction in IE, manual approval
            do{Start-Sleep 1}until($ie.LocationURL -match 'code=([^&]*)')
            $null = $ie.LocationURL -match 'code=([^&]*)'
            $authorizationCode = $matches[1]
            $null = $ie.Quit()

            # exchange the authorization code for a refresh token and access token
            $requestBody = "code=$authorizationCode&client_id=$appKey&client_secret=$appSecret&grant_type=authorization_code&redirect_uri=$redirectUri"
 
            $response = Invoke-RestMethod -Method Post -Uri $requestUri -ContentType "application/x-www-form-urlencoded" -Body $requestBody

            $props = @{
                accessToken = $response.access_token
                refreshToken = $response.refresh_token
            }
        }

        else
        { 
            # Exchange the refresh token for new tokens
            $requestBody = "refresh_token=$refreshToken&client_id=$appKey&client_secret=$appSecret&grant_type=refresh_token"
 
            $response = Invoke-RestMethod -Method Post -Uri $requestUri -ContentType "application/x-www-form-urlencoded" -Body $requestBody
            $props = @{
                accessToken = $response.access_token
                refreshToken = $refreshToken
            }
        }
        
    }
    End
    {
        return new-object psobject -Property $props
    }
}

#endregion

#region Permissions for Google Drive files

function Get-GFilePermissions
{
    <#
        .Synopsis
            Get Permissions on Google Drive File

        .DESCRIPTION
            Get Permission ID list on Google File

        .PARAMETER accessToken
            OAuth Access Token for authorization.
                  
        .PARAMETER fileID
            The fileID to query. This is returned when a new file is created.

        .EXAMPLE
            Get-GFilePermissions -fileID 'String of File ID'
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        #[Alias("spreadSheetID")]
        [Parameter(Mandatory)]
        [string]$fileID
    )

    Begin
    {
        $uri = "https://www.googleapis.com/drive/v3/files/$fileID/permissions"
        $headers = @{"Authorization"="Bearer $accessToken"}
    }

    Process
    {
        Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
    }
    End{}
}

function Move-GFile
{
    <#
        .Synopsis
            Change parent folder metadata

        .DESCRIPTION
            A function to change parent folder metadata of a file.

        .PARAMETER accessToken
            OAuth Access Token for authorization.
                  
        .PARAMETER fileID
            The fileID to move.

        .PARAMETER folderID
            The fileID of the new parent folder.

        .PARAMETER parentFolderID
            The fileID of the parentFolder. Optional parameter. root (My Drive) is assumed if not specified.

        .EXAMPLE
            MoveGFile -fileID 'String of File ID' -folderID 'String of folder's File ID'
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        #[Alias("spreadSheetID")]
        [Parameter(Mandatory)]
        [string]$fileID,

        [Parameter(Mandatory)]
        [string]$folderID,

        [string]$parentFolderID='root'
    )

    Begin
    {
        $uriAdd = "https://www.googleapis.com/drive/v3/files/$fileID"+"?removeParents=$parentFolderID"
        $uriRemove = "https://www.googleapis.com/drive/v3/files/$fileID"+"?addParents=$folderID"
        $headers = @{"Authorization"="Bearer $accessToken"}
    }

    Process
    {
        Invoke-RestMethod -Method patch -Uri $uriAdd -Headers $headers

        Invoke-RestMethod -Method patch -Uri $uriRemove -Headers $headers
    }
    End{}
}

function Remove-GFilePermissions
{
    <#
        .Synopsis
            Remove Permissions on Google Drive File

        .DESCRIPTION
            Remove Permission ID list on Google File

        .PARAMETER accessToken
            OAuth Access Token for authorization.
                  
        .PARAMETER fileID
            The fileID to query. This is returned when a new file is created.

        .PARAMETER permissionsID
            The permission ID to be removed. See Get-GFilePermissions

        .EXAMPLE
            Remove-GFilePermissions -fileID 'String of File ID' -accessToken $accessToken -permissionID 'ID of the permission'

        .NOTES
            A successfull removal returns no body data.
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        #[Alias("spreadSheetID")]
        [Parameter(Mandatory)]
        [string]$fileID,
        
        [Parameter(Mandatory)]
        [string]$permissionID

    )

    Begin
    {
        $uri = "https://www.googleapis.com/drive/v3/files/$fileId/permissions/$permissionId"
        $headers = @{"Authorization"="Bearer $accessToken"}
    }

    Process
    {
        Invoke-RestMethod -Method Delete -Uri $uri -Headers $headers
    }
    End{}
}

function Set-GFilePermissions
{
    <#
        .Synopsis
            Set Permissions on Google File

        .DESCRIPTION
            For use with any google drive file ID

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER emailAddress
            Email address of the user or group to grant permissions to
        
        .PARAMETER fileID
            The fileID to apply permissions to.

        .PARAMETER role
            Role to assign, select from 'writer','reader','commenter'

        .PARAMETER sendNotificationEmail
            Boolean response on sending email notification.

        .PARAMETER type
            This refers to the emailAddress, is it a user or a group

        .EXAMPLE
            set-GFilePermissions -emailAddress 'user@email.com' -role writer -sheetID $sheetID -type user

        .NOTES
            Requires drive and drive.file API scope.
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,
        
        [Parameter(Mandatory)]
        [string]$emailAddress,

        #[Alias("spreadhSheetID")]
        [Parameter(Mandatory)]
        [string]$fileID,
        
        [ValidateSet('writer','reader','commenter')]
        [string]$role = "writer",
        
        [ValidateSet($true,$false)]
        [boolean]$sendNotificationEmail = $false,

        [ValidateSet('user','group')]
        [string]$type
    )

    Begin{
        $json = @{emailAddress=$emailAddress;type=$type;role=$role} | ConvertTo-Json
        $ContentType = "application/json"
        $uri = "https://www.googleapis.com/drive/v3/files/$fileID/permissions/?sendNotificationEmail=$sendNotificationEmail"
        $headers = @{"Authorization"="Bearer $accessToken"}
    }
    Process
    {
        Invoke-RestMethod -Method post -Uri $uri -Body $json -ContentType $ContentType -Headers $headers
    }
    End{}
}

function Update-GFilePermissions
{
    <#
        .Synopsis
            Update Permissions on Google File

        .DESCRIPTION
            Update Permissions on Google File
     
        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER fileID
            The sheetID to apply permissions to. This is returned when a new sheet is created or use Get-GSheetID

        .PARAMETER permissionID
            The permission ID of the entiry with permissions. Sett Get-GFilePermissions to get a lsit
        
        .PARAMETER role
            Role to assign, select from 'writer','reader','commenter','Owner','Organizer'
        
        .PARAMETER supportTeamDrives
            Boolean for TeamDrive Support

        .PARAMETER transferOwnership
            Update ownership of file to permission ID

        .EXAMPLE
            Update-GFilePermissions -emailAddress 'user@email.com' -role writer -fileID $sheetID -permissionID 'ID of the permission'

        .NOTES
            This is usefull for changing ownership. You cannot change ownership from non-domain to domain.
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,
        
        #[Alias("spreadSheetID")]
        [Parameter(Mandatory)]
        [string]$fileID,
        
        [Parameter(Mandatory)]
        [string]$permissionID,

        [ValidateSet('writer','reader','commenter','owner','organizer')]
        [string]$role = "writer",

        [ValidateSet($true,$false)]
        [string]$supportTeamDrives = $false,

        [ValidateSet($true,$false)]
        [string]$transferOwnership = $false
    )

    Begin{
        $json = @{role=$role} | ConvertTo-Json
        $ContentType = "application/json"
        $uri = "https://www.googleapis.com/drive/v3/files/$fileID/permissions/$permissionID/?transferOwnership=$transferOwnership"
        $headers = @{"Authorization"="Bearer $accessToken"}
    }
    Process
    {

        Invoke-RestMethod -Method Patch -Uri $uri -Body $json -ContentType $ContentType -Headers $headers
    }
    End{}
}

#endregion

#region Spread Sheet API Functions

function Add-GSheetSheet
{
    <#
        .Synopsis
            Add named sheets to an existing spreadSheet file.
    
        .DESCRIPTION
            This function will add a specified sheet name to a google spreadsheet.

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService
    
        .PARAMETER sheetName
            Name to apply to new sheet
            
        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        
        .EXAMPLE
            Add-GSheetSheet -accessToken $accessToken -sheetName 'NewName' -spreadSheetID $spreadSheetID
    
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,
        
        [Parameter(Mandatory)]
        [string]$sheetName,

        [Parameter(Mandatory)]
        [string]$spreadSheetID


    )

    Begin
    {
        $properties = @{requests=@(@{addSheet=@{properties=@{title=$sheetName}}})} |convertto-json -Depth 10
    }

    Process
    {
        $suffix = "$spreadSheetID" + ":batchUpdate"
        $uri = "https://sheets.googleapis.com/v4/spreadsheets/$suffix"
        Invoke-RestMethod -Method Post -Uri $uri -Body $properties -ContentType 'application/json' -Headers @{"Authorization"="Bearer $accessToken"}
    }
    End{}
}

function Clear-GSheetSheet
{
    <#
        .Synopsis
            Clear all data and leave formatting intact for a sheet from a spreadsheet based on sheetID

        .DESCRIPTION
            This function will delete data from a sheet

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER sheetName
            Name of sheet to clear

        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .EXAMPLE
            $pageID = 0 ## using pageID to differentiate from sheetID --
            In this case, index 0 is the actual sheetID per the API and will be cleared.

            $sheetID = ## the id number of the file/spreadsheet

            clear-gsheet -pageID $pageID -sheetID $sheetID -accessToken $accessToken

        
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,
        
        [Parameter(Mandatory)]
        [string]$sheetName,

        [Parameter(Mandatory)]
        [string]$spreadSheetID

    )

    Begin{}
    Process
    {
        $sheetID = Get-GSheetSheetID -accessToken $accessToken -spreadSheetID $spreadSheetID -sheetName $sheetName
        $properties = @{requests=@(@{updateCells=@{range=@{sheetId=$sheetID};fields="userEnteredValue"}})} |ConvertTo-Json -Depth 10
        $suffix = "$spreadSheetID" + ":batchUpdate"
        $uri = "https://sheets.googleapis.com/v4/spreadsheets/$suffix"
        Invoke-RestMethod -Method Post -Uri $uri -Body $properties -ContentType 'application/json' -Headers @{"Authorization"="Bearer $accessToken"}
    }
    End{}
}

function Get-GSheetData
{
    <#
        .Synopsis
            Basic function for retrieving data from a specific Sheet in a Google SpreadSheet.

        .DESCRIPTION
            Basic function for retrieving data from a specific Sheet in a Google SpreadSheet.

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService
        
        .PARAMETER cell
            Required switch for getting all data, or a subset of cells.

        .PARAMETER rangeA1
            Range in A1 notation https://msdn.microsoft.com/en-us/library/bb211395(v=office.12).aspx. The dimensions of the $values you put in MUST fit within this range
        
        .PARAMETER sheetName
            Name of sheet to data from

        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .PARAMETER valueRenderOption
            How the data is renderd. Switch option from formatted to unformatted data or 'formula'

        .EXAMPLE
            Get-GSheetData -accessToken $accessToken -cell 'AllData' -sheetName 'Sheet1' -spreadSheetID $spreadSheetID

        .EXAMPLE
            Get-GSheetData -accessToken $accessToken -cell 'Range' -rangeA1 'A0:F77' -sheetName 'Sheet1' -spreadSheetID $spreadSheetID
        
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,
        
        [Parameter(Mandatory)]
        [ValidateSet('AllData','Range')]
        [string]$cell,

        [string]$rangeA1,
        
        [Parameter(Mandatory)]
        [string]$sheetName,

        [Parameter(Mandatory)]
        [string]$spreadSheetID,

        [Parameter()]
        [ValidateSet('FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA')]
        [string]$valueRenderOption = "FORMATTED_VALUE"

    )

    Begin{}
    Process
    {
        $uri = "https://sheets.googleapis.com/v4/spreadsheets/$spreadSheetID/values/$sheetName"

        if($cell -eq "Range") {
            $uri += "!$rangeA1"
        }

        $uri += "?valueRenderOption=$valueRenderOption"

        $result = Invoke-RestMethod -Method GET -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"}
        
        # Formatting the returned data
        $sheet = $result.values
        $Rows = $sheet.Count
        $Columns = $sheet[0].Count
        $HeaderRow = 0
        $Header = $sheet[0]
        foreach ($Row in (($HeaderRow + 1)..($Rows-1))) { 
            $h = [Ordered]@{}
            foreach ($Column in 0..($Columns-1)) {
                if ($sheet[0][$Column].Length -gt 0) {
                    $Name = $Header[$Column]
                    $h.$Name = $Sheet[$Row][$Column]
                }
            }
            [PSCustomObject]$h
        }
    }
    End{}
}

function Get-GSheetSheetID
{
    <#
        .Synopsis
            Get ID of specific sheet in a Spreadsheet

        .DESCRIPTION
         Get ID of specific sheet in a Spreadsheet

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService
        
        .PARAMETER sheetName
            The name of the sheet

        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .EXAMPLE
            Get-GSheetSheetID -accessToken $accessToken -sheetName 'Sheet1' -spreadSheetID $spreadSheetID
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        [Parameter(Mandatory)]
        [string]$sheetName,

        [Parameter(Mandatory)]
        [string]$spreadSheetID
    )

    Begin{}
    Process
    {
        $spreadSheet = Get-GSheetSpreadSheetProperties -spreadSheetID $spreadSheetID -accessToken $accessToken
        ($spreadSheet.sheets.properties | Where-Object {$_.title -eq $sheetName}).sheetID
    }
    End{}
}

function Get-GSheetSpreadSheetID
{
    <#
        .Synopsis
            Get a spreadsheet ID.

        .DESCRIPTION
            Provide a case sensative file name to the function to get back the sheetID used in many other API calls.
            mimeTymes are split out to only retrieve spreadSheet IDs (no folders or other files)

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER fileName
            Name of file to retrive ID for. Case sensitive
        
        .EXAMPLE
            Get-GSheetSpreadSheetID -accessToken $accessToken -fileName 'Name of some file'
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,
        [Parameter(Mandatory)]

        [Alias("spreadSheetName")] 
        [string]$fileName
    )

    Begin{}
    Process
    {
        $uri = "https://www.googleapis.com/drive/v3/files?q=name%3D'$fileName'"
        $spreadSheetID = (((Invoke-RestMethod -Method get -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"}).files) | where {$_.mimetype -eq "application/vnd.google-apps.spreadsheet"}).id
        
        # Logic on multiple IDs being returned
        If ($spreadSheetID.count -eq 0){Write-Warning "There are no files matching the name $fileName"}
        If ($spreadSheetID.count -gt 1){Write-Warning "There are $($spreadSheetID.Count) files matching the provided name. Please investigate the following sheet IDs to verify which file you want.";return($spreadSheetID)}
        Else{return($spreadSheetID)}
    }
    End{}
}

function Get-GSheetSpreadSheetProperties
{
    <#
        .Synopsis
            Get the properties of a SpreadSheet

        .DESCRIPTION
            Get all properties of a SpreadSheet

        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .EXAMPLE
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        [Parameter(Mandatory)]
        [string]$spreadSheetID
    )

    Begin{}
    Process
    {
        $uri = "https://sheets.googleapis.com/v4/spreadsheets/$spreadSheetID"
        Invoke-RestMethod -Method GET -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"}
    }
    End{}
}

function Move-GSheetData
{
    <#
        .Synopsis
            Move data around between sheets in a spreadSheet.

        .DESCRIPTION
            This is a cut and paste between sheets in a spreadsheet.
            The function will find the row index based on search criteria, and copy/paste between the sheets provided.

        .PARAMETER accessToken
            oAuth access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER columnKey
            Row 0 column value. A key to search for data by. $columnKey = 'Column header'

        .PARAMETER currentSheetName
            Name of sheet to be searched, and copied from.

        .PARAMETER newSheetName
            Name of destination sheet data is to be written to.

        .PARAMETER query
            Value to be queried for in specified column (see columnKey) $query = 'Cell Content'
        
        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .EXAMPLE
            Move-GSheetData -accessToken $accessToken -columnKey 'Column Header -destinationSheetName 'New Sheet!' -query 'Cell Content' -sourceSheetName 'Old Sheet' -spreadSheetID $spreadSheetID
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        [Parameter(Mandatory)]
        [string]$columnKey,

        [Parameter(Mandatory)]
        [string]$destinationSheetName,
        
        [Parameter(Mandatory)]
        [string]$query,
        
        [Parameter(Mandatory)]
        [string]$sourceSheetName,

        [Parameter(Mandatory)]
        [string]$spreadSheetID
    )

    Begin{}

    Process
    {
        ## Query all data from sheet
        $data = Get-GSheetData -spreadSheetID $spreadSheetID -accessToken $accessToken -sheetName $sourceSheetName -cell AllData
        $destinationData = Get-GSheetData -spreadSheetID $spreadSheetID -accessToken $accessToken -sheetName $destinationSheetName -cell AllData

        ## Get row query belongs to
        $Index = (0..($data.count -1) | where {$Data[$_].$columnKey -eq $query})
        
        ## Sanity Check - is this the data?
        if (-not $Index) {
            write-Warning "$Query in $columnKey does not exist"
            return $null
            }

        Else {
        $rowIndex = $index[0] + 2    
        $startRow = $Index[0] + 1
        $destinationRow = ($destinationData).count + 2
        $destinationStartRow = ($destinationData).count + 1
        }

        ## Get sheet index ID numbers
        $allSheetProperties = (Get-GSheetSpreadSheetProperties -spreadSheetID $spreadSheetID -accessToken $accessToken).sheets.properties

        $srcSheetIndex = ($allSheetProperties | where {$_.title -eq $sourceSheetName}).sheetID
        $dstSheetIndex = ($allSheetProperties | where {$_.title -eq $destinationSheetName}).sheetID                                

        $method = 'POST'
        $uri = "https://sheets.googleapis.com/v4/spreadsheets/$spreadSheetID"+":batchUpdate"
        $ContentType = "application/json"
        
   
        ## cutPaste row to row
        $values = @{"cutPaste"=@{"source"=@{"sheetId"=$srcSheetIndex;"startRowIndex"=$startRow;"endRowIndex"=$rowIndex};"destination"=@{"sheetId"=$dstSheetIndex;"rowIndex"=$destinationRow};"pasteType"="PASTE_NORMAL"}}
        $JSON = @{"requests"=$values} |ConvertTo-Json -Depth 20
            
        
        
        Invoke-RestMethod -Method $method -Uri $uri -Body $json -ContentType $ContentType -Headers @{"Authorization"="Bearer $accessToken"}
        
    }
    
    End{}
}

function New-GSheetSpreadSheet
{
    <#
        .Synopsis
            Create a new Google SpreadSheet.
        
        .DESCRIPTION
            Create a new Google SpreadSheet.

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService
        
        .PARAMETER properties
            Alternatively, the properties that can be set are extensive. Cell color, formatting etc. If you use this you MUST include @{properties=@{title='mY sheet'}} |convertto-json
            at a minimum. More details at https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/create

        .PARAMETER title
            Use this in the simplest case to just create a new sheet with a Title/name

        .EXAMPLE
            Create-GSheet -properties $properties -accessToken $accessToken

        .EXAMPLE
            create-GSheet -title 'My sheet' -accessToken $accessToken

    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        [Parameter(ParameterSetName='properties')]
        [array]$properties,

        [Parameter(ParameterSetName='title')]
        [string]$title
    )

    Begin
    {
        If (!$properties)
            {
                $properties = @{properties=@{title=$title}} |convertto-json
            }

        $uri = "https://sheets.googleapis.com/v4/spreadsheets"
    }

    Process
    {
        Invoke-RestMethod -Method Post -Uri $uri -Body $properties -ContentType "application/json" -Headers @{"Authorization"="Bearer $accessToken"}
    }

    End{}
}

function Remove-GSheetSheet
{
    <#
        .Synopsis
            Removes a sheet from a spreadsheet based on sheetID

        .DESCRIPTION
            This function will delete an individual sheet.

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER sheetName
            Name of sheet to delete

        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .EXAMPLE
            Remove-GSheetSheet -accessToken $accessToken -sheetName 'Name to delete' -spreadSheetID $spreadSheetID
        
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        [Parameter(Mandatory)]
        [string]$sheetName,

        [Parameter(Mandatory)]
        [string]$spreadSheetID
    )

    Begin{}
    Process
    {
        $sheetID = Get-GSheetSheetID -accessToken $accessToken -sheetName $sheetName -spreadSheetID $spreadSheetID
        $properties = @{requests=@(@{deleteSheet=@{sheetId=$sheetID}})} |convertto-json -Depth 10
        $suffix = "$spreadSheetID" + ":batchUpdate"
        $uri = "https://sheets.googleapis.com/v4/spreadsheets/$suffix"
        $data = Invoke-RestMethod -Method Post -Uri $uri -Body $properties -ContentType "application/json" -Headers @{"Authorization"="Bearer $accessToken"}
    }
    End
    {
    return([array]$data)
    }
}

function Remove-GSheetSpreadSheet
{
    <#
        .Synopsis
            Delete a SpreadSheet

        .DESCRIPTION
            Uses the google File Drive API to delete a file.

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER fileID
            ID for the target file/spreadSheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .EXAMPLE
            Remove-GSheetSpreadSheet -accessToken $accessToken -spreadSheetID $spreadSheetID
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        #[Alias("spreadSheetID")]
        [Parameter(Mandatory)]
        [string]$fileID
    )

    Begin{}
    Process
    {
        $uri = "https://www.googleapis.com/drive/v3/files/$fileID"
        Invoke-RestMethod -Method Delete -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"}
    }
    End{}
}

function Set-GSheetColumnWidth
{
    <#
        .Synopsis
            Set the width of a column on a sheet

        .DESCRIPTION
            This function calls the bulk update method to set column dimensions to 'autoResize'.

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER numberOfColumns
            An optional parameter to specify how many columns to autosize. Default to 26

        .PARAMETER sheetName
            Name of sheet in spreadSheet

        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .EXAMPLE
            Set-GSheetColumnWidth -spreadSheetID $id -sheetName 'Sheet1' -accessToken $token -numberOfColumns ($property.count)
        
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        [string]$numberOfColumns = '26',
        
        [Parameter(Mandatory)]
        [string]$sheetName,

        [Parameter(Mandatory)]
        [string]$spreadSheetID
    )

    Begin
    {
        $sheetID = Get-GSheetSheetID -accessToken $accessToken -spreadSheetID $spreadSheetID -sheetName $sheetName
        $json = @{requests=@(@{autoResizeDimensions=@{dimensions=@{sheetId=$sheetID;dimension='COLUMNS';startIndex='0';endIndex='26'}}})} |ConvertTo-Json -Depth 20
        $suffix = "$spreadSheetID" + ":batchUpdate"
        $uri = "https://sheets.googleapis.com/v4/spreadsheets/$suffix"
    }

    Process
    {
        Invoke-RestMethod -Method Post -Uri $uri -Body $json -ContentType "application/json" -Headers @{"Authorization"="Bearer $accessToken"}
    }
    
    End{}
}

function Set-GSheetData
{
    <#
        .Synopsis
            Set values in sheet in specific cell locations or append data to a sheet

        .DESCRIPTION
            Set json data values on a sheet in specific cell locations or append data to a sheet

        .PARAMETER accessToken
            access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

        .PARAMETER append
            Switch option to append data. See rangeA1 if not appending

        .PARAMETER rangeA1
            Range in A1 notation https://msdn.microsoft.com/en-us/library/bb211395(v=office.12).aspx . The dimensions of the $values you put in MUST fit within this range

        .PARAMETER sheetName
            Name of sheet to set data in

        .PARAMETER spreadSheetID
            ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

        .PARAMETER valueInputOption
            Default to RAW. Optionally, you can specify if you want it processed as a formula and so forth.

        .PARAMETER values
            The values to write to the sheet. This should be an array list. Each list array represents one ROW on the sheet.

        .EXAMPLE
            Set-GSheetData -accessToken $accessToken -rangeA1 'A1:B2' -sheetName 'My Sheet' -spreadSheetID $spreadSheetID -values @(@("a","b"),@("c","D"))

        .EXAMPLE
            Set-GSheetData -accessToken $accessToken -append 'Append'-sheetName 'My Sheet' -spreadSheetID $spreadSheetID -values $arrayValues

    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [string]$accessToken,

        [Parameter(ParameterSetName='Append')]
        [switch]$append,

        [Parameter(ParameterSetName='set')]
        [string]$rangeA1,

        [Parameter(Mandatory)]
        [string]$sheetName,

        [Parameter(Mandatory)]
        [string]$spreadSheetID,

        [string]$valueInputOption = 'RAW',

        [Parameter(Mandatory)]
        [System.Collections.ArrayList]$values
    )

    Begin
    {
        if ($append)
            {
                $method = 'POST'
                $uri = "https://sheets.googleapis.com/v4/spreadsheets/$spreadSheetID/values/$sheetName"+":append?valueInputOption=$valueInputOption"
            }
        else
            {
                $method = 'PUT'
                $uri = "https://sheets.googleapis.com/v4/spreadsheets/$spreadSheetID/values/$sheetName!$rangeA1"+"?valueInputOption=$valueInputOption"
            }
    }

    Process
    {
        $json = @{values=$values} | ConvertTo-Json
        Invoke-RestMethod -Method $method -Uri $uri -Body $json -ContentType "application/json" -Headers @{"Authorization"="Bearer $accessToken"}     
    }

    End{}
}

#region Set-GSheetDropDownList
    function Set-GSheetDropDownList
    {
        <#
            .Synopsis
                Set Drop Down List Data validation on cells in a column

            .DESCRIPTION
                Set Drop Down List Data validation on cells in a column

            .PARAMETER accessToken
                access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService

            .PARAMETER columnIndex
                Index of column to update

            .PARAMETER startRowIndex
                Index of row to start updating
            
            .PARAMETER endRowIndex
                Index of last row to update

            .PARAMETER values
                List of string values that the use can chose from in an array. Google API only takes strings

            .PARAMETER inputMessage
                A message to show the user when adding data to the cell.

            .PARAMETER showCustomUi
                True if the UI should be customized based on the kind of condition. If true, $values will show a dropdown.

            .PARAMETER sheetName
                Name of sheet in spreadSheet

            .PARAMETER spreadSheetID
                ID for the target Spreadsheet. This is returned when a new sheet is created or use Get-GSheetSpreadSheetID

            .EXAMPLE Set-GSheetDropDownList -accessToken $accessToken -startRowIndex 1 -endRowIndex 10 -columnIndex 9 -sheetName 'Sheet1' -spreadSheetID $spreadSheetID -inputMessage "Must be one of 'Public','Private Restricted','Private, Highly-Restricted'" -values @('Public','Private Restricted','Private, Highly-Restricted')
                
            
        #>

        [CmdletBinding()]
        Param
        (
            [Parameter(Mandatory)]
            [string]$accessToken,
            
            [Parameter(Mandatory)]
            [int]$startRowIndex,

            [Parameter(Mandatory)]
            [int]$endRowIndex,

            [Parameter(Mandatory)]
            [int]$columnIndex,

            [Parameter(Mandatory)]
            [string]$sheetName,

            [Parameter(Mandatory)]
            [string]$spreadSheetID,

            [Parameter(Mandatory)]
            [string[]]$values,

            [string]$inputMessage,

            [boolean]$showCustomUi=$true

        )

        Begin
        {
            $sheetID = Get-GSheetSheetID -accessToken $accessToken -spreadSheetID $spreadSheetID -sheetName $sheetName
            $valueList = [Collections.ArrayList]@()
            foreach ($value in $values){$valueList.Add(@{userEnteredValue=$value})}
            $validation = @{
                setDataValidation = @{
                    range=@{sheetId = $sheetID;startRowIndex=$startRowIndex;endRowIndex=$endRowIndex;startColumnIndex=$columnIndex;endColumnIndex=($columnIndex+1)};
                    rule=@{
                        condition = @{
                            type= 'ONE_OF_LIST';
                            values=$valueList
                        };
                        inputMessage=$inputMessage;strict=$true;showCustomUi=$showCustomUi
                    }
                }
            }
            $json = @{requests=@($validation)} | ConvertTo-Json -Depth 20
            $suffix = "$spreadSheetID" + ":batchUpdate"
            $uri = "https://sheets.googleapis.com/v4/spreadsheets/$suffix"
            $json
            $uri
        }

        Process
        {
            Invoke-RestMethod -Method Post -Uri $uri -Body $json -ContentType "application/json" -Headers @{"Authorization"="Bearer $accessToken"}
        }
        
        End{}
    }
#endregion
#endregion

Export-ModuleMember -Function *
# SIG # Begin signature block
# MIIaxgYJKoZIhvcNAQcCoIIatzCCGrMCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUWn57v0fk9+rs6roXwvxLmHZx
# 7mmgghW3MIIEmTCCA4GgAwIBAgIPFojwOSVeY45pFDkH5jMLMA0GCSqGSIb3DQEB
# BQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQg
# TGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNV
# BAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJG
# aXJzdC1PYmplY3QwHhcNMTUxMjMxMDAwMDAwWhcNMTkwNzA5MTg0MDM2WjCBhDEL
# MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
# BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKjAoBgNVBAMT
# IUNPTU9ETyBTSEEtMSBUaW1lIFN0YW1waW5nIFNpZ25lcjCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBAOnpPd/XNwjJHjiyUlNCbSLxscQGBGue/YJ0UEN9
# xqC7H075AnEmse9D2IOMSPznD5d6muuc3qajDjscRBh1jnilF2n+SRik4rtcTv6O
# KlR6UPDV9syR55l51955lNeWM/4Og74iv2MWLKPdKBuvPavql9LxvwQQ5z1IRf0f
# aGXBf1mZacAiMQxibqdcZQEhsGPEIhgn7ub80gA9Ry6ouIZWXQTcExclbhzfRA8V
# zbfbpVd2Qm8AaIKZ0uPB3vCLlFdM7AiQIiHOIiuYDELmQpOUmJPv/QbZP7xbm1Q8
# ILHuatZHesWrgOkwmt7xpD9VTQoJNIp1KdJprZcPUL/4ygkCAwEAAaOB9DCB8TAf
# BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUjmstM2v0
# M6eTsxOapeAK9xI1aogwDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAwFgYD
# VR0lAQH/BAwwCgYIKwYBBQUHAwgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny
# bC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDA1BggrBgEF
# BQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20w
# DQYJKoZIhvcNAQEFBQADggEBALozJEBAjHzbWJ+zYJiy9cAx/usfblD2CuDk5oGt
# Joei3/2z2vRz8wD7KRuJGxU+22tSkyvErDmB1zxnV5o5NuAoCJrjOU+biQl/e8Vh
# f1mJMiUKaq4aPvCiJ6i2w7iH9xYESEE9XNjsn00gMQTZZaHtzWkHUxY93TYCCojr
# QOUGMAu4Fkvc77xVCf/GPhIudrPczkLv+XZX4bcKBUCYWJpdcRaTcYxlgepv84n3
# +3OttOe/2Y5vqgtPJfO44dXddZhogfiqwNGAwsTEOYnB9smebNd0+dmX+E/CmgrN
# Xo/4GengpZ/E8JIh5i15Jcki+cPwOoRXrToW9GOUEB1d0MYwggV3MIIEX6ADAgEC
# AhAT6ihwW/Ts7Qw2YwmAYUM2MA0GCSqGSIb3DQEBDAUAMG8xCzAJBgNVBAYTAlNF
# MRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJu
# YWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJv
# b3QwHhcNMDAwNTMwMTA0ODM4WhcNMjAwNTMwMTA0ODM4WjCBiDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4w
# HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVz
# dCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzO
# iZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwW
# IJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YU
# VD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1da
# t//O+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+
# UzeQc0PzMsNT79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/z
# JSZrM233bkf6c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLa
# qUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxb
# gtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9A
# qURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+
# eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwID
# AQABo4H0MIHxMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8DveAky1QaMB0GA1Ud
# DgQWBBRTeb9aqitKz1SA4dibwJ3ysgNmyzAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
# AQH/BAUwAwEB/zARBgNVHSAECjAIMAYGBFUdIAAwRAYDVR0fBD0wOzA5oDegNYYz
# aHR0cDovL2NybC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3Qu
# Y3JsMDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNl
# cnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAQEAk2X2N4OVD17Dghwf1nfnPIrA
# qgnw6Qsm8eDCanWhx3nJuVJgyCkSDvCtA9YJxHbf5aaBladG2oJXqZWSxbaPAyJs
# M3fBezIXbgfOWhRBOgUkG/YUBjuoJSQOu8wqdd25cEE/fNBjNiEHH0b/YKSR4We8
# 3h9+GRTJY2eR6mcHa7SPi8BuQ33DoYBssh68U4V93JChpLwt70ZyVzUFv7tGu25t
# N5m2/yOSkcZuQPiPKVbqX9VfFFOs8E9h6vcizKdWC+K4NB8m2XsZBWg/ujzUOAai
# 0+aPDuO0cW1AQsWEtECVK/RloEh59h2BY5adT3Xg+HzkjqnR8q2Ks4zHIc3C7zCC
# BawwggSUoAMCAQICEHJNXiAT1cKRQFXzfFSJVHEwDQYJKoZIhvcNAQELBQAwfDEL
# MAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQHEwlBbm4gQXJib3IxEjAQ
# BgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21tb24xJTAjBgNVBAMTHElu
# Q29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTcxMjE0MDAwMDAwWhcNMjAx
# MjEzMjM1OTU5WjCByzELMAkGA1UEBhMCVVMxDjAMBgNVBBEMBTU1NDU1MRIwEAYD
# VQQIDAlNaW5uZXNvdGExFDASBgNVBAcMC01pbm5lYXBvbGlzMRgwFgYDVQQJDA8x
# MDAgVW5pb24gU3QgU0UxIDAeBgNVBAoMF1VuaXZlcnNpdHkgb2YgTWlubmVzb3Rh
# MSQwIgYDVQQLDBtDb21wdXRlciBhbmQgRGV2aWNlIFN1cHBvcnQxIDAeBgNVBAMM
# F1VuaXZlcnNpdHkgb2YgTWlubmVzb3RhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
# MIIBCgKCAQEAwk6kLE9u+tWv0JUkIJSn5pWfa09g6cqFLucCXomNj9NYj8t+JfPn
# a3gC6LHv3OQAUDHOoC5H+8N3ea7qVGYIiwPRHzXOGqG/tVaiU5s5hG3vBhfRX8W1
# /2g4/hpgeXUzrxYn/2c5SOGGy0MU1ZJyUSFEdsjXHEV7HXK4qmFGV9RJxtiLZH1q
# UldCglxcj7zw0QnUdG6oAxpwTCeVp057/WXbnIR8a0gPse+y/new5+CBUGTAvrw6
# K2BrJQVsdIIVn/q+BbcZxh9PpeZfTtsi6lgkvy0bUWtl5sSpd75+hvw4Sl3HAaWZ
# toWN7LPmbDbbVRO2Arv4doh4Chod4wJ5xQIDAQABo4IB2DCCAdQwHwYDVR0jBBgw
# FoAUrjUjF///Bj2cUOCMJGUzHnAQiKIwHQYDVR0OBBYEFF4LEhElVUvT8n5txOJS
# NAczooSAMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoG
# CCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBmBgNVHSAEXzBdMFsGDCsGAQQB
# riMBBAMCATBLMEkGCCsGAQUFBwIBFj1odHRwczovL3d3dy5pbmNvbW1vbi5vcmcv
# Y2VydC9yZXBvc2l0b3J5L2Nwc19jb2RlX3NpZ25pbmcucGRmMEkGA1UdHwRCMEAw
# PqA8oDqGOGh0dHA6Ly9jcmwuaW5jb21tb24tcnNhLm9yZy9JbkNvbW1vblJTQUNv
# ZGVTaWduaW5nQ0EuY3JsMH4GCCsGAQUFBwEBBHIwcDBEBggrBgEFBQcwAoY4aHR0
# cDovL2NydC5pbmNvbW1vbi1yc2Eub3JnL0luQ29tbW9uUlNBQ29kZVNpZ25pbmdD
# QS5jcnQwKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3NwLmluY29tbW9uLXJzYS5vcmcw
# GQYDVR0RBBIwEIEOb2l0bXB0QHVtbi5lZHUwDQYJKoZIhvcNAQELBQADggEBAENR
# lesMKmBaZ0g68lttYEMtaPiz+DaNpOlXBs1gH66aghB1aP6iiRJcFVasGLUVFncd
# G1xbw503LTrBUc5PECMVDVF7KKCfHA1OeFV9vOWyvdVgbe3paDy1sj4CADO2D0gn
# xcGiZoFhEZiBkTvSsj4S3GXZEvoFHJxJLw2kvdLnzy0gH/b/b/yblwA1fKXw4loc
# UpDM6qTwM7SiKgkQ5W7/280EYu8BI6c8rpiJmqM1tZLcpswuavB00T52Y+ZZmz3t
# MMVgFHn9pFFltYr3s3bEek7I6pU8unISbiyQzxqhIUKaBi8hy8LgoY5UnGjX5jHs
# IvINzms+JX5Ity02sL0wggXrMIID06ADAgECAhBl4eLj1d5QRYXzJiSABeLUMA0G
# CSqGSIb3DQEBDQUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNl
# eTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1Qg
# TmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1
# dGhvcml0eTAeFw0xNDA5MTkwMDAwMDBaFw0yNDA5MTgyMzU5NTlaMHwxCzAJBgNV
# BAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFyYm9yMRIwEAYDVQQK
# EwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMSUwIwYDVQQDExxJbkNvbW1v
# biBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
# CgKCAQEAwKAvix56u2p1rPg+3KO6OSLK86N25L99MCfmutOYMlYjXAaGlw2A6O2i
# gTXrC/Zefqk+aHP9ndRnec6q6mi3GdscdjpZh11emcehsriphHMMzKuHRhxqx+85
# Jb6n3dosNXA2HSIuIDvd4xwOPzSf5X3+VYBbBnyCV4RV8zj78gw2qblessWBRyN9
# EoGgwAEoPgP5OJejrQLyAmj91QGr9dVRTVDTFyJG5XMY4DrkN3dRyJ59UopPgNwm
# ucBMyvxR+hAJEXpXKnPE4CEqbMJUvRw+g/hbqSzx+tt4z9mJmm2j/w2nP35MViPW
# Cb7hpR2LB8W/499Yqu+kr4LLBfgKCQIDAQABo4IBWjCCAVYwHwYDVR0jBBgwFoAU
# U3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFK41Ixf//wY9nFDgjCRlMx5w
# EIiiMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1UdJQQM
# MAoGCCsGAQUFBwMDMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNVHR8ESTBHMEWgQ6BB
# hj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQ2VydGlmaWNh
# dGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEEajBoMD8GCCsGAQUFBzAChjNo
# dHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQWRkVHJ1c3RDQS5j
# cnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZI
# hvcNAQENBQADggIBAEYstn9qTiVmvZxqpqrQnr0Prk41/PA4J8HHnQTJgjTbhuET
# 98GWjTBEE9I17Xn3V1yTphJXbat5l8EmZN/JXMvDNqJtkyOh26owAmvquMCF1pKi
# QWyuDDllxR9MECp6xF4wnH1Mcs4WeLOrQPy+C5kWE5gg/7K6c9G1VNwLkl/po9OR
# PljxKKeFhPg9+Ti3JzHIxW7LdyljffccWiuNFR51/BJHAZIqUDw3LsrdYWzgg4x0
# 6tgMvOEf0nITelpFTxqVvMtJhnOfZbpdXZQ5o1TspxfTEVOQAsp05HUNCXyhznlV
# Lr0JaNkM7edgk59zmdTbSGdMq8Ztuu6VyrivOlMSPWmay5MjvwTzuNorbwBv0DL+
# 7cyZBp7NYZou+DoGd1lFZN0jU5IsQKgm3+00pnnJ67crdFwfz/8bq3MhTiKOWEb0
# 4FT3OZVp+jzvaChHWLQ8gbCORgClaZq1H3aqI7JeRkWEEEp6Tv4WAVsr/i7LoXU7
# 2gOb8CAzPFqwI4Excdrxp0I4OXbECHlDqU4sTInqwlMwofmxeO4u94196qIqJQl+
# 8Sykl06VktqMux84Iw3ZQLH08J8LaJ+WDUycc4OjY61I7FGxCDkbSQf3npXeRFm0
# IBn8GiW+TRDk6J2XJFLWEtVZmhboFlBLoUlqHUCKu0QOhU/+AEOqnY98j2zRMYIE
# eTCCBHUCAQEwgZAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1JMRIwEAYDVQQH
# EwlBbm4gQXJib3IxEjAQBgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5Db21t
# b24xJTAjBgNVBAMTHEluQ29tbW9uIFJTQSBDb2RlIFNpZ25pbmcgQ0ECEHJNXiAT
# 1cKRQFXzfFSJVHEwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKEC
# gAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwG
# CisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFMUpa2rhL4TGsG/cCC/MeGhTL5IJ
# MA0GCSqGSIb3DQEBAQUABIIBAAvPb7DFBtmF0pIaWWs6SZAmxHyrITAoYNKdxkcJ
# N212kZDhbX6fI4QuokKlc0FwvFmVVeykNwair+CaFo10u0C4sBqBXvDPRWU5YXDT
# VjLAozrU8QE3QqwagFOXTp6rZigQ2vJ9+kR433lU5cHfH3s+yqHB4XGq/Udzh9AP
# JnSojWCWTUbYOrRbClR5SD0kLPNdH/IHXfD7CK5ln1wrWRNnsfeBAFi7GldueBJE
# IZwmmPZL2EThPmDuIGrn3vTbYDbiArnucMdgWBD2yj9A1X2+WM0k3Ip8fW56VolI
# Vb50QXkmj5tbP21zF/Jaaa7g5Fn06TmW1m8ehuDi6FRq966hggJDMIICPwYJKoZI
# hvcNAQkGMYICMDCCAiwCAQEwgakwgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJV
# VDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJV
# U1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0w
# GwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdAIPFojwOSVeY45pFDkH5jMLMAkG
# BSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
# BTEPFw0xODA0MTgyMTMxMTJaMCMGCSqGSIb3DQEJBDEWBBQfp63tfg962nTcBRYA
# W8UCSMjaKTANBgkqhkiG9w0BAQEFAASCAQB0J32RWomWNy0fWO4EDiSjCpINFhpg
# vIYKGVuQkVrAkzi4kTjaThvVuPQ7xFBZCkGzGRj2LSskVKXviO1K4Hy0y1JI16Y0
# oaTAqOrMYPpzpWNTswAFgu3adtyUzH3wpnN3uqUK5gZ9+KazGRaGNndW7uXU5lLo
# UMhL9w4HblApeBmtn10ImaBaOOk/W7QTpcgcq/NEJpbl9v5kPE2Bz4rQj5jCi55m
# ZpGR4bI05KFpRGRrMSNfw/EyJ0NIGfpdWbJOpDKQwWSEhDPfv6QJ9G/3241FssFY
# 3IyXlqknzGEt6JsC6bbKwircca0oum8DneU9LEddcKB/mQk0FsKMszeY
# SIG # End signature block