An NPM package allowing the export (backup) and import (restoration) of your precious Microsoft Todo tasks for the paranoid among us that aren't willing to risk a decade of notes on the whims and stability of Microsoft's services.
Currently, one or more lists and all of their tasks can be exported. For lists, their display names and unique id are exported. For tasks, the following are exported: body, body last modified date and time, categories, completed date and time, created date and time, due date and time, unique id, importance, last modified date and time, recurrence pattern, reminder date and time, start date and time, status, title, attachments, and checklist items (so-called "steps").
This tool was built using streams to ensure proper function in low-memory environments. Additionally, unattended backups, throttling, and automatic retrying are supported out of the box.
However, while attachment and checklist items are backed up and can be restored, linked resources and extensions are ignored.
You can install this package globally:
npm install --global msft-todo-backup
Alternatively, you can use npx to call this package without pre-installation:
npx msft-todo-backup ...
For a full list of available commands and flags, use
msft-todo-backup --help
.
Before using msft-todo-backup
, you'll have to acquire a Microsoft Graph tenant
(directory) and client (application) ID:
-
Create a free new app registration by clicking "New registration". Select "Only associate the new app with your personal account". Name the application "msft-todo-backup". For Supported account types you must select "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)". No redirect URI should be configured. Click "Register". Once you're taken to your app's dashboard, copy the Application (client) ID and Directory (tenant) ID.
Microsoft has more detailed instructions on setting up a new app registration here.
-
Once the new app registration is created, it must be configured. Using the sidebar, click "Authentication" > scroll down to Advanced settings and set Allow public client flows to "Yes". Click "Save".
-
Register your app IDs with the CLI tool by running
msft-todo-backup authenticate
. You'll have to provide the Application ID and Directory ID, after which you'll be presented with instructions on how to link up with your Microsoft account using the device login flow. The IDs will be saved to~/msft-todo-backups/auth.json
while your sensitive account access tokens will be encrypted and stored elsewhere.
Note that, if you do not run msft-todo-backup anywhere from 30 to 90 days after
authenticating (or if you revoke access to the msft-todo-backup app from your
Microsoft account), your access/refresh tokens may expire. If this happens, just
rerun msft-todo-backup authenticate
.
Also note that, for some reason, you must complete authentication using your actual account. You cannot use the incredibly convenient "Sign in with another account" as of this writing. For example, you cannot authenticate successfully using your GitHub account to login to your Microsoft account. If you attempt to authenticate by entering the device code in your browser but are not presented with a permissions approval screen for the "msft-todo-backup" app, you must authenticate using your real account credentials instead.
Manually registering a new application is necessary because interacting with the Microsoft Graph API to backup your tasks can be an intensive and data-heavy process, especially if you have a lot of files attached to your tasks. Hence, creating a shared API service would quickly run into throttling and bandwidth limit issues.
You can backup all your lists:
msft-todo-backup backup
Or you can backup specific lists by their display name:
msft-todo-backup backup list-1 list-2 list-3
The backup process is non-destructive (read-only). Backups are stored at
~/msft-todo-backups
on Linux and %USERPROFILE%\msft-todo-backups
on Windows.
You can specify how many backups to keep around with the --keep-num-backups
flag:
# Will keep five backups in storage before overwriting the oldest backup
msft-todo-backup backup list-1 list-2 --keep-num-backups 5
If the backup is interrupted or fails, a partial backup will be available at
~/msft-todo-backups/...-partial.json
. Reattempting the backup process will
immediately delete any partial backup files, so save it someplace else if you
want to keep it.
Once msft-todo-backup authenticate
has executed successfully at least once,
and your access/refresh tokens have not expired, backups can be performed
automatically in the background without you having to do anything. For example,
to backup your Microsoft Todo tasks once a day, you can run
msft-todo-backup backup --keep-num-backups 5
as a daily cron (Linux) or
via the task scheduler (Windows).
You can restore all your lists:
msft-todo-backup restore
Or you can restore specific lists:
msft-todo-backup restore list-1 list-2 list-3
Or you can restore from a specific backup if you have multiple backups saved (defaults to the most recent "first" backup):
# Restore from the second stored backup (one-indexed)
msft-todo-backup restore list-1 list-2 list-3 --from-backup-index 2
# Restore from the first stored backup, making the following two lines equivalent
msft-todo-backup restore list-1 list-2 list-3 --from-backup-index 1
msft-todo-backup restore list-1 list-2 list-3
Restoration is non-destructive by default. Lists and tasks are never overwritten or deleted; all operations are append-only. Therefore, if the restoration is interrupted or fails, it can be safely restarted without worrying about duplicates or missing/lost/partial data.
Further, restored items are deeply deduplicated by default: lists with the same display name will not be recreated. Similarly, tasks with the same title will not be recreated.
In addition to the default --deep-deduplication
mode, there are three
other non-default "dangerous" restoration modes:
Unlike --deep-deduplication
, which deduplicates using list display names and
task titles, --shallow-deduplication
deduplicates using the IDs of lists and
tasks. For accounts with a lot of data-heavy tasks, this can potentially speed
things up somewhat.
However, since the creation of new lists/tasks results in those lists/tasks
having new IDs, restoration operations using --shallow-deduplication
are
NOT idempotent and NOT resumable if interrupted. That is: executing two
--shallow-deduplication
restoration operations to the same account
back-to-back without creating a new backup before the second restoration
operation will always result in duplicates.
msft-todo-backup restore "My Special List" --shallow-deduplication
No checks for duplicate lists or tasks are performed before restoration. When using this mode, all lists and tasks will always be restored separately from the lists and tasks that already exist in the account. This will likely result in many duplicates and will require manual resolution and list merging by the user.
You can use this mode to create an exact copy of an existing list. For example, to make an identical copy of "My Special List", you could issue the following command:
msft-todo-backup restore "My Special List" --no-deduplication
All existing lists and tasks will be deleted before the restoration process is performed. When using this mode, all existing lists and tasks in the entire account will be irreversibly destroyed. Afterwards, the restoration process will proceed as usual. While useful for performing a sort of "system restore" to return your lists back to a previous state in time, since this mode involves deletion of data, it is extremely dangerous and should be invoked only with the utmost caution.
msft-todo-backup restore --clean-before-restore
You can also enumerate and delete backups:
# List all backups along with the lists they contain and some metadata
msft-todo-backup list
# Delete all but the most recent two backup files
msft-todo-backup clean --keep-num-backups 2
# Delete all backup files
msft-todo-backup clean --keep-num-backups 0
# Will fail without the --keep-num-backups flag to prevent accidental usage
msft-todo-backup clean
Enumerating backups with msft-todo-backup list
will also reveal the internal
index of each backed up list, which can be provided when executing commands in
lieu of the list's display name. This is useful when two lists might have the
same name. When two or more lists have the same name, referring to that name
when executing a restoration operation will result in an error. However,
referring to the list using its internal index will allow the operation to
proceed normally.
For example, suppose your account has the following lists: "list-A" (index=1), "list-B" (index=2), and a second "list-A" (index=3). The following command, which attempts to restore only the second list-A, will fail:
# Fails due to ambiguity
msft-todo-backup restore list-A
However, the following command will succeed, restoring the second list-A and ignoring the first:
# Succeeds
msft-todo-backup restore --list-index 3
# You can also specify multiple IDs (one-indexed)
msft-todo-backup restore --list-index 2 --list-index=3
When two or more lists have the same name, referring to that name when executing a backup operation will not error. Instead, all of the lists with a matching display name will be backed up. For example, the following will backup all lists named "list-A":
# Succeeds
msft-todo-backup backup list-A
Further documentation can be found under docs/
.
First and foremost, though I've tested this on my own account and use it daily to back up thousands of tasks, make sure you test this package thoroughly to ensure it meets your needs before you use it on precious or sensitive data. You alone are responsible for the integrity, safety, and existence of your data.
Second, this package shares ZERO data or account permissions with any service or external entity except for Microsoft itself. Your data is yours.
Third, it seems the current Microsoft Graph API does not expose any method of capturing or specifying task list groups. This means any restoration action that requires new lists to be created will result in said lists not being added to any task list groups. Deduplicated restorations (the default kind of restoration), when adding tasks to existing task lists, are unaffected by this limitation. The old beta version of Microsoft's Graph API did have a taskGroups endpoint, but it looks like it was removed for some reason. Perhaps this can be revisited at a later point. PRs welcome!
Fourth, linked resources and extensions are not backed up, though the API exposes methods of capturing and perhaps restoring them. I haven't played around with it though since the functionality would be of limited use to me. PRs welcome!
Finally, note that moving a task from one list to another will change its ID. This can result in unexpected duplicates during restorations when using non-default restoration modes.
Thanks to Dan O'Sullivan for giving me an idea of how Microsoft's Graph API works and the knowledge that backing up Microsoft Todo tasks was even possible.
This is a CJS2 package with statically-analyzable exports built by Babel for Node14 and above.
Expand details
That means both CJS2 (via require(...)
) and ESM (via import { ... } from ...
or await import(...)
) source will load this package from the same entry points
when using Node. This has several benefits, the foremost being: less code
shipped/smaller package size, avoiding dual package
hazard entirely, distributables are not
packed/bundled/uglified, and a less complex build process.
Each entry point (i.e. ENTRY
) in package.json
's
exports[ENTRY]
object includes one or more export
conditions. These entries may or may not include: an
exports[ENTRY].types
condition pointing to a type
declarations file for TypeScript and IDEs, an
exports[ENTRY].module
condition pointing to
(usually ESM) source for Webpack/Rollup, an exports[ENTRY].node
condition
pointing to (usually CJS2) source for Node.js require
and import
, an
exports[ENTRY].default
condition pointing to source for browsers and other
environments, and other conditions not enumerated
here. Check the package.json file to see which export
conditions are supported.
Though package.json
includes
{ "type": "commonjs" }
, note that any ESM-only entry points will
be ES module (.mjs
) files. Finally, package.json
also
includes the sideEffects
key, which is false
for
optimal tree shaking.
See LICENSE.
New issues and pull requests are always welcome and greatly appreciated! 🤩 Just as well, you can star 🌟 this project to let me know you found it useful! ✊🏿 Thank you!
See CONTRIBUTING.md and SUPPORT.md for more information.
Thanks goes to these wonderful people (emoji key):
Bernard 🚇 💻 📖 🚧 |
||||||
Add your contributions |
This project follows the all-contributors specification. Contributions of any kind welcome!