Exporting Microsoft Todo Data

Exporting Microsoft Todo Data
Photo by Rubaitul Azad / Unsplash

In today's digital age, managing and migrating data between different applications can be a daunting task. When you export your data from Microsoft Todo you will need to do a full export from outlook of your entire postbox, this will result in a Outlook PST file which you need to convert to JSON for use in another applications, this guide will walk you through the process using libpff and a custom Go script on macOS to convert the PST into normalized JSON for you to import into other tools like Heaper.

Prerequisites

  • Basic understanding of the terminal and Go programming language.
  • libpff library installed on macOS.
  • Exported PST data using pffexport.

Step 1: Install libpff on macOS

First, we need to install the libpff library, which allows us to work with PST files. Here’s how you can install it using Homebrew:

bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install autoconf automake libtool pkg-config gcc
brew install wget

wget https://github.com/libyal/libpff/releases/download/20231205/libpff-alpha-20231205.tar.gz
tar -xzf libpff-alpha-20231205.tar.gz
cd libpff-alpha-20231205

./configure
make
sudo make install

Step 2: Export PST Data to Files

Using pffexport, you can extract the contents of a PST file into a directory. This directory will contain Task.txt and possibly Message.txt files.

shmkdir pst_export
pffexport -t pst_export yourfile.pst

look for the exported directory in this folder and insert the path into the script below

Step 3: Write a Go Script to Convert Data to JSON

We need a script that will:

  1. Scan the exported directory for Task.txt files.
  2. Check for corresponding Message.txt files.
  3. Extract the parent folder name two levels up.
  4. Save all data into a JSON file.

Here’s the Go script to achieve this:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

// Task represents a single task with key-value pairs.
type Task struct {
	ParentFolder string            `json:"parent_folder"`
	Fields       map[string]string `json:"fields"`
	Message      string            `json:"message,omitempty"`
}

// ExtractTasks walks through the given directory and extracts tasks from Task.txt files.
func ExtractTasks(directory string) ([]Task, error) {
	var tasks []Task
	err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && strings.HasSuffix(info.Name(), "Task.txt") {
			task, err := parseTask(path)
			if err != nil {
				return err
			}
			tasks = append(tasks, task)
		}
		return nil
	})
	return tasks, err
}

// ParseTask reads and parses a single task from the given Task.txt file path.
func parseTask(path string) (Task, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return Task{}, err
	}
	lines := strings.Split(string(content), "\n")
	fields := make(map[string]string)
	for _, line := range lines {
		if strings.Contains(line, ":") {
			parts := strings.SplitN(line, ":", 2)
			key := strings.TrimSpace(parts[0])
			value := strings.TrimSpace(parts[1])
			fields[key] = value
		}
	}

	// Check for a corresponding Message.txt file
	messagePath := strings.Replace(path, "Task.txt", "Message.txt", 1)
	var message string
	if _, err := os.Stat(messagePath); err == nil {
		msgContent, err := ioutil.ReadFile(messagePath)
		if err == nil {
			message = string(msgContent)
		}
	}

	// Get parent folder name two levels up
	parentFolder := filepath.Base(filepath.Dir(filepath.Dir(path)))

	task := Task{
		ParentFolder: parentFolder,
		Fields:       fields,
		Message:      message,
	}
	return task, nil
}

// WriteJSON writes the tasks to a JSON file.
func WriteJSON(tasks []Task, jsonFile string) error {
	file, err := os.Create(jsonFile)
	if err != nil {
		return err
	}
	defer file.Close()

	encoder := json.NewEncoder(file)
	encoder.SetIndent("", "  ")
	err = encoder.Encode(tasks)
	if err != nil {
		return err
	}

	return nil
}

func main() {
	exportedDirectory := "./Data/Top of Personal Folders/Aufgaben" // Adjust the directory name to your actual path
	jsonOutput := "tasks.json"

	tasks, err := ExtractTasks(exportedDirectory)
	if err != nil {
		fmt.Printf("Error extracting tasks: %v\n", err)
		return
	}

	err = WriteJSON(tasks, jsonOutput)
	if err != nil {
		fmt.Printf("Error writing JSON: %v\n", err)
		return
	}

	fmt.Println("JSON file created successfully.")
}

Step 4: Run the Go Script

  1. Save the script to a file named convert_to_json.go.
  2. Open a terminal and navigate to the directory containing the script.
  3. Run the script using:
go run convert_to_json.go

Ensure that the pst export directory contains the appropriate files (Task.txt and optional Message.txt). The script will process these files and create a tasks.json file in the same directory.

Conclusion

With this approach, you can efficiently convert Outlook PST files into JSON format on macOS, enabling seamless data migration to other applications. This method leverages the power of Go for parsing and processing files, providing a robust and customizable solution.