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:
- @richdizz for a quick tip: https://twitter.com/richdizz/status/653695833364234240 which points to
- @FoxDeploy for this article: http://foxdeploy.com/2015/09/25/using-powershell-and-oauth/
- @coatsy for running O365dev Day in Sydney, which I attended and all the OAuth stuff is very fresh in my head. This stuff one needs to do it, and practice and practice and you'll get it.
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).
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.