
Here’s the entire changelog entry: “Added the getNotebookFromWebUrl method.” That’s it. A helper that takes a OneNote share URL and hands you back the notebook object instead of making you parse IDs by hand. Genuinely useful if you write Graph automation against OneNote. Completely uninteresting as identity news.
Except it isn’t, and here’s why. Every call to that method rides on a Graph permission — Notes.Read, Notes.ReadWrite, or their tenant-wide .All variants. And if you’ve been running an Entra tenant for more than a year, I will bet you a coffee that at least one app registration in your directory holds Notes.ReadWrite.All as an application permission that nobody remembers granting, that reads every notebook belonging to every user in your org, and that hasn’t rotated its secret since the last admin left. This changelog is a reminder to go look.
What actually shipped, and where
Feature: the getNotebookFromWebUrl action on the OneNote API. Status: GA, promoted into Microsoft Graph v1.0 (it lived in beta before). Affected tenants: all of them — this is a service-side Graph capability, not a tenant rollout you opt into or schedule. Endpoint: POST /me/onenote/notebooks/getNotebookFromWebUrl (and the /users/{id}/ and /groups/{id}/ variants).
The improvement over the old approach is real if narrow. Previously, to go from a notebook link a user pasted into a ticket back to the notebook resource, you cracked the URL apart yourself, extracted the section-group and notebook identifiers, and hoped Microsoft didn’t change the URL format. Now you POST the web URL and get the notebook back. Fewer brittle regexes, fewer support tickets from automation that broke when the share-link format shifted. Fine. Good. Moving on.
The permission surface is the story
What matters to you as the identity owner is the consent footprint. The relevant scopes:
Notes.Read/Notes.ReadWrite— delegated, acts as the signed-in user, bounded to what that user can see. Low blast radius.Notes.Read.All/Notes.ReadWrite.All— as application permissions these read or write every notebook across the tenant with no user in the loop. High blast radius. These require admin consent, and once granted they sit there silently forever.
That last category is the one that bites people in six months. An integration vendor asks for Notes.ReadWrite.All “to sync notes,” an admin clicks grant during onboarding, and eighteen months later the vendor is out of contract but the service principal still has standing access to every OneNote in the company. The getNotebookFromWebUrl method just makes that access marginally more convenient to use. Time to inventory it.
Audit which apps hold Notes permissions
Modern module only. If you still have AzureAD or MSOnline loaded, stop — both were retired on 30 March 2025 and no longer receive updates or, increasingly, work at all against the service. Use Microsoft.Graph (or the GA Microsoft.Entra module). This script reports every service principal holding a Notes-related application permission on Microsoft Graph, with pagination and error handling, and changes nothing:
#requires -Modules Microsoft.Graph.Authentication, Microsoft.Graph.Applications
# Read-only audit. Nothing here modifies the directory.
try {
Connect-MgGraph -Scopes 'Application.Read.All','Directory.Read.All' -NoWelcome -ErrorAction Stop
}
catch {
throw "Connect-MgGraph failed: $($_.Exception.Message)"
}
$graphAppId = '00000003-0000-0000-c000-000000000000' # Microsoft Graph
try {
$graphSp = Get-MgServicePrincipal -Filter "appId eq '$graphAppId'" -ErrorAction Stop
}
catch {
throw "Could not resolve the Microsoft Graph service principal: $($_.Exception.Message)"
}
# Map the Notes app-role GUIDs to their friendly names
$notesRoles = @{}
$graphSp.AppRoles |
Where-Object { $_.Value -like 'Notes.*' } |
ForEach-Object { $notesRoles[$_.Id] = $_.Value }
Write-Host "Graph exposes $($notesRoles.Count) Notes.* application roles." -ForegroundColor Cyan
$results = [System.Collections.Generic.List[object]]::new()
# -All handles pagination for you; do NOT hand-roll @odata.nextLink here
try {
$servicePrincipals = Get-MgServicePrincipal -All -ErrorAction Stop
}
catch {
throw "Failed to enumerate service principals: $($_.Exception.Message)"
}
foreach ($sp in $servicePrincipals) {
try {
$assignments = Get-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId $sp.Id -All -ErrorAction Stop
}
catch {
Write-Warning "Skipping $($sp.DisplayName): $($_.Exception.Message)"
continue
}
foreach ($a in $assignments) {
if ($a.ResourceId -eq $graphSp.Id -and $notesRoles.ContainsKey($a.AppRoleId)) {
$results.Add([pscustomobject]@{
App = $sp.DisplayName
AppId = $sp.AppId
SpObjectId = $sp.Id
Permission = $notesRoles[$a.AppRoleId]
AssignmentId = $a.Id
TenantWide = $notesRoles[$a.AppRoleId] -like '*.All'
})
}
}
}
$results |
Sort-Object TenantWide -Descending |
Format-Table App, Permission, TenantWide, AppId -AutoSize
Write-Host "`n$($results.Count) Notes application-permission grant(s) found." -ForegroundColor Yellow
$results | Export-Csv .\notes-app-permissions.csv -NoTypeInformation
Run it, then look hard at every row where TenantWide is True. Each of those is an app that can read — or in the ReadWrite case, silently modify — any notebook in the organization. Cross-check against a list of applications you actually still use.
Don’t forget the delegated grants
Application permissions are the loud risk, but a widely user-consented delegated grant matters too, especially if you never locked down user consent. This reports OAuth2 delegated grants for Notes scopes:
try {
$grants = Get-MgOauth2PermissionGrant -All -ErrorAction Stop |
Where-Object { $_.Scope -match 'Notes\.' }
}
catch {
throw "Failed to read OAuth2 permission grants: $($_.Exception.Message)"
}
$grants | Select-Object ClientId, ConsentType, PrincipalId,
@{n='NotesScopes';e={ ($_.Scope -split ' ' | Where-Object {$_ -like 'Notes.*'}) -join ',' }} |
Format-Table -AutoSize
A ConsentType of AllPrincipals means an admin consented on behalf of the whole tenant. Principal means individual users clicked through a consent prompt themselves — which tells you your user consent policy was open when they did it.
If you decide to revoke — do it deliberately
Never pipe the audit straight into a bulk delete. Confirm the app, confirm the owner, then remove one grant at a time. This defaults to -WhatIf so a copy-paste can’t nuke access:
param(
[Parameter(Mandatory)] [string] $SpObjectId,
[Parameter(Mandatory)] [string] $AssignmentId
)
# Requires AppRoleAssignment.ReadWrite.All — connect with that scope separately.
# Remove the -WhatIf ONLY after you have verified this specific assignment.
Remove-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId $SpObjectId `
-AppRoleAssignmentId $AssignmentId `
-WhatIf
The adjacent changes worth folding into the same sweep
Since you’re already in here, a few things Microsoft has moved on that touch this exact workflow:
- Microsoft Entra PowerShell module is GA. It reached general availability in 2025 and sits on the same Graph SDK auth stack, with cmdlets shaped closer to admin tasks than the raw
Microsoft.Graph.*surface. If you’re writing new governance scripts, it’s a reasonable default; the Graph SDK remains the lowest-common-denominator choice for coverage. - App consent policies and the admin consent request workflow are how you stop the next forgotten
Notes.ReadWrite.All. In Entra admin center under Identity > Applications > Enterprise applications > Consent and permissions, set user consent to “allow for verified publishers, selected permissions” or disable it outright, and turn on the admin consent request flow so grants route to a reviewer instead of a self-service click. - App instance lock and credential hygiene. The apps holding tenant-wide Notes access are also the ones most likely running on a secret nobody rotates. Pair this audit with a credential-expiry report on the same service principals.
Where to verify in the portal
To confirm the PowerShell output by hand: Entra admin center > Identity > Applications > Enterprise applications, pick the app, then Permissions. Application permissions show under the “granted for [tenant]” view; delegated show alongside. The Sign-in logs for that service principal tell you whether it’s actually still being used or just squatting on access it hasn’t exercised in a year.
Bottom line
- The method itself needs zero action.
getNotebookFromWebUrlis a convenience call in Graph v1.0. Your developers will thank Microsoft; you don’t have to do anything. - Run the application-permission audit this week. Any
Notes.*.Allgrant to an app you can’t account for is standing tenant-wide access to every notebook — treat it as an incident to investigate, not a curiosity. - Close the intake. Lock down user consent and enable the admin consent workflow so the next broad grant gets reviewed instead of clicked. That’s the fix that prevents the re-run of this audit in six months.