Public/Set-GDriveItemContent.ps1
<# .SYNOPSIS Creates or updates GoogleDrive Item, set metadata and upload content .DESCRIPTION Creates or updates GoogleDrive Item, set metadata and upload content If needed tp update existing file, use ID If only metadata update required, use Set-GDriveItemProperty .PARAMETER ID File ID to update .PARAMETER StringContent Content to upload as string .PARAMETER Encoding Enconding used for string .PARAMETER RawContent Content to upload as raw byte[] array .PARAMETER InFile Content to upload as path to file .PARAMETER Name Name of an item to be created .PARAMETER ParentID Folder ID(s) in which new item will be placed .PARAMETER JsonProperty Json-formatted string with all needed file metadata .PARAMETER ResumeID Upload ID to resume operations in case of uploading errors .PARAMETER ContentType Uploaded item Content type (seems google automatically set it to most of uploaded files) .PARAMETER ChunkSize Upload request size .PARAMETER ShowProgress Show progress bar while uploading .PARAMETER AccessToken Access Token for request .EXAMPLE #Named File based upload Set-GDriveItemContent -AccessToken $access_token -InFile D:\SomeDocument.doc -Name SomeDocument.doc .EXAMPLE #Named Raw data upload with ParentID [byte[]]$Content = Get-Content D:\SomeDocument.doc -Encoding Bytes $ParentFolder = Find-GDriveItem -AccessToken $access_token -Query 'name="myparentfolder"' Set-GDriveItemContent -AccessToken $access_token -RawContent -Name SomeDocument.doc -ParentID $ParentFolder.files.id .EXAMPLE #String based upload with metadata Add-GDriveItem -AccessToken $access_token -StringContent 'test file' -JsonProperty '{ "name":"myfile.txt" }' .EXAMPLE #String based update upload Set-GDriveItemContent -AccessToken $access_token -ID '0BAjkl4cBDNVpVbB5nGhKQ195aU0' -StringContent 'test file' .EXAMPLE #File based resume operation $Result = Set-GDriveItemContent -AccessToken $access_token -InFile D:\SomeDocument.doc -Name SomeDocument.doc if ($Result.Error) { Set-GDriveItemContent -AccessToken $access_token -InFile D:\SomeDocument.doc ` -Name SomeDocument.doc -ResumeID $Result.ResumeID } .OUTPUTS PSObject with properties: Item: Json with item metadata as PSObject ResultID: Upload ID for resume operations Error: Error info if happen .NOTES Author: Max Kozlov .LINK Add-GDriveItem Set-GDriveItemProperty https://developers.google.com/drive/api/v3/reference/files/create https://developers.google.com/drive/api/v3/reference/files/update https://developers.google.com/drive/api/v3/resumable-upload #> function Set-GDriveItemContent { [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Position=0, ParameterSetName='dataMeta')] [Parameter(Position=0, ParameterSetName='stringMeta')] [Parameter(Position=0, ParameterSetName='fileMeta')] [string]$ID, [Parameter(Mandatory, ParameterSetName='stringName')] [Parameter(Mandatory, ParameterSetName='stringMeta')] [string]$StringContent, [Parameter(ParameterSetName='stringName')] [Parameter(ParameterSetName='stringMeta')] [System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8, [Parameter(Mandatory, ParameterSetName='dataName')] [Parameter(Mandatory, ParameterSetName='dataMeta')] [byte[]]$RawContent, [Parameter(Mandatory, ParameterSetName='fileName')] [Parameter(Mandatory, ParameterSetName='fileMeta')] [string]$InFile, [Parameter(Mandatory, ParameterSetName='dataName')] [Parameter(Mandatory, ParameterSetName='stringName')] [Parameter(Mandatory, ParameterSetName='fileName')] [string]$Name, [Parameter(ParameterSetName='dataName')] [Parameter(ParameterSetName='stringName')] [Parameter(ParameterSetName='fileName')] [Alias('DestinationID')] [string[]]$ParentID = @('root'), [Parameter(ParameterSetName='dataMeta')] [Parameter(ParameterSetName='stringMeta')] [Parameter(ParameterSetName='fileMeta')] [Alias('Metadata')] [string]$JsonProperty = '', [string]$ResumeID, [string]$ContentType = 'application/octet-stream', [ValidateScript({ (-not ($_ -band 0x3FFFF)) -or ( & { throw 'ChunkSize must be in multiples of 256 KB (256 x 1024 bytes) in size' } ) })] [int]$ChunkSize = 4Mb, [switch]$ShowProgress, [Parameter(Mandatory)] [string]$AccessToken ) $UploadResult = [PSCustomObject]@{ Item = $null ResumeID = '' Error = $null } if ($PSCmdlet.ParameterSetName -in 'stringName','stringMeta') { [byte[]]$RawContent = $Encoding.GetBytes($StringContent) Write-Verbose "Encoded $($StringContent.Length) characters to $($RawContent.Count) bytes ($($Encoding.EncodingName))" } if ($PSCmdlet.ParameterSetName -in 'stringName','dataName','fileName') { $JsonProperty = '{{ "name": "{0}", "parents": ["{1}"] }}' -f $Name, ($ParentID -join '","') Write-Verbose "Constructed Metadata: $JsonProperty" } try { if ($PSCmdlet.ParameterSetName -in 'fileName','fileMeta') { $stream = New-Object System.IO.FileStream $InFile, 'Open' } else { $stream = New-Object System.IO.MemoryStream $RawContent, $false } } catch { $UploadResult.Error = $_.Exception $UploadResult Write-Error $_.Exception return } try { $Headers = @{ "Authorization" = "Bearer $AccessToken" "X-Upload-Content-Type" = $ContentType "X-Upload-Content-Length" = $stream.Length } $WebRequestParams = @{ Headers = $Headers ContentType = "application/json; charset=utf-8" MaximumRedirection = 0 UseBasicParsing = $true Body = $JsonProperty } if ($PSBoundParameters.ContainsKey('ID')) { Write-Verbose "Updating File $ID" # Patch instead of Put! docs are wrong? Put give 404 $WebRequestParams.Method = 'Patch' $WebRequestParams.Uri = "$($GDriveUploadUri)$($ID)?supportsTeamDrive=true&uploadType=resumable&fields=kind,id,name,mimeType,parents" } else { Write-Verbose "Creating New file" $WebRequestParams.Method = 'Post' $WebRequestParams.Uri = "$($GDriveUploadUri)?supportsAllDrives=true&uploadType=resumable&fields=kind,id,name,mimeType,parents" } Write-Verbose ("URI: " + $WebRequestParams.Uri) if ($ResumeID) { #To request the upload status, create an empty PUT request to the resumable session URI. Write-Verbose "Use Resume ID $ResumeID" $WebRequestParams.Uri += '&upload_id=' + $ResumeID $WebRequestParams.Method = 'Put' [void]$WebRequestParams.Remove('Body') $WebRequestParams.Headers['Content-Range'] = 'bytes */{0}' -f $stream.Length } Write-Verbose ('Metadata upload, resumable, {0} bytes, {1}' -f $stream.Length, $ContentType) $uploadString = 'Uploading ' + $PSCmdlet.ParameterSetName -replace '(Name|Meta)' if ($PSCmdlet.ParameterSetName -match 'Name') { $uploadString += " named '$Name'" } if ($PSCmdlet.ParameterSetName -match 'file') { $uploadString += " from [$InFile]" } elseif ($PSCmdlet.ParameterSetName -match 'string') { $uploadString += " from [string]" } elseif ($PSCmdlet.ParameterSetName -match 'data') { $uploadString += " from [byte[] array]" } if ($ShowProgress) { Write-Progress -Activity $uploadString -Status 'Metadata upload' -PercentComplete 1 } $wr = $null try { if ($PSCmdlet.ShouldProcess($ID, $uploadString)) { $wr = Invoke-WebRequest @WebRequestParams @GDriveProxySettings } } catch { $UploadResult.Error = $_.Exception $UploadResult Write-Error $_.Exception return } if ($wr.StatusCode -in 200,308) { $UploadResult.ResumeID = $wr.Headers['X-GUploader-UploadID'] Write-Verbose "ResumeID: $($UploadResult.ResumeID)" try { # Resume already have the right URI if ($wr.Headers['Location']) { $WebRequestParams['Uri'] = $wr.Headers['Location'] } [long]$UploadedSize = 0 Write-Verbose "Received Range: $($wr.Headers['Range'])" if ($wr.Headers['Range'] -match 'bytes=(\d+)-(\d+)') { $UploadedSize = ([long]$matches[2]) + 1 Write-Verbose "Stream Position: $($stream.Position), UploadedSize:$($UploadedSize)" if ($stream.Position -ne $UploadedSize) { Write-Verbose "Fast Forward to:$($UploadedSize)" [void]$stream.Seek($UploadedSize, [System.IO.SeekOrigin]::Begin) } } $WebRequestParams.Method = 'Put' if ($PSCmdlet.ShouldProcess($ID, $uploadString)) { if ($UploadedSize -eq 0 -and $stream.Length -le $ChunkSize -and ($PSCmdlet.ParameterSetName -notin 'fileName','fileMeta')) { Write-Verbose 'Single request upload' $WebRequestParams.Headers = @{ "Authorization" = "Bearer $AccessToken" "Content-Type" = $ContentType "Content-Length" = $stream.Length } $WebRequestParams.Body = $RawContent Write-Verbose ("Content-Length: {0}" -f $WebRequestParams.Headers['Content-Length']) if ($ShowProgress) { Write-Progress -Activity $uploadString -Status "Content upload [0-$($stream.Length)/$($stream.Length)]" -PercentComplete 99 } $wr = Invoke-WebRequest @WebRequestParams @GDriveProxySettings } else { Write-Verbose 'Multiple requests upload' [byte[]]$buffer = New-Object byte[] $ChunkSize do { [long]$nextSize = [Math]::Min($UploadedSize + $ChunkSize, $stream.Length) $Range = "bytes $($UploadedSize)-$($nextSize-1)/$($stream.Length)" $Length = [Math]::Min($stream.Length - $UploadedSize, $ChunkSize) $WebRequestParams.Headers = @{ "Authorization" = "Bearer $AccessToken" "Content-Type" = $ContentType "Content-Range" = $Range "Content-Length" = $Length } # last buffer can be smaller if ($Length -lt $ChunkSize) { [byte[]]$buffer = New-Object byte[] $Length } $len = $stream.Read($buffer, 0, $Length); if ($len -ne $Length) { throw "Stream read error: Readed $len bytes instead of $Length" } $WebRequestParams.Body = $buffer Write-Verbose ("Content-Length: {0}, Content-Range {1}, readed: {2}" -f $WebRequestParams.Headers["Content-Length"], $WebRequestParams.Headers['Content-Range'], $len) if ($ShowProgress) { Write-Progress -Activity $uploadString -Status "Content upload [$($Range -replace 'bytes ')]" -PercentComplete ($nextSize*100/$stream.Length) } $wr = Invoke-WebRequest @WebRequestParams @GDriveProxySettings switch ($wr.StatusCode) { 308 { # bytes=0-262143 Write-Verbose "Received Range: $($wr.Headers['Range'])" if ($wr.Headers['Range'] -match 'bytes=(\d+)-(\d+)') { $UploadedSize = ([long]$matches[2]) + 1 Write-Verbose "Stream Position: $($stream.Position), UploadedSize:$($UploadedSize)" if ($stream.Position -ne $UploadedSize) { Write-Verbose "Fast Forward to:$($UploadedSize)" [void]$stream.Seek($UploadedSize, [System.IO.SeekOrigin]::Begin) } } } } } until ($wr.StatusCode -eq 200) } $UploadResult.Item = ($wr.Content | ConvertFrom-Json) } $UploadResult } catch { $UploadResult.Error = $_.Exception $UploadResult Write-Error $_.Exception } } } finally { if ($ShowProgress) { Write-Progress -Activity $uploadString -Completed } $stream.Close() $stream.Dispose() } } |