Skip to content

Instantly share code, notes, and snippets.

@Kenya-West
Created September 12, 2025 14:30
Show Gist options
  • Select an option

  • Save Kenya-West/29049f39aeaf9d3b6730fc3ad529047c to your computer and use it in GitHub Desktop.

Select an option

Save Kenya-West/29049f39aeaf9d3b6730fc3ad529047c to your computer and use it in GitHub Desktop.
A Node.js utility script to parse Microsoft Launcher backup JSON files and list apps (by package ID) folders-aware.

Microsoft Launcher Folder Extractor

A Node.js utility script to parse Microsoft Launcher backup JSON files and list which apps (by package ID) are placed inside which home screen folders.

This is useful when migrating, auditing, or just inspecting your Launcher setup.

Note

You must first export a backup from Microsoft Launcher (via its settings) and unwrap it via this script by command node .\script.js .\launcher_backup_main.bak .\backup.json --unwrap-strings


Features

  • Reads a Microsoft Launcher JSON backup.
  • Extracts folders (KeyForAllDesktopFolders) and their contained shortcuts (KeyForAllDesktopShortcuts).
  • Parses package identifiers from Android intents.
  • Outputs two files by default:
    • list.json → machine-readable structured JSON
    • list.txt → human-readable text summary
  • CLI arguments let you customize output filenames.
  • Optionally print results to STDOUT.

Installation

Clone or download the script, then install dependencies (only Node.js standard library is used, no npm install required).

git clone https://gist.github.com/your-gist-id.git launcher-folder-extractor
cd launcher-folder-extractor

Make the script executable (optional):

chmod +x script.js

Usage

Basic

node script.js backup.json
  • Reads backup.json (exported from Microsoft Launcher).

  • Writes:

    • list.json (JSON output)
    • list.txt (human-readable summary)

Custom Output Paths

node script.js backup.json --json-out out/folders.json --text-out out/folders.txt

Print JSON to STDOUT

node script.js backup.json --stdout

CLI Options

Flag Alias Default Description
--json-out -j list.json Path to JSON output file
--text-out -t list.txt Path to human-readable text output file
--stdout off Also print JSON result to terminal (stdout)

Example Output

JSON (list.json)

[
  {
    "folderId": 101,
    "folderTitle": "Work",
    "screenId": 0,
    "items": [
      { "package": "com.microsoft.teams", "title": "Teams" },
      { "package": "com.microsoft.outlook", "title": "Outlook" }
    ]
  },
  {
    "folderId": 102,
    "folderTitle": "Social",
    "screenId": 0,
    "items": [
      { "package": "com.whatsapp", "title": "WhatsApp" },
      { "package": "org.telegram.messenger", "title": "Telegram" }
    ]
  }
]

Text (list.txt)

Folders summary (2025-09-12T12:00:00.000Z):

• Folder "Work" [id=101, screen=0] — 2 item(s)
   - Teams  <com.microsoft.teams>
   - Outlook  <com.microsoft.outlook>

• Folder "Social" [id=102, screen=0] — 2 item(s)
   - WhatsApp  <com.whatsapp>
   - Telegram  <org.telegram.messenger>

Notes

  • Only home screen folders are processed. If your backup contains app drawer folders under a different key, extend the script to handle them.
  • Shortcuts that aren’t apps (e.g., contacts or deep links) will still appear, but package may be null.
  • The script intentionally omits raw Android intents in the output.

License

MIT — use freely for your backups and launcher management.

#!/usr/bin/env node
/**
* script.js
*
* Usage:
* node script.js <launcher-backup.json> [--json-out list.json] [--text-out list.txt] [--stdout]
*
* What it does:
* - Reads Microsoft Launcher backup JSON
* - Maps home screen folders to their contained app shortcuts
* - Extracts app package IDs from intents
* - Writes:
* 1) JSON file (array of folders with {folderId, folderTitle, screenId, items[{package, title}]})
* 2) Text file (human-readable)
*
* Notes:
* - rawIntent is intentionally NOT included in output items (bug fix).
*/
const fs = require('fs');
const path = require('path');
function die(msg) {
console.error(msg);
process.exit(2);
}
function parseArgs(argv) {
const args = { jsonOut: 'list.json', textOut: 'list.txt', stdout: false, input: null };
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === '--json-out' || a === '-j') {
if (!argv[i + 1]) die('Missing value for --json-out');
args.jsonOut = argv[++i];
} else if (a === '--text-out' || a === '-t') {
if (!argv[i + 1]) die('Missing value for --text-out');
args.textOut = argv[++i];
} else if (a === '--stdout') {
args.stdout = true;
} else if (!args.input) {
args.input = a;
} else {
die(`Unrecognized or extra argument: ${a}`);
}
}
if (!args.input) die('Usage: node script.js <launcher-backup.json> [--json-out list.json] [--text-out list.txt] [--stdout]');
return args;
}
function safeReadJSON(filePath) {
try {
const raw = fs.readFileSync(filePath, 'utf8');
return JSON.parse(raw);
} catch (e) {
die(`Failed to read/parse JSON "${filePath}": ${e.message}`);
}
}
function parsePackageFromIntent(intent) {
if (!intent || typeof intent !== 'string') return null;
// Pattern 1: URI-style "#Intent;...;component=com.pkg/.Activity;end"
let m = intent.match(/(?:component=)([^\/;\s]+)\//);
if (m && m[1]) return m[1];
// Pattern 2: "Intent { ... cmp=com.pkg/.Activity }"
m = intent.match(/(?:\bcmp=)([^\/\s}]+)\//);
if (m && m[1]) return m[1];
// Pattern 3: "cmp=com.pkg/.Something" (fallback parsing)
m = intent.match(/\bcmp=([^\s;}]+)/);
if (m && m[1]) return m[1].split('/')[0];
// Pattern 4: "package=com.pkg"
m = intent.match(/\bpackage=([^\s;]+)/);
if (m && m[1]) return m[1];
return null;
}
function buildFolderMap(data) {
const folders = Array.isArray(data?.KeyForAllDesktopFolders) ? data.KeyForAllDesktopFolders : [];
const shortcuts = Array.isArray(data?.KeyForAllDesktopShortcuts) ? data.KeyForAllDesktopShortcuts : [];
// Index shortcuts by folder container id
const byContainer = new Map();
for (const sc of shortcuts) {
const container = sc?.container;
if (typeof container !== 'number') continue;
if (!byContainer.has(container)) byContainer.set(container, []);
byContainer.get(container).push(sc);
}
const result = folders.map(f => {
const contained = byContainer.get(f.id) || [];
const items = contained.map(sc => ({
package: parsePackageFromIntent(sc.intent),
title: sc.title ?? null,
// rawIntent intentionally omitted (bug fix)
}));
return {
folderId: f.id,
folderTitle: f.title ?? '',
screenId: f.screenId ?? null,
items,
};
});
return result;
}
function toHumanReadableText(folderArray) {
const lines = [];
lines.push(`Folders summary (${new Date().toISOString()}):`);
lines.push('');
if (!folderArray.length) {
lines.push('No folders found.');
return lines.join('\n');
}
for (const f of folderArray) {
const header = `• Folder "${f.folderTitle || '(untitled)'}" [id=${f.folderId}, screen=${f.screenId ?? 'n/a'}] — ${f.items.length} item(s)`;
lines.push(header);
if (f.items.length === 0) {
lines.push(' (empty)');
} else {
for (const it of f.items) {
const pkg = it.package || '(unknown package)';
const title = it.title || '(no title)';
lines.push(` - ${title} <${pkg}>`);
}
}
lines.push(''); // blank line between folders
}
return lines.join('\n');
}
function ensureParentDir(filePath) {
const dir = path.dirname(path.resolve(filePath));
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
function main() {
const args = parseArgs(process.argv);
const data = safeReadJSON(args.input);
const folderArray = buildFolderMap(data);
// Write JSON
ensureParentDir(args.jsonOut);
fs.writeFileSync(args.jsonOut, JSON.stringify(folderArray, null, 2), 'utf8');
// Write text
const text = toHumanReadableText(folderArray);
ensureParentDir(args.textOut);
fs.writeFileSync(args.textOut, text + '\n', 'utf8');
// Optional STDOUT
if (args.stdout) {
console.log(JSON.stringify(folderArray, null, 2));
}
console.error(`✔ Wrote JSON: ${path.resolve(args.jsonOut)}`);
console.error(`✔ Wrote text: ${path.resolve(args.textOut)}`);
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment