6

I am working on a series of Powershell scripts for my company to use to transfer data to and from Box.com. The one thing I just can't figure out is uploading files. The Box API requires a multipart POST operation for uploads, and I've seen a few answers here on SO indicating that I should be able to do that in Powershell (such as this one). But I can't seem to get it working.

Here's the code I have right now:

Function Post-File {
    Param(
            [Parameter(Mandatory=$True,Position=1)]
            [string]$SourcePath,
            [Parameter(Mandatory=$False,Position=2)]            
            [string]$FolderId = ############
    )

    #Variables for building URIs
    $baseUrl = "https://upload.box.com/api/2.0/files/content"

    #Set Authorization for API requests
    $headers = @{}
    $AccessToken = Refresh-Tokens #A reference to another function that definitely works
    $headers.Add("Authorization", "Bearer $AccessToken")

    #Set POST content
    $body = @{}
    $body.Add("filename",  [IO.File]::ReadAllBytes($SourcePath))
    $body.Add("parent_id", $FolderId)

    #Upload the file
    Invoke-RestMethod -Uri $baseUrl -Method Post -Headers $headers -ContentType "multipart/form-data" -Body $body
}

Here's the response I get back:

{
 "type":"error",
 "status":400,
 "code":"invalid_request_parameters",
 "help_url":"http://developers.box.com/docs/#errors",
 "message":"Invalid input parameters in request",
 "request_id":"1764475572534bcddfe04b7"
}

I've also tried a couple of other permutations that aren't working. I've tried using the -InFile switch in Invoke-RestMethod instead of -Body. I've also tried using Get-Content -Raw in place of [IO.File]::ReadAllBytes. Both of those return a more generic error: The server encountered an internal error or misconfiguration and was unable to complete your request..

I'm pretty sure this has something to do with my filename parameter not being correct, but I'm not sure how to fix it. According to the Box API, here's what it should look like in curl. Can someone help me properly translate this for Powershell?

https://upload.box.com/api/2.0/files/content \
-H "Authorization: Bearer ACCESS_TOKEN" \
-F filename=@FILE_NAME \
-F parent_id=PARENT_FOLDER_ID
1
  • From Spray 1.1.1 server, gives the error: "Content-Type with a multipart media type must have a non-empty 'boundary' parameter" Commented Aug 1, 2014 at 13:21

4 Answers 4

8

There are a couple things that make this a little tricky in PowerShell:

  1. The filename parameter specifies the name of the file, not the contents.
  2. A request body is required in order to specify the file name and destination.
  3. PowerShell treats -InFile and -Body arguments as mutually exclusive.
  4. PowerShell does not appear to natively support multipart/form-data POSTs, per the question that you referenced.

Since the request body is required (1,2) -- and thus -InFile can't be used (3) -- I think you might need to roll your own multipart/form-data body (4) that contains the necessary metadata and file content. This blog post describes a method for doing so. The content there is a short string (a tweet) but the principle is the same.

Below is a Fiddler trace of an upload I just performed using the Box Windows SDK. This shows how the request should look as it goes across the wire. The $BOUNDARY is an arbitrary, unique string -- a GUID works great.

POST https://upload.box.com/api/2.0/files/content HTTP/1.1
Authorization: Bearer $TOKEN
Content-Type: multipart/form-data; boundary="$BOUNDARY"
Host: upload.box.com
Content-Length: 2118
Accept-Encoding: gzip, deflate

--$BOUNDARY

Content-Disposition: form-data; name="file"; filename="$FILENAME"

<THE CONTENT OF $FILENAME>    

--$BOUNDARY

Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name="metadata"

{"parent":{"id":"$PARENT_FOLDER_ID"},"name":"$FILENAME"}

--$BOUNDARY--

Here is the response I received:

HTTP/1.1 201 Created
Date: Mon, 14 Apr 2014 12:52:33 GMT
Server: Apache
Cache-Control: no-cache, no-store
Content-Length: 364
Connection: close
Content-Type: application/json

{"total_count":1,"entries":[{"type":"file","id":"$ID","name":"$FILENAME", ... }]}
Sign up to request clarification or add additional context in comments.

4 Comments

Awesome, thank you. I have a feeling it may take me a while to implement this, but I'll definitely start working on it and accept this answer if I can get it to work.
Hot dog! Glad that helped.
@JeffRosenberg Would you consider posting back how you rolled your own? There are many SO questions where people are struggling with this.
Ditto @JeffRosenberg 's suggestion.
0

I may sound dumb, but what happens when you do this?

$body.Add("filename",  $([IO.File]::ReadAllBytes($SourcePath)))

1 Comment

Same response, I'm afraid. :-(
0

I cannot add a comment to the original and this one is pretty old, but here is how I 'rolled my own' multipart/form-data:

    $boundary = [System.Guid]::NewGuid().ToString()

    # Read file into memory
    # encoding required to prevent corruption of file on upload
    $enc = [System.Text.Encoding]::GetEncoding("ISO-8859-1")  
    $fileContent = $enc.GetString([IO.File]::ReadAllBytes($file.FullName))

    # the body syntax, including line breaks, is very specific
    $body = "--$boundary"
    $body += "`r`n" + 'Content-Disposition: form-data; name="file"; filename="' + $($file.Name) + """"
    $body += "`r`n" + 'Content-Type: application/octet-stream'
    $body += "`r`n"
    $body += "`r`n"
    $body += $fileContent
    $body += "`r`n" + "--$($boundary)--"

    # header will contain the application access token
    $headers = @{
        'Authorization' = "Bearer $accessToken"
        'Content-Type'   = "multipart/form-data; boundary=$boundary"
    }

    $uploadEndpoint = "https://upload.box.com/api/2.0/files/content?filename=$($file.Name)&parent_id=$folderId"

    $fileUploadResponse = Invoke-RestMethod -Uri $uploadEndpoint -Method Post -Headers $headers -Body $body

Comments

0
# ==============================
# Upload file to Box API (PowerShell 5 compatible)
# ==============================

# ---- Configuration ----
$accessToken = "<YOUR_ACCESS_TOKEN>"          # Replace with your Box API access token
$filePath = "C:\Users\User\Documents\demo.txt" # Path to the file you want to upload
$folderId = "0"                               # Target folder ID on Box (0 = root folder)

try {
    # Extract file name from file path
    $fileName = [System.IO.Path]::GetFileName($filePath)
    $uri = "https://upload.box.com/api/2.0/files/content"
    $boundary = [System.Guid]::NewGuid().ToString()   # Generate a unique boundary
    $LF = "`r`n"                                      # Line break (CRLF) for multipart format

    # Create the JSON attributes required by Box
    $attributesJson = "{""name"": ""$fileName"", ""parent"": {""id"": ""$folderId""}}"

    # Read the file bytes into memory
    $fileBytes = [System.IO.File]::ReadAllBytes($filePath)

    # Create a memory stream to build the multipart request body
    $bodyStream = New-Object System.IO.MemoryStream
    $writer = New-Object System.IO.StreamWriter($bodyStream)

    # ---- Write the "attributes" part ----
    $writer.Write("--$boundary$LF")
    $writer.Write("Content-Disposition: form-data; name=`"attributes`"$LF$LF")
    $writer.Write("$attributesJson$LF")

    # ---- Write the "file" part ----
    $writer.Write("--$boundary$LF")
    $writer.Write("Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"$LF")
    $writer.Write("Content-Type: application/octet-stream$LF$LF")
    $writer.Flush()

    # Write the actual file content (binary data)
    $bodyStream.Write($fileBytes, 0, $fileBytes.Length)

    # ---- End of multipart ----
    $end = "$LF--$boundary--$LF"
    $endBytes = [System.Text.Encoding]::ASCII.GetBytes($end)
    $bodyStream.Write($endBytes, 0, $endBytes.Length)

    # Reset stream position to the beginning
    $bodyStream.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null

    # ---- Create the HTTP request ----
    $request = [System.Net.HttpWebRequest]::Create($uri)
    $request.Method = "POST"
    $request.Headers.Add("Authorization", "Bearer $accessToken")
    $request.ContentType = "multipart/form-data; boundary=$boundary"
    $request.AllowWriteStreamBuffering = $true

    # ---- Write the multipart body into the request stream ----
    $requestStream = $request.GetRequestStream()
    $bodyStream.CopyTo($requestStream)
    $requestStream.Close()

    # ---- Get the server response ----
    $response = $request.GetResponse()
    $reader = New-Object System.IO.StreamReader($response.GetResponseStream())
    $responseText = $reader.ReadToEnd()

    # Convert JSON response into PowerShell object
    $responseJson = $responseText | ConvertFrom-Json

    # ---- Display success message ----
    Write-Host "✅ Upload successful!"
    Write-Host "File name: $($responseJson.entries[0].name)"
    Write-Host "File ID:   $($responseJson.entries[0].id)"
}
catch {
    # ---- Handle upload failure ----
    Write-Error "❌ Upload failed."

    # If the server returned an error response, read it and show details
    if ($_.Exception.Response -ne $null) {
        $stream = $_.Exception.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($stream)
        $errorResponse = $reader.ReadToEnd()
        Write-Error "Server error response: $errorResponse"
    } else {
        Write-Error $_.Exception.Message
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.