OneDrive.psm1

function Get-ODAuthentication
{
    <#
    .DESCRIPTION
    Connect to OneDrive for authentication with a given client id (get your free client id on https://dev.onedrive.com/app-registration.htm#register-your-app-for-onedrive)
    .PARAMETER ClientID
    ClientID of your "app" from https://dev.onedrive.com/app-registration.htm#register-your-app-for-onedrive)
    .PARAMETER ClientID
    Comma seperated string defining the authentication scope (https://dev.onedrive.com/auth/msa_oauth.htm). Default: "onedrive.readwrite"
    .PARAMETER RedirectURI
    Don't use this parameter. You only need this to write your own web based app for OneDrive. Default is https://login.live.com/oauth20_desktop.srf
    .EXAMPLE
    $Authentication=Get-ODAuthentication -ClientID "0000000012345678"
    $AuthToken=$Authentication.access_token
    Connect to OneDrive for authentication and save the token to $AuthToken
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$ClientID = "unknown",
        [string]$Scope = "onedrive.readwrite",
        [string]$RedirectURI ="https://login.live.com/oauth20_desktop.srf"
    )
    [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | out-null
    [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | out-null
    [Reflection.Assembly]::LoadWithPartialName("System.Web") | out-null  # ????????????????????????????????????????????????
    $URIGetAccessTokenRedirect=$RedirectURI
    $URIGetAccessToken="https://login.live.com/oauth20_authorize.srf?client_id="+$ClientID+"&scope="+$Scope+"&response_type=token&redirect_uri="+$URIGetAccessTokenRedirect
    $form = New-Object Windows.Forms.Form
    $form.text = "Authenticate to OneDrive"
    $form.size = New-Object Drawing.size @(700,600)
    $form.Width = 675
    $form.Height = 750
    $web = New-object System.Windows.Forms.WebBrowser
    $web.IsWebBrowserContextMenuEnabled = $true
    $web.Width = 600
    $web.Height = 700
    $web.Location = "25, 25"
    $web.navigate($URIGetAccessToken)
    $DocComplete  = {
        $Global:uri = $web.Url.AbsoluteUri
        if ($web.Url.AbsoluteUri -match "access_token=|error") {$form.Close() }
    }
    $web.Add_DocumentCompleted($DocComplete)
    $form.Controls.Add($web)
    $form.showdialog() | out-null
    # Build object from last URI (which should contains the token)
    $ReturnURI=($web.Url).ToString().Replace("#","&")
    $Authentication = New-Object PSObject
    ForEach ($element in $ReturnURI.Split("?")[1].Split("&")) 
    {
        $Authentication | add-member Noteproperty $element.split("=")[0] $element.split("=")[1]
    }
    if ($Authentication.PSobject.Properties.name -match "expires_in")
    {
        $Authentication | add-member Noteproperty "expires" ([System.DateTime]::Now.AddSeconds($Authentication.expires_in))
    }
    if (!($Authentication.PSobject.Properties.name -match "expires_in"))
    {
        write-warning("There is maybe an errror, because there is no access_token!")
    }
    return $Authentication 
}

function Get-ODWebContent 
{
    <#
    .DESCRIPTION
    Internal function to interact with the OneDrive API
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .PARAMETER rURI
    Relative path to the API
    .PARAMETER Method
    Webrequest method like PUT, GET, ...
    .PARAMETER Body
    Payload of a webrequest
    .PARAMETER BinaryMode
    Do not convert response to JSON
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken,
        [string]$rURI = "",
        [ValidateSet("PUT","GET","POST","PATCH","DELETE")] 
        [String]$Method="GET",
        [String]$Body,
        [switch]$BinaryMode
    )
    if ($Body -eq "") 
    {
        $xBody=$null
    } else
    {
        $xBody=$Body
    }
    $ODRootURI="https://api.onedrive.com/v1.0"
    try {
        $webRequest=Invoke-WebRequest -Method $Method -Uri ($ODRootURI+$rURI) -Header @{ Authorization = "BEARER "+$AccessToken} -ContentType "application/json" -Body $xBody -ErrorAction SilentlyContinue
    } 
    catch
    {
        write-error("Cannot access the api. Webrequest return code is: "+$_.Exception.Response.StatusCode+"`n"+$_.Exception.Response.StatusDescription)
        break
    }
    switch ($webRequest.StatusCode) 
    { 
        200 
        {
            if (!$BinaryMode) {$responseObject = ConvertFrom-Json $webRequest.Content}
            return $responseObject
        } 
        201 
        {
            write-debug("Success: "+$webRequest.StatusCode+" - "+$webRequest.StatusDescription)
            if (!$BinaryMode) {$responseObject = ConvertFrom-Json $webRequest.Content}
            return $responseObject
        } 
        204 
        {
            write-debug("Success: "+$webRequest.StatusCode+" - "+$webRequest.StatusDescription+" (item deleted)")
            $responseObject = "0"
            return $responseObject
        } 
        default {write-warning("Cannot access the api. Webrequest return code is: "+$webRequest.StatusCode+"`n"+$webRequest.StatusDescription)}
    }
}

function Get-ODDrives
{
    <#
    .DESCRIPTION
    Get user's drives
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .EXAMPLE
    Get-ODDrives -AccessToken $AuthToken
    List all OneDrives available for your account (there is normally only one)
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken
    )
    $ResponseObject=Get-ODWebContent -AccessToken $AccessToken -Method GET -rURI "/drives"
    return $ResponseObject.Value
}

function Format-ODPathorIDString
{
    <#
    .DESCRIPTION
    Formats a given path like '/myFolder/mySubfolder/myFile' into an expected uri format
    .PARAMETER Path
    Specifies the path of an element. If it is not given, the path is "/"
    .PARAMETER ElementID
    Specifies the id of an element. If Path and ElementID are given, the ElementID is used with a warning
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [string]$Path="",
        [string]$DriveID="",
        [string]$ElementID=""
    )
    if (!$ElementID -eq "")
    {
        #Use ElementID parameters
        if (!$Path -eq "") {write-debug("Warning: Path and ElementID parameters are set. Only ElementID is used!")}
        return "/drive/items/"+$ElementID
    }
    else
    {
        #Use Path parameter
        #remove substring starts with "?"
        if ($Path.Contains("?")) {$Path=$Path.Substring(1,$Path.indexof("?")-1)}
        #replace "\" with "/"
        $Path=$Path.Replace("\","/")
        #filter possible string at the end "/children" (case insensitive)
        $Path=$Path+"/"
        $Path=$Path -replace "/children/",""
        #encoding of url parts
        $tmpString=""
        foreach ($Sub in $Path.Split("/")) {$tmpString+=$Sub+"/"}
        $Path=$tmpString
        #remove last "/" if exist
        $Path=$Path.TrimEnd("/")
        #insert drive part of url
        if ($DriveID -eq "") 
        {    
            #Default drive
            $Path="/drive/root:"+$Path+":"
        }
        else
        {
            #Named drive
            $Path="/drives/"+$DriveID+"/root:"+$Path+":"
        }
        return $Path
    }
}

function Get-ODItemProperty
{
    <#
    .DESCRIPTION
    Get the properties of an item (file or folder)
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path to the element/item. If not given, the properties of your default root drive are listed
    .PARAMETER ElementID
    Specifies the id of the element/item. If Path and ElementID are given, the ElementID is used with a warning
    .PARAMETER SelectProperties
    Specifies a comma separated list of the properties to be returned for file and folder objects (case sensitive). If not set, name, size, lastModifiedDateTime and id are used. (See https://dev.onedrive.com/odata/optional-query-parameters.htm).
    If you use -SelectProperties "", all properties are listed. Warning: A complex "content.downloadUrl" is listed/generated for download files without authentication for several hours
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    .EXAMPLE
    Get-ODItemProperty -AccessToken $AuthToken -Path "/Data/documents/2016/AzureML with PowerShell.docx"
    Get the default set of metadata for a file or folder (name, size, lastModifiedDateTime, id)
    Get-ODItemProperty -AccessToken $AuthToken -ElementID 8BADCFF017EAA324!12169 -SelectProperties ""
    Get all metadata of a file or folder by element id ("" select all properties)
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken,
        [string]$Path="/",
        [string]$ElementID="",
        [string]$SelectProperties="name,size,lastModifiedDateTime,id",
        [string]$DriveID=""
    )
    return Get-ODChildItems -AccessToken $AccessToken -Path $Path -ElementID $ElementID -SelectProperties $SelectProperties -DriveID $DriveID -ItemPropertyMode
}

function Get-ODChildItems
{
    <#
    .DESCRIPTION
    Get child items of a path. Return count is not limited.
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path of elements to be listed. If not given, the path is "/"
    .PARAMETER ElementID
    Specifies the id of an element. If Path and ElementID are given, the ElementID is used with a warning
    .PARAMETER SelectProperties
    Specifies a comma separated list of the properties to be returned for file and folder objects (case sensitive). If not set, name, size, lastModifiedDateTime and id are used. (See https://dev.onedrive.com/odata/optional-query-parameters.htm).
    If you use -SelectProperties "", all properties are listed. Warning: A complex "content.downloadUrl" is listed/generated for download files without authentication for several hours
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    .EXAMPLE
    Get-ODChildItems -AccessToken $AuthToken -Path "/" | ft
    Lists files and folders in your OneDrives root folder and displays name, size, lastModifiedDateTime, id and folder property as a table
    Get-ODChildItems -AccessToken $AuthToken -Path "/" -SelectProperties ""
    Lists files and folders in your OneDrives root folder and displays all properties
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken,
        [string]$Path="/",
        [string]$ElementID="",
        [string]$SelectProperties="name,size,lastModifiedDateTime,id",
        [string]$DriveID="",
        [Parameter(DontShow)]
        [switch]$ItemPropertyMode,
        [Parameter(DontShow)]
        [string]$SearchText
    )
    $ODRootURI="https://api.onedrive.com/v1.0"
    if ($Path.Contains("$skiptoken="))
    {    
        #Recures mode of odata.nextLink detection
        write-debug("Recurce call")
        $rURI=$Path    
    }
    else
    {
        $rURI=Format-ODPathorIDString -path $Path -ElementID $ElementID -DriveID $DriveID
        $rURI=$rURI.Replace("::","")
        $SelectProperties=$SelectProperties.Replace(" ","")
        if ($SelectProperties -eq "")
        {
            $opt=""
        } else
        {
            $SelectProperties=$SelectProperties.Replace(" ","")+",folder"
            $opt="?select="+$SelectProperties
        }
        if ($ItemPropertyMode)
        {
            #item property mode
            $rURI=$rURI+$opt
        }
        else
        {
            if (!$SearchText -eq "") 
            {
                #Search mode
                $opt="/view.search?q="+$SearchText+"&select="+$SelectProperties
                $rURI=$rURI+$opt
            }
            else
            {
                #child item mode
                $rURI=$rURI+"/children"+$opt
            }
        }
    }
    write-debug("Accessing API with GET to "+$rURI)
    $ResponseObject=Get-ODWebContent -AccessToken $AccessToken -Method GET -rURI $rURI
    if ($ResponseObject.PSobject.Properties.name -match "@odata.nextLink") 
    {
        write-debug("Getting more elements form service (@odata.nextLink is present)")
        Get-ODChildItems -AccessToken $AccessToken -SelectProperties $SelectProperties -Path $ResponseObject."@odata.nextLink".Replace($ODRootURI,"")
    }
    if ($ItemPropertyMode)
    {
        #item property mode
        return $ResponseObject
    }
    else
    {
        #child item mode
        return $ResponseObject.value
    }
}

function Search-ODItems
{
    <#
    .DESCRIPTION
    Search for items starting from Path or ElementID
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .PARAMETER SearchText
    Specifies search string
    .PARAMETER Path
    Specifies the path of the folder to start the search. If not given, the path is "/"
    .PARAMETER ElementID
    Specifies the element id of the folder to start the search. If Path and ElementID are given, the ElementID is used with a warning
    .PARAMETER SelectProperties
    Specifies a comma separated list of the properties to be returned for file and folder objects (case sensitive). If not set, name, size, lastModifiedDateTime and id are used. (See https://dev.onedrive.com/odata/optional-query-parameters.htm).
    If you use -SelectProperties "", all properties are listed. Warning: A complex "content.downloadUrl" is listed/generated for download files without authentication for several hours
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    .EXAMPLE
    Search-ODItems -AccessToken $AuthToken -Path "/My pictures" -SearchText "FolderA"
    Search for items in a sub folder recursively. Take a look at OneDrives API documentation to see how search (preview) works (file and folder names, in files, …)
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken,
        [Parameter(Mandatory=$True)]
        [string]$SearchText,
        [string]$Path="/",
        [string]$ElementID="",
        [string]$SelectProperties="name,size,lastModifiedDateTime,id",
        [string]$DriveID=""
    )
    return Get-ODChildItems -AccessToken $AccessToken -Path $Path -ElementID $ElementID -SelectProperties $SelectProperties -DriveID $DriveID -SearchText $SearchText    
}

function New-ODFolder
{
    <#
    .DESCRIPTION
    Create a new folder
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .PARAMETER FolderName
    Name of the new folder
    .PARAMETER Path
    Specifies the parent path for the new folder. If not given, the path is "/"
    .PARAMETER ElementID
    Specifies the element id for the new folder. If Path and ElementID are given, the ElementID is used with a warning
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    .EXAMPLE
    New-ODFolder -AccessToken $AuthToken -Path "/data/documents" -FolderName "2016"
    Creates a new folder "2016" under "/data/documents"
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken,
        [Parameter(Mandatory=$True)]
        [string]$FolderName,
        [string]$Path="/",
        [string]$ElementID="",
        [string]$DriveID=""
    )
    $rURI=Format-ODPathorIDString -path $Path -ElementID $ElementID -DriveID $DriveID
    $rURI=$rURI+"/children"
    return Get-ODWebContent -AccessToken $AccessToken -Method POST -rURI $rURI -Body ('{"name": "'+$FolderName+'","folder": { },"@name.conflictBehavior": "fail"}')
}

function Remove-ODItem
{
    <#
    .DESCRIPTION
    Delete an item (folder or file)
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path of the item to be deleted
    .PARAMETER ElementID
    Specifies the element id of the item to be deleted
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    .EXAMPLE
    Remove-ODItem -AccessToken $AuthToken -Path "/Data/documents/2016/Azure-big-picture.old.docx"
    Deletes an item
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken,
        [string]$Path="",
        [string]$ElementID="",
        [string]$DriveID=""
    )
    if (($ElementID+$Path) -eq "") 
    {
        debug-error("Path nor ElementID is set")
    }
    else
    {
        $rURI=Format-ODPathorIDString -path $Path -ElementID $ElementID -DriveID $DriveID
        return Get-ODWebContent -AccessToken $AccessToken -Method DELETE -rURI $rURI 
    }
}

function Get-ODItem
{
    <#
    .DESCRIPTION
    Download an item/file. Warning: A local file will be overwritten
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path of the file to download.
    .PARAMETER ElementID
    Specifies the element id of the file to download. If Path and ElementID are given, the ElementID is used with a warning
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    .PARAMETER LocalPath
    Save file to path (if not given, the current local path is used)
    .PARAMETER LocalFileName
    Local filename. If not given, the file name of OneDrive is used
    .EXAMPLE
    Get-ODItem -AccessToken $AuthToken -Path "/Data/documents/2016/Powershell array custom objects.docx"
    Downloads a file from OneDrive
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken,
        [string]$Path="",
        [string]$ElementID="",
        [string]$DriveID="",
        [string]$LocalPath="",
        [string]$LocalFileName
    )
    if (($ElementID+$Path) -eq "") 
    {
        debug-error("Path nor ElementID is set")
    }
    else
    {
        $Download=Get-ODItemProperty -AccessToken $AccessToken -Path $Path -ElementID $ElementID -DriveID $DriveID -SelectProperties "name,@content.downloadUrl"
        if ($LocalPath -eq "") {$LocalPath=Get-Location}
        if ($LocalFileName -eq "")
        {
            $SaveTo=$LocalPath.TrimEnd("\")+"\"+$Download.name
        }
        else
        {
            $SaveTo=$LocalPath.TrimEnd("\")+"\"+$LocalFileName        
        }
        try
        {
            [System.Net.WebClient]::WebClient
            $client = New-Object System.Net.WebClient
            $client.DownloadFile($Download."@content.downloadUrl",$SaveTo)
            write-verbose("Download complete")
            return 0
        }
        catch
        {
            write-error("Download error: "+$_.Exception.Response.StatusCode+"`n"+$_.Exception.Response.StatusDescription)
            return -1
        }
    }    
}


function Add-ODItem
{
    <#
    .DESCRIPTION
    Upload an item/file. Warning: An existing file will be overwritten
    .PARAMETER AccessToken
    A valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path for the upload folder. If not given, the path is "/"
    .PARAMETER ElementID
    Specifies the element id for the upload folder. If Path and ElementID are given, the ElementID is used with a warning
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    .PARAMETER LocalFile
    Path and file of the local file to be uploaded (C:\data\data.csv)
    .EXAMPLE
    Add-ODItem -AccessToken $AuthToken -Path "/Data/documents/2016" -LocalFile "AzureML with PowerShell.docx"
    Upload a file to OneDrive "/data/documents/2016"
    .NOTES
    Author: Marcel Meurer, marcel.meurer@sepago.de, Twitter: MarcelMeurer
    #>

    PARAM(
        [Parameter(Mandatory=$True)]
        [string]$AccessToken,
        [string]$Path="/",
        [string]$ElementID="",
        [string]$DriveID="",
        [Parameter(Mandatory=$True)]
        [string]$LocalFile=""
    )
    $rURI=Format-ODPathorIDString -path $Path -ElementID $ElementID -DriveID $DriveID
    try
    {
        $ODRootURI="https://api.onedrive.com/v1.0"
        $rURI=(($ODRootURI+$rURI).TrimEnd(":")+"/"+[System.IO.Path]::GetFileName($LocalFile)+":/content").Replace("/root/","/root:/")
        return $webRequest=Invoke-WebRequest -Method PUT -InFile $LocalFile -Uri $rURI -Header @{ Authorization = "BEARER "+$AccessToken} -ContentType "multipart/form-data" -ErrorAction SilentlyContinue
    }
    catch
    {
        write-error("Upload error: "+$_.Exception.Response.StatusCode+"`n"+$_.Exception.Response.StatusDescription)
        return -1
    }    
}