/msft-todo-backup

🤖✨ A CLI for exporting (backing up) and importing (restoring) your precious Microsoft Todo lists and tasks for ultimate peace of mind

Primary LanguageTypeScriptMIT LicenseMIT

Black Lives Matter! Last commit timestamp Codecov Source license Monthly Downloads NPM version Uses Semantic Release!

msft-todo-backup

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.


Install

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 ...

Usage

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:

  1. 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.

  2. 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".

  3. 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.

Backing up Lists

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.

Automatic Backups

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).

Restoring Lists

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.

Dangerous Restoration

In addition to the default --deep-deduplication mode, there are three other non-default "dangerous" restoration modes:

--shallow-deduplication

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-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
--clean-before-restore

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

Enumerating and Deleting Stored Backups

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

Appendix

Further documentation can be found under docs/.

Limitations and Considerations

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.

Inspiration

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.

Published Package Details

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.

License

See LICENSE.

Contributing and Support

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.

Contributors

All Contributors

Thanks goes to these wonderful people (emoji key):

Bernard
Bernard

🚇 💻 📖 🚧 ⚠️ 👀
Add your contributions

This project follows the all-contributors specification. Contributions of any kind welcome!