Modify Google Sheets (API) Using PowerShell / Uploading CSV Files

PowerShell Generated Google Sheet
PowerShell Generated Google Sheet

I recently had a need to interact with a Google Sheets spreadsheet and it turned out to be pretty easy and worked very well! I wanted to write this post to share what I learned and hopefully it can help some others out there.

In this guide we’ll explore how to create a Google API key then and use PowerShell to create a new spreadsheets and upload a CSV file into Google Sheets.

Acquiring Google Sheets API

First head to the official Google Developers Console. If you have never used the Google API before go ahead and create a new project. I called mine Scripts-jamesachambers.

After creating a project it should take you to the main dashboard. From here we are going to click “ENABLE APIS AND SERVICES” at the top of the page. It looks like this:

Google Sheets Dashboard - Enable APIs and Services
Google Sheets Dashboard – Enable APIs and Services

This will bring up a page showing all the different Google APIs available. From here we are going to scroll down to the G-Suite section and click on “Google Sheets” and then choose the “Enable” button.

Google Developer Console - Add API Menu
Google Developer Console – Add API Menu
Google Sheets Enable Button
Google Sheets – API Enable Button

Enable Google Drive API

Now enable the Google Drive API using the exact same steps. This will be needed for some access/permission controls later.

To enable the Google Drive API follow the same steps we did in the “Acquiring Google Sheets API” but choose “Google Drive” from the G-Suite options instead.

Creating a Service Account

There is a lot of security built into Google’s APIs. Some types of them require user consent but can access private user data. The kind of account we want to create to work with Google Sheets using scripts is a service account.

This type of account uses a certificate to validate your identity instead of a username/password. It is able to create new spreadsheets and give permissions for others to view them without requiring user consent since the service account will own the spreadsheet sidestepping the private user data issue. It is also able to update these sheets right from PowerShell!

When you clicked the “Enable” button it should have taken you into the “Google Sheets API Console” but sometimes I’ve noticed the button just turns into “Manage”. Click Manage of you aren’t already in the Google Sheets developer console. You should be at a screen that looks like this:

Google Sheets API Credentials
Google Sheets API – Credentials Tab

Choose the “Credentials” tab on the left hand of the screen as shown above and click “Create Credentials” and choose the “Service Account” option.

Choose a name for your account. You can set an optional description here as well if you would like. Click the “Create” button and it will take you to the next screen.

Google Sheets API - Service Account Roles
Google Sheets API – Service Account Roles

Give your service account the “Project” – “Owner” role. This will give your service account full access to all resources in the project you created. You can create finer grained permissions if desired but the service account is only going to have access to the project you created so it should be safe unless you have other resources in the project.

Press the button again and leave the final screen on the defaults. Your service account is now created!

Create Service Account Access Key/Certificate

Now that we have created our service account we need to create an access key and download the certificate for it.

At the official Google Developers Console dashboard choose the “Service Accounts” tab from the left side of the screen and then click on the new service account you created:

Google Sheets - API Service Accounts Menu
Google API Service Accounts Menu

Make a note or copy of your service account’s email address listed here. This will be the $iss credential we’ll be using in PowerShell to authenticate with Google very shortly.

At the bottom of your “Service account details” page will be an option to add keys. Click the “Add Key” button and choose “Create new key” as shown here:

Google Sheets - Create API Key
Google API Service Account Details – Create New Key

Choose the “P12” format:

Google Service Account - Create P12 Key
Google Service Account – Create P12 Key

Press “Create”:

Google Sheets - Download API Key
Google Service Account – Private Key Download

A download dialog will pop up and the screen above will be displayed. This is your certificate file your scripts will use to access Google Sheets.

Warning: This is the only time Google will give you this key. If you lose it you cannot recover it or redownload it and have to create a new key and update all your scripts that use that key accordingly. Make sure you have this key backed up!

Once you have safely backed up this key we are all done with configuration and it’s time to head to PowerShell!

Install UMN-Google PowerShell Module

We are going to use the UMN-Google PowerShell module to be able to communicate with Google Sheet’s API. You will want to install this module on any machine you will be running scripts. Open PowerShell and type:

Install-Module UMN-Google

This PowerShell module is developed and maintained by the University of Minnesota. You can see more information at the PowerShell Gallery here: The UMN-Google PowerShellGallery Repository

Getting Authorization Token

This block will appear at the top of your scripts. It will use our service account certificate to get an authorization code from Google.

Create a new PowerShell .ps1 file with the editor of your choice. Copy and paste the code block below:

Import-Module UMN-Google

# Set security protocol to TLS 1.2 to avoid TLS errors
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Google API Authozation
$scope = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
$certPath = "C:/Users/james/Desktop/PowerShell/jamesachambers-1551564735346.p12"
$iss = 'scripts@jamesachambers-1551564735346.iam.gserviceaccount.com'
$certPswd = 'notasecret'
try {
    $accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPswd -iss $iss
} catch {
    $err = $_.Exception
    $err | Select-Object -Property *
    "Response: "
    $err.Response
}

$accessToken

Modify the values below to match your certificate file you downloaded in the previous step and your $iss (service account email address) information that we made a note of/copied earlier. The 2 lines you need to change to your own values are listed in bold.

I recommend fully qualifying the path to your certificate file to avoid working path problems (especially when doing background/automation scheduled tasks). Make sure you remember to copy the certificate from your downloads folder to the location where you created your PowerShell .ps1 file and that you change the path to yours.

After you have modified the values run the script to make sure you don’t receive any errors. If everything ran successfully you will see a valid Google API authorization token! It should look like this:

Testing Google Sheets
PowerShell – Google API Authorization Test

If your screen shows a long string of numbers/letters/symbols and does not give an error then congratulations, you’ve completed your API key setup successfully! If you received an error double check that you copied the certificate and gave the right path and run through the earlier steps quickly and verify everything looks correct.

Create New Google Sheet

Now that you have working authentication it’s time for the fun part! Now we get to start creating/modifying Google Sheet documents. Let’s create a new sheet and display the spreadsheet ID.

Add the following commands to the end of your .ps1 file with the title of your choice:

# Create new spreadsheet
$Title = 'Patching Spreadsheet'
$SpreadsheetID = (New-GSheetSpreadSheet -accessToken $accessToken -title $Title).spreadsheetId
$SpreadsheetID

# Create new sheet
$Sheet = 'Computers'
Add-GSheetSheet -accessToken $accessToken -sheetName $Sheet -spreadSheetID $SpreadsheetID

Now run your .ps1 file again:

Power Shell Google Sheets New Sheet
PowerShell – Google Sheets New Spreadsheet

The first line is your access token again, but the second line is the new spreadsheet ID! To access this spreadsheet you append the spreadsheet ID on the end of the following base URL:

https://docs.google.com/spreadsheets/d/

So for example, to access the spreadsheet I just created in this example go to https://docs.google.com/spreadsheets/d/1anI7Bdr0ZjkSDwMbGpCb0KZc1SpSC-3JdQiycvoM5CM/

However, when you go to the URL you will see:

Google Sheets - You Need Access
Google Sheets – You Need Access

This is because by default these sheets are private. We need to set the permissions for other people to be able to access our new sheet!

Setting Google Sheet Permissions

Let’s start by adding individual users. Comment out the lines that create a new spreadsheet and sheet and add the following lines to the end of the file:

$SpreadsheetID = '1anI7Bdr0ZjkSDwMbGpCb0KZc1SpSC-3JdQiycvoM5CM'
Set-GFilePermissions -accessToken $accessToken -fileID $SpreadsheetID -role writer -type user -emailAddress 'youremail@yourdomain.com'

Change the $SpreadsheetID to the ID you received in the last step and the email to match your own personal email address. This will add “writer” permissions to your account to the spreadsheet. The bolded lines are things you need to adjust. The final result should look like this:

Import-Module UMN-Google

# Set security protocol to TLS 1.2 to avoid TLS errors
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Google API Authozation
$scope = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
$certPath = "C:/Users/james/Desktop/PowerShell/yourcertificate.p12"
$iss = 'scripts@jamesachambers-1551564735346.iam.gserviceaccount.com'
$certPswd = 'notasecret'
try {
    $accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPswd -iss $iss
} catch {
    $err = $_.Exception
    $err | Select-Object -Property *
    "Response: "
    $err.Response
}

$SpreadsheetID = '1anI7Bdr0ZjkSDwMbGpCb0KZc1SpSC-3JdQiycvoM5CM'

Set-GFilePermissions -accessToken $accessToken -fileID $SpreadsheetID -role writer -type user -emailAddress 'youremail@yourdomain.com'

Now run the script.

If you received the following error:

Invoke-RestMethod : The remote server returned an error: (403) Forbidden.

It means you very likely did not enable the “Google Drive” API. Go back to the “Enable Google Drive API” step from earlier and double check that the Google Drive API got enabled!

Now go back to your browser and refresh the “Access Denied” page. You should now see the blank spreadsheet!

Uploading a CSV File to Google Sheets

Now that you’ve set up your Google Sheets API keys and environment we are ready to modify new Google Sheets! If you already have a CSV file to upload you can use that, otherwise let’s create a quick testing CSV. Open Notepad or your favorite editor up to a new document and paste the following text:

Computer,"Operating System",RAM
"iMac 27""",OSX,"16 GB"
"HP ZBook 15""",Windows 10,"16 GB"

Now save the document as test.csv in the same directory you have our “Setting Google Sheets Permissions” PowerShell script from the last step. Remove or comment out the very last Set-GFilePermissions line of the script.

Google Sheets expects an ArrayList of values to upload to Google Sheets. We are going to load our test CSV and convert it to an ArrayList and submit the data to Google Sheets with the following code:

# Import CSV
$import = New-Object System.Collections.ArrayList($null)

# Build sheet header as it isn't included automatically
$import.Add( @("Computer", "Operating System", "RAM")) | Out-Null

# Build ArrayList
$inputCsv = Import-Csv "test.csv"
$inputCsv | ForEach-Object { 
    $import.Add( @($_.Computer, $_."Operating System", $_.RAM)) | Out-Null
}

# Upload CSV data to Google Sheets with Set-GSheetData
try {
    Set-GSheetData -accessToken $accessToken -rangeA1 "A1:C$($import.Count)" -sheetName "Computers" -spreadSheetID $SpreadsheetID -values $import -Debug -Verbose
} catch {
    $err = $_.Exception
    $err | Select-Object -Property *
    "Response: "
    $err.Response
}

This code loads the CSV into a variable called $inputCsv and then iterates through each object and adds it to the ArrayList we are going to send to Google Sheets. If you wanted to modify the data before sending it you absolutely can.

Once the CSV is imported you will have an object array and the names of the variables will be the name of the header of that column. You have to be careful if it has a quote in it. You can still access them by quoting the variable. For example:

$_."Operating System"

This will allow you to access the variables even if there are spaces in the column’s header. The actual first line of the CSV with the headers in it is consumed by PowerShell when you import it so we also create our own header line for our spreadsheet.

Your final code should look like this:

Import-Module UMN-Google

# Set security protocol to TLS 1.2 to avoid TLS errors
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Google API Authozation
$scope = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
$certPath = "C:/Users/james/Desktop/PowerShell/yourcertificate.p12"
$iss = 'scripts@jamesachambers-1551564735346.iam.gserviceaccount.com'
$certPswd = 'notasecret'
try {
    $accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPswd -iss $iss
} catch {
    $err = $_.Exception
    $err | Select-Object -Property *
    "Response: "
    $err.Response
}

# Spreadsheet ID # of the Google Sheet
$SpreadsheetID = '1anI7Bdr0ZjkSDwMbGpCb0KZc1SpSC-3JdQiycvoM5CM'

# Import CSV and build ArrayList
$import = New-Object System.Collections.ArrayList($null)
$inputCsv = Import-Csv "test.csv"
$inputCsv | ForEach-Object { 
    $import.Add( @($_.Computer, $_.'Operating System', $_.RAM)) | Out-Null
}

# Build sheet header as it isn't included automatically
$import.Add( @("Computer", "Operating System", "RAM")) | Out-Null

# Build ArrayList

# Upload CSV data to Google Sheets with Set-GSheetData
try {
    Set-GSheetData -accessToken $accessToken -rangeA1 "A1:C$($import.Count)" -sheetName "Computers" -spreadSheetID $SpreadsheetID -values $import -Debug -Verbose
} catch {
    $err = $_.Exception
    $err | Select-Object -Property *
    "Response: "
    $err.Response
}

Now we’re ready to run the code. Execute your script in PowerShell:

PowerShell - Test of uploading CSV to Google Sheets
PowerShell – Test of uploading CSV to Google Sheets

If everything was set up you should immediately see the changes in your browser window and it should look like this:

Patching Test Spreadsheet
Patching Test Spreadsheet

Wrapping Up

That’s it! You’ve now set up your Google API console access and can now modify Google Sheets through the power of PowerShell automation.

Make sure you check out the University of Minnesota’s GitHub repository to see any new changes or announcements related to the PowerShell extension. It is open source!

International Language Fix (Required for computers not set to “English” as the default language)

Once this post started gaining some traction I learned that international users (specifically ones that didn’t have “English” set as their default language) were having trouble with authentication. I suspected this was due to language differences because I’ve encountered it before on some of my other scripts (like my Minecraft Bedrock dedicated server setup script) that gained international traction as well and once we tested for it sure enough that was the issue.

Yoan graciously shared the solution in the comments:

Function Using-Culture (
[System.Globalization.CultureInfo]$culture = (throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"),
[ScriptBlock]$script= (throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"))
{
    $OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
    trap 
    {
        [System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
    }
    [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture
    Invoke-Command $script
    [System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
}

This was found at Microsoft Developer Blogs – Using Culture Culture ScriptBlock and the blog post contains a ton of information about how to use it and what it’s doing!

In a nutshell it lets you wrap your calls to UMN-Google (or your entire script if you wanted) like this:

Using-Culture en-US {$accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPswd -iss $iss}

This is a really easy way to wrap your functions if you are getting errors related to language differences / default headers / etc. and is definitely worth a try if your PC’s default language is not set to English!

90 thoughts on “Modify Google Sheets (API) Using PowerShell / Uploading CSV Files”

  1. Avatar for Aaron W

    I’m having a hard time installing the UMN-Google Module
    I’m getting an errorPackageManagement\Install-Package : No match was found for the specified search criteria and module name ‘UMN-Google’. Try Get-PSRepository to see all available registered module repositories.
    At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:1772 char:21
    + … $null = PackageManagement\Install-Package @PSBoundParameters
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (Microsoft.Power….InstallPackage:InstallPackage) [Install-Package], Exception
    + FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage

    1. Avatar for James A. Chambers

      Hello Aaron,

      It looks like you are trying to use Install-Package instead of Install-Module.

      Install-Module UMN-Google

      If that isn’t working then I’d imagine the package manager on your computer is broken. Try installing any module if that is the case. Try installing Microsoft’s 365 online module with:

      Install-Module MSOnline

      If you still get a package missing error then you can be sure your package repository is completely broken. Hopefully that helps!

  2. Avatar for jeroen

    I want to run something in powershell remote. So I cant add a pk12 file.
    It worked using the pk12.
    I wanted to use a json and add the content to the ps1 file. But I cant seem to make a connection.
    Did anybody manage to get this to work? And how?

  3. Avatar for Kyle Weeks

    Hey – I’m the original primary author of this module, and I just did a complete overhaul of the UMN-Google powerShell module to work with pwsh 7.4 / backwards compatible to 5.1 tested in Ubuntu as well. The cmdlets have had an overhaul to support the .JSON files directly instead of the pfx files, selenium support for automation over IE11, Device only (No GUI) situations, added cmdlets for dealing with Google Workspace google groups, and am now looking at the back log of somewhat abandoned fix requests and PRs. The cultural time zone can be worked around, but easier to just code it in.

    Cheers to anyone making use of this module ~ Can’t believe there has been over 400K downloads. Kudos to James on an excellent write up of leveraging it!

  4. Avatar for Chris Klemm

    This was a fantastic guide: Quick question, do you know a way to empty a cell using Set-GSheetData? (Or write a $null value)
    I’d like to clear a range before writing new data:

    Simple example:
    Set-GSheetData -accessToken $accessToken -rangeA1 ‘A10:B11’ -sheetName $sheet -spreadSheetID $spreadSheetID -values @(@(“”,””),@(“”,””))
    That command looks like it erases the cell, but it is leaving a ‘value’ there (if you do an =counta(A10:B11), it returns ‘4’ even though it looks blank!

  5. Avatar for Stav

    Hi James,

    Just wanted to let you know I found this post terribly useful. My local game store needed a data integration to expose a certain subset of products to end users, and we figured a periodic sync with a Google Sheet would suit our needs.

    Thanks very much for putting this together!

  6. Avatar for Andrei

    Hi, after I followed your indications my returned output is the following
    Running command:
    Import-Module UMN-Google

    # Set security protocol to TLS 1.2 to avoid TLS errors
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    # Google API Authozation
    $scope = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
    $certPath = "C:\Users\at-admin\Documents\PowerShell\Credentials\prtgsensorsexport-662fedd24e3a.p12"
    $iss = 'mfoo-141@prtgsensorsexport.iam.gserviceaccount.com'
    $certPswd = 'notasecret'
    try {
    $accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPswd -iss $iss
    } catch {
    $err = $_.Exception
    $err | Select-Object -Property *
    "Response: "
    $err.Response
    }

    # Spreadsheet ID # of the Google Sheet
    $SpreadsheetID = '1tMCL-ntbo81EO7pF8cgcPzpEjttnknHwF_v9iHd3P7M'

    # Import CSV and build ArrayList
    $import = New-Object System.Collections.ArrayList($null)
    $inputCsv = Import-Csv "C:\Users\a.turta\Documents\PowerShell\Exports\SheetsExport.csv"
    $inputCsv | ForEach-Object {
    $import.Add( @($_.Computer, $_.'Operating System', $_.RAM)) | Out-Null
    }

    # Build sheet header as it isn't included automatically
    $import.Add( @("Computer", "Operating System", "RAM")) | Out-Null

    # Build ArrayList

    # Upload CSV data to Google Sheets with Set-GSheetData
    try {
    Set-GSheetData -accessToken $accessToken -rangeA1 "A1:C$($import.Count)" -sheetName "Computers" -spreadSheetID $SpreadsheetID -values $import -Debug -Verbose
    } catch {
    $err = $_.Exception
    $err | Select-Object -Property *
    "Response: "
    $err.Response
    }

    Response:

    VERBOSE: HTTP/1.1 PUT with 231-byte payload
    VERBOSE: received -byte response of content type application/json

    Response : StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
    {
    Vary: X-Origin
    Vary: Referer
    Vary: Origin,Accept-Encoding
    Date: Wed, 16 Nov 2022 09:05:38 GMT
    Server: ESF
    Cache-Control: private
    X-XSS-Protection: 0
    X-Frame-Options: SAMEORIGIN
    X-Content-Type-Options: nosniff
    Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443";
    ma=2592000,quic=":443"; ma=2592000; v="46,43"
    Accept-Ranges: none
    Transfer-Encoding: chunked
    Content-Type: application/json; charset=UTF-8
    }
    StatusCode :
    TargetSite : Void ThrowTerminatingError(System.Management.Automation.ErrorRecord)
    Message : Response status code does not indicate success: 400 (Bad Request).
    Data : {}
    InnerException :
    HelpLink :
    Source : System.Management.Automation
    HResult : -2146233088
    StackTrace : at System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecord errorRecord)

    Response:
    Version : 1.1
    Content : System.Net.Http.HttpConnectionResponseContent
    StatusCode : BadRequest
    ReasonPhrase : Bad Request
    Headers : {[Vary, System.String[]], [Date, System.String[]], [Server, System.String[]], [Cache-Control, System.String[]]…}
    TrailingHeaders : {}
    RequestMessage : Method: PUT, RequestUri:
    'https://sheets.googleapis.com/v4/spreadsheets/1tMCL-ntbo81EO7pF8cgcPzpEjttnknHwF_v9iHd3P7M/values/Computers!A1:C3?valueInputOption=RAW',
    Version: 1.1, Content: System.Net.Http.ByteArrayContent, Headers:
    {
    Authorization: Bearer ya29.c.b0Aa9VdykRwaTl1G9UDOqsPXk4oD1N3tz6Mn1OF0xltF4dC6s18enMjIDuPFoavQ6CVPhtxx2A_GJAp77NiuRuT0nEvWs9roIeBNwhbSU0ae
    LLjCCOXWkhVmFGHyhhbXjb1o4DsXCw01QehqVxfnx9x0LSxqFacaKoaCsNhnP5Ez-p-Wi48Lk1Sj3emY6d5a5QBlRbgYidwN-AczvOUOZisZ6idym8pBYSOg...................
    ...........................................................................................................................................
    ...........................................................................................................................................
    ...........................................................................................................................................
    ...........................................................................................................................................
    ...........................................................................................................................................
    ...........................................................................
    User-Agent: Mozilla/5.0
    User-Agent: (Windows NT 10.0; Microsoft Windows 10.0.19041; en-US)
    User-Agent: PowerShell/7.3.0
    Content-Length: 231
    Content-Type: application/json
    }
    IsSuccessStatusCode : False

    Somehow it cannot add content to the new sheets, I managed to clear a sheet that was manually filled, also managed to created a new sheet, but no content can be added.

    1. Avatar for James A. Chambers

      Hey Andrei,

      There’s no way of knowing. I take it you are using English as the main language on your computer as that is the usual problem with this that we’ve covered many times before. There’s no way for us to know though. Those error messages don’t tell us anything.

      You need to log into the Google API console and see why Google is rejecting your request. All your script output tells us is that Google thinks your request is wrong. Only they can tell us why they think your request is wrong though.

      Hopefully that helps!

      1. Avatar for Andrei

        I got it working on my laptop. But when I try to run it from a server I do not pass the accessToken. Do you have any idea how to fix this?

      2. Avatar for Andrei

        # Google API Authozation
        $scope = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
        $certPath = "C:\Automation\Credentials\prtgsensorsexport-a30192e7be2a.p12"
        $iss = 'orchestration@prtgsensorsexport.iam.gserviceaccount.com'
        $certPswd = 'notasecret'
        try {
        $accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPswd -iss $iss -Debug -Verbose
        } catch {
        $err = $_.Exception
        $err | Select-Object -Property *
        "Response: "
        $err.Response
        }

        $accessToken
        VERBOSE: Assembling RSA object based on given certificate file and password
        VERBOSE: POST https://www.googleapis.com/oauth2/v4/token with -1-byte payload

        Status : ProtocolError
        Response : System.Net.HttpWebResponse
        Message : The remote server returned an error: (400) Bad Request.
        Data : {}
        InnerException :
        TargetSite : System.Net.WebResponse GetResponse(System.Net.WebRequest)
        StackTrace : at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.GetResponse(WebRequest request)
        at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.ProcessRecord()
        HelpLink :
        Source : Microsoft.PowerShell.Commands.Utility
        HResult : -2146233079

        Response:
        IsMutuallyAuthenticated : False
        Cookies : {}
        Headers : {Vary, X-XSS-Protection, X-Frame-Options, X-Content-Type-Options...}
        SupportsHeaders : True
        ContentLength : -1
        ContentEncoding :
        ContentType : application/json; charset=UTF-8
        CharacterSet : UTF-8
        Server : scaffolding on HTTPServer2
        LastModified : 7-12-2022 12:18:44
        StatusCode : BadRequest
        StatusDescription : Bad Request
        ProtocolVersion : 1.1
        ResponseUri : https://www.googleapis.com/oauth2/v4/token
        Method : POST
        IsFromCache : False

        1. Avatar for James A. Chambers

          Hello Andrei,

          Welcome! What does Google say is wrong with your request? The PowerShell errors are absolutely useless for the Google API. You need to log into the Google API dashboard and see why Google says your request is bad.

          The PowerShell code is a tiny, tiny wrapper that does almost nothing. It’s literally sending a web request to Google is all PowerShell is doing. It won’t even give you an error. All it’s telling you is Google thinks your request is bad.

          To be successful with this you need to be working with Google Sheets. You are not working with PowerShell. There is no such thing as Google Sheet support for PowerShell. There is only support for using the Google API via REST web requests (which is not specific to PowerShell at all and can be done with any language).

          To work with Google Sheets you need to use the Google API console and tools. Click on your project and it will show you your requests and the errors related to them.

          You can actually work with the Google Sheets API without PowerShell at all. You could even use curl or a web browser. The requests are exactly the same. All this code is doing is sending a web request to Google’s API servers. If you look at the code below UMN-Google it’s literally just a wrapper calling Invoke-RestAPI. You could do this from any language and the requests would be identical whether you’re using PowerShell, raw HTTP, Java, Python, it doesn’t matter.

          If I were to guess there’s still some sort of permissions issue but it really is pointless to speculate without checking the Google console and seeing what your real error is. Hopefully that helps!

  7. Avatar for Saurabh

    Hi James,

    I got an issue with the code lines and after all trail, I surrendered to seek help. The code line was working fine before the format of the machine. But after formatting, I am error getting errors. Detailed error is as –

    PS C:\work\Jenkins\workspace\Batch-Command-Script\GoogleSheetUpdate> ./new1.ps1
    matp9-462@matp9-333504.iam.gserviceaccount.com
    ya29.c.b0AXv0zTPKnUaW_RuTfRxZXC2RAmm4Pm5R_Fwo3t4SLSaMNGNCGsLdURDTD5iB5cUTD_aIj_EUZpKymajs-sdvBM4_40bd-xHV5dAMJIUJ3f3iZx0TdWXdffzDcC54w0IIQZ2My3Sbg698uooGF76vFAx5ZYCAxo2BHXIbIdfezwgNLBixs4yfhz0ZTNyh6Z7eG0tABSEZLvUjnfsrzzh2mL2fB27b-hnzww.....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
    Invoke-RestMethod : The remote server returned an error: (403) Forbidden.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:994 char:23
    + ... $result = Invoke-RestMethod -Method GET -Uri $uri -Headers @{"Autho ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

    Cannot index into a null array.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:999 char:13
    + $Columns = $sheet[0].Count
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Cannot index into a null array.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:1001 char:13
    + $Header = $sheet[0]
    + ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Cannot index into a null array.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:1005 char:25
    + if ($sheet[0][$Column].Length -gt 0) {
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Cannot index into a null array.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:1005 char:25
    + if ($sheet[0][$Column].Length -gt 0) {
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Cannot index into a null array.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:1005 char:25
    + if ($sheet[0][$Column].Length -gt 0) {
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Cannot index into a null array.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:1005 char:25
    + if ($sheet[0][$Column].Length -gt 0) {
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Cannot index into a null array.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:1005 char:25
    + if ($sheet[0][$Column].Length -gt 0) {
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Cannot index into a null array.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:1005 char:25
    + if ($sheet[0][$Column].Length -gt 0) {
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    2:ZZ72
    2:ZZ72
    VERBOSE: PUT https://sheets.googleapis.com/v4/spreadsheets/1Jy6nsYbvx-ituVb_UStVihNivAzy2GKWNaNhWdIIH2s/values/Test1!2:ZZ72?valueInputOption=RAW with -1-byte payload
    Invoke-RestMethod : The remote server returned an error: (403) Forbidden.
    At C:\Program Files\WindowsPowerShell\Modules\UMN-Google\1.2.12\UMN-Google.psm1:1594 char:9
    + Invoke-RestMethod -Method $method -Uri $uri -Body $json -Cont ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

    My Code line is as (the code is working till line 28) -

    Install-Module -Name UMN-Google
    # Set security protocol to TLS 1.2 to avoid TLS errors
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    # Google API Authozation
    $scope = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file"
    #$certPath = "c:\del\Google\erdasimagine-5f4cf65b0153.p12"
    #$iss = 'update@erdasimagine.iam.gserviceaccount.com'
    $certPath = "c:\del\Google\matp9-333504-9736e9264e76.p12"
    $iss = 'matp9-462@matp9-333504.iam.gserviceaccount.com'
    $iss
    $certPswd = 'notasecret'
    try {
    $accessToken = Get-GOAuthTokenService -scope $scope -certPath $certPath -certPswd $certPswd -iss $iss
    } catch {
    $err = $_.Exception
    $err | Select-Object -Property *
    "Response: "
    $err.Response
    }

    $accessToken
    $SpreadsheetID = '1Jy6nsYbvx-ituVb_UStVihNivAzy2GKWNaNhWdIIH2s'
    $import = New-Object System.Collections.ArrayList($null)
    $import.Add( @("Time")) | Out-Null
    $inputCsv = Import-Csv "C:\work\Jenkins\workspace\ExecutionTimings\DE\DEExecutionTime_Final.txt" -UseCulture
    $inputCsv | ForEach-Object {
    $import.Add( @($_.Time)) | Out-Null
    }
    $EmptyColumn = Get-GSheetData -accessToken $accessToken -cell 'Range' -rangeA1 'A1:A1' -sheetName 'Test1' -spreadSheetID $SpreadsheetID
    Write-Host $EmptyColumn
    $StartRow = 2
    #End row in google sheet wrt number of rows in txt file
    $EndRow = 72

    #End column to be considered
    $EndColumn = "ZZ"
    #Formating to generate the range- Start
    $Sample = $EmptyColumn -split '='
    Write-Host $Sample[0]
    $Sample1 = $Sample -split '{'
    Write-Host $Sample1[1]
    "{0}{1}:{2}{3}" -f $Sample1[1],$StartRow,$EndColumn,$EndRow
    $RangeUpdated = $Sample1[1] + $StartRow + ':' + $EndColumn + $EndRow
    Write-Output $RangeUpdated
    #Formating to generate the range- End

    #Updating the Goolge Sheet
    Set-GSheetData -accessToken $accessToken -rangeA1 $RangeUpdated -sheetName "Test1" -spreadSheetID $SpreadsheetID -values $import -Debug -Verbose

    1. Avatar for James A. Chambers

      Hey Saurabh,

      Whoa, that is a strange one! At first glance I assumed it was a permissions error but things got a little weird there in the middle with something like 8 exceptions not to mention it was working before your reimage so it’s definitely not permissions on this one.

      I looked at the UMN-Google code to see what your stack trace means:

      994 $result = Invoke-RestMethod -Method GET -Uri $uri -Headers @{"Authorization"="Bearer $accessToken"}
      995
      996 # Formatting the returned data
      997 $sheet = $result.values
      998 $Rows = $sheet.Count
      999 $Columns = $sheet[0].Count
      1000 $HeaderRow = 0
      1001 $Header = $sheet[0]
      1002 foreach ($Row in (($HeaderRow + 1)..($Rows-1))) {
      1003 $h = [Ordered]@{}
      1004 foreach ($Column in 0..($Columns-1)) {
      1005 if ($sheet[0][$Column].Length -gt 0) {

      We can see why it’s throwing so many errors. What is actually happening is that it’s giving you a forbidden error trying to access the sheet. The rest of the errors are just it choking on the fact that the data is empty. It doesn’t seem to have any checks for this which would have just made it a single error but since it keeps trying to go forward with empty/null variables it ends up throwing like 8 errors.

      It looks like you are getting a token. I’m a little confused by your output what all of these dots are here:

      ya29.c.b0AXv0zTPKnUaW_RuTfRxZXC2RAmm4Pm5R_Fwo3t4SLSaMNGNCGsLdURDTD5iB5cUTD_aIj_EUZpKymajs-sdvBM4_40bd-xHV5dAMJIUJ3f3iZx0TdWXdffzDcC54w0IIQZ2My3Sbg698uooGF76vFAx5ZYCAxo2BHXIbIdfezwgNLBixs4yfhz0ZTNyh6Z7eG0tABSEZLvUjnfsrzzh2mL2fB27b-hnzww.....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

      What is that? Is that a part of the access token variable? That would probably make it very angry and I’ve never seen anything quite like that. That should somehow be a part of the access token variable it looks like.

      If it’s sending that to Google it probably thinks you are trying to attack their servers. Why? That is called dotwalking. Many old web applications (and some still today) are vulnerable to using .. characters for path traversal to access things they aren’t supposed to. For example they could get the Linux “passwd” file like this if Google had no clue what they were doing: http://google.com/../../../../etc/passwd

      I think you may be triggering a security response. I’m not sure why you are getting all these dots. Can you check the access token variable and confirm that the dots are a part of that variable and not coming from somewhere else? I don’t know where they are coming from but theoretically you could just trim/remove the dots from the access token but they should not be there for sure. You would not want to remove the single dots earlier in the string that are separators. You would only want to remove the massive cluster of dots at the end of the string.

      Is the computer set to the right locale / localization settings / language / timezone / all of that after a reimage? One of the only ways I could think that this could happen is if your localization/language settings might be different than the previous install. Hopefully that helps!

      1. Avatar for Saurabh

        Hi James, sorry for the delayed reply! The problem got resolved after creating a fresh Service Account Key. About your query on dotwalking not sure from where those are coming (not that good in programming) but the good thing is the problem got resolved even though dots are present.

Leave a Comment

Your email address will not be published. Required fields are marked *

Type here..

Exit mobile version