My organization is currently in the process of migrating from Office 365 to G Suite, as well as migrating to Team Drives from a traditional SMB file server. This has resulted in a need for an easy method to interact with Google Drive using PowerShell, as I have a number of scripts that store and read data on SMB file shares. Thanks to Montel Edwards for getting me started with this post.

I am working on a GDrive module that uses concepts from this post to mirror many built-in PowerShell functions, such as: Get-Item, Get-ChildItem, New-Item, etc. The GDrive module would include functions such as: Get-GDriveItem, Get-GDriveChildItem, New-GDriveItem, etc.

Create Project and OAuth 2 Tokens

Write stuff

Get Refresh Token

# Set the Google Auth parameters. Fill in your RefreshToken, ClientID, and ClientSecret
$params = @{
    Uri = 'https://accounts.google.com/o/oauth2/token'
    Body = @(
        "refresh_token=$RefreshToken", # Replace $RefreshToken with your refresh token
        "client_id=$ClientID",         # Replace $ClientID with your client ID
        "client_secret=$ClientSecret", # Replace $ClientSecret with your client secret
        "grant_type=refresh_token"
    ) -join '&'
    Method = 'Post'
    ContentType = 'application/x-www-form-urlencoded'
}
$accessToken = (Invoke-RestMethod @params).access_token

Download from Drive

Downloading from Drive can be done two different ways:

  1. Exporting Google Apps formats to standard formats.
  2. Downloading binary data as-is.

We can pull the file metadata and then look at the MIME type to determine which method to use.

First we have to provide the fileId, the destinationPath, and set the authentication headers.

# Change this to the id of the file you want to download. Right click a file and click 'Get shareable link' to find the ID
$fileId = 'fileId'

# Change this to where you want the resulting file to be placed. Just the path; do not include the file name
$destinationPath = 'Path\To\Store\File'

# Set the authentication headers
$headers = @{
    'Authorization' = "Bearer $accessToken"
    'Content-type' = 'application/json'
}

We can now use this data to get the file metadata, determine which format to download the file in, and then download or export the file.

This will download the file to $destinationPath with the name of the file in Google Drive.

# Get the file metadata
$fileMetadata = Invoke-RestMethod -Uri "https://www.googleapis.com/drive/v3/files/$fileId" -Headers $headers

# If this is a Google Apps filetype, export it
if ($fileMetadata.mimetype -like 'application/vnd.google-apps.*') {
    # Determine which mimeType to use when exporting the files
    switch ($fileMetadata.mimetype) {
        'application/vnd.google-apps.document' {
            $exportMime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
            $exportExt = '.docx'
        }
        'application/vnd.google-apps.presentation' {
            $exportMime = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
            $exportExt = '.pptx'
        }
        'application/vnd.google-apps.spreadsheet' {
            $exportMime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
            $exportExt = '.xlsx'
        }
        'application/vnd.google-apps.drawings' {
            $exportMime = 'image/png'
            $exportExt = '.png'
        }
        'application/vnd.google-apps.script' {
            $exportMime = 'application/vnd.google-apps.script+json'
            $exportExt = '.json'
        }
    }
    $exportFileName = "$destinationPath\$($fileMetadata.name)$exportExt"
    Invoke-RestMethod -Uri "https://www.googleapis.com/drive/v3/files/$fileId/export?mimeType=$exportMime" -Method Get -OutFile $exportFileName -Headers $headers
} else {
    # If this is a binary file, just download it as-is.
    $exportFileName = "$destinationPath\$($fileMetadata.name)"
    Invoke-RestMethod -Uri "https://www.googleapis.com/drive/v3/files/${fileId}?alt=media" -Method Get -OutFile $exportFileName -Headers $headers
}

Upload to Drive

Get the source file, encode in Base64 and get the MIME type.

# Change this to the file you want to upload
$SourceFile = 'C:\Path\To\File'

# Get the source file contents and details, encode in base64
$sourceItem = Get-Item $sourceFile
$sourceBase64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($sourceItem.FullName))
$sourceMime = [System.Web.MimeMapping]::GetMimeMapping($sourceItem.FullName)

Set the file metadata. Additional properties can be added as needed. Reference the files/create documentation for additional properties.

# Set the file metadata
$uploadMetadata = @{
    originalFilename = $sourceItem.Name
    name = $sourceItem.Name
    description = $sourceItem.VersionInfo.FileDescription
}

Build the multipart upload body from the base64 data and the file metadata.

# Set the upload body
$uploadBody = @"
--boundary
Content-Type: application/json; charset=UTF-8

$($uploadMetadata | ConvertTo-Json)

--boundary
Content-Transfer-Encoding: base64
Content-Type: $sourceMime

$sourceBase64
--boundary--
"@

Set the upload headers and perform the upload.

# Set the upload headers
$uploadHeaders = @{
    "Authorization" = "Bearer $accessToken"
    "Content-Type" = 'multipart/related; boundary=boundary'
    "Content-Length" = $uploadBody.Length
}
# Perform the upload
$response = Invoke-RestMethod -Uri 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart' -Method Post -Headers $uploadHeaders -Body $uploadBody

Uploading to a Specific Folder

To upload to a specific folder rather than to the root of your Google Drive, you need to specify a parent ID in the file metadata. The API documentation says that parents should be an array, but it seems to error out if you provide more than one value. I don’t fully understand the use case for multiple parents, but for simple uploads having 1 works just fine.

The folder ID can be seen in your address bar when browsing Google Drive. For example: https://drive.google.com/drive/folders/1hUL97Xd6tEiR-44fV7PYQ9BZaulA3ASg

$uploadMetadata = @{
    originalFilename = $sourceItem.Name
    name = $sourceItem.Name
    description = $sourceItem.VersionInfo.FileDescription
    parents = @(‘folderId’)
}

Uploading to a Team Drive

If you would like to upload to a Team Drive rather than to your ‘My Drive’, you have to modify the $uploadMetadata and add &supportsTeamDrives=true to the upload URI. Similar to the ‘Uploading to a Specific Folder’ scenario, we must provide a parent ID. In this case the parent ID is either the team drive ID (to upload to the root of the team drive), or a folder ID (to upload to a folder within the team drive). We also have to provide a teamDriveId, which can be found in your address bar, just like the folder ID.

$uploadMetadata = @{
    originalFilename = $sourceItem.Name
    name = $sourceItem.Name
    description = $sourceItem.VersionInfo.FileDescription
    parents = @(‘teamDriveId or folderId’)
    teamDriveId = ‘teamDriveId’
}

When performing the upload, we change the -Uri parameter to include the parameter &supportsTeamDrives=true.

$response = Invoke-RestMethod -Uri 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsTeamDrives=true' -Method Post -Headers $uploadHeaders -Body $uploadBody

Completed Scripts

Download

Upload