Category Archives: OneDrive for Business

M365AutoLink v1.3

Today brings quite the update to M365AutoLink! Big improvements to central management and performance! Read below for all changes.

Or just get it here: https://github.com/jflieben/M365AutoLink

Security & authentication

  • PKCE + state added to the browser authorization-code flow. Any callback whose state does not match is ignored, so another local process can no longer inject an authorization code.
  • v2 (v2.0) Entra endpoints are now used for both the authorization and token requests (scope= instead of the legacy resource=), aligning with the OAuth 2.1 public-client baseline.
  • Loopback listener hardened: switched from a raw TcpListener on a fixed port to System.Net.HttpListener on an ephemeral port (fixes collisions on multi-session RDS/AVD hosts and races between instances). All reflected error text is HTML-encoded, and a small branded landing page is shown after sign-in.
  • TLS 1.2/1.3 is enforced at startup on Windows PowerShell 5.1.
  • Uninstall now deletes the refresh-token cache and log (RefreshToken.xml is a usable credential and must never survive an uninstall).
  • Consent/sign-in failures no longer call Exit from deep in the request path (which silently killed the tray). They surface as an error balloon with the admin-consent URL and keep the tray alive for a retry.

Reliability

  • Access-token caching fixed: a valid cached access token is now reused instead of performing a full refresh-token exchange (+ DPAPI encrypt + disk write) on every API call. Token lifetime comes from expires_in with a 5-minute renewal margin; the refresh token is written to disk only when Entra rotates it.
  • Deletion circuit breaker: the delete phase is skipped (with a warning + balloon) when SharePoint Search returns incomplete results or the desired set shrank by more than $DeletionSafetyRatio. A tombstone model requires a target to be absent on $DeletionTombstoneRuns consecutive runs before deletion. $ForceReconcile overrides both.
  • Single-instance guard: a session mutex prevents two tray icons / token-rotation races / log contention. A second launch signals the running instance to run and exits.
  • Metadata lookup no longer aborts the whole run on one transient error; failed items are skipped with a warning.
  • Unified retry pipelineInvoke-GraphRaw (config load/save) now retries transient 429/5xx/network errors, with correct Retry-After handling on both PowerShell 5.1 and 7.
  • Logging is written directly to lastRun.log (no longer dependent on Start-Transcript) and rotated (run-<timestamp>.log, keeping $LogHistoryCount).
  • Pre-flight checks warn (in one balloon) when OneDrive is not set up for a work account or the identity provider is unreachable.

Performance

  • Existing-shortcut metadata is fetched in a single RenderListDataAsStream call (with an automatic per-item fallback) instead of one call per shortcut.
  • The fixed Start-Sleep -Milliseconds 500 after every create/rename/delete was removed; throttling is handled by the shared retry/back-off.

UX

  • High-DPI: the process declares per-monitor-v2 DPI awareness and the floating progress bar scales with the display, so the UI is crisp at 125–200% scaling.
  • Manage shortcuts dialog: text filter, click-to-sort columns, Exclude-all/Include-all, and the window is resizable and remembers its size.
  • Periodic auto-refresh ($AutoRefreshHours, default off) re-runs on an interval while resident in the tray and shortly after the device resumes from sleep.
  • First-run onboarding balloon explains where the shortcuts are and points at Manage shortcuts.
  • Reduced the logon console flash on Windows 11 by launching through conhost.exe --headless.

Compatibility & correctness

  • PowerShell 7: large SharePoint JSON payloads use ConvertFrom-Json -AsHashtable instead of the .NET-Framework-only JavaScriptSerializerAdd-Type -AssemblyName replaces the deprecated LoadWithPartialName.
  • Wildcard exclusion/inclusion now uses PowerShell -like (correct anchor semantics). Note: exclusions tighten slightly — a trailing * is required for prefix matches (e.g. */sites/HR*).
  • Window-drag handler resolves the form at event time (dragging borderless dialogs works reliably).
  • Dead-code sweep (unused launch-mode set, per-page blocking GC, stale unique-name state).

Getting rid of onedrive library sync

Microsoft has been pushing ‘Add to Onedrive’ over ‘Synchronize’ for a while now. I missed the enhancement from 500k to 1000k max items.

This makes a lot more viable!

Ensuring users don’t sync is easy with the HideSyncButtonOnTeamSite parameter of Set-SPOTenant, but that does not invalidate existing sync relationships.

So how do we detect which users are syncing, and which libraries? Because we want to transition them too, right?

Here’s a nice remediation script to throw into Intune that’ll help you do just that!

download/readme: https://github.com/jflieben/assortedFunctionsV2/tree/main/O4B/Unsync

M365AutoLink unattended

Doing logon scripts is easy for some, less easy for others, and in general not great in certain types of environments.

And this led to some questions if I could also make a version of M365AutoLink that can run centrally. There was a hurdle to overcome: how do we know what libraries a user has access to?

M365Permissions already has the answer, so a quick copy paste from the code there and voila, we now have a centrally runnable version of M365AutoLink!

It can run either as managed identity, or cert-based service principal. I recommend running it as a runbook, and don’t run it on tenants with thousands of users or commercially….for commercial use click here 🙂

For full documentation and code: https://github.com/jflieben/M365AutoLink

Unexpected Onedrive Owners

How do you know who the original owner of a Onedrive site was?

The url says a lot, it’s usually in a username_domain_com format. If you have a fixed naming structure and don’t use _’s or other characters that are translated to _, it can be reversed with reasonable accuracy, right?

But what if you have two people with the same name? Or did some migrations or takeovers? Then your logic breaks 🙁

So reverse engineering the URL won’t work, and if an admin takes ownership or if the user leaves and the manager gets ownership, the Owner/Full Control ACL on the site also won’t tell you who the original owner was because it overwrites the Owner property.

In M365permissions the option to audit ownership of Onedrive is built in, as this is a common question during certain types of audits.

But how did we solve it there?

Well, it turns out that Sharepoint’s own metadata service maintains a pretty good track record of who was ever a user on a site, even after they are deleted from Entra.

Thus by doing a call to /_api/web/siteusers?$orderby=Id, we get a nicely ordered list of all users ever assigned to the site.

Pick the first non-system user, and we have our original user! See above 🙂