cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
BenjaminSelby
Community Participant

How to upload a file via API using Powershell?

Jump to solution

I'm trying to upload a file via the API using Powershell. I am an administrator with 'impersonate' authorisation. I have tried following the API documentation for this process but am having trouble putting it into practice. 

My understanding is that the first step is to notify of a pending file upload and obtain a target URL: 

Invoke-WebRequest -headers $headers -method POST -URI https://<URL>:443/api/v1/folders/<FOLDER_ID>/files

This generates the following response: 

file_param : file
progress :
upload_url : https://inst-fs-syd-prod.inscloudgate.net/files?token=<TOKEN>;
upload_params : @{filename=; content_type=}

So, my understanding is that I now POST the file contents to that UPLOAD_URL, right? Except that I can't seem to get anything to work. I have tried the following: 

$uri = '<UPLOAD_URL>'

$body = @{
   'filename'='<FILE_NAME>;
   'content_type'='text/plain';
   'file'=[IO.File]::ReadAllBytes('<FILE_PATH>')}

Invoke-RestMethod -uri $uri -method POST -body $body

I receive the following response: 

Invoke-RestMethod: {"error":"No #file uploaded"}

I have tried various other permutations of command configurations, but can't get anything to work. Can anyone help, please? 

If there is no way of doing this with Powershell, maybe somebody can provide a method using a different language? I'd be interested in any input. 

Thank you. 

1 Solution

Accepted Solutions
BenjaminSelby
Community Participant

I have found a solution to this, posting it here for anyone else who has a similar query in the
future. 

I struggled with this for days. It was fairly easy to come up with a means of submitting text
files, but binary data files (e.g. images) proved to be more difficult. It wasn't easy to read
the binary file in and embed the binary values within a Powershell string, which was necessary
because I was using a string to create the multipart/form-data POST body, like this:

>> $requestBody = @"
--$boundary
Content-Disposition: form-data; name ="file"; filename ="$($file.Name)"
Content-type: $contentType

$([system.io.file]::readallbytes('<FILE_PATH>') )

--$boundary--
"@

I tried a variety of methods to achieve this, but nothing worked. In the end, it turns out that
the following fairly simple solution works well. 

(I'll describe the entire process, and not just the file upload, to help anyone else who is stuck with any part of this procedure.) 

First, you have to obtain the ID number of the folder you want to upload the file into. To do this, make a call to the
API listing all the user's folders, and filter to get the folder ID number.


>> $Token = '<TOKEN>'
>> $headers = @{"Authorization"="Bearer "+$token}
>> $uri = 'https://<HOSTNAME>:443/api/v1/users/<USER_ID>/folders'
>> $response = Invoke-WebRequest -Headers $headers -URI $uri
>> $response.content | ConvertFrom-JSON | Where-Object -Property Name -EQ 'my files'


This yields a Powershell object which contains the folder id (can be saved to a variable).


id : <FOLDER_ID>
name : my files
full_name : my files
context_id : ...
context_type : User
parent_folder_id :
created_at : 11/10/2019 3:43:51 AM
... etc


Then initiate the file upload transaction using this folder id number to obtain an upload URI:


>> $response = Invoke-WebRequest `
-URI 'https://<HOSTNAME>:443/api/v1/folders/<FOLDER_ID>/files' `
-headers $headers `
-method POST

>> $uri_upload = $($response.content | ConvertFrom-JSON).upload_url


This upload URI has a life span of 30 minutes, and cannot be used after timeout. Also note that
the response content field contains a list of parameters which should be included in the POST
submission body along with the file data. In my case, these parameters are FILENAME and
CONTENT_TYPE.

>> $response.content | ConvertFrom-JSON | Format-List

file_param : file
progress :
upload_url : https://<UPLOAD_URL>;
upload_params : @{filename=; content_type=}


Then construct a hashmap which includes the file to be uploaded, along with the file parameters
specified in the response above. This hashmap is passed to the Invoke-RestMethod powershell
command which sends the file as part of a form submission.


>> $form = @{
   filename = '<FILE_NAME>'
   file = Get-Item -Path '<FILE_PATH>'
   content_type = 'image/bmp'}

>> $response = Invoke-RestMethod `
   -URI $uri_upload `
   -Method POST `
   -Form $form

>> Write-Host "$($response.size) bytes uploaded."


The file should now appear in the target folder for that user in Canvas. 

View solution in original post

0 Kudos
3 Replies
BenjaminSelby
Community Participant

I have found a solution to this, posting it here for anyone else who has a similar query in the
future. 

I struggled with this for days. It was fairly easy to come up with a means of submitting text
files, but binary data files (e.g. images) proved to be more difficult. It wasn't easy to read
the binary file in and embed the binary values within a Powershell string, which was necessary
because I was using a string to create the multipart/form-data POST body, like this:

>> $requestBody = @"
--$boundary
Content-Disposition: form-data; name ="file"; filename ="$($file.Name)"
Content-type: $contentType

$([system.io.file]::readallbytes('<FILE_PATH>') )

--$boundary--
"@

I tried a variety of methods to achieve this, but nothing worked. In the end, it turns out that
the following fairly simple solution works well. 

(I'll describe the entire process, and not just the file upload, to help anyone else who is stuck with any part of this procedure.) 

First, you have to obtain the ID number of the folder you want to upload the file into. To do this, make a call to the
API listing all the user's folders, and filter to get the folder ID number.


>> $Token = '<TOKEN>'
>> $headers = @{"Authorization"="Bearer "+$token}
>> $uri = 'https://<HOSTNAME>:443/api/v1/users/<USER_ID>/folders'
>> $response = Invoke-WebRequest -Headers $headers -URI $uri
>> $response.content | ConvertFrom-JSON | Where-Object -Property Name -EQ 'my files'


This yields a Powershell object which contains the folder id (can be saved to a variable).


id : <FOLDER_ID>
name : my files
full_name : my files
context_id : ...
context_type : User
parent_folder_id :
created_at : 11/10/2019 3:43:51 AM
... etc


Then initiate the file upload transaction using this folder id number to obtain an upload URI:


>> $response = Invoke-WebRequest `
-URI 'https://<HOSTNAME>:443/api/v1/folders/<FOLDER_ID>/files' `
-headers $headers `
-method POST

>> $uri_upload = $($response.content | ConvertFrom-JSON).upload_url


This upload URI has a life span of 30 minutes, and cannot be used after timeout. Also note that
the response content field contains a list of parameters which should be included in the POST
submission body along with the file data. In my case, these parameters are FILENAME and
CONTENT_TYPE.

>> $response.content | ConvertFrom-JSON | Format-List

file_param : file
progress :
upload_url : https://<UPLOAD_URL>;
upload_params : @{filename=; content_type=}


Then construct a hashmap which includes the file to be uploaded, along with the file parameters
specified in the response above. This hashmap is passed to the Invoke-RestMethod powershell
command which sends the file as part of a form submission.


>> $form = @{
   filename = '<FILE_NAME>'
   file = Get-Item -Path '<FILE_PATH>'
   content_type = 'image/bmp'}

>> $response = Invoke-RestMethod `
   -URI $uri_upload `
   -Method POST `
   -Form $form

>> Write-Host "$($response.size) bytes uploaded."


The file should now appear in the target folder for that user in Canvas. 

View solution in original post

0 Kudos

 @BenjaminSelby ‌

It was so generous of you to take the time to share your solution with the Canvas Community. Thanks for being so helpful to your fellow community members, especially in these difficult times. Please check your direct messages for a small token of our appreciation. :0)

Thanks again for all that you do and keep up the good work!

Sincerely,

Canvas Community Team

BenjaminSelby
Community Participant

Thank you very much, Stefanie. My pleasure.