Created
March 9, 2016 00:43
-
-
Save dindoliboon/3b16c4eb24df1fa4c56c to your computer and use it in GitHub Desktop.
Upload large files to OneDrive Personal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
Created this a few years ago to upload large files to OneDrive Personal. | |
Uses BITSAdmin (this is deprecated) to set the bearer token in the header. | |
OneDrive API at the time did not support uploading large files. Script will | |
need to be updated to use https://dev.onedrive.com/items/upload_large_files.htm#create-an-upload-session | |
Tested on Windows 10, PowerShell 5.0.10240.16384. | |
Register your app and configure its settings | |
============================================ | |
1. Log into Microsoft account Developer Center | |
https://account.live.com/developers/applications | |
2. Click on "My apps", click on "Create application". | |
3. Enter your "Application name", "Language", click on "I accept". | |
4. Go to "API Settings", click on "Yes" for "Mobile or desktop client app". Click on "Save". | |
5. Go to "App Settings", write down your "Client ID" and optionally your "Client secret (v1)". | |
6. Run PowerShell with code below: | |
# Grab a token. | |
$token = Get-WindowsLiveAccessToken -ClientId '0000000000000000' -Scopes @('wl.signin', 'wl.skydrive', 'wl.skydrive_update') -Verbose | |
# List only folders. | |
Get-FolderListing -Path '/' -AccessToken $token.access_token | Select name,id,parent_id | Where {$_.id -ilike 'folder.*'} | |
# List folders and files. | |
Get-FolderListing -AccessToken $token.access_token | Select name,type,id| FT | |
# Get the /Documents path + the new file. | |
$targetFile = (Get-BitsFolderPath -Path '/Documents' -AccessToken $token.access_token) + '/large-file.zip' | |
# Upload file to /Documents/ folder. | |
$job = Start-OneDriveFileTransfer -Source 'V:\archive\large-file.zip' -Destination $targetFile -AccessToken $token.access_token -Asynchronous | |
# Pause the upload. | |
Suspend-OneDriveFileTransfer -AccessToken $token.access_token -JobId $job.JobId -Asynchronous | |
# See status of upload. | |
Get-BitsTransfer -JobId $job.JobId | |
# Continue uploading the file. | |
Resume-OneDriveFileTransfer -AccessToken $token.access_token -JobId $job.JobId -Asynchronous | |
# Remove from transfer job. | |
Complete-BitsTransfer -BitsJob $job.JobId | |
#> | |
#Requires -Version 3 | |
#Requires -Assembly System.Web.dll | |
#Requires -Assembly System.Windows.Forms.dll | |
#Requires -Modules BitsTransfer | |
Set-StrictMode -Version 3.0 | |
$defaultApiEndpoint = 'https://apis.live.net/v5.0' | |
$authEndPoint = 'https://login.live.com' | |
$authorizePath = '/oauth20_authorize.srf' | |
$tokenPath = '/oauth20_token.srf' | |
$desktopPath = '/oauth20_desktop.srf' | |
$logoutPath = '/oauth20_logout.srf' | |
$defaultRedirectUri = [System.Uri]::EscapeDataString([string]::Format('{0}{1}', $authEndPoint , $desktopPath)) | |
function Get-WindowsLiveAccessToken | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
[parameter(Mandatory=$false)] | |
[PSCredential] | |
$Credential, | |
[parameter(Mandatory=$true)] | |
[String] | |
$ClientId, | |
[parameter(Mandatory=$false)] | |
[AllowEmptyString()] | |
[AllowNull()] | |
[String] | |
$ClientSecret, | |
[parameter(Mandatory=$false)] | |
[AllowEmptyString()] | |
[AllowNull()] | |
[String] | |
$RefreshToken, | |
[parameter(Mandatory=$false)] | |
[String[]] | |
$Scopes = @('wl.signin'), | |
[parameter(Mandatory=$false)] | |
[String] | |
$RedirectUri, | |
[parameter(Mandatory=$false)] | |
[ValidateSet('ImplicitGrant', 'AuthorizationCodeGrant', 'SignInControl')] | |
[String] | |
$OAuthFlow = 'ImplicitGrant', | |
# Currently only for Forms.WebBrowser. | |
[parameter(Mandatory=$false)] | |
[ValidateSet('FormsWebBrowser')] | |
[String] | |
$WebClient = 'FormsWebBrowser', | |
# Currently not implemented. | |
[parameter(Mandatory=$false)] | |
[Switch] | |
$AcceptConsent = $false | |
) | |
$redirectUriUse = $defaultRedirectUri | |
if ($RedirectUri.Equals('') -eq $false) { | |
$redirectUriUse = $RedirectUri | |
} | |
$scopesSafe = [System.Uri]::EscapeDataString($Scopes -join ' ') | |
$ret = [hashtable]::Synchronized(@{object=$null}) | |
$web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width=440; Height=640; Dock=[System.Windows.Forms.DockStyle]::Fill} | |
$form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width=420; Height=600; StartPosition='CenterScreen'; Text='Add your Microsoft Account'} | |
if ($OAuthFlow.Equals('AuthorizationCodeGrant', [System.StringComparison]::CurrentCultureIgnoreCase) -eq $true) | |
{ | |
$url = [string]::Format('{0}{1}?client_id={2}&scope={3}&response_type=code&redirect_uri={4}', $authEndPoint, $authorizePath, $ClientId, $scopesSafe, $defaultRedirectUri) | |
} | |
elseif ($OAuthFlow.Equals('SignInControl', [System.StringComparison]::CurrentCultureIgnoreCase) -eq $true) | |
{ | |
$url = [string]::Format('{0}{1}?client_id={2}&scope={3}&response_type=code&redirect_uri={4}', $authEndPoint, $authorizePath, $ClientId, $scopesSafe, $redirectUriUse) | |
Write-Verbose $url | |
} | |
else | |
{ | |
$url = [string]::Format('{0}{1}?client_id={2}&scope={3}&response_type=token&redirect_uri={4}', $authEndPoint, $authorizePath, $ClientId, $scopesSafe, $defaultRedirectUri) | |
} | |
$onDocumentCompleted = { | |
Write-Debug "`$onDocumentCompleted received url -> $($web.Url.AbsoluteUri)" | |
# Close web browser on error. | |
if ($web.Url.AbsoluteUri -match 'error=([^&]*)&error_description=([^&]*)') | |
{ | |
Write-Error "$($Matches[1]): $([System.Uri]::UnescapeDataString($Matches[2]))" | |
$ret.object = New-Object -TypeName PSCustomObject -Property @{error=$Matches[1]; error_description=[System.Uri]::UnescapeDataString($Matches[2]);} | |
$form.Close() | |
} | |
# Implicit grant flow: Access token | |
if ($web.Url.AbsoluteUri -match 'access_token=([^&]*)&token_type=([^&]*)&expires_in=([^&]*)&scope=([^&]*)&user_id=([^&]*)') | |
{ | |
Write-Debug "Implicit grant flow -> $($web.Url.AbsoluteUri)" | |
$ret.object = New-Object -TypeName PSCustomObject -Property @{token_type=$Matches[2]; valid_thru=((Get-Date).AddSeconds([int]$Matches[3])); expires_in=$Matches[3]; scope=[System.Uri]::UnescapeDataString($Matches[4]); access_token=$Matches[1]; refresh_token=''; user_id=$Matches[5]; authentication_token=''; code=''} | |
$form.Close() | |
} | |
# Authorization code grant flow | |
if ($OAuthFlow.Equals('AuthorizationCodeGrant', [System.StringComparison]::CurrentCultureIgnoreCase) -eq $true -and $web.Url.AbsoluteUri -match 'code=([^&]*)') | |
{ | |
Write-Debug "Authorization code grant flow -> $($web.Url.AbsoluteUri)" | |
$url = [string]::Format('{0}{1}?client_id={2}&redirect_uri={3}&client_secret={4}&code={5}&grant_type=authorization_code', $authEndPoint, $tokenPath, $ClientId, $defaultRedirectUri, $ClientSecret, $Matches[1]) | |
Write-Debug "Authorization code grant flow, call REST method -> $url" | |
$im = Invoke-RestMethod -Method Get -Uri $url | |
$ret.object = New-Object -TypeName PSCustomObject -Property @{token_type=$im.token_type; valid_thru=((Get-Date).AddSeconds([int]$im.expires_in)); expires_in=$im.expires_in; scope=[System.Uri]::UnescapeDataString($im.scope); access_token=$im.access_token; refresh_token=$im.refresh_token; user_id=$im.user_id; authentication_token=''; code=''} | |
$form.Close() | |
} | |
# Sign-in control flow | |
if ($OAuthFlow.Equals('SignInControl', [System.StringComparison]::CurrentCultureIgnoreCase) -eq $true -and $web.Url.AbsoluteUri -match 'code=([^&]*)') | |
{ | |
Write-Debug "Sign-in control flow -> $($web.Url.AbsoluteUri)" | |
$ret.object = New-Object -TypeName PSCustomObject -Property @{token_type=''; valid_thru=''; expires_in=''; scope=''; access_token=''; refresh_token=''; user_id=''; authentication_token=''; code=$Matches[1]} | |
$form.Close() | |
} | |
} | |
$web.Add_DocumentCompleted($onDocumentCompleted) | |
Write-Debug "Starting navigation -> $url" | |
$web.Navigate($url) | |
$form.Add_Shown({$form.Activate()}) | |
$form.Controls.Add($web) | |
$form.ShowDialog() | Out-Null | |
$web.Dispose() | |
$form.Close() | |
$form.Dispose() | |
return $ret.object | |
} | |
function Get-FolderListing | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
[parameter(Mandatory=$false)] | |
[String] | |
$Path, | |
[parameter(Mandatory=$true)] | |
[String] | |
$AccessToken | |
) | |
$rootPath = '/me/skydrive' | |
$folderPath = $rootPath | |
if ($Path.Equals('') -eq $true -or $Path.Equals('/') -eq $true) | |
{ | |
# Use default root path. | |
} else { | |
$Path.Split('/', [System.StringSplitOptions]::RemoveEmptyEntries) |% { | |
$splitFolder = $_ | |
(Invoke-RestMethod -Method Get -Uri ([string]::Format('{0}{1}/files?access_token={2}', $defaultApiEndpoint, $folderPath, $AccessToken))).data | % { | |
if ($splitFolder -ilike $_.name -and $_.id -ilike 'folder.*') { | |
$folderPath = "/$($_.id)" | |
} | |
} | |
} | |
} | |
return (Invoke-RestMethod -Method Get -Uri ([string]::Format('{0}{1}/files?access_token={2}', $defaultApiEndpoint, $folderPath, $AccessToken))).data | |
} | |
function Get-BitsFolderPath | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
[parameter(Mandatory=$false)] | |
[String] | |
$Path, | |
[parameter(Mandatory=$true)] | |
[String] | |
$AccessToken | |
) | |
$rootPath = '/me/skydrive' | |
$folderPath = $rootPath | |
$parentFolderId = '' | |
$folderUrl = '' | |
if ($Path.Equals('') -eq $true -or $Path.Equals('/') -eq $true) | |
{ | |
# Use default root path. | |
throw 'Not implemented. Cannot save to ROOT path using BITS.' | |
} else { | |
$Path.Split('/', [System.StringSplitOptions]::RemoveEmptyEntries) |% { | |
$splitFolder = $_ | |
(Invoke-RestMethod -Method Get -Uri ([string]::Format('{0}{1}/files?access_token={2}', $defaultApiEndpoint, $folderPath, $AccessToken))).data | % { | |
if ($splitFolder -ilike $_.name -and $_.id -ilike 'folder.*') { | |
$folderPath = "/$($_.id)" | |
$parentFolderId = $folderPath | |
} | |
} | |
} | |
} | |
if ($parentFolderId.Equals('') -eq $true) | |
{ | |
# Nothing found. | |
} else { | |
$s = $parentFolderId.Split('.', [System.StringSplitOptions]::RemoveEmptyEntries) | |
if ($s.Count -eq 3) | |
{ | |
$folderUrl = [string]::Format('https://cid-{0}.users.storage.live.com/items/{1}', $s[1], $s[2]) | |
} | |
} | |
return $folderUrl | |
} | |
function Start-OneDriveFileTransfer | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
[parameter(Mandatory=$true)] | |
[String] | |
$Source, | |
[parameter(Mandatory=$true)] | |
[String] | |
$Destination, | |
[parameter(Mandatory=$true)] | |
[String] | |
$AccessToken, | |
[parameter(Mandatory=$false)] | |
[Switch] | |
$Asynchronous = $false | |
) | |
$job = Start-BitsTransfer -Source $Source -Destination $Destination -TransferType Upload -Suspended | |
$tmp = & 'bitsadmin.exe' @('/SetCustomHeaders', "{$($job.JobId)}", "Authorization: bearer $($AccessToken)", 'Accept: application/wls-response-headers+json') | |
$tmp = Resume-BitsTransfer -BitsJob $job -Asynchronous:$Asynchronous | |
return $job | |
} | |
function Suspend-OneDriveFileTransfer | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
[parameter(Mandatory=$true)] | |
[String] | |
$AccessToken, | |
[parameter(Mandatory=$true)] | |
[String] | |
$JobId, | |
[parameter(Mandatory=$false)] | |
[Switch] | |
$Asynchronous = $false | |
) | |
$job = Get-BitsTransfer -JobId $JobId | |
$tmp = & 'bitsadmin.exe' @('/SetCustomHeaders', $JobId, "Authorization: bearer $($AccessToken)", 'Accept: application/wls-response-headers+json') | |
$tmp = Suspend-BitsTransfer -BitsJob $job | |
return $job | |
} | |
function Resume-OneDriveFileTransfer | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
[parameter(Mandatory=$true)] | |
[String] | |
$AccessToken, | |
[parameter(Mandatory=$true)] | |
[String] | |
$JobId, | |
[parameter(Mandatory=$false)] | |
[Switch] | |
$Asynchronous = $false | |
) | |
$job = Get-BitsTransfer -JobId $JobId | |
$tmp = & 'bitsadmin.exe' @('/SetCustomHeaders', $JobId, "Authorization: bearer $($AccessToken)", 'Accept: application/wls-response-headers+json') | |
$tmp = Resume-BitsTransfer -BitsJob $job -Asynchronous:$Asynchronous | |
return $job | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment