import fs from "node:fs"; import { parse, resolve } from "node:path"; import { stringify } from "csv-stringify/sync"; import { PSTMessage, PSTFile, PSTFolder } from "pst-extractor"; export const getOutputPath = (pstPath: string, csvPath: string) => csvPath.endsWith(".csv") ? csvPath : fs.existsSync(csvPath) && fs.lstatSync(csvPath).isDirectory() ? `${csvPath}/${parse(pstPath).name}.csv` : `${csvPath}.csv`; interface EmailExchange { sender: string; to: string; cc: string; bcc: string; replyTo: string; subject: string; timestamp: string | null; } type Options = { stdout?: boolean; }; class PSTError extends Error { error: Error; constructor(error: Error) { super(error.message); this.error = error; } } const parsePstFile = (pstPath: string) => { try { return new PSTFile(pstPath); } catch (error) { if (error instanceof Error) { throw new PSTError(error); } throw error; } }; export function commandExchange( pstPath: string, csvPath: string, options: Options, ) { try { const pstFile = parsePstFile(resolve(pstPath)); const emails: EmailExchange[] = []; /** * Walk the folder tree recursively and process emails. * @param {PSTFolder} folder */ function processFolder(folder: PSTFolder) { // go through the folders... if (folder.hasSubfolders) { const childFolders = folder.getSubFolders(); for (const childFolder of childFolders) { processFolder(childFolder); } } // and now the emails for this folder if (folder.contentCount > 0) { let email: PSTMessage = folder.getNextChild(); while (email != null) { const nbRecipients = email.numberOfRecipients; const allRecipients = Array(nbRecipients) .fill(0) .map((_, i) => email.getRecipient(i)?.emailAddress) .join(", "); emails.push({ sender: email.senderEmailAddress, to: allRecipients, cc: email.originalDisplayCc, bcc: email.displayBCC, replyTo: email.replyRecipientNames, subject: email.subject, timestamp: email.messageDeliveryTime?.toISOString() ?? null, }); email = folder.getNextChild(); } } } processFolder(pstFile.getRootFolder()); const output = stringify(emails, { header: true, delimiter: "," }); const outputFilepath = getOutputPath(pstPath, csvPath); if (options.stdout) { console.log(output); } else { fs.writeFileSync(outputFilepath, output); console.log( "PST file", resolve(pstPath), "extracted into CSV file", resolve(outputFilepath), ); } } catch (error) { if (error instanceof PSTError) { console.error( `Unable to process the PST file. Are you sure ${resolve(pstPath)} is a valid PST file?`, ); } else if (error instanceof Error) { console.error(error.message); } else { throw error; } } }