Graph upload large file with powershell

Rick Brown 96 Reputation points
2021-10-12T16:46:12.797+00:00

I have looked at previous posts on this topic and still have not managed to be able to upload a large file to SharePoint using the Resumable Upload. I have some specific areas where I have questions. My current code is:

$fileName = "myfile.jpg"
$createUploadSessionUri = "https://graph.microsoft.com/v1.0/sites/$($site.id)/drive/items/$($drive.id):/$($fileName):/createUploadSession"
$uploadSession = Invoke-MgGraphRequest -Method POST -Uri $createUploadSessionUri -Body $body -ContentType 'application/json'
Write-Host $uploadSession.uploadUrl #correctly creates upload session
Write-host $uploadSession.expirationDateTime

#### Send bytes to the upload session
$path = "myfile.jpg"

$fileInBytes = [System.IO.File]::ReadAllBytes($path)
$fileLength = $fileInBytes.Length
write-host "fileinbytes length=$fileLength" # correct length of file 4653349

## Split the file up
$PartSizeBytes = 320 * 1024 # 327680
$index = 0
$start = 0 #start at 0
$end = 0

while ($fileLength -gt $end) {
  $start = $index * $PartSizeBytes # first iteration = 0
  $end = $start + $PartSizeBytes - 1 # first iteration = 327679
  $body = $fileInBytes[$start..$end]

  write-host $body.Length.ToString()
  $headers = @{
    'Content-Length' = $body.Length.ToString()
    'Content-Range' = "bytes $start-$end/$fileLength"
  }
  write-Host "bytes $start-$end/$fileLength" # correct content range "bytes 0-327679/4653349"

  write-host $body # correct number of bytes 327679
  $response = Invoke-RestMethod -Method 'Put' -Uri $uploadSession.uploadUrl -Body $body -Headers $headers -SkipHeaderValidation # <<<<<fail

  $index++
}

So, everything seems to be correct according to documentation, except it errors out on the rest call to upload the chunks.

Bad Request  Bad Request - Invalid Content Length HTTP
     | Error 400. There is an invalid content length or chunk length
     | in the request.

Questions are:

  1. Do I have to pass in a Content-Type to the upload call?
  2. Am I sending the correct body (255 216...)?
  3. Are there any other headers I'm missing, OR that I should not be including
  4. Is the PartSizeBytes variable appropriate?
  5. Am I properly chunking the file?
Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
10,614 questions
SharePoint Development
SharePoint Development
SharePoint: A group of Microsoft Products and technologies used for sharing and managing content, knowledge, and applications.Development: The process of researching, productizing, and refining new or existing technologies.
2,673 questions
{count} votes

Accepted answer
  1. Rick Brown 96 Reputation points
    2021-10-14T12:48:57.227+00:00

    I was able to upload with the following code:

    $fileName = "myfile.jpg"
    $createUploadSessionUri = "https://graph.microsoft.com/v1.0/sites/$($site.id)/drives/$($drive.id)/root:/$($fileName):/createUploadSession"
    $uploadSession = Invoke-MgGraphRequest -Method POST -Uri $createUploadSessionUri -Body $body -ContentType 'application/json'
    Write-Host $uploadSession.uploadUrl
    Write-host $uploadSession.expirationDateTime
    
    #### Send bytes to the upload session
    $path = "myfile.jpg"
    
    $fileInBytes = [System.IO.File]::ReadAllBytes($path)
    $fileLength = $fileInBytes.Length
    
    $headers = @{
      'Content-Range' = "bytes 0-$($fileLength-1)/$fileLength"
    }
    $response = Invoke-RestMethod -Method 'Put' -Uri $uploadSession.uploadUrl -Body $fileInBytes -Headers $headers
    

    Needed to remove the Content-Length. Hope this helps someone else struggling as I was.


4 additional answers

Sort by: Most helpful
  1. Mike Crowley 121 Reputation points
    2024-04-18T16:55:35.19+00:00

    Here is my approach, which uses Invoke-MgGraphRequest to simplify authentication. This places a file on My OneDrive desktop.

    Connect-MgGraph -TenantId baseline.consulting -ContextScope Process
    
    $FileToUpload = Get-Item "C:\tmp\BigFile.mike"
    
    $FileName = $FileToUpload.name
    
    $parentItemId = ((Invoke-MgGraphRequest -Uri "beta/me/drive/root/children").value | Where-Object name -eq 'Desktop').id # My Desktop
    
    $uri = "v1.0/me/drive/items/$parentItemId`:/$FileName`:/createUploadSession"
    
    $uploadSession = Invoke-MgGraphRequest -Uri $uri -Method POST -Body  @{name = $FileName }
    
    $FileBytes = [System.IO.File]::ReadAllBytes($FileToUpload.FullName)
    
    $FileSize = $FileBytes.length
    
    $ChunkSize = 320KB * 191 # adjust for perf
    
    if ($ChunkSize -ge 60MB) { Write-Error "ChunkSize Too Big" } # https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0#upload-bytes-to-the-upload-session
    
    $RangeMin = 0
    
    While ($RangeMin -lt $FileSize) {
    
        $Final = $RangeMin + $ChunkSize -gt $FileSize
    
        if (-not $Final) {
    
            $RangeMax = [math]::Min($RangeMin + $ChunkSize - 1, $FileSize)
    
            $FileChunk = [byte[]]$FileBytes[$RangeMin..$RangeMax]
    
        }
    
        else {
    
            $RangeMax = $FileSize - 1
    
            $FileChunk = [byte[]]$FileBytes[$RangeMin..$RangeMax]
    
        }
    
        $Headers = @{
    
            [string]'Content-Type'   = "application/octet-stream"
    
            [string]'Content-Length' = $FileChunk.length
    
            [string]'Content-Range'  = "bytes $RangeMin-$RangeMax/$FileSize"
    
        }
    
        # Uncomment to write chunk data
    
        # $Headers | ConvertTo-Json | Write-Host -fore cyan
    
        # $FileChunk.Length | Write-Host -fore cyan
    
        Invoke-MgGraphRequest -Uri $uploadSession.uploadUrl -Method PUT -Headers $Headers -Body $FileChunk -SkipHeaderValidation
    
        $RangeMin += $ChunkSize
    
    }
    
    0 comments No comments

  2. OndrejG 126 Reputation points
    2022-08-29T14:30:58.707+00:00

    I was finally able to achieve the upload using script bellow. I had to make some modifications with counting the chunks. Hope someone might find it useful :).

    $driveid = "*******************"  
    $fileName = "20220627_143840.jpg"  
    $path = "C:\testUpload\20220627_143840.jpg"  
      
    $AZcred = New-Object -TypeName PSObject -Property @{  
        [string]"appID"    = "*******************";  
        [string]"tenantID" = "*******************";  
        [string]"secret"   = (ConvertTo-SecureString "*******************" -AsPlainText -Force)  
    }  
      
    $AuthToken = Get-MsalToken -TenantId $AZcred.tenantID -ClientId $AZcred.appID -ClientSecret $AZcred.secret  
    $AuthToken = @{'Authorization' = "Bearer $($AuthToken.AccessToken)" }  
      
    $createUploadSessionUri = "https://graph.microsoft.com/v1.0/sites/<siteID>/drives/$($driveid)/root:/$($fileName):/createUploadSession"  
    $uploadSession = (Invoke-WebRequest -Method POST -Uri $createUploadSessionUri -Headers $AuthToken -ContentType 'application/json').content | ConvertFrom-Json  
      
    #### Send bytes to the upload session  
    $fileInBytes = [System.IO.File]::ReadAllBytes($path)  
    $fileLength = $fileInBytes.Length  
      
    ## Split the file up  
    $PartSizeBytes = 320 * 1024 # 327680  
    $index = 0  
    $start = 0   
    $end = 0  
    while ($fileLength -gt ($end + 1)) {  
        $start = $index * $PartSizeBytes  
        if (($start + $PartSizeBytes - 1) -lt $fileLength) {  
            $end = ($start + $PartSizeBytes - 1)  
        }  
        else {  
            $end = ($start + ($fileLength - ($index * $PartSizeBytes)) - 1)  
        }  
          
        [byte[]]$body = $fileInBytes[$start..$end]  
        write-host $body.Length.ToString()  
        $headers = @{  
            'Content-Length' = $body.Length.ToString()  
            'Content-Range'  = "bytes $start-$end/$fileLength"  
        }  
        write-Host "bytes $start-$end/$fileLength | Index: $index and ChunkSize: $PartSizeBytes"  
        $response = Invoke-WebRequest -Method Put -Uri $uploadSession.uploadUrl -Body $body -Headers $headers  
      
        $index++  
    }  
    
      
    
    1 person found this answer helpful.

  3. MichaelHan-MSFT 18,016 Reputation points
    2021-10-13T05:48:12.673+00:00

    Hi @Rick Brown ,

    You can try to upload the entire file in one go like this:

     $headers=@{  
    	'Content-Length' = $fileLength  
        'Content-Range' = "bytes 0-$($fileLength-1)/$fileLength"  
    	'Content-Type'="image/jpeg"  
     }  
     Invoke-RestMethod -Method 'Put' -Uri $uploadUrl -Body $fileInBytes -Headers $headers  
    

    If an Answer is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  4. Gunnar T. Gunnarsson 1 Reputation point
    2022-07-29T10:33:57.647+00:00

    I was having similar issues to you and I found out that the line
    $body = $fileInBytes[$start..$end]
    creates an object array instead of a byte array that causes the REST api to fail. After I changed the code to
    [byte[]]$body = $fileInBytes[$start..$end]
    it started to work as expected. This is of course if your file is on the larger side, else uploading in one go works fine for most files.

    0 comments No comments