OneDrive.psm1

function Get-ODAuthentication
{
    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  = {
        $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 handle request to the OneDrive API
    .PARAMETER AccessToken
    Specifies a valid access token for bearer authorization
    .PARAMETER rURI
    Relative path to the API
    .PARAMETER Method
    Webreques method like PUT, GET, ...
    .PARAMETER Body
    Payload of a webrequest
    .PARAMETER BinaryMode
    Do not convert response to JSON
    #>

    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
    Specifies a valid access token for bearer authorization
    #>

    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 elements. If not given it's the root is used "/"
    .PARAMETER ElementID
    Specifies the id of an elements. If Path and ElementID are given, ElementID is used with a warning
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    #>

    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+=[System.Web.HttpUtility]::UrlEncode($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
    Specifies a valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path to the element/item. If not given the property of your default root are listed
    .PARAMETER ElementID
    Specifies the id of the element/item. If Path and ElementID are given, ElementID is used with a warning
    .PARAMETER SelectProperties
    Specifies comma separated the properties to return from file and folder objects (case sensitive). If not set name, size, lastModifiedDateTime and id is used. (See https://dev.onedrive.com/odata/optional-query-parameters.htm).
    If yout 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
    #>

    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
    Specifies a valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path of elements to list. If not given it's the root ist used "/"
    .PARAMETER ElementID
    Specifies the id of an elements. If Path and ElementID are given, ElementID is used with a warning
    .PARAMETER SelectProperties
    Specifies comma separated the properties to return from file and folder objects (case sensitive). If not set name, size, lastModifiedDateTime and id is used. (See https://dev.onedrive.com/odata/optional-query-parameters.htm).
    If yout 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
    #>

    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
        $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 at Path or ElementID
    .PARAMETER AccessToken
    Specifies 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 it's the root ist used "/"
    .PARAMETER ElementID
    Specifies the element id of the folder to start the search. If Path and ElementID are given, ElementID is used with a warning
    .PARAMETER SelectProperties
    Specifies comma separated the properties to return from file and folder objects (case sensitive). If not set name, size, lastModifiedDateTime and id is used. (See https://dev.onedrive.com/odata/optional-query-parameters.htm).
    If yout 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
    #>

    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
    Creates a new folder
    .PARAMETER AccessToken
    Specifies a valid access token for bearer authorization
    .PARAMETER FolderName
    Name of the new folder
    .PARAMETER Path
    Specifies the path for the new folder. If not given it's the root ist used "/"
    .PARAMETER ElementID
    Specifies the element id for the new folder. If Path and ElementID are given, ElementID is used with a warning
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    #>

    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
    Specifies a valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path for the item to delete
    .PARAMETER ElementID
    Specifies the element id for the item to delete
    .PARAMETER DriveID
    Specifies the OneDrive drive id. If not set, the default drive is used
    #>

    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
    Specifies a valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path of the file to download. If not given it's the root ist used "/"
    .PARAMETER ElementID
    Specifies the element id of the file to download. If Path and ElementID are given, 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 path is used)
    .PARAMETER LocalFileName
    Filename. If not given the file name from OneDrive is used
    #>

    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 New-ODItem
{
    <#
    .DESCRIPTION
    Upload an item/file. Warning: A local file will be overwritten
    .PARAMETER AccessToken
    Specifies a valid access token for bearer authorization
    .PARAMETER Path
    Specifies the path for the upload folder. If not given it's the root ist used "/"
    .PARAMETER ElementID
    Specifies the element id for the upload folder. If Path and ElementID are given, 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 upload (C:\data\data.csv)
    #>

    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+"/"+[System.IO.Path]::GetFileName($LocalFile)+":/content").Replace("::",":")
        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
    }    
}