Skip to content

Instantly share code, notes, and snippets.

@dindoliboon
Created March 9, 2016 00:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dindoliboon/3b16c4eb24df1fa4c56c to your computer and use it in GitHub Desktop.
Save dindoliboon/3b16c4eb24df1fa4c56c to your computer and use it in GitHub Desktop.
Upload large files to OneDrive Personal
<#
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