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 2024 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 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 #region Get-GOAuthTokenService function Get-GOAuthTokenService { <# .Synopsis Get google oAuth 2 token for a service account .DESCRIPTION This is used in server-server OAuth token generation This function will use a certificate to generate an RSA token that will be used to sign a JWT token which is needed to generate the access key. The certificate can be specified as file path and password to read the certificate from. It can also be specified as an object, such was when running in Automation that will return a certificate object The RSA token can also be specified directly if needed instead of generating it from a certificate .PARAMETER jsonPath Local or network path to JSON auth file generated from GCP Service Principal .PARAMETER certPath Local or network path to .p12 used to sign the JWT token, requires certPswd to also be specified .PARAMETER certPswd Password to access the private key in the .p12, requires certPath to also be specified .PARAMETER certObj Certificate object that will be used to sign the JWT token .PARAMETER RSA provide the System.Security.Cryptography.RSACryptoServiceProvider object directly that will be used to sign the JWT token .PARAMETER iss This is the Google Service account address .PARAMETER 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" Generates an access token using the given certificate file and password .EXAMPLE Get-GOAuthTokenService -rsa $rsaSecurityObject -scope "https://www.googleapis.com/auth/spreadsheets" -iss "serviceAccount@googleProjectName.iam.gserviceaccount.com" Generates an access token using the given rsa object .EXAMPLE Get-GOAuthTokenService -certObj $GoogleCert -scope "https://www.googleapis.com/auth/spreadsheets" -iss "serviceAccount@googleProjectName.iam.gserviceaccount.com" Generates an access token using the given certificate object #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$iss, [Parameter(Mandatory)] [string]$scope, [Parameter(Mandatory,ParameterSetName='CertificateFile')] [string]$certPath, [Parameter(Mandatory,ParameterSetName='CertificateFile')] [string]$certPswd, [Parameter(Mandatory,ParameterSetName='CertificateObject')] [System.Security.Cryptography.X509Certificates.X509Certificate2]$certObj, [Parameter(Mandatory,ParameterSetName='jsonFile')] [string]$jsonPath, [Parameter(Mandatory,ParameterSetName='RSA')] [System.Security.Cryptography.RSACryptoServiceProvider]$rsa ) Begin { # build JWT header $headerJSON = [Ordered]@{ alg = "RS256" typ = "JWT" } | ConvertTo-Json -Compress $headerBase64 = ConvertTo-Base64URL -text $headerJSON } Process { # Build claims for JWT $iat = [int64]([double]::Parse((get-date -date ([DateTime]::UtcNow) -uformat "%s"),[cultureinfo][system.threading.thread]::currentthread.currentculture)) $exp = $iat + 59*60 $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 switch ($PSCmdlet.ParameterSetName) { 'CertificateFile' { Write-Verbose "Assembling RSA object based on given certificate file and password" $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)) } 'CertificateObject' { Write-Verbose "Assembling RSA object based on given certificate object" $rsaPrivate = $certObj.PrivateKey $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider $null = $rsa.ImportParameters($rsaPrivate.ExportParameters($true)) } 'RSA' { Write-Verbose "Using given RSA object as is" } 'jsonFile' { Write-Verbose "Pulling private key from jsonAuth file" $versionTest = $psversiontable.psversion.major If ($versionTest -eq '5') { write-error "powerShell major version 5 detected, and not supported for JSON Auth file. Please use .p12 or RSA key" } Else { $jsonContents = Get-Content -Raw -Path $jsonPath | ConvertFrom-Json $privateKeyRSA = $jsonContents.private_key $rsa_parameters = [System.Security.Cryptography.RSA]::create() [System.Security.Cryptography.RSACryptoServiceProvider]$rsa_parameters.ImportFromPem($privateKeyRSA) $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider $rsa.ImportParameters($rsa_parameters.ExportParameters($true)) } } Default { throw "Unknown parameter set" } } # 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 } } #endregion #region Get-GOAuthTokenUser function Get-GOAuthTokenUser { <# .Synopsis Get Valid OAuth Token. Requires a version of Selenium Web Browser driver to work with web interface. .PARAMETER appKey The google project App Key .PARAMETER appSecret The google project application secret .PARAMETER redirectUri An https project redirect. Can be anything as long as https .PARAMETER refreshToken A refresh token if refreshing .PARAMETER scope The API scopes to be included in the request. Space delimited, "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive" .PARAMETER webDriverPath File path to selenium webdriver.dll - will add-type the driver .dll if needed and provided. .EXAMPLE Get-GOAuthTokenUser -appKey $appKey -appSecret $appSecret -redirectUri $redirectUri -scope $scope .EXAMPLE Get-GOAuthTokenUser -appKey $appKey -appSecret $appSecret -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]$redirectUri, [Parameter(Mandatory)] [string]$scope, [string]$refreshToken, [string]$webDriverPath ) Begin { $requestUri = "https://accounts.google.com/o/oauth2/token" } Process { if(!($refreshToken)) { ### Get the authorization code - browser 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" Add-Type -Path ("$($webDriverPath)\WebDriver.dll") $edgeDriver = New-Object OpenQA.Selenium.Edge.EdgeDriver start-sleep 2 $edgeDriver.Navigate().GoToURL("$auth_string") #Wait for user interaction in selenium browser, manual approval do{Start-Sleep 1}until($edgeDriver.Url -match 'code=([^&]*)') $null = $edgeDriver.LocationURL -match 'code=([^&]*)' $authorizationCode = $matches[1] $null = $edgeDriver.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 } 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 Add-Member -InputObject $response -NotePropertyName refreshToken -NotePropertyValue $refreshToken } } End { # add alias for backwards compatability Add-Member -InputObject $response -MemberType AliasProperty -Name accesstoken -Value access_token return $response } } #endregion #region Get-GOAuthTokenDevice function Get-GOAuthTokenDevice { <# .Synopsis Get Valid OAuth Token. Provides login URL for any browser to allow for lack of web driver. .PARAMETER appKey The google project App Key .PARAMETER appSecret The google project application secret .PARAMETER redirectUri An https project redirect. Can be anything as long as https .PARAMETER refreshToken A refresh token if refreshing .PARAMETER 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-GOAuthTokenDevice -appKey $appKey -redirectUri $redirectUri -scope $scope .EXAMPLE Get-GOAuthTokenDevice -appKey $appKey -appSecret $appSecret -redirectUri $redirectUri -scope $scope -refreshToken $refreshToken .NOTES Requires 2nd device with browser. User interaction required. #> [CmdletBinding()] [OutputType([array])] Param ( [Parameter(Mandatory)] [string]$appKey, [Parameter(Mandatory)] [string]$appSecret, [Parameter(Mandatory)] [string]$redirectUri, [Parameter(Mandatory)] [string]$scope, [string]$refreshToken ) Begin { $requestUri = "https://accounts.google.com/o/oauth2/token" } Process { if(!($refreshToken)) { ### Get the authorization code - browser 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" $authorizationUrl = Read-Host -Prompt "Visit ` $auth_string ` to authenticate and authorize the app. Paste the all text beyond the code= response in order to continue" -MaskInput $authorizationUrl -match 'code=([^&]*)' $authorizationCode = $matches[1] # 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 } 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 Add-Member -InputObject $response -NotePropertyName refreshToken -NotePropertyValue $refreshToken } } End { # add alias for backwards compatability Add-Member -InputObject $response -MemberType AliasProperty -Name accesstoken -Value access_token return $response } } #endregion #region Get-GOAuthIdTokenSelenium function Get-GOAuthIdTokenSelenium { <# .Synopsis Get Valid OAuth ID token for a user. .DESCRIPTION The ID token is signed by google to represent a user https://developers.google.com/identity/sign-in/web/backend-auth. .PARAMETER clientID Client ID within app project .PARAMETER redirectUri An https project redirect. Can be anything as long as https .PARAMETER scope The API scopes to be included in the request. Space delimited, "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive" .PARAMETER webDriverPath Path to selenium webdriver - will update path of system to include. .EXAMPLE Get-GOAuthIdToken -clientID $clientID -scope $scope -redirectUri $redirectURI .NOTES Requires GUI with Edge and Selenium web browser. #> [CmdletBinding()] [OutputType([array])] Param ( [Parameter(Mandatory)] [string]$clientID, [Parameter(Mandatory)] [string]$redirectUri, [Parameter(Mandatory)] [string]$scope, [string]$webDriverPath ) Begin { $requestUri = "https://accounts.google.com/o/oauth2/token" Add-Type -Path ("$($webDriverPath)\WebDriver.dll") } Process { ### Get the ID Token - Browser Popup and user interaction section $auth_string = "https://accounts.google.com/o/oauth2/auth?scope=$scope&response_type=token%20id_token&redirect_uri=$redirectUri&client_id=$clientID&approval_prompt=force" $edgeDriver = New-Object OpenQA.Selenium.Edge.EdgeDriver start-sleep 2 $edgeDriver.Navigate().GoToURL("$auth_string") #Wait for user interaction in selenium browser, manual approval do{Start-Sleep 1}until($edgeDriver.Url -match 'id_token=([^&]*)') $null = $edgeDriver.LocationURL -match 'id_token=([^&]*)' $id_token = $matches[1] $null = $edgeDriver.Quit() return $id_token } End{} } #endregion #region Get-GOAuthIdToken function Get-GOAuthIdToken { <# .Synopsis Get Valid OAuth ID token for a user. .DESCRIPTION The ID token is signed by google to represent a user https://developers.google.com/identity/sign-in/web/backend-auth. .PARAMETER clientID Client ID within app project .PARAMETER redirectUri An https project redirect. Can be anything as long as https .PARAMETER 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-GOAuthIdToken -clientID $clientID -scope $scope -redirectUri $redirectURI .NOTES Requires GUI with Internet Explorer to get first token. #> [CmdletBinding()] [OutputType([array])] Param ( [Parameter(Mandatory)] [string]$clientID, [Parameter(Mandatory)] [string]$redirectUri, [Parameter(Mandatory)] [string]$scope ) Begin { $requestUri = "https://accounts.google.com/o/oauth2/token" } Process { ### Get the ID Token - IE Popup and user interaction section $auth_string = "https://accounts.google.com/o/oauth2/auth?scope=$scope&response_type=token%20id_token&redirect_uri=$redirectUri&client_id=$clientID&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 'id_token=([^&]*)') $null = $ie.LocationURL -match 'id_token=([^&]*)' Write-Debug $ie.LocationURL $id_token = $matches[1] $null = $ie.Quit() return $id_token } End{} } #endregion #region invoke wrapper function Invoke-GWrapper { <# .Synopsis Wraps any invoke-restmethod in a retry wrapper. Needs work... .DESCRIPTION Finding many Google APIs fail for many non critical reasons. .PARAMETER uri The uri to be invoked .PARAMETER headers The provided header .PARAMETER method The method to invoke .PARAMETER body A hashtable body .EXAMPLE Invoke-GWrapper -uri $uri -header $header #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $uri, [Parameter(Mandatory=$true)] [hashtable] $headers, [Parameter(Mandatory=$true)] [ValidateSet("get","post","patch","delete")] $method, [hashtable] $body ) Begin{ $completed = $false [int]$retrycount = 0 [int]$SecondsDelay = 5 [int]$retries = 5 if ($body) { $bodyJson = $body | ConvertTo-Json -Depth 10 Write-Verbose -Message "Method set to: Post" } } Process { while (-not $completed) { try{ If($bodyJson){$response = Invoke-RestMethod -Uri $uri -Headers $header -Body $bodyJson -Method $method} Else{$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method $method} $completed = $true } catch { if ($retrycount -ge $Retries) { throw } else { Start-Sleep $SecondsDelay $retrycount++ $secondsDelay = $SecondsDelay * $retryCount } } } } End{return $response} } #endregion #region Get-GFile function Get-GFile { <# .Synopsis Download a Google File. .DESCRIPTION Download a Google File based on a case sensative file or fileID. .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 .PARAMETER fileID File ID. Can be gotten from Get-GFileID .PARAMETER outFilePath Path to output file including file name. .EXAMPLE Get-GFile -accessToken $accessToken -fileName 'Name of some file' .EXAMPLE Get-GFile -accessToken $accessToken -fileID 'ID of some file' .NOTES Written by Travis Sobeck #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(ParameterSetName='fileName')] [string]$fileName, [Parameter(ParameterSetName='fileID')] [string]$fileID, [Parameter(Mandatory)] [string]$outFilePath #[string]$mimetype ) Begin{} Process { if ($fileName){$fileID = Get-GFileID -accessToken $accessToken -fileName $fileName} If ($fileID.count -eq 0 -or $fileID.count -gt 1){break} $uri = "https://www.googleapis.com/drive/v3/files/$($fileID)?alt=media" Invoke-RestMethod -Method Get -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"} -OutFile $outFilePath } End{} } #endregion #region Get-GFileRevisions function Get-GFileRevisions { <# .Synopsis Get a files revision history .DESCRIPTION Get a files revision history .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 .PARAMETER fileID File ID. Can be gotten from Get-GFileID .EXAMPLE Get-GFileRevisions -accessToken $accessToken -fileName 'Name of some file' # Get last modified Get-Date ((Get-GFileRevisions -fileName $filename -accessToken $accessToken).revisions.modifiedTime[-1]) .EXAMPLE Get-GFileRevisions -accessToken $accessToken -fileID 'ID of some file' # Get last modified Get-Date ((Get-GFileRevisions -fileName $filename -accessToken $accessToken).revisions.modifiedTime[-1]) .NOTES Written by Travis Sobeck #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(ParameterSetName='fileName')] [string]$fileName, [Parameter(ParameterSetName='fileID')] [string]$fileID ) Begin{} Process { if ($fileName){$fileID = Get-GFileID -accessToken $accessToken -fileName $fileName} If ($fileID.count -eq 0 -or $fileID.count -gt 1){break} $uri = "https://www.googleapis.com/drive/v3/files/$($fileID)/revisions" Invoke-RestMethod -Method Get -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"} } End{} } #endregion #region Get-GFileID function Get-GFileID { <# .Synopsis Get a Google File ID. .DESCRIPTION Provide a case sensative file name to the function to get back the gFileID used in many other API calls. .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 .PARAMETER mimetype Use this to specify a specific mimetype. See google docs https://developers.google.com/drive/api/v3/search-parameters .EXAMPLE Get-GFileID -accessToken $accessToken -fileName 'Name of some file' .NOTES Written by Travis Sobeck #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$fileName, [string]$mimetype ) Begin{} Process { $uri = "https://www.googleapis.com/drive/v3/files?q=name%3D'$fileName'" if ($mimetype){$fileID = (((Invoke-RestMethod -Method get -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"}).files) | Where-Object {$_.mimetype -eq $mimetype}).id} else{$fileID = (((Invoke-RestMethod -Method get -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"}).files)).id} # Logic on multiple IDs being returned If ($fileID.count -eq 0){Write-Warning "There are no files matching the name $fileName"} If ($fileID.count -gt 1){Write-Warning "There are $($fileID.Count) files matching the provided name. Please investigate the following sheet IDs to verify which file you want.";return($fileID)} Else{return($fileID)} } End{} } #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. .PARAMETER permissionID If specified will query only that specific permission for the file, rather than all permissions .PARAMETER DefaultFields If specified, will only query "default" rather than querying all fields of Permission object. Added primarily for backwards compatibility .EXAMPLE Get-GFilePermissions -accessToken $accessToken -fileID 'String of File ID' -permissionID 'String of Permission ID' .OUTPUTS If only a fileID, this will return an object with two properties, the first is kind, and will always be drive#permissionList The second will be permissions, which includes the individual permissions objects. Each one of these will have the same format as if a specific PermissionID was requested If a permissionID is also specified, only that specific permission will be returned. It will have a kind property of drive#permission as well as all properties of that specific permission. More details on the permission object available here: https://developers.google.com/drive/api/v2/reference/permissions #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, #[Alias("spreadSheetID")] [Parameter(Mandatory)] [string]$fileID, # Parameter help description [Parameter()] [string] $permissionID, # Parameter help description [Parameter()] [switch] $DefaultFields ) Begin { $uri = "https://www.googleapis.com/drive/v3/files/$fileID/permissions" if ($permissionID) { $uri += "/$permissionID" } if (-not $DefaultFields) { $uri += "/?fields=*" } $headers = @{"Authorization"="Bearer $accessToken"} } Process { write-host $uri 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 #region Add-GSheetSheet 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{} } #endregion #region Clear-GSheetSheet 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{} } #endregion #region Get-GSheetData 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"} #Check if the sheet data is a positive return with no values (blank sheet) If(!$result.values){ write-warning "Sheet returned no values! Success response from API! $spreadSheetID $sheetName" } Else{ # 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] if ($sheet[$row].count -gt ($column)) { $h.$Name = $Sheet[$Row][$Column] } else { $h.$Name = "" } } } [PSCustomObject]$h } } } End{} } #endregion #region Get-GSheetSheetID 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{} } #endregion #region Get-GSheetSpreadSheetID 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 { return (Get-GFileID -accessToken $accessToken -fileName $fileName -mimetype "application/vnd.google-apps.spreadsheet") } End{} } #endregion #region Get-GSheetSpreadSheetProperties 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{} } #endregion #region Move-GSheetData 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-object {$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-object {$_.title -eq $sourceSheetName}).sheetID $dstSheetIndex = ($allSheetProperties | where-object {$_.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{} } #endregion #region New-GSheetSpreadSheet 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 New-GSheet -properties $properties -accessToken $accessToken .EXAMPLE New-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{} } #endregion #region Remove-GSheetSheet 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) } } #endregion #region Remove-GSheetSpreadSheet 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 -fileID $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{} } #endregion #region Remove-GSheetSheetRowColumn function Remove-GSheetSheetRowColumn { <# .Synopsis Remove row(s) or column(s) .DESCRIPTION Remove row(s) or column(s) .PARAMETER accessToken access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService .PARAMETER startIndex Index of row or column to start deleting .PARAMETER endIndex Index of row or column to stop deleting .PARAMETER dimension Remove Rows or Columns .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 Remove-GSheetSheetRowColumn -accessToken $accessToken -sheetName "Sheet1" -spreadSheetID $spreadSheetID -dimension ROWS -startIndex 5 -endIndex 10 #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [int]$startIndex, [Parameter(Mandatory)] [int]$endIndex, [Parameter(Mandatory)] [ValidateSet("COLUMNS", "ROWS")] [string]$dimension, [Parameter(Mandatory)] [string]$sheetName, [Parameter(Mandatory)] [string]$spreadSheetID ) Begin { $sheetID = Get-GSheetSheetID -accessToken $accessToken -spreadSheetID $spreadSheetID -sheetName $sheetName if ($startIndex -eq $endIndex){$endIndex++} } Process { $request = @{"deleteDimension" = @{"range" = @{"sheetId" = $sheetID; "dimension" = $dimension; "startIndex" = $startIndex; "endIndex" = $endIndex}}} $json = @{requests=@($request)} | ConvertTo-Json -Depth 20 $suffix = "$spreadSheetID" + ":batchUpdate" $uri = "https://sheets.googleapis.com/v4/spreadsheets/$suffix" write-verbose -Message $json Invoke-RestMethod -Method Post -Uri $uri -Body $json -ContentType "application/json" -Headers @{"Authorization"="Bearer $accessToken"} } End{} } #endregion #region Set-GSheetColumnWidth 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{} } #endregion #region Set-GSheetData 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 ranges, specific cells, or append a new row 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 contenttype The contenttype specifies the content type of the web request. Default value is 'application/json'. .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 .EXAMPLE Set-GSheetData -accessToken $accessToken -rangeA1 "B2" -sheetName 'My Sheet' -spreadSheetID $spreadSheetID -values @(@('only_one_updated_cell'),@()) #> [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, [string]$contenttype = 'application/json' ) 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 $contenttype -Headers @{"Authorization"="Bearer $accessToken"} } End{} } #endregion #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 #region App Scripts and Projects #region Get-gAppScriptProject function Get-gAppScriptsProject { <# .Synopsis Get information about a google App Script project .DESCRIPTION Provide scriptID to get google project info for Google Apps Script .PARAMETER accessToken OAuth Access Token for authorization. .PARAMETER scriptID The scriptID to query. Found as a project property. .EXAMPLE Get-gAppScriptsProject -scriptID $scriptID -accessToken $accessToken .OUTPUTS #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$scriptID ) Begin { $uri = "https://script.googleapis.com/v1/projects/$scriptID" $headers = @{"Authorization"="Bearer $accessToken"} } Process { Invoke-RestMethod -Method get -Uri $uri -Headers $headers -UseBasicParsing } End{} } #endregion #region Get-gAppScriptProjectContent function Get-gAppScriptsProjectContent { <# .Synopsis Get content about a google App Script project .DESCRIPTION Provide scriptID to get google project content for Google Apps Script .PARAMETER accessToken OAuth Access Token for authorization. .PARAMETER scriptID The scriptID to query. Found as a project property. .EXAMPLE Get-gAppScriptsProjectContent -scriptID $scriptID -accessToken $accessToken .OUTPUTS Provides the actual scripts of the project. #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$scriptID ) Begin { $uri = "https://script.googleapis.com/v1/projects/$scriptID/content" $headers = @{"Authorization"="Bearer $accessToken"} } Process { Invoke-RestMethod -Method get -Uri $uri -Headers $headers -UseBasicParsing } End{} } #endregion #region Send-gAppsScriptFunction function Send-gAppsScriptFunction { <# .Synopsis Send parameters to a Google App Scripts Function by name .DESCRIPTION Call and send parameters to a Google App Scripts Function by name. .PARAMETER accessToken OAuth Access Token for authorization. .PARAMETER scriptID The scriptID associated to the Google App Scripts project. .PARAMETER requestBody The PSCustom Object or hashtable to be send to the Google App Scripts API. Will be converted to JSON string. Example requestBody $requestbody = @{ "function"= 'DeleteTrigger'; "parameters"=@( formID123fjn4 ); "devMode"= $false } .PARAMETER function The google App Script function to be called. Function name is CASE Sensitive. .PARAMETER parameter Optional paramteter to pass through to the google function. .PARAMETER devMode Switch flag for dev mode. Run the last saved script instead of last published. .EXAMPLE Execute a function by providing your own hashtable. send-gAppsScriptFunction -accessToken $accessToken -scriptID $scriptID -requestBody $requestbody .EXAMPLE Execute a function with no parameters. send-gAppsScriptFunction -accessToken $accessToken -scriptID $scriptID -function CreateForm .EXAMPLE Execute a function by name with a paramter to pass into the Google App Script function. send-gAppsScriptFunction -accessToken $accessToken -scriptID $scriptID -function DeleteTrigger -parameter formID1234fjdnejf .EXAMPLE Execute a function by name with a paramter to pass into the Google App Script function using Dev mode (Last saved script version instead of latest published) send-gAppsScriptFunction -accessToken $accessToken -scriptID $scriptID -function DeleteTrigger -parameter formID1234fjdnejf -DevMode true .EXAMPLE test.gs script in project function DeleteTrigger(formid){ triggers = ScriptApp.getProjectTriggers() for (var i = 0; i < triggers.length; i++) { if (triggers[i].getTriggerSourceId() == formid) { ScriptApp.deleteTrigger(triggers[i]) Logger.log("deleted " + formid) } } } Sample $RequestBody for above Function in .gs $requestbody = @{ "function"= 'DeleteTrigger'; "parameters"=@( $item ); "devMode"= 'false' } .OUTPUTS The following may come up if you did not publish the App Script as API Executable. "error": { "code": 404, "message": "Requested entity was not found.", "status": "NOT_FOUND" .OUTPUTS done response ---- -------- True @{@type=type.googleapis.com/google.apps.script.v1.ExecutionResponse} .OUTPUTS done error ---- ----- True @{code=3; message=ScriptError; details=System.Object[]} $return.error.details.errorMessage Script function not found: ShowProperties } #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$scriptID, [Parameter(Mandatory, ParameterSetName = 'PSObject')] [validateScript({$_.GetType().Fullname -in 'System.Management.Automation.PSCustomObject','System.Collections.Hashtable'})] $requestBody, [Parameter(Mandatory, ParameterSetName = 'Body Options')] [string]$function, [Parameter(ParameterSetName = 'Body Options')] [string]$parameter, [Parameter(ParameterSetName = 'Body Options')] [switch]$devMode ) Begin { $uri = "https://script.googleapis.com/v1/scripts/$scriptID"+":run" $headers = @{"Authorization"="Bearer $accessToken"} If($requestbody){ $body = $requestbody |convertto-json } If($function){ $body = @{ "function"= $function; "parameters"=@( $parameter ); "devMode"= [bool]$devMode } |convertto-json } } Process { $return = Invoke-RestMethod -Method post -Uri $uri -Headers $headers -Body $body -ContentType 'application/json' -UseBasicParsing } End{ return $return } } #endregion #endregion #region Google Group management #region Add-gGroupMembership function Add-gGroupMembership { <# .Synopsis Add member to google group based on group ID .DESCRIPTION Provide email address of new member to group - requires Cloud Identity API enabled on project .PARAMETER accessToken OAuth Access Token for authorization. .PARAMETER groupID The groupID of the group. See get-gGroupID for reference. .PARAMETER member The email address of the new member to add .EXAMPLE Add-gGroupMembership -accessToken $accessToken -groupID 'hfiou08uf3' -member 'user@domain.com' .OUTPUTS Success of group member add #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$groupID, [Parameter(Mandatory)] [string]$member ) Begin { $uri = "https://content-cloudidentity.googleapis.com/v1/groups/$groupID/memberships" $headers = @{"Authorization"="Bearer $accessToken"} $body = @{ preferredMemberKey = @{ id = $member } roles = @(@{ name = "MEMBER" }) } |convertto-json } Process { $response = Invoke-RestMethod -contentType 'application/json' -Method post -Uri $uri -Headers $headers -body $body -UseBasicParsing } End{ return $response } } #endregion #region Get-GroupID function Get-gGroupID { <# .Synopsis Get group ID based on Cloud Identity lookup .DESCRIPTION Provide group email address to get group ID lookup for Cloud Identity .PARAMETER accessToken OAuth Access Token for authorization. .PARAMETER groupEmail The email address of the group .EXAMPLE Get-gGroupID -accessToken $accessToken -groupEmail 'email@domain.edu' .OUTPUTS Provides the groups/groupID (9ofjh37fijruf) #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$groupEmail ) Begin { $groupEmail = $groupEmail.replace("@","%40") $uri = "https://cloudidentity.googleapis.com/v1/groups:lookup?groupKey.id=$groupEmail" $headers = @{"Authorization"="Bearer $accessToken"} } Process { $response = (Invoke-RestMethod -Method get -Uri $uri -Headers $headers -UseBasicParsing).name $id = ($response.split("/"))[1] } End{ return $id } } #endregion #region function Get-gGroupMembership { <# .Synopsis Get group ID based on Cloud Identity lookup .DESCRIPTION Provide group email address to get group ID lookup for Cloud Identity .PARAMETER accessToken OAuth Access Token for authorization. .PARAMETER groupID The groupID of the group. See get-gGroupID for reference. .EXAMPLE Get-gGroupMembership -accessToken $accessToken -groupID 'hfiou08uf3' .OUTPUTS Provides list of memberships of the group #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$groupID ) Begin { $uri = "https://content-cloudidentity.googleapis.com/v1/groups/$groupID/memberships" $headers = @{"Authorization"="Bearer $accessToken"} } Process { $response = Invoke-RestMethod -Method get -Uri $uri -Headers $headers -UseBasicParsing } End{ return $response } } #endregion #region Remove-gGroupMembership function Remove-gGroupMembership { <# .Synopsis Remove member to google group based on group ID .DESCRIPTION Provide email address of new member to group .PARAMETER accessToken OAuth Access Token for authorization. .PARAMETER membershipName The memberhsip name in format groups/groupID/memberships/membershipsID, such as 'groups/03ep43zb2bc7vzi/memberships/111396014913618483233' .EXAMPLE Remove-gGroupMembership -accessToken $accessToken -groupID 'hfiou08uf3' -member 'user@domain.com' .OUTPUTS Success of group member remove #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$membershipName ) Begin { $uri = "https://content-cloudidentity.googleapis.com/v1/$membershipName" $headers = @{"Authorization"="Bearer $accessToken"} } Process { $response = Invoke-RestMethod -contentType 'application/json' -Method delete -Uri $uri -Headers $headers -UseBasicParsing } End{ return $response } } #regionend #region Set-gSuiteGroupMembership function Set-gSuiteGroupMembership { <# .Synopsis Sets members of a google group .DESCRIPTION Adds members from an Arraylist of users to a Google Group .PARAMETER accessToken access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService .PARAMETER groupID Unique ID of the group. Keys from group ID, group email address, etc. .PARAMETER groupMembership Arraylist of users to be added to the group. .PARAMETER groupDomain Domain of the group. If the input is lacking the @domain, will be appended .EXAMPLE New-gSuiteGroup -accessToken $accessToken -groupname $groupName -groupMembership $groupHash .NOTES Written by Kyle Weeks #> [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$groupID, [Parameter(Mandatory)] [string]$groupMembership, [Parameter(Mandatory)] [string]$groupDomain ) Begin{ $header = @{"Authorization"="Bearer $accessToken"} $uri = "https://www.googleapis.com/admin/directory/v1/groups/$groupID/members" } Process { Foreach($user in $groupMembership){ If($user -notlike "*$groupDomain"){$user+=$groupDomain} $body = @{ 'email' = $user 'role' = 'Member' } |ConvertTo-json $return+=(Invoke-RestMethod -Method Post -Uri $uri -Headers $header -Body $body).id } } End{ return $return } } #endregion #region New-gSuiteGroup function New-gSuiteGroup { <# .Synopsis Create a new group - requires super admin permissions .DESCRIPTION Creates a new gSuite Group .PARAMETER accessToken access token used for authentication. Get from Get-GOAuthTokenUser or Get-GOAuthTokenService .PARAMETER groupEmail Email of the group. .PARAMETER groupMembership Arraylist of users to be added to the group. .PARAMETER groupDomain Domain of the group. If the input is lacking the @domain, will be appended .PARAMETER displayName The display name of the group to be created. .PARAMETER groupDescription A group description as required .EXAMPLE New-gSuiteGroup -accessToken $accessToken -groupname $groupName -groupMembership $groupHash .NOTES Written by Kyle Weeks #> [OutputType('System.String')] [CmdletBinding()] Param ( [Parameter(Mandatory)] [string]$accessToken, [Parameter(Mandatory)] [string]$groupEmail, [Parameter(Mandatory)] [string]$displayName, [Parameter(Mandatory)] [string]$groupDomain, [Parameter(Mandatory)] [string]$groupDescription, [string]$groupMembership ) Begin{ $header = @{"Authorization"="Bearer $accessToken"} $uri = 'https://www.googleapis.com/admin/directory/v1/groups' If($groupName -notlike "*@$groupDomain"){$groupName+=$groupDomain} } Process { $body = @{ 'email' = $groupEmail 'name' = $displayName 'description' = $groupDescription } |ConvertTo-json $return = Invoke-RestMethod -Method Post -Uri $uri -Headers $header -Body $body $groupID = $return.id If($groupMembership){ $members = Set-gSuiteGroupMembership -groupID $groupID -groupMembership $groupMembership -accessToken $accessToken } } End{ return "$groupID $members" } } #endregion #endregion Export-ModuleMember -Function * |