Posting to Office 365 OneNote via PowerShell

This is a guide of links and gotchas of how to post a new page to your OneNote document sitting in Office 365.

Lots of this was learning for me, so thank you:

Background

Todd Klindt asked over the weekend if there's any examples of manipulating OneNote with PowerShell.

Now Todd is a master of PowerShell, which is NOT A DEVELOPER TOOL.  But I did recall OneNote has an ongoing REST API and recently announced they have it working on Office 365 (Preview).

http://blogs.msdn.com/b/onenotedev/archive/2015/04/30/support-for-work-and-school-notebooks-on-office-365-in-preview.aspx

And as a developer I thought I can at least try to write some PowerShell to get this working.  Besides, I want to play with the OAuth stuff and new APIs.

The LEGO pieces are in place, and the cogs in my head are churning.

To talk to Office 365 Unified API you need an Azure Application. 

Register it like this http://blogs.msdn.com/b/onenotedev/archive/2015/04/30/register-your-application-in-azure-ad.aspx

Here's mine:

These are the OneNote permissions you need

 

What you need:

  • ClientID
  • ClientSecret - copy this out if you lose it you'll have to make a new key
  • The Redirect URL

The Redirect URL is an interesting one.  In the case of a web application - this would be your provider hosted web application.  In PowerShell however, we don't actually _need_ a valid web application.  We just need the returned URL which contains the code. 

It has to be https though.  So https://localhost:12345 will work fine.  
In my picture you also see https://dev.office.com that's actually not necessary.  

Onto the PowerShell:

 

#region mini window, made by (Insert credits here)
Function Show-OAuthWindow {
    Add-Type -AssemblyName System.Windows.Forms

    $form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width=440;Height=640}
    $web  = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width=420;Height=600;Url=($url -f ($Scope -join "%20")) }

    $DocComp  = {
        $Global:uri = $web.Url.AbsoluteUri        
        if ($Global:uri -match "error=[^&]*|code=[^&]*") {$form.Close() }
    }
    $web.ScriptErrorsSuppressed = $true
    $web.Add_DocumentCompleted($DocComp)
    $form.Controls.Add($web)
    $form.Add_Shown({$form.Activate()})
    $form.ShowDialog() | Out-Null
}
#endregion

Note

This is a brilliant dialog.  WinForms, hosting a web control.  Genius.

 

#login to get an auth code
$clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="
$redirectUri = "https://localhost:12345"

#$resource = "https://graph.microsoft.com/"
#most examples of Unified API is under graph.microsoft.com, but OneNote is a different resource.
$resource = "https://onenote.com"

#these need to be UrlEncoded so do it first
Add-Type -AssemblyName System.Web

$clientSecret = [System.Web.HttpUtility]::UrlEncode($clientSecret)
$redirectUri = [System.Web.HttpUtility]::UrlEncode($redirectUri)
$resource = [System.Web.HttpUtility]::UrlEncode($resource)

With all the variables we needed, we can begin

#show auth window
$url = "https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&redirect_uri=$redirectUri&client_id=$clientId&resource=$resource"
Show-OAuthWindow

This invokes the WinForm dialog above.

Haha!  WinForm in 2015 - love it!

#grab the auth code
#note the redirect localhost:12345 doesn't need to work - we grab the authcode right off the URL
#then throw away the window

$regex = '(?<=code=)(.*)(?=&)'
$authCode  = ($uri | Select-string -pattern $regex).Matches[0].Value

Write-output "Received an authCode, $authCode"

You have an authCode - this is step 1.  You can now ask Office 365 for Access token and Refresh Tokens.

We don't need to do this from the browser window anymore.  Invoke-RestMethod cmd works great.

#get access code and refresh token
$body = "grant_type=authorization_code&redirect_uri=$redirectUri&client_id=$clientId&client_secret=$clientSecret&code=$authCode&resource=$resource"
$result = Invoke-RestMethod https://login.microsoftonline.com/common/oauth2/token `
    -Method Post -ContentType "application/x-www-form-urlencoded" `
    -Body $body `
    -ErrorAction STOP

Write-output $result

Well well!  What have we got here.

#grab the access token
$access_token = $result.access_token

$result=Invoke-RestMethod https://www.onenote.com/api/beta/me/notes/pages `
    -Method GET -ContentType "application/json; charset=utf-8" `
    -Headers @{"Authorization" = "Bearer $access_token" } `
    -ErrorAction STOP

Write-output $result

And finally...  Create Page

$body = "<ol><li>Hello</li><li>OneNote</li><li>World</li></ol>"
$result=Invoke-RestMethod https://www.onenote.com/api/beta/me/notes/pages `
    -Method POST `
    -ContentType "text/html" `
    -Headers @{"Authorization" = "Bearer $access_token" } `
    -Body $body `
    -ErrorAction STOP

Write-output $result

Beauty!

Summary of Gotchas

  • On this development environment I didn't have OneNote available in Azure App Permissions.  I found out I need to create a OneNote document in Office 365 first and open it with OneNote online.  That seems to register that I need OneNote API.
  • I then forgot to grant the app permissions for OneNote
  • I didn't UrlEncode the parameters
  • I also had trouble getting Unauthorized when talking to OneNote - because I was requesting the graph.microsoft.com resource instead of onenote.com
  • Sometimes I still get an occasional error in the browser dialog.  Re-running it it disappears. 
  • Also, the point of the refresh token is that you don't need to pop up the user dialog anymore after the first time.  The refresh token should be stored securely and used to request new access-token on subsequent runs.

Had a few head-scratching moments like this.  But it's all great fun.

Download