Bastion.Pst

API Reference · version 2.0.0.0 · fully managed .NET PST/OST library — read, write, merge, split, repair & validate

Introduction

Bastion.Pst is a fully managed .NET library for working with Microsoft Outlook data files — .pst and .ost — without Outlook, without MAPI, and without any external runtime. It reads both legacy ANSI and modern Unicode stores and writes standards-compliant Unicode PST files that open cleanly in every version of Outlook from Outlook 2003 onwards (Outlook 2003, 2007, 2010, 2013, 2016, 2019, 2021 and Microsoft 365). The Unicode PST format was introduced in Outlook 2003; Outlook 2002 and earlier read only the legacy ANSI format, so Bastion PST SDK will not silently upgrade an ANSI store to Unicode without your explicit consent (see the ANSI → Unicode safety gate).

What it does

Designed for shipping software

Outlook compatibility — which versions open the files it writes

Bastion PST SDK always writes the Unicode PST format (the modern large-file format, introduced with Outlook 2003). Every Outlook release from 2003 onward opens the files it produces:

Outlook versionYearOpens Bastion PST SDK output (Unicode PST)?
Outlook 971997No — ANSI-only
Outlook 981998No — ANSI-only
Outlook 20001999No — ANSI-only
Outlook 2002 / XP2001No — ANSI-only
Outlook 20032003Yes — Unicode introduced here
Outlook 20072007Yes
Outlook 20102010Yes
Outlook 20132013Yes
Outlook 20162016Yes
Outlook 20192018Yes
Outlook 20212021Yes
Outlook 20242024Yes
Outlook for Microsoft 365currentYes

It will not open in Outlook 2002/XP or earlier — those releases only understand the legacy ANSI PST format (2 GB ceiling) and cannot read a Unicode PST. If you must target one of those, the file is not compatible; there is no ANSI-write mode. Note the reverse works: Bastion PST SDK happily reads those old ANSI PST/OST files and converts them to Unicode. (The classic “new Outlook” Windows app and Outlook on the web do not use on-disk PST files at all, so this table concerns desktop Outlook.)

.NET compatibility — the library ships for every current runtime

Reference the assembly under lib/ that matches your project. The API is identical across all of them.

AssemblyRuntimeUse from
lib/net46 … lib/net481.NET Framework 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1.NET Framework apps — a native build for your exact version
lib/netcoreapp2.0 … lib/netcoreapp3.1.NET Core 2.0, 2.1, 3.0, 3.1.NET Core apps
lib/net5.0 … lib/net10.0.NET 5, 6, 7, 8 (LTS), 9, 10 (LTS)Modern .NET apps, Windows/Linux/macOS

Eighteen builds, one per framework. There is no netstandard build: each runtime gets a native assembly instead of a shim. A WinForms app targeting net8.0-windows uses lib/net8.0 — the library is portable and needs no Windows-specific build.

On .NET Core and .NET 5+ also add the Microsoft package System.Text.Encoding.CodePages (for correct ANSI→Unicode transcoding). The eight .NET Framework builds need nothing beyond the framework: those code pages are built in, and the assembly does not reference the package at all, so Bastion.Pst.dll can simply be dropped next to your executable.

PowerShell

Because it is an ordinary managed assembly with no native parts, Bastion PST SDK is also a practical PowerShell library — useful for auditing a directory of archives, exporting a mailbox, or merging a decade of PST files from a scheduled job. Load lib\net48 from Windows PowerShell 5.1 and lib\net8.0 from PowerShell 7+. The distribution ships a ready-made module and fourteen worked tasks in samples/PowerShell; see example 32.

Product & support

Websitebastionsoftwaresolutions.com
Supportsupport@bastionsoftwaresolutions.com
NamespacesBastion.Pst.Messaging (read API), Bastion.Pst.Views (bindable read model, queries, item operations), Bastion.Pst.Convert (merge / split / convert), Bastion.Pst.Authoring (create / edit), Bastion.Pst.Import / .Export, Bastion.Pst.Validation, Bastion.Pst.Repair, Bastion.Pst.Diagnostics

© 2026 Bastion Software Solutions Ltd — bastionsoftwaresolutions.com. This page documents the public API only; internal types are intentionally omitted.

Evaluation build. The library runs as a thirty-day evaluation with every feature available. During the evaluation, input .pst and .ost files are limited to 100 MB, and merged or stacked output is split into 500 MB volumes instead of the 50 GB a licensed build uses.

Quick Start

Every example below shows the same operation in VB.NET and C#, and every one demonstrates the return-code pattern — check ErrorCode first, then read the human-readable description. The library never throws into your code, so you never need a Try/Catch around a call for control flow.

Imports / usings

VB.NET

Imports Bastion.Pst               ' enmErrorCode, clsOperationResult
Imports Bastion.Pst.Convert       ' clsStoreMerger, clsDedup, enmMergeLayout
Imports Bastion.Pst.Diagnostics   ' clsDiagnostics
Imports Bastion.Pst.Messaging     ' enmItemKind, clsPersonalStorage

C#

using Bastion.Pst;               // enmErrorCode, clsOperationResult
using Bastion.Pst.Convert;       // clsStoreMerger, clsDedup, enmMergeLayout
using Bastion.Pst.Diagnostics;   // clsDiagnostics
using Bastion.Pst.Messaging;     // enmItemKind, clsPersonalStorage

1. Simplest merge (Stacked) — and how to read the return code

Merge several PSTs into one. Each source keeps its own folder tree under the destination root. overwrite:=True replaces the destination if it already exists (otherwise you get ErrorCode 7 DestinationExists).

VB.NET

Dim report = clsStoreMerger.Merge({"a.pst", "b.pst", "c.pst"}, "merged.pst", overwrite:=True)

' --- return-code handling (applies to every operation in the library) ---
If report.ErrorCode <> enmErrorCode.Ok Then
    Console.WriteLine("Error number : " & CInt(report.ErrorCode))     ' e.g. 3
    Console.WriteLine("Error text   : " & report.ErrorDescription)    ' "The file is locked ..."
    Console.WriteLine("Combined     : " & report.ToString())          ' "3: The file is locked ..."
    Console.WriteLine("Technical    : " & report.Detail)              ' full detail for support
    Return
End If
Console.WriteLine($"{report.TotalNodes:N0} items written to merged.pst.")

C#

var report = clsStoreMerger.Merge(new[] { "a.pst", "b.pst", "c.pst" }, "merged.pst", overwrite: true);

// --- return-code handling (applies to every operation in the library) ---
if (report.ErrorCode != enmErrorCode.Ok)
{
    Console.WriteLine("Error number : " + (int)report.ErrorCode);      // e.g. 3
    Console.WriteLine("Error text   : " + report.ErrorDescription);    // "The file is locked ..."
    Console.WriteLine("Combined     : " + report.ToString());          // "3: The file is locked ..."
    Console.WriteLine("Technical    : " + report.Detail);              // full detail for support
    return;
}
Console.WriteLine($"{report.TotalNodes:N0} items written to merged.pst.");

report.Succeeded is a convenience boolean equal to ErrorCode == Ok. The full list of numbers is in the enmErrorCode reference below.

2. Unified merge with duplicate removal, a custom store name, and cleanup

Unified blends same-named folders across all sources into one (one Inbox, one Sent Items). Passing a clsDedup removes duplicate messages; rootName sets the name Outlook shows for the mounted store; pruneEmpty drops folders left empty; blank “junk” messages are removed by default and written to the user log.

VB.NET

Dim diag As New clsDiagnostics With {.CreateUserLog = True, .RecoverFromCorruption = True}

Dim report = clsStoreMerger.Merge(
    {"2019.pst", "2020.pst", "2021.pst"}, "combined.pst",
    dedup:=New clsDedup(),
    diag:=diag,
    overwrite:=True,
    layout:=enmMergeLayout.Unified,
    rootName:="All Mail 2019-2021",
    pruneEmpty:=True)

If report.ErrorCode = enmErrorCode.Ok Then
    Console.WriteLine($"OK - {report.DuplicatesRemoved:N0} duplicates, " &
                      $"{report.BlankMessagesRemoved:N0} blank and " &
                      $"{report.FoldersPruned:N0} empty folders removed.")
    Console.WriteLine($"Activity log: {diag.UserLogPath}")
Else
    Console.WriteLine(report.ToString())
End If

C#

var diag = new clsDiagnostics { CreateUserLog = true, RecoverFromCorruption = true };

var report = clsStoreMerger.Merge(
    new[] { "2019.pst", "2020.pst", "2021.pst" }, "combined.pst",
    dedup: new clsDedup(),
    diag: diag,
    overwrite: true,
    layout: enmMergeLayout.Unified,
    rootName: "All Mail 2019-2021",
    pruneEmpty: true);

if (report.ErrorCode == enmErrorCode.Ok)
{
    Console.WriteLine($"OK - {report.DuplicatesRemoved:N0} duplicates, " +
                      $"{report.BlankMessagesRemoved:N0} blank and " +
                      $"{report.FoldersPruned:N0} empty folders removed.");
    Console.WriteLine($"Activity log: {diag.UserLogPath}");
}
else
{
    Console.WriteLine(report.ToString());
}

3. Merge only certain item types

Pass a set of enmItemKind values to keep only those (here: mail and contacts); everything else is filtered out and counted in report.FilteredOut.

VB.NET

Dim kinds As New HashSet(Of enmItemKind) From {enmItemKind.Mail, enmItemKind.Contact}

Dim report = clsStoreMerger.Merge({"source.pst"}, "mail-and-contacts.pst",
                                  keepKinds:=kinds, overwrite:=True)
Console.WriteLine($"{report.MessagesMerged:N0} kept, {report.FilteredOut:N0} filtered out.")

C#

var kinds = new HashSet<enmItemKind> { enmItemKind.Mail, enmItemKind.Contact };

var report = clsStoreMerger.Merge(new[] { "source.pst" }, "mail-and-contacts.pst",
                                  keepKinds: kinds, overwrite: true);
Console.WriteLine($"{report.MessagesMerged:N0} kept, {report.FilteredOut:N0} filtered out.");

4. Keep everything verbatim (no dedup, no blank drop, no prune)

For a byte-faithful archive, turn the cleanups off.

VB.NET

Dim report = clsStoreMerger.Merge({"a.pst", "b.pst"}, "verbatim.pst",
                                  overwrite:=True,
                                  pruneEmpty:=False,
                                  dropBlankMessages:=False)   ' keep blank/empty messages too

C#

var report = clsStoreMerger.Merge(new[] { "a.pst", "b.pst" }, "verbatim.pst",
                                  overwrite: true,
                                  pruneEmpty: false,
                                  dropBlankMessages: false);  // keep blank/empty messages too

5. Merge into size-limited volumes

When the combined data would exceed a size you want per file, MergeToVolumes writes a numbered series of PSTs, each capped at capBytes. The result carries per-volume details.

VB.NET

Dim vol = clsStoreMerger.MergeToVolumes(
    {"big1.pst", "big2.pst"}, "archive.pst",
    capBytes:=10L * 1024 * 1024 * 1024,       ' 10 GB per volume
    dedup:=New clsDedup(),
    overwrite:=True,
    rootName:="Archive")

If vol.ErrorCode <> enmErrorCode.Ok Then
    Console.WriteLine(vol.ToString())
Else
    Console.WriteLine($"{vol.Volumes.Count} volume(s), {vol.TotalMessages:N0} messages.")
    For Each v In vol.Volumes
        Console.WriteLine($"  {v.Path}: {v.Messages:N0} msgs, {v.SizeBytes / 1048576.0:N0} MB")
    Next
End If

C#

var vol = clsStoreMerger.MergeToVolumes(
    new[] { "big1.pst", "big2.pst" }, "archive.pst",
    capBytes: 10L * 1024 * 1024 * 1024,       // 10 GB per volume
    dedup: new clsDedup(),
    overwrite: true,
    rootName: "Archive");

if (vol.ErrorCode != enmErrorCode.Ok)
{
    Console.WriteLine(vol.ToString());
}
else
{
    Console.WriteLine($"{vol.Volumes.Count} volume(s), {vol.TotalMessages:N0} messages.");
    foreach (var v in vol.Volumes)
        Console.WriteLine($"  {v.Path}: {v.Messages:N0} msgs, {v.SizeBytes / 1048576.0:N0} MB");
}

Merge options at a glance

ParameterTypeDefaultEffect
sourcePathsIList(Of String)—The PST/OST files to merge (one or many).
outPathString—Destination Unicode PST.
dedupclsDedupNothingPass New clsDedup() to remove duplicate messages (keep-first by Internet Message-ID, falling back to subject + sender + sent-time). Scoped to each destination folder: the same message filed in two different folders is kept in both, so the folder tree you put in is the folder tree you get back. Set AcrossFolders = True to collapse duplicates across the whole output instead — this compacts the folder tree, leaving folders empty whose contents existed elsewhere (and deleting them outright if pruneEmpty is on). Only offer it where the person choosing has been told that plainly.
keepKindsHashSet(Of enmItemKind)NothingKeep only these item types; Nothing keeps all.
pruneEmptyBooleanFalseRemove folders that end up empty. Off by default — empty folders carry through. A folder the user created and left empty is part of their filing; a merge does not remove it unless asked. Note that search folders are never carried into the output whatever this is set to: a search folder is a saved query over other folders, not a container of mail, and its results live in the real folders, which are merged.
diagclsDiagnosticsNothingProgress events, user log, corruption recovery.
overwriteBooleanFalseReplace the destination if it exists.
maxBytesLong0Hard output size guard (0 = format ceiling).
layoutenmMergeLayoutStackedStacked or Unified.
rootNameStringNothingStore name Outlook shows; defaults to “Stacked Merge”/“Unified Merge”.
pruneEmptyBooleanFalseRemove folders that end up empty.
dropBlankMessagesBooleanTrueRemove provably-empty messages (logged); set False to keep everything.

More VB.NET merge recipes

The same Merge / MergeToVolumes methods cover a wide range of tasks. These VB.NET snippets assume the imports shown at the top.

VB.NET — live progress and corruption recovery

Dim diag As New clsDiagnostics With {.RecoverFromCorruption = True, .CreateUserLog = True}
AddHandler diag.WriteProgress, Sub(s, ev) Console.Write($"{vbCr}Writing {ev.PercentComplete}%   ")
AddHandler diag.ErrorOccurred, Sub(s, ev) Console.WriteLine($"  recovered: {ev.Message}")

Dim report = clsStoreMerger.Merge({"a.pst", "b.pst"}, "merged.pst", diag:=diag, overwrite:=True)
Console.WriteLine()
Console.WriteLine(If(report.Succeeded, "Done.", report.ToString()))

VB.NET — cap output size, and fall back to volumes if it won't fit

Dim sources = {"2019.pst", "2020.pst", "2021.pst"}
Dim cap As Long = 20L * 1024 * 1024 * 1024        ' 20 GB

Dim report = clsStoreMerger.Merge(sources, "merged.pst",
                                  dedup:=New clsDedup(), overwrite:=True, maxBytes:=cap)

If report.ErrorCode = enmErrorCode.OutputSizeExceeded Then
    Console.WriteLine("Too big for one file - writing 20 GB volumes instead.")
    Dim vol = clsStoreMerger.MergeToVolumes(sources, "merged.pst", cap,
                                            dedup:=New clsDedup(), overwrite:=True)
    Console.WriteLine(If(vol.Succeeded, $"{vol.Volumes.Count} volume(s) written.", vol.ToString()))
ElseIf Not report.Succeeded Then
    Console.WriteLine(report.ToString())
End If

VB.NET — clean up a single PST (dedup + drop blanks + prune empties, keep its name)

Dim report = clsStoreMerger.Merge({"messy.pst"}, "clean.pst",
                                  dedup:=New clsDedup(),
                                  overwrite:=True,
                                  pruneEmpty:=True)   ' single source keeps its original store name
Console.WriteLine($"{report.DuplicatesRemoved:N0} duplicates, " &
                  $"{report.BlankMessagesRemoved:N0} blank, " &
                  $"{report.FoldersPruned:N0} empty folders removed.")

VB.NET — merge, then verify the result is complete and valid

Imports Bastion.Pst.Validation   ' clsPstHealth

Dim report = clsStoreMerger.Merge(sources, "merged.pst", dedup:=New clsDedup(), overwrite:=True)
If report.Succeeded Then
    Dim health = clsPstHealth.Check("merged.pst")
    Console.WriteLine(If(health.IsCorrupted, "PROBLEM: " & health.ToString(), "Output verified OK."))
End If

VB.NET — read every figure the merge report carries

Dim report = clsStoreMerger.Merge(sources, "merged.pst", dedup:=New clsDedup(),
                                  overwrite:=True, layout:=enmMergeLayout.Unified, pruneEmpty:=True)

Console.WriteLine($"code            : {CInt(report.ErrorCode)} ({report.ErrorDescription})")
Console.WriteLine($"items written   : {report.TotalNodes:N0}")
Console.WriteLine($"messages merged : {report.MessagesMerged:N0}")
Console.WriteLine($"duplicates      : {report.DuplicatesRemoved:N0}")
Console.WriteLine($"blank removed   : {report.BlankMessagesRemoved:N0}")
Console.WriteLine($"filtered out    : {report.FilteredOut:N0}")
Console.WriteLine($"folders pruned  : {report.FoldersPruned:N0}")
If report.Warning IsNot Nothing Then Console.WriteLine($"warning         : {report.Warning}")

VB.NET — react to specific error codes

Dim report = clsStoreMerger.Merge(sources, "merged.pst")   ' overwrite defaults to False

Select Case report.ErrorCode
    Case enmErrorCode.Ok
        Console.WriteLine("Merged.")
    Case enmErrorCode.DestinationExists
        Console.WriteLine("Output already exists - pass overwrite:=True to replace it.")
    Case enmErrorCode.FileLocked
        Console.WriteLine("A file is open in Outlook - close Outlook and retry.")
    Case enmErrorCode.DiskFull, enmErrorCode.InsufficientDiskSpace
        Console.WriteLine("Not enough disk space.")
    Case Else
        Console.WriteLine(report.ToString())    ' "code: description"
End Select

6. Export messages out of a PST (to EML or MSG files)

Export every message in a store to its own file — .eml (RFC 5322 / MIME) or .msg (Outlook message, [MS-OXMSG]) — with recipients and attachments, under a folder tree that mirrors the PST. Pass enmExportFormat.Eml or enmExportFormat.Msg. Add Imports Bastion.Pst.Export / using Bastion.Pst.Export;.

VB.NET

Dim diag As New clsDiagnostics With {.CreateUserLog = True, .RecoverFromCorruption = True}

Dim rep = clsStoreExporter.ExportToFolder("mail.pst", "C:\out\mail",
                                          enmExportFormat.Eml, diag, overwrite:=True)
If rep.ErrorCode <> enmErrorCode.Ok Then
    Console.WriteLine(rep.ToString())
Else
    Console.WriteLine($"{rep.FilesWritten:N0} message(s) written, " &
                      $"{rep.FoldersVisited:N0} folder(s), {rep.Failed:N0} skipped.")
End If

C#

var diag = new clsDiagnostics { CreateUserLog = true, RecoverFromCorruption = true };

var rep = clsStoreExporter.ExportToFolder("mail.pst", @"C:\out\mail",
                                          enmExportFormat.Eml, diag, overwrite: true);
if (rep.ErrorCode != enmErrorCode.Ok)
    Console.WriteLine(rep.ToString());
else
    Console.WriteLine($"{rep.FilesWritten:N0} message(s) written, " +
                      $"{rep.FoldersVisited:N0} folder(s), {rep.Failed:N0} skipped.");

To export a single message you already have in hand, use clsMessageExporter.ExportEml(msg, "message.eml") or clsMessageExporter.ExportMsg(msg, "message.msg") (each returns a clsFileOpResult), or clsMessageExporter.ToEml(msg) for the MIME text. To export the whole store to .msg, pass enmExportFormat.Msg to ExportToFolder.

Reading a file safely (bonus)

The non-throwing open returns a result you check the same way.

VB.NET

Dim opened = clsPersonalStorage.TryOpen("mail.pst")
If opened.ErrorCode <> enmErrorCode.Ok Then
    Console.WriteLine(opened.ToString())
    Return
End If
Using store = opened.Store
    For Each folder In store.RootFolder.SubFolders()
        Console.WriteLine($"{folder.DisplayName}: {folder.ContentCount} items")
    Next
End Using

C#

var opened = clsPersonalStorage.TryOpen("mail.pst");
if (opened.ErrorCode != enmErrorCode.Ok)
{
    Console.WriteLine(opened.ToString());
    return;
}
using (var store = opened.Store)
{
    foreach (var folder in store.RootFolder.SubFolders())
        Console.WriteLine($"{folder.DisplayName}: {folder.ContentCount} items");
}

Examples

A task-by-task cookbook covering every feature of the library, each in both VB.NET and C#. All snippets assume these imports/usings and follow the return-code pattern — check ErrorCode (0 = Ok); the library never throws into your code.

VB.NET

Imports Bastion.Pst               ' enmErrorCode, result types
Imports Bastion.Pst.Messaging     ' clsPersonalStorage, clsFolder, clsMessage, clsMessageQuery, enmItemKind
Imports Bastion.Pst.Convert       ' clsStoreMerger, clsStoreConverter, clsStoreSplitter, clsDedup, enmMergeLayout
Imports Bastion.Pst.Validation    ' clsPstValidator, clsPstHealth
Imports Bastion.Pst.Repair        ' clsPstRepair
Imports Bastion.Pst.Export        ' clsStoreBuilder, clsStoreExporter, clsMessageExporter, enmExportFormat
Imports Bastion.Pst.Import        ' clsEmlReader, clsMsgReader, clsMboxReader, clsStoreImporter
Imports Bastion.Pst.Authoring     ' clsAuthor, clsAuthoringSession, clsItemBuilder
Imports Bastion.Pst.Views         ' clsStoreViews, clsItemQuery, clsItemOps, clsContactMerger
Imports Bastion.Pst.Diagnostics   ' clsDiagnostics

C#

using Bastion.Pst;
using Bastion.Pst.Messaging;
using Bastion.Pst.Convert;
using Bastion.Pst.Validation;
using Bastion.Pst.Repair;
using Bastion.Pst.Export;
using Bastion.Pst.Import;
using Bastion.Pst.Authoring;
using Bastion.Pst.Views;
using Bastion.Pst.Diagnostics;

1. Check a PST for corruption and repair it

clsPstHealth.Check inspects a file the moment you read it and never throws. If it is damaged, RepairTo rebuilds it into a fresh, validated file, and SaveSupportReport writes a privacy-safe report (structural metadata only).

VB.NET

Dim health = clsPstHealth.Check("mail.pst")
If health.ErrorCode <> enmErrorCode.Ok Then Console.WriteLine(health.ToString()) : Return

If health.IsCorrupted Then
    Console.WriteLine("Damage detected - repairing…")
    Dim report = health.SaveSupportReport()              ' empty path -> user temp; safe to email
    Console.WriteLine("Support report: " & report.Path)
    Dim fix = health.RepairTo("mail-repaired.pst")
    Console.WriteLine(If(fix.Succeeded, "Repaired -> mail-repaired.pst", fix.ToString()))
Else
    Console.WriteLine("Healthy.")
End If

C#

var health = clsPstHealth.Check("mail.pst");
if (health.ErrorCode != enmErrorCode.Ok) { Console.WriteLine(health.ToString()); return; }

if (health.IsCorrupted)
{
    Console.WriteLine("Damage detected - repairing…");
    var report = health.SaveSupportReport();             // empty path -> user temp; safe to email
    Console.WriteLine("Support report: " + report.Path);
    var fix = health.RepairTo("mail-repaired.pst");
    Console.WriteLine(fix.Succeeded ? "Repaired -> mail-repaired.pst" : fix.ToString());
}
else Console.WriteLine("Healthy.");

For direct control, clsPstRepair.Repair(src, out, diag, overwrite, stripPassword) returns a clsRepairReport whose Succeeded is true only when the rebuilt file passes strict validation; it also carries before/after validation reports and NodesWritten.

2. Create a new, empty PST

clsStoreBuilder.CreateEmpty writes a fresh, Outlook-mountable empty store at the path you pass, and validates it (reopens it) before returning. The optional storeName sets the title Outlook shows for the store in its nav pane (omit it to keep the default “Outlook Data File”).

VB.NET

Dim res = clsStoreBuilder.CreateEmpty("C:\data\new.pst", overwrite:=True, storeName:="My Archive")
If res.ErrorCode = enmErrorCode.Ok Then
    Console.WriteLine("Created " & res.Path)
Else
    Console.WriteLine(res.ToString())      ' e.g. "7: The file ... already exists ..."
End If

C#

var res = clsStoreBuilder.CreateEmpty(@"C:\data\new.pst", overwrite: true, storeName: "My Archive");
if (res.ErrorCode == enmErrorCode.Ok)
    Console.WriteLine("Created " + res.Path);
else
    Console.WriteLine(res.ToString());     // e.g. "7: The file ... already exists ..."

3. Merge two or more PSTs into one

See the Quick Start for the full option set. The essentials:

VB.NET

Dim diag As New clsDiagnostics With {.CreateUserLog = True, .RecoverFromCorruption = True}
Dim rep = clsStoreMerger.Merge({"a.pst", "b.pst", "c.pst"}, "merged.pst",
                               dedup:=New clsDedup(), diag:=diag, overwrite:=True,
                               layout:=enmMergeLayout.Unified, rootName:="All Mail", pruneEmpty:=True)
Console.WriteLine(If(rep.Succeeded,
    $"{rep.TotalNodes:N0} items, {rep.DuplicatesRemoved:N0} duplicates removed", rep.ToString()))

C#

var diag = new clsDiagnostics { CreateUserLog = true, RecoverFromCorruption = true };
var rep = clsStoreMerger.Merge(new[] { "a.pst", "b.pst", "c.pst" }, "merged.pst",
                               dedup: new clsDedup(), diag: diag, overwrite: true,
                               layout: enmMergeLayout.Unified, rootName: "All Mail", pruneEmpty: true);
Console.WriteLine(rep.Succeeded
    ? $"{rep.TotalNodes:N0} items, {rep.DuplicatesRemoved:N0} duplicates removed" : rep.ToString());

4. Merge into size-capped volumes

VB.NET

Dim vol = clsStoreMerger.MergeToVolumes({"big1.pst", "big2.pst"}, "archive.pst",
                                        capBytes:=10L * 1024 * 1024 * 1024, overwrite:=True)
For Each v In vol.Volumes : Console.WriteLine($"{v.Path}: {v.Messages:N0} msgs") : Next

C#

var vol = clsStoreMerger.MergeToVolumes(new[] { "big1.pst", "big2.pst" }, "archive.pst",
                                        capBytes: 10L * 1024 * 1024 * 1024, overwrite: true);
foreach (var v in vol.Volumes) Console.WriteLine($"{v.Path}: {v.Messages:N0} msgs");

5. Open a PST and walk its folders and messages

VB.NET

Dim opened = clsPersonalStorage.TryOpen("mail.pst")
If opened.ErrorCode <> enmErrorCode.Ok Then Console.WriteLine(opened.ToString()) : Return
Using store = opened.Store
    Walk(store.RootFolder, 0)
End Using

Sub Walk(folder As clsFolder, depth As Integer)
    Console.WriteLine(New String(" "c, depth * 2) & $"{folder.DisplayName} ({folder.ContentCount})")
    For Each m In folder.Messages()
        Console.WriteLine(New String(" "c, depth * 2 + 2) & $"- {m.Subject}")
    Next
    For Each child In folder.SubFolders() : Walk(child, depth + 1) : Next
End Sub

C#

var opened = clsPersonalStorage.TryOpen("mail.pst");
if (opened.ErrorCode != enmErrorCode.Ok) { Console.WriteLine(opened.ToString()); return; }
using (var store = opened.Store)
    Walk(store.RootFolder, 0);

void Walk(clsFolder folder, int depth)
{
    Console.WriteLine(new string(' ', depth * 2) + $"{folder.DisplayName} ({folder.ContentCount})");
    foreach (var m in folder.Messages())
        Console.WriteLine(new string(' ', depth * 2 + 2) + $"- {m.Subject}");
    foreach (var child in folder.SubFolders()) Walk(child, depth + 1);
}

6. Read a message in full — sender, recipients, body, attachments

VB.NET

Dim m As clsMessage = ...   ' from folder.Messages() or a search hit
Console.WriteLine($"Subject : {m.Subject}")
Console.WriteLine($"From    : {m.SenderName} <{m.SenderEmail}>")
Console.WriteLine($"Sent    : {m.DeliveryTime}")
Console.WriteLine($"Unread  : {m.IsUnread}")
For Each r In m.Recipients
    Console.WriteLine($"  {r.RecipientType}: {r.DisplayName} <{r.SmtpAddress}>")
Next
Console.WriteLine(m.Body)
For Each a In m.Attachments
    Console.WriteLine($"  attachment: {a.FileName} ({a.Size:N0} bytes, {a.MimeTag})")
    Dim bytes = a.GetData()
    If bytes IsNot Nothing Then IO.File.WriteAllBytes(a.FileName, bytes)   ' save it
Next

C#

clsMessage m = ...;   // from folder.Messages() or a search hit
Console.WriteLine($"Subject : {m.Subject}");
Console.WriteLine($"From    : {m.SenderName} <{m.SenderEmail}>");
Console.WriteLine($"Sent    : {m.DeliveryTime}");
Console.WriteLine($"Unread  : {m.IsUnread}");
foreach (var r in m.Recipients)
    Console.WriteLine($"  {r.RecipientType}: {r.DisplayName} <{r.SmtpAddress}>");
Console.WriteLine(m.Body);
foreach (var a in m.Attachments)
{
    Console.WriteLine($"  attachment: {a.FileName} ({a.Size:N0} bytes, {a.MimeTag})");
    var bytes = a.GetData();
    if (bytes != null) System.IO.File.WriteAllBytes(a.FileName, bytes);   // save it
}

7. Read non-mail items — appointments, contacts, tasks, notes

Every item exposes its Kind; calendar/contact fields are available directly.

VB.NET

For Each m In folder.Messages()
    Select Case m.Kind
        Case enmItemKind.Appointment
            Console.WriteLine($"Appt: {m.Subject}  {m.AppointmentStart}-{m.AppointmentEnd} @ {m.Location}")
        Case enmItemKind.Contact
            Console.WriteLine($"Contact: {m.Subject}  {m.ContactEmail}")
        Case enmItemKind.Mail
            Console.WriteLine($"Mail: {m.Subject}")
    End Select
Next

C#

foreach (var m in folder.Messages())
{
    switch (m.Kind)
    {
        case enmItemKind.Appointment:
            Console.WriteLine($"Appt: {m.Subject}  {m.AppointmentStart}-{m.AppointmentEnd} @ {m.Location}"); break;
        case enmItemKind.Contact:
            Console.WriteLine($"Contact: {m.Subject}  {m.ContactEmail}"); break;
        case enmItemKind.Mail:
            Console.WriteLine($"Mail: {m.Subject}"); break;
    }
}

8. Search a PST with a fluent query

VB.NET

Using store = clsPersonalStorage.Open("mail.pst")
    Dim q = New clsMessageQuery().SubjectContains("invoice").
                                  SenderContains("acme.com").
                                  OfKind(enmItemKind.Mail).
                                  WithAttachments().Unread()
    For Each hit In store.Search(q)
        Console.WriteLine($"{hit.FolderPath}\{hit.Message.Subject}")
    Next
End Using

C#

using (var store = clsPersonalStorage.Open("mail.pst"))
{
    var q = new clsMessageQuery().SubjectContains("invoice")
                                 .SenderContains("acme.com")
                                 .OfKind(enmItemKind.Mail)
                                 .WithAttachments().Unread();
    foreach (var hit in store.Search(q))
        Console.WriteLine($@"{hit.FolderPath}\{hit.Message.Subject}");
}

9. Convert a legacy ANSI PST/OST to Unicode

VB.NET

Dim res = clsStoreConverter.ConvertToUnicode("old-ansi.pst", "unicode.pst",
                                             New clsDiagnostics(), overwrite:=True)
Console.WriteLine(If(res.Succeeded, "Converted.", res.ToString()))

C#

var res = clsStoreConverter.ConvertToUnicode("old-ansi.pst", "unicode.pst",
                                             new clsDiagnostics(), overwrite: true);
Console.WriteLine(res.Succeeded ? "Converted." : res.ToString());

10. Split a large PST into smaller parts

By size, or by any predicate over each message row (here: subject contains a word).

VB.NET

' by size: <= 2 GB parts
Dim r1 = clsStoreSplitter.SplitBySize("big.pst", "C:\out", 2L * 1024 * 1024 * 1024, New clsDiagnostics())
For Each p In r1.Parts : Console.WriteLine($"{p.Path}: {p.MessageCount:N0} msgs") : Next

' by predicate: everything whose subject contains "2024" into one file, the rest into another
Dim pred As Func(Of clsMessage, Boolean) =
    Function(m) (If(m.Subject, "")).IndexOf("2024", StringComparison.OrdinalIgnoreCase) >= 0
Dim r2 = clsStoreSplitter.SplitByPredicate("big.pst", "2024.pst", "rest.pst", pred, New clsDiagnostics())

C#

// by size: <= 2 GB parts
var r1 = clsStoreSplitter.SplitBySize("big.pst", @"C:\out", 2L * 1024 * 1024 * 1024, new clsDiagnostics());
foreach (var p in r1.Parts) Console.WriteLine($"{p.Path}: {p.MessageCount:N0} msgs");

// by predicate: subject contains "2024" -> one file, the rest -> another
Func<clsMessage, bool> pred =
    m => (m.Subject ?? "").IndexOf("2024", StringComparison.OrdinalIgnoreCase) >= 0;
var r2 = clsStoreSplitter.SplitByPredicate("big.pst", "2024.pst", "rest.pst", pred, new clsDiagnostics());

11. Export messages out of a PST (EML, MSG, ICS, VCF, MHTML, MBOX)

The bulk exporter mirrors the folder tree; the per-message exporters produce one file. ICS covers appointments, VCF covers contacts (non-matching items are skipped), and MBOX writes one file per folder.

VB.NET

' whole store -> a folder tree of files in any format
Dim rep = clsStoreExporter.ExportToFolder("mail.pst", "C:\out", enmExportFormat.Eml,
                                          New clsDiagnostics(), overwrite:=True)
Console.WriteLine($"{rep.FilesWritten:N0} written, {rep.Failed:N0} skipped")
' …or enmExportFormat.Msg / .Ics / .Vcf / .Mhtml / .Mbox

' a single item you already hold
clsMessageExporter.ExportEml(m, "message.eml")     ' RFC 5322 / MIME
clsMessageExporter.ExportMsg(m, "message.msg")     ' Outlook [MS-OXMSG]
clsMessageExporter.ExportMhtml(m, "message.mhtml") ' web archive (RFC 2557)
clsItemExporter.ExportIcs(appt, "event.ics")       ' appointment -> iCalendar
clsItemExporter.ExportVcf(contact, "card.vcf")     ' contact -> vCard 3.0

C#

// whole store -> a folder tree of files in any format
var rep = clsStoreExporter.ExportToFolder("mail.pst", @"C:\out", enmExportFormat.Eml,
                                          new clsDiagnostics(), overwrite: true);
Console.WriteLine($"{rep.FilesWritten:N0} written, {rep.Failed:N0} skipped");
// …or enmExportFormat.Msg / .Ics / .Vcf / .Mhtml / .Mbox

// a single item you already hold
clsMessageExporter.ExportEml(m, "message.eml");     // RFC 5322 / MIME
clsMessageExporter.ExportMsg(m, "message.msg");     // Outlook [MS-OXMSG]
clsMessageExporter.ExportMhtml(m, "message.mhtml"); // web archive (RFC 2557)
clsItemExporter.ExportIcs(appt, "event.ics");       // appointment -> iCalendar
clsItemExporter.ExportVcf(contact, "card.vcf");     // contact -> vCard 3.0

12. Validate a PST's structure (Outlook-style, without Outlook)

VB.NET

Dim v = clsPstValidator.Validate("mail.pst")
If v Is Nothing Then
    Console.WriteLine("ANSI/legacy or unreadable header.")
ElseIf v.IsValid Then
    Console.WriteLine($"VALID - {v.PagesChecked:N0} pages, {v.BlocksChecked:N0} blocks.")
Else
    Console.WriteLine($"INVALID - {v.ErrorCount} issue(s):")
    For Each grp In v.Issues.GroupBy(Function(i) i.Category)
        Console.WriteLine($"  {grp.Key} x{grp.Count()}: {grp.First().Message}")
    Next
End If

C#

var v = clsPstValidator.Validate("mail.pst");
if (v == null)
    Console.WriteLine("ANSI/legacy or unreadable header.");
else if (v.IsValid)
    Console.WriteLine($"VALID - {v.PagesChecked:N0} pages, {v.BlocksChecked:N0} blocks.");
else
{
    Console.WriteLine($"INVALID - {v.ErrorCount} issue(s):");
    foreach (var grp in v.Issues.GroupBy(i => i.Category))
        Console.WriteLine($"  {grp.Key} x{grp.Count()}: {grp.First().Message}");
}

13. Verify folder counts match reality

VB.NET

Using store = clsPersonalStorage.Open("mail.pst")
    For Each f In AllFolders(store.RootFolder)
        Dim declared = f.ContentCount, actual = f.Messages().Count()
        If declared <> actual Then Console.WriteLine($"MISMATCH {f.DisplayName}: {declared} vs {actual}")
    Next
End Using

C#

using (var store = clsPersonalStorage.Open("mail.pst"))
    foreach (var f in AllFolders(store.RootFolder))
    {
        int declared = f.ContentCount, actual = f.Messages().Count();
        if (declared != actual) Console.WriteLine($"MISMATCH {f.DisplayName}: {declared} vs {actual}");
    }

14. Track progress, log activity, and recover from corruption

One clsDiagnostics drives progress events, a plain-language user log, an automatic corruption scan on open, and skip-on-error recovery — pass it to any operation.

VB.NET

Dim diag As New clsDiagnostics With {
    .CreateUserLog = True,          ' plain-language activity log for end users
    .RecoverFromCorruption = True,  ' skip damaged items instead of aborting
    .ScanSourcesOnOpen = True}      ' detect corruption as soon as a source is opened
AddHandler diag.WriteProgress, Sub(s, e) Console.Write($"{vbCr}{e.PercentComplete}%   ")
AddHandler diag.ErrorOccurred, Sub(s, e) Console.WriteLine($"recovered: {e.Message}")

Dim rep = clsStoreMerger.Merge({"a.pst", "b.pst"}, "merged.pst", diag:=diag, overwrite:=True)
Console.WriteLine(vbCrLf & "activity log: " & diag.UserLogPath)

C#

var diag = new clsDiagnostics {
    CreateUserLog = true,           // plain-language activity log for end users
    RecoverFromCorruption = true,   // skip damaged items instead of aborting
    ScanSourcesOnOpen = true };     // detect corruption as soon as a source is opened
diag.WriteProgress += (s, e) => Console.Write($"\r{e.PercentComplete}%   ");
diag.ErrorOccurred += (s, e) => Console.WriteLine($"recovered: {e.Message}");

var rep = clsStoreMerger.Merge(new[] { "a.pst", "b.pst" }, "merged.pst", diag: diag, overwrite: true);
Console.WriteLine("\nactivity log: " + diag.UserLogPath);

15. Handle every error the library can report

VB.NET

Dim rep = clsStoreMerger.Merge(sources, "merged.pst")
Select Case rep.ErrorCode
    Case enmErrorCode.Ok                     : Console.WriteLine("Done.")
    Case enmErrorCode.FileNotFound           : Console.WriteLine("A source is missing.")
    Case enmErrorCode.FileLocked             : Console.WriteLine("Close Outlook and retry.")
    Case enmErrorCode.DestinationExists      : Console.WriteLine("Pass overwrite:=True.")
    Case enmErrorCode.DiskFull,
         enmErrorCode.InsufficientDiskSpace  : Console.WriteLine("Free up disk space.")
    Case enmErrorCode.OutputSizeExceeded,
         enmErrorCode.NodeIdCapacityExceeded : Console.WriteLine("Use MergeToVolumes.")
    Case Else                                : Console.WriteLine(rep.ToString() & " / " & rep.Detail)
End Select

C#

var rep = clsStoreMerger.Merge(sources, "merged.pst");
switch (rep.ErrorCode)
{
    case enmErrorCode.Ok:                     Console.WriteLine("Done."); break;
    case enmErrorCode.FileNotFound:           Console.WriteLine("A source is missing."); break;
    case enmErrorCode.FileLocked:             Console.WriteLine("Close Outlook and retry."); break;
    case enmErrorCode.DestinationExists:      Console.WriteLine("Pass overwrite: true."); break;
    case enmErrorCode.DiskFull:
    case enmErrorCode.InsufficientDiskSpace:  Console.WriteLine("Free up disk space."); break;
    case enmErrorCode.OutputSizeExceeded:
    case enmErrorCode.NodeIdCapacityExceeded: Console.WriteLine("Use MergeToVolumes."); break;
    default:                                  Console.WriteLine(rep.ToString() + " / " + rep.Detail); break;
}

16. Read an EML / MSG file into the message model

The import readers (Imports Bastion.Pst.Import) parse an interchange file into a neutral clsImportedMessage — headers, addresses, both bodies, and attachments (bytes decoded). They validate the input and return a result code; they never throw.

VB.NET

Dim res = clsEmlReader.ParseFile("message.eml")     ' or clsMsgReader.ParseFile("message.msg")
If res.ErrorCode <> enmErrorCode.Ok Then Console.WriteLine(res.ToString()) : Return
Dim m = res.Message
Console.WriteLine($"{m.Subject} — from {m.FromEmail}, {m.To.Count} to, {m.Attachments.Count} attachment(s)")
For Each a In m.Attachments
    IO.File.WriteAllBytes(a.FileName, a.Data)        ' save each attachment
Next

C#

var res = clsEmlReader.ParseFile("message.eml");    // or clsMsgReader.ParseFile("message.msg")
if (res.ErrorCode != enmErrorCode.Ok) { Console.WriteLine(res.ToString()); return; }
var m = res.Message;
Console.WriteLine($"{m.Subject} — from {m.FromEmail}, {m.To.Count} to, {m.Attachments.Count} attachment(s)");
foreach (var a in m.Attachments)
    System.IO.File.WriteAllBytes(a.FileName, a.Data); // save each attachment

17. Read every message out of an MBOX file

VB.NET

For Each m In clsMboxReader.ReadFile("archive.mbox")
    Console.WriteLine($"{m.Date}  {m.FromEmail}  {m.Subject}")
Next

C#

foreach (var m in clsMboxReader.ReadFile("archive.mbox"))
    Console.WriteLine($"{m.Date}  {m.FromEmail}  {m.Subject}");

18. Import EML / MSG / MBOX files into a PST

clsStoreImporter writes imported messages into a real PST — subject, sender, recipients, both bodies, internet headers and attachments become native Outlook properties. Pass an existing store to add to (its content is preserved; the result goes to a new output file), or Nothing/null to start from a fresh, empty store. The target folder path is relative to the mail root and is created if missing ("/" separates subfolders). Folder counts are maintained and the output is validated (reopened) before the call returns.

VB.NET

Imports Bastion.Pst.Import

' A whole MBOX archive into a brand-new PST. storeName sets the title Outlook shows in its nav
' pane (the same idea as the merge tool's rootName); omit it to keep the default "Outlook Data File".
Dim rep = clsStoreImporter.ImportMbox(Nothing, "archive.pst", "archive.mbox", "Imported/2026",
                                      storeName:="2026 Mail Archive")
Console.WriteLine(rep.ToString())        ' "OK: 433 message(s) imported (…)"

' Individual EML/MSG files into an existing store's Inbox (result written to out.pst):
rep = clsStoreImporter.ImportFiles("mail.pst", "out.pst",
                                   IO.Directory.GetFiles("mails", "*.eml"), "Inbox",
                                   overwrite:=True)
If rep.ErrorCode <> enmErrorCode.Ok Then Console.WriteLine(rep.ToString())
For Each line In rep.Failures : Console.WriteLine(line) : Next   ' per-file parse problems

C#

using Bastion.Pst.Import;

// A whole MBOX archive into a brand-new PST. storeName titles the store in Outlook's nav pane.
var rep = clsStoreImporter.ImportMbox(null, "archive.pst", "archive.mbox", "Imported/2026",
                                      storeName: "2026 Mail Archive");
Console.WriteLine(rep.ToString());       // "OK: 433 message(s) imported (…)"

// Individual EML/MSG files into an existing store's Inbox (result written to out.pst):
rep = clsStoreImporter.ImportFiles("mail.pst", "out.pst",
                                   System.IO.Directory.GetFiles("mails", "*.eml"), "Inbox",
                                   overwrite: true);
if (rep.ErrorCode != enmErrorCode.Ok) Console.WriteLine(rep.ToString());
foreach (var line in rep.Failures) Console.WriteLine(line);      // per-file parse problems

19. Set, verify or remove the PST open password

A PST password is not encryption — Outlook stores only a CRC hash of the password on the message store and prompts on open. clsStorePassword reads that state (GetStatus), sets or clears it (SetPassword / RemovePassword — atomic, validated output; pass the same path twice with overwrite:=True to change a file in place), and computes the hash Outlook would store (ComputeCrc) so you can verify a user-typed password before opening. The hash is bit-identical to Outlook's (verified against an independent implementation).

VB.NET

' Is the file password-protected?
Dim status = clsStorePassword.GetStatus("mail.pst")
If status.ErrorCode <> enmErrorCode.Ok Then Console.WriteLine(status.ToString()) : Return
Console.WriteLine(If(status.HasPassword, "Password is set.", "No password."))

' Verify what the user typed without touching the file:
If clsStorePassword.ComputeCrc(userTyped) = status.PasswordCrc Then Console.WriteLine("Correct.")

' Set a password (writes locked.pst; mail.pst is untouched):
Dim setRep = clsStorePassword.SetPassword("mail.pst", "locked.pst", "s3cret!")
Console.WriteLine(If(setRep.Succeeded, "Password set and output validated.", setRep.ToString()))

' Remove it in place:
Dim remRep = clsStorePassword.RemovePassword("locked.pst", "locked.pst", overwrite:=True)
Console.WriteLine(If(remRep.Succeeded, "Opens without a prompt now.", remRep.ToString()))

C#

// Is the file password-protected?
var status = clsStorePassword.GetStatus("mail.pst");
if (status.ErrorCode != enmErrorCode.Ok) { Console.WriteLine(status.ToString()); return; }
Console.WriteLine(status.HasPassword ? "Password is set." : "No password.");

// Verify what the user typed without touching the file:
if (clsStorePassword.ComputeCrc(userTyped) == status.PasswordCrc) Console.WriteLine("Correct.");

// Set a password (writes locked.pst; mail.pst is untouched):
var setRep = clsStorePassword.SetPassword("mail.pst", "locked.pst", "s3cret!");
Console.WriteLine(setRep.Succeeded ? "Password set and output validated." : setRep.ToString());

// Remove it in place:
var remRep = clsStorePassword.RemovePassword("locked.pst", "locked.pst", overwrite: true);
Console.WriteLine(remRep.Succeeded ? "Opens without a prompt now." : remRep.ToString());

20. Author Outlook items from code (mail, appointment, contact, task, note, journal, list)

The fluent builders in Bastion.Pst.Authoring produce every item type. Hand the items to clsAuthor.CreateStoreWith (a new store) or AddItems (an existing one); the target folder is created on demand.

VB.NET

Dim mail = clsItemBuilder.Mail().Subject("Welcome").From("Team", "team@x.test").
              [To]("Ada", "ada@x.test").BodyText("Hello!").
              AddAttachment("logo.png", pngBytes, "image/png").Build()
Dim contact = clsItemBuilder.Contact().Name("Ada", "Lovelace").Company("Analytical").
                 Email("ada@x.test").Phone(business:="+44 20 0000").Build()
Dim task = clsItemBuilder.Task().Subject("Ship v1").Status(1).PercentComplete(0.5).
              DueDate(New Date(2026, 7, 20)).Build()
Dim note = clsItemBuilder.Note().Text("Remember the milk").Color(3).Build()
Dim journal = clsItemBuilder.Journal().Subject("Call with Ada").JournalType("Phone call").
                 DurationMinutes(15).Build()
Dim list = clsItemBuilder.DistributionList().Name("Team").
              AddMember("Ada", "ada@x.test").AddMember("Alan", "alan@x.test").Build()

Dim r = clsAuthor.CreateStoreWith("new.pst", {mail}, "Inbox", storeName:="My Data")
clsAuthor.AddItems("new.pst", "new.pst", {contact, list}, "Contacts", overwrite:=True)

C#

var appt = clsItemBuilder.Appointment().Subject("Weekly standup")
              .StartsAt(new DateTime(2026, 7, 8, 9, 0, 0, DateTimeKind.Utc))
              .EndsAt(new DateTime(2026, 7, 8, 9, 30, 0, DateTimeKind.Utc))
              .RecurWeekly(days: enmRecurDays.Monday, occurrences: 10)   // a recurring series…
              .ExcludeOccurrences(new DateTime(2026, 8, 31))             // …skipping a bank holiday
              .Build();
var r = clsAuthor.AddItems("new.pst", "new.pst", new[] { appt }, "Calendar", overwrite: true);

21. Batch several edits into one atomic write (authoring session)

The session verbs are RenameFolder, SetFolderClass, MoveFolder, DeleteFolder, MoveItems, DeleteItems, EditItems and RemoveAttachments. Creating a folder and adding items are not session verbs — use clsAuthor.CreateFolder and clsAuthor.AddItems for those, then batch the edits.

VB.NET

Dim rep = New clsAuthoringSession("data.pst").
    RenameFolder("Old", "Archive").
    SetFolderClass("Archive", clsFolderClass.Mail).
    DeleteItems("Spam", Function(m) m.Subject.Contains("[AD]")).
    Commit("out.pst", overwrite:=True)      ' one rewrite; ErrorCode 0 == all applied

C#

var rep = new clsAuthoringSession("data.pst")
    .RenameFolder("Old", "Archive")
    .SetFolderClass("Archive", clsFolderClass.Mail)
    .DeleteItems("Spam", m => m.Subject.Contains("[AD]"))
    .Commit("out.pst", overwrite: true);      // one rewrite; ErrorCode 0 == all applied

22. Make a folder a calendar, contacts or tasks folder

A folder is a mail folder ("IPF.Note") until you say otherwise. Its container class (PidTagContainerClass) is what tells Outlook — and any other client — to render a calendar grid instead of a message list, so this is the step that turns an authored folder into a real Calendar or Contacts folder. Use the clsFolderClass constants rather than typing the strings.

The value is written to both the folder's own property context and its row in the parent's hierarchy table, because different readers consult different ones.

VB.NET

' One folder, one call. The source is never modified — every edit is an atomic rewrite.
Dim rep = clsAuthor.SetFolderClass("data.pst", "out.pst", "Team Calendar",
                                   clsFolderClass.Appointment, overwrite:=True)
Console.WriteLine($"{rep.FoldersReclassified} folder(s) reclassified.")

' Read it back — this is also how you audit an existing store's folder kinds.
Using pst = clsPersonalStorage.Open("out.pst")
    For Each top In pst.RootFolder.SubFolders()
        For Each f In top.SubFolders()
            Console.WriteLine($"[{f.DisplayName}] {f.ContainerClass}")
        Next
    Next
End Using

C#

// One folder, one call. The source is never modified — every edit is an atomic rewrite.
var rep = clsAuthor.SetFolderClass("data.pst", "out.pst", "Team Calendar",
                                   clsFolderClass.Appointment, overwrite: true);
Console.WriteLine($"{rep.FoldersReclassified} folder(s) reclassified.");

// Read it back — this is also how you audit an existing store's folder kinds.
using var pst = clsPersonalStorage.Open("out.pst");
foreach (var top in pst.RootFolder.SubFolders())
    foreach (var f in top.SubFolders())
        Console.WriteLine($"[{f.DisplayName}] {f.ContainerClass}");

Every authoring call rewrites the whole store, so type a whole mailbox in one rewrite with a session rather than one rewrite per folder:

VB.NET

Dim rep = New clsAuthoringSession("data.pst").
    SetFolderClass("Team Calendar", clsFolderClass.Appointment).
    SetFolderClass("Team Contacts", clsFolderClass.Contact).
    Commit("mailbox.pst", overwrite:=True)

C#

var rep = new clsAuthoringSession("data.pst")
    .SetFolderClass("Team Calendar", clsFolderClass.Appointment)
    .SetFolderClass("Team Contacts", clsFolderClass.Contact)
    .Commit("mailbox.pst", overwrite: true);

Creating a folder is not a session verb — use clsAuthor.CreateFolder first, then type the finished tree in one commit.

PowerShell

Set-PstFolderClass C:\mail.pst -Destination C:\out.pst `
                   -Folder 'Team Calendar' -ContainerClass IPF.Appointment
Get-PstFolder C:\out.pst | Select-Object Name, Class

The classes. clsFolderClass.Mail ("IPF.Note", the default), Appointment, Contact, Task, StickyNote, Journal, Birthday, Homepage. An unset class is treated as mail, so a mail folder does not strictly need one stamped on it.

It identifies a kind, not an identity. The class is inherited by subfolders in practice — every folder under a Calendar is usually IPF.Appointment too — so two sibling folders can legitimately share one. Never use it alone to decide that two folders are "the same folder".

23. Clone just the folder structure of a store

C#

// A new, empty Unicode PST mirroring the source's folder tree (no messages copied).
var rep = clsStoreImporter.CloneStructure("live.pst", "template.pst", overwrite: true);
Console.WriteLine($"{rep.FoldersCreated} folders mirrored.");

24. Import an Outlook-for-Mac .olm archive into a PST

OLM is Outlook for Mac's export format (a ZIP of XML). ImportOlm reads the mail and recreates the OLM folder hierarchy in a new Unicode PST.

C#

var rep = clsStoreImporter.ImportOlm("MacExport.olm", "converted.pst", overwrite: true);
Console.WriteLine($"{rep.MessagesImported} messages into {rep.FoldersCreated} folders.");

// …or read the items into the neutral model without writing a PST:
foreach (var m in clsOlmReader.ReadFile("MacExport.olm"))
    Console.WriteLine($"[{m.SourceFolder}] {m.Subject}  ({m.Attachments.Count} attachments)");

25. Cancel a long operation, and stay safe on legacy ANSI stores

Every long operation honours a CancellationToken; because writes are atomic, a cancellation leaves the destination untouched. And because the library only writes Unicode, adding to or editing a legacy ANSI store would rewrite it as Unicode — which Outlook 2002 and earlier cannot open — so that upgrade is refused by default and must be authorised explicitly.

C#

// --- cancellation ---
var cts = new CancellationTokenSource();
var diag = new clsDiagnostics { CancellationToken = cts.Token };
// cts.Cancel() from your UI thread… the call returns enmErrorCode.OperationCancelled, nothing written.
var conv = clsStoreConverter.ConvertToUnicode("huge.pst", "out.pst", diag, overwrite: true);

// --- ANSI safety gate ---
if (clsPersonalStorage.IsAnsi("legacy.pst"))                 // cheap header-only pre-flight
    Warn("Legacy ANSI file — adding items upgrades it to Unicode (Outlook 2002- can't open it).");

var g = new clsDiagnostics();
g.AnsiUpgradeRequired += (s, e) => e.Proceed = UserConfirmed;  // the hard yes/no
var add = clsAuthor.AddItems("legacy.pst", "out.pst", items, "Inbox", g);
if (add.ErrorCode == enmErrorCode.AnsiUpgradeRequired) { /* declined — source untouched */ }

26. Show a mailbox — one bindable list per section

store.Views is the read model a host application binds to: flat rows, not a message graph, so a grid binds to Mail(), a card view to Contacts() and a scheduler to Appointments(). The rows are plain data — no store handles, no lazy reads — so they are safe to hold and to bind, and a damaged item yields a row with blank fields rather than taking the view down. Which folders hold contacts, which hold feeds, and which are Outlook's hidden address-book plumbing is decided here, once, instead of in every host that shows a mailbox.

Note that RSS posts are a section of their own. Outlook files them under a container class that is a variation on “mail”, so anywhere else they are swept in with your messages — in one real archive that was nearly 13,000 posts inflating the mail counts.

VB.NET

Using store = clsPersonalStorage.Open("mail.pst")

    For Each m In store.Views.Mail(New clsItemQuery().Take(200))
        Console.WriteLine($"{m.FromDisplay}  {m.Display}  {m.Date:d}" &
                          If(m.IsUnread, "  unread", "") & If(m.HasAttachments, "  clip", ""))
    Next

    For Each c In store.Views.Contacts()
        Console.WriteLine($"{c.SortKey}  {c.Email}  {c.Phone}")     ' c.Photo is the card picture
    Next

    For Each a In store.Views.Appointments()
        Console.WriteLine($"{a.Display}  {a.StartTime:g}-{a.EndTime:t} @{a.Location}")
    Next

    For Each t In store.Views.Tasks()
        Console.WriteLine($"{t.Display}  {t.StatusText}  {t.PercentComplete:P0}" &
                          If(t.IsOverdue, "  OVERDUE", ""))
    Next

    For Each n In store.Views.Notes() : Console.WriteLine(n.Display) : Next
    For Each r In store.Views.Rss()   : Console.WriteLine($"[{r.FeedName}] {r.Display}") : Next
End Using

C#

using var store = clsPersonalStorage.Open("mail.pst");

foreach (var m in store.Views.Mail(new clsItemQuery().Take(200)))
    Console.WriteLine($"{m.FromDisplay}  {m.Display}  {m.Date:d}"
                      + (m.IsUnread ? "  unread" : "") + (m.HasAttachments ? "  clip" : ""));

foreach (var c in store.Views.Contacts())
    Console.WriteLine($"{c.SortKey}  {c.Email}  {c.Phone}");        // c.Photo is the card picture

foreach (var a in store.Views.Appointments())
    Console.WriteLine($"{a.Display}  {a.StartTime:g}-{a.EndTime:t} @{a.Location}");

foreach (var t in store.Views.Tasks())
    Console.WriteLine($"{t.Display}  {t.StatusText}  {t.PercentComplete:P0}"
                      + (t.IsOverdue ? "  OVERDUE" : ""));

foreach (var n in store.Views.Notes()) Console.WriteLine(n.Display);
foreach (var r in store.Views.Rss())   Console.WriteLine($"[{r.FeedName}] {r.Display}");

Every view is a lazy IEnumerable, so LINQ composes over it and nothing is materialised until you enumerate. Each row carries Nid, FolderPath, FolderNid and StorePath — enough to act on the item later without re-walking the tree, which matters when the host has several files open at once.

27. List one folder, then read the one item the user clicked

This is what a mailbox UI actually does, and the two halves have deliberately different shapes. A list row must be thin — a mail list of 97,803 rows must not carry 97,803 message bodies — so MailIn reads the folder's contents table and nothing else: a 23,000-message Inbox costs one table read rather than 23,000 message opens. Detail is the read that happens on selection, and it decides which body to show (a message may hold HTML, RTF, plain, or several) rather than leaving every caller to guess. Attachment bytes are fetched only when the user actually saves or opens one.

VB.NET

Using store = clsPersonalStorage.Open("mail.pst")
    Dim folder = ...                                    ' the folder the user clicked

    ' The list: one table read, no message opened.
    Dim rows = store.Views.MailIn(folder).ToList()

    ' The selection: everything needed to display ONE item, in a single pass.
    Dim d = store.Views.Detail(folder, rows(0).Nid)
    Console.WriteLine($"{d.Subject} - from {d.SenderName} <{d.SenderEmail}> to {d.DisplayTo}")
    RenderBody(If(d.HasHtml, d.BodyHtml, d.BodyPlain), asHtml:=d.HasHtml)

    ' Inline images belong in the body, not the paperclip list.
    For Each a In d.Attachments.Where(Function(x) Not x.IsInline)
        Dim bytes = store.Views.AttachmentBytes(folder, d.Nid, a.Index)     ' on demand
        IO.File.WriteAllBytes(IO.Path.Combine(saveDir, a.FileName), bytes)
    Next

    ' The folders that make up a section, so the host can offer them as a list.
    For Each sf In store.Views.SectionFolders(enmSection.Calendar)
        Console.WriteLine($"{sf.FullPath}  [{sf.ItemCount:N0}]")
    Next
End Using

C#

using var store = clsPersonalStorage.Open("mail.pst");
var folder = ...;                                       // the folder the user clicked

// The list: one table read, no message opened.
var rows = store.Views.MailIn(folder).ToList();

// The selection: everything needed to display ONE item, in a single pass.
var d = store.Views.Detail(folder, rows[0].Nid);
Console.WriteLine($"{d.Subject} - from {d.SenderName} <{d.SenderEmail}> to {d.DisplayTo}");
RenderBody(d.HasHtml ? d.BodyHtml : d.BodyPlain, asHtml: d.HasHtml);

// Inline images belong in the body, not the paperclip list.
foreach (var a in d.Attachments.Where(x => !x.IsInline))
{
    var bytes = store.Views.AttachmentBytes(folder, d.Nid, a.Index);        // on demand
    File.WriteAllBytes(Path.Combine(saveDir, a.FileName), bytes);
}

// The folders that make up a section, so the host can offer them as a list.
foreach (var sf in store.Views.SectionFolders(enmSection.Calendar))
    Console.WriteLine($"{sf.FullPath}  [{sf.ItemCount:N0}]");

28. Filter before an item is opened

A LINQ predicate over items can only run once each item has been read, so “mail from Chris in 2011” expressed as a Where opens all 108,892 messages in a large archive and discards almost every one. A folder's contents table already holds the columns a list shows — subject, sender, dates, class, size, flags — so a clsItemQuery is matched against the table row first, and only rows that survive cause the item to be opened.

Anything the table cannot answer (a body search, a contact's phone number) still needs the item and is expressed with Where(…) or BodyContains(…), which run after opening. Order matters: cheap criteria in the query, expensive ones after.

VB.NET

' Answered entirely by the contents table - nothing is opened to reject a row.
Dim cheap = New clsItemQuery().
    SenderContains("chris").
    DateFrom(#2011-01-01#).DateBefore(#2012-01-01#).
    WithAttachments().
    LargerThan(64 * 1024).
    Unread().
    Take(200)
For Each m In store.Views.Mail(cheap) : Console.WriteLine(m.Display) : Next

' Needs the item - but only for rows the cheap pass already let through.
Dim expensive = New clsItemQuery().SubjectContains("invoice").BodyContains("overdue")

' Confine the walk to part of the tree.
Dim sent = store.Views.Mail(New clsItemQuery().InFolder("Sent Items"))
Dim archives = store.Views.Mail(New clsItemQuery().InFolders(Function(p) p.Contains("Archive")))

C#

// Answered entirely by the contents table - nothing is opened to reject a row.
var cheap = new clsItemQuery()
    .SenderContains("chris")
    .DateFrom(new DateTime(2011, 1, 1)).DateBefore(new DateTime(2012, 1, 1))
    .WithAttachments()
    .LargerThan(64 * 1024)
    .Unread()
    .Take(200);
foreach (var m in store.Views.Mail(cheap)) Console.WriteLine(m.Display);

// Needs the item - but only for rows the cheap pass already let through.
var expensive = new clsItemQuery().SubjectContains("invoice").BodyContains("overdue");

// Confine the walk to part of the tree.
var sent     = store.Views.Mail(new clsItemQuery().InFolder("Sent Items"));
var archives = store.Views.Mail(new clsItemQuery().InFolders(p => p.Contains("Archive")));

29. One card per person — folding duplicate contacts

An archive stores the same person over and over: once per source mailbox, plus a stub every time a field was added. In a real 9.4 GB archive 852 stored contacts were 307 people — the same name five times, one entry holding the phone, another the e-mail, another the company. clsContactMerger folds them together for display: nothing is written and nothing is thrown away. Fields come from whichever entry has them, values that genuinely disagree are kept as alternates, and each row records how many entries it came from and which folders they were in.

Outlook's hidden address-book folders (Recipient Cache, Suggested Contacts, PersonMetadata…) carry a contacts container class but hold plumbing rather than people — in one archive they outnumbered the genuine contacts ten to one — so they are excluded unless you ask for them.

A contacts folder also holds groups (distribution lists), which have none of a person's fields. A row for one carries IsDistributionList, its Members (name and address apiece, decoded from the one-off EntryIDs) and a ready-made MembershipSummary; Combine never folds a group into a person. A host that ignores the flag draws a card with every line blank, which is exactly what the property model used to give it.

C#

foreach (var row in store.Views.Contacts())
{
    if (row.IsDistributionList)
        Console.WriteLine($"{row.Display}  ({row.MembershipSummary}): {row.MemberNames}");
    else
        Console.WriteLine($"{row.Display}  {row.Email}  {row.Phone}");
}

VB.NET

Using store = clsPersonalStorage.Open("mail.pst")
    store.Views.IncludeHiddenFolders = False            ' the default: no Recipient Cache et al.

    Dim stored = store.Views.Contacts().ToList()
    Dim people = clsContactMerger.Combine(stored)       ' or CombineByEmail(...) to key on address
    Console.WriteLine($"{stored.Count:N0} entries -> {people.Count:N0} cards")

    For Each p In people
        Console.WriteLine($"{p.Display}  {p.Email}  {p.Phone}")
        If p.MergedFrom > 1 Then
            Console.WriteLine($"   built from {p.MergedFrom} entries in {String.Join(", ", p.SourceFolders)}")
            For Each alt In p.AlternateEmails : Console.WriteLine("   also: " & alt) : Next
        End If
        If p.Photo IsNot Nothing Then IO.File.WriteAllBytes($"{p.Display}.jpg", p.Photo)
    Next
End Using

C#

using var store = clsPersonalStorage.Open("mail.pst");
store.Views.IncludeHiddenFolders = false;               // the default: no Recipient Cache et al.

var stored = store.Views.Contacts().ToList();
var people = clsContactMerger.Combine(stored);          // or CombineByEmail(...) to key on address
Console.WriteLine($"{stored.Count:N0} entries -> {people.Count:N0} cards");

foreach (var p in people)
{
    Console.WriteLine($"{p.Display}  {p.Email}  {p.Phone}");
    if (p.MergedFrom > 1)
    {
        Console.WriteLine($"   built from {p.MergedFrom} entries in {string.Join(", ", p.SourceFolders)}");
        foreach (var alt in p.AlternateEmails) Console.WriteLine("   also: " + alt);
    }
    if (p.Photo != null) File.WriteAllBytes($"{p.Display}.jpg", p.Photo);
}

30. Watch what the library is doing (progress, without polling)

Subscribe once to clsPstEvents.Default and you are told when a store opens, how far a folder read has got, when a folder's items have been counted, when a store closes and when something goes wrong — from anywhere in the library, with no callback threaded through every call. The read event carries a real total, known the moment the contents table is open, so a host can show a percentage rather than an indeterminate bar: the point is to reassure the user that a six-figure folder is loading, not that the application has hung.

Threading: events are raised on whatever thread does the work — often a background reader — so a UI host must marshal (Control.BeginInvoke) before touching controls.

VB.NET

AddHandler clsPstEvents.Default.PstOpen,
    Sub(s, e) Log($"opened {e.Path}")
AddHandler clsPstEvents.Default.PstFolderSelected,
    Sub(s, e) ShowLoading($"{e.Folder}: {e.ItemCount:N0} items")
AddHandler clsPstEvents.Default.PstRead,
    Sub(s, e) UiThread(Sub() bar.Value = e.Percent)         ' Current / Total / Percent
AddHandler clsPstEvents.Default.PstError,
    Sub(s, e) Log($"{e.ErrorCode}: {e.Message} ({e.Path})")
AddHandler clsPstEvents.Default.PstClose,
    Sub(s, e) HideLoading()

C#

clsPstEvents.Default.PstOpen           += (s, e) => Log($"opened {e.Path}");
clsPstEvents.Default.PstFolderSelected += (s, e) => ShowLoading($"{e.Folder}: {e.ItemCount:N0} items");
clsPstEvents.Default.PstRead           += (s, e) => UiThread(() => bar.Value = e.Percent);
clsPstEvents.Default.PstError          += (s, e) => Log($"{e.ErrorCode}: {e.Message} ({e.Path})");
clsPstEvents.Default.PstClose          += (s, e) => HideLoading();

31. Save, copy, move, delete and import — the verbs a host application needs

clsItemOps is the operation layer an application would otherwise write itself, and get subtly wrong: it knows how to copy a contact as well as a message, how to save a .vcf as well as a .msg, and it answers “can this store be written to?” in one place. Two rules hold throughout:

VB.NET

' Ask the library why, rather than inventing your own wording for the user.
If Not clsItemOps.IsWritable(target) Then MessageBox.Show(clsItemOps.WhyNotWritable(target)) : Return

' Save to disk in a format other applications open: mail/.msg, contacts/.vcf, appointments/.ics,
' notes/.txt - or force one format for the lot. Cancellable, and it reports progress.
Dim rep = clsItemOps.SaveItems(storePath, folderPath, nids, "C:\out",
                               enmSaveFormat.Native, cts.Token, Sub(n) bar.Value = n)
Console.WriteLine(If(rep.Ok, $"saved {rep.Saved} file(s)", rep.Error))

' Copy into another data file. Every kind copies with full fidelity - recipients, attachments,
' MAPI properties - because each item goes out as a native .msg and back in.
Dim c = clsItemOps.CopyItems(sourcePath, sourceFolder, nids, targetPath, targetOut, "Inbox", diag)

' Move and delete are the same shape.
Dim m = clsItemOps.MoveItems(storePath, outPath, "Inbox", "Archive", nids, diag)
Dim d = clsItemOps.DeleteItems(storePath, outPath, "Inbox", nids, diag)

' Import .msg .eml .ics .vcf .mbox into a folder.
Dim i = clsItemOps.ImportFiles(storePath, outPath, "Inbox", files, diag)

C#

// Ask the library why, rather than inventing your own wording for the user.
if (!clsItemOps.IsWritable(target)) { MessageBox.Show(clsItemOps.WhyNotWritable(target)); return; }

// Save to disk in a format other applications open: mail/.msg, contacts/.vcf, appointments/.ics,
// notes/.txt - or force one format for the lot. Cancellable, and it reports progress.
var rep = clsItemOps.SaveItems(storePath, folderPath, nids, @"C:\out",
                               enmSaveFormat.Native, cts.Token, n => bar.Value = n);
Console.WriteLine(rep.Ok ? $"saved {rep.Saved} file(s)" : rep.Error);

// Copy into another data file. Every kind copies with full fidelity - recipients, attachments,
// MAPI properties - because each item goes out as a native .msg and back in.
var c = clsItemOps.CopyItems(sourcePath, sourceFolder, nids, targetPath, targetOut, "Inbox", diag);

// Move and delete are the same shape.
var m = clsItemOps.MoveItems(storePath, outPath, "Inbox", "Archive", nids, diag);
var d = clsItemOps.DeleteItems(storePath, outPath, "Inbox", nids, diag);

// Import .msg .eml .ics .vcf .mbox into a folder.
var i = clsItemOps.ImportFiles(storePath, outPath, "Inbox", files, diag);

Every write is an atomic rewrite through the authoring pipeline: the new file is built, verified and swapped in, so an interrupted operation leaves the original untouched.

32. Build a whole mailbox — attendees, photos, feed posts, and many folders in one pass

The builders cover the parts of a mailbox that are easy to get wrong by hand: a meeting's organiser and attendee list, a recurring series with one instance moved and another cancelled, a contact photo, read state and importance, and RSS posts filed where Outlook files them. Exception dates must land on an occurrence of the series — the library refuses one that does not, rather than writing a calendar Outlook would silently misread.

Then fill the store in one pass. Because every write is a whole-file rewrite, an import call per folder is O(S·k) I/O where one call that groups by folder is O(S) — the difference between minutes and hours on a large store.

VB.NET

Dim items As New List(Of clsImportedMessage)()

Dim mail = clsItemBuilder.Mail().
    Subject("Q3 numbers").From("Dana Ruiz", "dana@example.com").To("You", "you@example.com").
    BodyHtml("<p>Attached.</p>").SentOn(Date.Now.AddDays(-2)).
    Read(False).Importance(2).Build()                       ' arrives unread, flagged high
mail.SourceFolder = "Inbox" : items.Add(mail)

Dim first = NextTuesday(Date.Today).AddHours(10)
Dim meeting = clsItemBuilder.Appointment().
    Subject("Design review").Location("Room 2").
    StartsAt(first).EndsAt(first.AddHours(1)).
    Organiser("Dana Ruiz", "dana@example.com").
    Attendee("You", "you@example.com").OptionalAttendee("Sam Okafor", "sam@example.com").
    Reminder(15).
    RecurWeekly(enmRecurDays.Tuesday, occurrences:=8).
    ModifyOccurrence(first.AddDays(7), newStart:=first.AddDays(7).AddHours(4)).   ' moved to 14:00
    ExcludeOccurrences(first.AddDays(14)).                                        ' cancelled
    Build()
meeting.SourceFolder = "Calendar" : items.Add(meeting)

Dim contact = clsItemBuilder.Contact().
    Name("Sam", "Okafor").Company("Example Ltd").JobTitle("Architect").
    Email("sam@example.com").Phone(business:="+44 20 7946 0000", mobile:="+44 7700 900000").
    Photo(IO.File.ReadAllBytes("sam.jpg")).Build()          ' the picture on the card
contact.SourceFolder = "Contacts" : items.Add(contact)

Dim post = clsItemBuilder.Rss().
    Subject("Release 3.1 is out").Channel("Example Blog", "https://example.com/feed").
    Article("https://example.com/3-1").PostedOn(Date.Now.AddDays(-1)).Read(False).Build()
post.SourceFolder = "RSS Feeds\Example Blog" : items.Add(post)

' ONE pass over the store for every folder; extraFolders are created empty, ready for the user.
Dim rep = clsStoreImporter.ImportMessagesByFolder(Nothing, "new-mailbox.pst", items,
              defaultFolder:="Inbox", extraFolders:={"Tasks", "Notes"},
              diag:=New clsDiagnostics(), overwrite:=True, storeName:="Authored Mailbox")

C#

var items = new List<clsImportedMessage>();

var mail = clsItemBuilder.Mail()
    .Subject("Q3 numbers").From("Dana Ruiz", "dana@example.com").To("You", "you@example.com")
    .BodyHtml("<p>Attached.</p>").SentOn(DateTime.Now.AddDays(-2))
    .Read(false).Importance(2).Build();                     // arrives unread, flagged high
mail.SourceFolder = "Inbox"; items.Add(mail);

var first = NextTuesday(DateTime.Today).AddHours(10);
var meeting = clsItemBuilder.Appointment()
    .Subject("Design review").Location("Room 2")
    .StartsAt(first).EndsAt(first.AddHours(1))
    .Organiser("Dana Ruiz", "dana@example.com")
    .Attendee("You", "you@example.com").OptionalAttendee("Sam Okafor", "sam@example.com")
    .Reminder(15)
    .RecurWeekly(enmRecurDays.Tuesday, occurrences: 8)
    .ModifyOccurrence(first.AddDays(7), newStart: first.AddDays(7).AddHours(4))    // moved to 14:00
    .ExcludeOccurrences(first.AddDays(14))                                         // cancelled
    .Build();
meeting.SourceFolder = "Calendar"; items.Add(meeting);

var contact = clsItemBuilder.Contact()
    .Name("Sam", "Okafor").Company("Example Ltd").JobTitle("Architect")
    .Email("sam@example.com").Phone(business: "+44 20 7946 0000", mobile: "+44 7700 900000")
    .Photo(File.ReadAllBytes("sam.jpg")).Build();           // the picture on the card
contact.SourceFolder = "Contacts"; items.Add(contact);

var post = clsItemBuilder.Rss()
    .Subject("Release 3.1 is out").Channel("Example Blog", "https://example.com/feed")
    .Article("https://example.com/3-1").PostedOn(DateTime.Now.AddDays(-1)).Read(false).Build();
post.SourceFolder = @"RSS Feeds\Example Blog"; items.Add(post);

// ONE pass over the store for every folder; extraFolders are created empty, ready for the user.
var rep = clsStoreImporter.ImportMessagesByFolder(null, "new-mailbox.pst", items,
              defaultFolder: "Inbox", extraFolders: new[] { "Tasks", "Notes" },
              diag: new clsDiagnostics(), overwrite: true, storeName: "Authored Mailbox");

32. Drive Bastion PST SDK from PowerShell

Bastion PST SDK is an ordinary .NET assembly, so PowerShell can drive all of it with Add-Type and no COM, no Outlook and no MAPI — which makes it a practical tool for the jobs administrators actually have: auditing a directory full of archives, pulling a mailbox out to files, merging a decade of PSTs, or checking a hundred files for corruption overnight. The distribution ships a ready-made module, samples/PowerShell/Bastion.Pst.psm1, with fourteen worked tasks beside it in Examples.ps1.

POWERSHELL

Import-Module .\Bastion.Pst.psm1

# What is in this file?
Get-PstInfo C:\mail.pst

# The ten fullest folders.
Get-PstFolder C:\mail.pst | Sort-Object Items -Descending | Select-Object -First 10

# Big unread mail with attachments, as a spreadsheet.
Get-PstMail C:\mail.pst -Unread -WithAttachments -Since '2024-01-01' -LargerThanKB 256 |
    Export-Csv unread.csv -NoTypeInformation

# The address book, one card per person, photographs written out beside it.
Get-PstContact C:\mail.pst -PhotoDir C:\photos | Export-Csv contacts.csv -NoTypeInformation

# Audit a whole directory of archives; then merge them.
Get-ChildItem C:\Archives\*.pst | Test-PstHealth | Where-Object Corrupted
Get-ChildItem C:\Archives\*.pst | Merge-Pst -Destination C:\merged.pst -Layout Unified

# Rebuild a damaged file into a new copy (the original is never touched).
Repair-Pst C:\damaged.pst -Destination C:\repaired.pst

# Everything out as .msg, with the library's own progress bar.
Register-PstProgress
Export-PstItem C:\mail.pst -Destination C:\out -Format Msg
Unregister-PstProgress

Three things catch everybody out once.

POWERSHELL

# Loading, and calling the raw API without the module.
$tfm = if ($PSVersionTable.PSEdition -eq 'Core') { 'net8.0' } else { 'net48' }
Add-Type -Path "C:\BastionPstSdk\lib\$tfm\Bastion.Pst.dll"

$diag = New-Object Bastion.Pst.Diagnostics.clsDiagnostics
$diag.RecoverFromCorruption = $true

# Merge(sources, out, dedup, keepKinds, diag, overwrite, extraDrops, maxBytes, layout, rootName)
# - every argument supplied, because PowerShell binds positionally.
$sources = New-Object 'System.Collections.Generic.List[string]'
Get-ChildItem C:\Archives\*.pst | ForEach-Object { $sources.Add($_.FullName) }
$rep = [Bastion.Pst.Convert.clsStoreMerger]::Merge(
           $sources, 'C:\merged.pst',
           (New-Object Bastion.Pst.Convert.clsDedup), $null, $diag, $true, $null, 0,
           [Bastion.Pst.Convert.enmMergeLayout]::Unified, $null)

# The library never throws into your script - check the code.
if ($rep.ErrorCode -ne 0) { Write-Warning $rep.ErrorDescription }
else { "merged {0:N0} item(s), {1:N0} duplicate(s) removed" -f $rep.MessagesMerged, $rep.DuplicatesRemoved }

# Progress and errors from anywhere in the library, in one subscription.
$hub = [Bastion.Pst.clsPstEvents]::Default
Register-ObjectEvent -InputObject $hub -EventName PstRead -SourceIdentifier Bastion.Pst.Read -Action {
    $e = $Event.SourceEventArgs
    Write-Progress -Activity "Reading $($e.Folder)" -Status "$($e.Current) of $($e.Total)" -PercentComplete $e.Percent
} | Out-Null

Samples

The distribution ships ready-to-open sample projects under the samples/ folder. Each references the library from dist/lib and can be opened directly in Visual Studio. Build one, point it at a PST, and step through the API.

ProjectLanguage / targetWhat it does
samples/FeatureTourCSharp C# · SDK-style · .NET 8 The comprehensive C# tour. A single guided walkthrough that exercises the whole library in sixteen labelled sections: open & inspect, walk folders & read messages, typed items (AsItem()), fluent search, export, merge & split, health & repair, password, authoring (clsAuthor / clsAuthoringSession) and import — then the host-application half: the bindable section views, listing one folder and reading one item (MailIn / Detail / AttachmentBytes), filtering before an item is opened (clsItemQuery), progress events (clsPstEvents), item operations (clsItemOps save / copy / move / delete / import) and building a whole mailbox — meeting attendees, recurrence exceptions, contact photos, feed posts — in one pass. Run it as FeatureTourCSharp <source.pst> <workDir> and read the console top to bottom to see every major API called and result-checked.
samples/FeatureTourVB VB.NET · SDK-style · .NET 8 The comprehensive VB.NET tour. The same sixteen-section walkthrough as the C# tour, written idiomatically in VB.NET — the fastest way to see the full feature set from VB.
samples/WinForms
Bastion.Pst.Samples.WinForms
VB.NET · classic project · .NET Framework 4.7.2 The flagship GUI sample — a tabbed WinForms app exercising the whole library: Batch (merge / convert / split / export to EML / export to MSG), Search (fluent query), and Verify / Validate / Repair. Streams progress and a plain-language activity log into an on-form console. Deliberately written in VB 11 syntax so it opens in every Visual Studio from 2012 onward.
samples/PowerShell
Bastion.Pst.psm1 + Examples.ps1
PowerShell · Windows PowerShell 5.1 and PowerShell 7+ The whole library as PowerShell functions. Bastion.Pst.psm1 wraps the calls a script actually wants — Get-PstInfo, Get-PstFolder, Get-PstMail, Get-PstContact, Get-PstAppointment, Export-PstItem, Merge-Pst, Test-PstHealth, Repair-Pst, Import-PstFile, Register-PstProgress — as cmdlet-shaped functions that emit objects, so the output pipes into Where-Object, Export-Csv and Out-GridView. Examples.ps1 is fourteen worked tasks: audit a directory of archives, chart mail volume by year, dump the address book with photos, merge a decade of PSTs, repair a damaged file. See the PowerShell example for the three things that catch everyone out.
samples/ConsoleExportCSharp C# · SDK-style · .NET 8 Minimal command-line exporter. Takes a PST, an output folder and a format (eml | msg | ics | vcf | mhtml | mbox) and writes every message out via clsStoreExporter.ExportToFolder, showing the result-code pattern and a progress counter. The shortest end-to-end example of the export API in C#.
samples/ConsoleImportVB VB.NET · SDK-style · .NET 8 Minimal command-line importer. Reads an .eml, .msg or .mbox file into the neutral clsImportedMessage model (clsEmlReader / clsMsgReader / clsMboxReader) and prints subject, addresses, body sizes and attachments — or, given an output path, writes the messages into a new PST via clsStoreImporter. Shows how to consume the import API and check result codes.

Every sample writes a log

Each sample writes a log file for every run, whether or not anything goes wrong, to %TEMP%\BastionPstSdk-Samples\. When something fails you get one plain sentence on screen and the offer to open the log; the exception type and stack go to the file, not the console. The WinForms sample also has a View Log File button.

The samples are written the way we suggest you write your own code against the library: every call is checked by ErrorCode rather than wrapped in a catch, because the library reports failure in its result and does not throw into your application. The SampleLog file in each sample is self-contained — copy it into your own project if it is useful.

One limit worth knowing: a managed try/catch only sees managed exceptions. If a process is killed by a native fault (heap corruption, an access violation, a stack overflow) no handler runs at all, because Windows terminates it without unwinding. The log still helps, because the last line written tells you how far it got — which is why every line is flushed as it happens rather than buffered.

Building a sample

All samples follow the house rule demonstrated throughout this document: check each call's ErrorCode (0 = Ok) rather than catching exceptions — the library never throws into your application.

Frequently asked questions

Which build do I reference, and how do I switch the sample between them?

The distribution ships the library built for every current .NET. Reference the one that matches your project (the API is identical across all of them):

FolderTargetUse from
lib/net46 … lib/net481.NET Framework 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1.NET Framework apps; Visual Studio 2012–2022
lib/netcoreapp2.0 … lib/netcoreapp3.1.NET Core 2.0, 2.1, 3.0, 3.1.NET Core apps (VS 2017 15.3+)
lib/net5.0 … lib/net10.0.NET 5, 6, 7, 8 (LTS), 9, 10 (LTS)Modern .NET apps (VS 2022 17.8+ for net8.0, 17.12+ for net9.0, 17.14+ for net10.0)

Eighteen builds, one per framework — there is no netstandard build. Pick the folder matching your target exactly. A WinForms app on net8.0-windows uses lib/net8.0: the library is portable and needs no Windows-specific build.

The shipped WinForms sample (samples/WinForms) references lib/net472 so it opens in Visual Studio 2012 — the oldest supported IDE. To point the same sample at a different build, change one line and (for modern targets) the target framework:

To use a different .NET Framework build — edit the <HintPath> in the .vbproj. The Include is the assembly name, Bastion.Pst, not the product name:

<Reference Include="Bastion.Pst">
  <HintPath>..\..\lib\net462\Bastion.Pst.dll</HintPath>
</Reference>

Off .NET Framework, add the companion package: System.Text.Encoding.CodePages (needed for correct ANSI→Unicode transcoding; it is built into .NET Framework, so you only need it on .NET Core / .NET 5+).

To use net8.0 — convert the project to the SDK style and set the framework and reference:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="Bastion.Pst">
      <HintPath>..\..\lib\net8.0\Bastion.Pst.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

The same pattern works for net48, net9.0 or net10.0 — point the <HintPath> at that folder (and, for the modern runtimes, set the matching <TargetFramework>, e.g. net10.0-windows). The library API is identical across every build, so the sample source does not change — only the reference and target framework do. (The shipped sample deliberately uses VB 11 syntax so it also compiles on the oldest toolchains; newer language features are fine on newer targets.)

A folder shows a message count, but opens empty — or a message opens blank

Almost always this is Outlook’s view/filter state, not missing data. The folder count comes from the PST’s stored count; Outlook then applies the active View (filter / group / sort) to decide what to show. A stray filter can hide a full folder.

To prove the data is present independent of the view, call clsPstHealth.Check — it compares each folder’s stored count (and the folder-tree’s cached count) against the real messages. Zero mismatches means every message is there and the display is the only variable.

Why did some folders disappear or lose messages after a merge?

By design a merge removes provably-empty messages (no subject, no body in any form, no attachments — typically old spam remnants that open to nothing) and prunes empty folders. Every removal is itemised in the user log when CreateUserLog is on, with folder, source, sender and date. To keep everything verbatim, set dropBlankMessages:=False and pruneEmpty:=False (see Quick Start example 4).

How do I check whether an operation succeeded?

Every operation returns an object derived from clsOperationResult. Check ErrorCode (an enmErrorCode; 0 = Ok). For display use ErrorDescription (human text) or ToString() ("code: description"); Detail carries the full technical text for a support ticket, and Succeeded is a boolean shortcut. The library never throws into your application, so you never need a try/catch for control flow.

What do the error numbers mean?

#NameTypical cause / fix
0OkSuccess.
1UnknownErrorUnclassified — read Detail.
2FileNotFoundA source/destination path does not exist.
3FileLockedUsually the PST is mounted in Outlook — close Outlook.
4DiskFullDestination disk ran out of space.
5AccessDeniedPermissions / read-only path.
6InvalidPstFormatNot a readable PST/OST; try Repair.
7DestinationExistsPass overwrite:=True.
8OutputSizeExceededOutput hit the size guard; use MergeToVolumes.
9InvalidArgumentA parameter is out of range (message says which).
10InvalidRootNameRootName empty after cleaning, or > 200 code units.
11NodeIdCapacityExceededToo many items for one file; merge into volumes.
12InsufficientDiskSpaceNot enough free space for the estimated output.

The authoritative list is the enmErrorCode reference below.

Can I set the name Outlook shows for the merged store? Any character limits?

Yes — pass rootName. Any printable Unicode is accepted (CJK, emoji, right-to-left scripts, symbols such as \ / : * ? < > | & %). Control characters are stripped and whitespace trimmed. The length limit is 200 UTF-16 code units (a safety cap, not a PST limit): 200 CJK characters fit; emoji count as two units each. Defaults are “Stacked Merge” / “Unified Merge” for multi-source merges, and the original store name is preserved for a single-source rewrite.

My merge fails with FileLocked (3) but the file exists

The PST is almost certainly mounted in Outlook, which holds it open with no read sharing. Close Outlook (or remove the store from the profile) and retry. The library pre-checks this before writing and reports it as code 3 with a message that names the likely cause.

Can I use Bastion PST SDK from PowerShell?

Yes — it is an ordinary managed assembly, so Add-Type -Path Bastion.Pst.dll is all it takes; no COM, no Outlook, no MAPI. samples/PowerShell ships a module wrapping the whole library as cmdlet-shaped functions (Get-PstInfo, Get-PstMail, Merge-Pst, Test-PstHealth, Repair-Pst…) that emit objects, so results pipe into Export-Csv and the rest. Load lib\net48 from Windows PowerShell 5.1 and lib\net8.0 from PowerShell 7+; the wrong one fails with a misleading “Bad IL format”. And remember PowerShell cannot skip .NET optional arguments — pass the whole list, with $null for the defaults. See example 32.

Non-ASCII text looks wrong on .NET Core / .NET 5+

On .NET Core / .NET 5+ you must register the code-page provider once at startup so ANSI PSTs transcode correctly — add the System.Text.Encoding.CodePages package and call Encoding.RegisterProvider(CodePagesEncodingProvider.Instance). This is built into .NET Framework, so the net46–net481 consumers need nothing.

Will the merged/converted PST open in my version of Outlook?

Yes, for Outlook 2003 and every later version (2007, 2010, 2013, 2016, 2019, 2021, 2024 and Microsoft 365). Bastion PST SDK always writes the modern Unicode PST format. It will not open in Outlook 2002/XP or earlier, which only support the legacy ANSI PST format — there is no ANSI-write mode. Bastion PST SDK can, however, read those old ANSI files and convert them to Unicode. See the Outlook compatibility table in the Introduction.

Because of this, adding to or editing an existing ANSI store would rewrite it as Unicode and make it unopenable in Outlook 2002 and earlier. To protect users on legacy Outlook, Bastion PST SDK refuses that upgrade by default (the operation returns enmErrorCode.AnsiUpgradeRequired and writes nothing). Authorise it explicitly with clsDiagnostics.AllowAnsiToUnicodeUpgrade = True or by handling the AnsiUpgradeRequired event. Detect the format up front with the cheap, header-only clsPersonalStorage.IsAnsi / PeekFormat.

How do I prove a merged/repaired PST is complete and valid before shipping it to a user?

How do I get messages out of a PST (export to individual files)?

Use clsStoreExporter.ExportToFolder(pstPath, outDir, enmExportFormat.Eml, diag) to write every message to its own .eml file under a folder tree that mirrors the store (attachments included, RFC 5322 / MIME). For a single message you hold, call clsMessageExporter.ExportEml(msg, path) or ExportMsg(msg, path). Six formats ship: EML (RFC 5322 / MIME), MSG (Outlook [MS-OXMSG]), ICS, VCF, MHTML and MBOX. If you are saving a user's selection rather than a whole store, use clsItemOps.SaveItems instead: it picks the right format per item kind (mail .msg, contact .vcf, appointment .ics, note .txt), is cancellable and reports progress. The command-line Export tool and the WinForms sample's “Export messages to EML/MSG files” batch operations both drive the store exporter.

How do I show a mailbox in my own application?

Bind to store.Views rather than writing the projection yourself: Mail(), Contacts(), Appointments(), Tasks(), Notes() and Rss() return flat, bindable rows, and MailIn(folder) is what a message list binds to when the user clicks a folder. Read one item with Views.Detail(folder, nid) when it is selected, and fetch attachment bytes with Views.AttachmentBytes only when the user saves or opens one. Editing verbs — save, copy, move, delete, import — are on clsItemOps. See examples 25–31.

Listing a big folder is slow, or the UI looks frozen

Two causes, both avoidable.

Do the logs or support reports contain private data?

No. Diagnostic logs and support reports contain structural metadata only — never message content, subjects, addresses or attachments. They are safe for a user to email to your support (or to support@bastionsoftwaresolutions.com).

Visual Studio 2012 won’t load the sample (“these files could not be found”)

Delete any bin\, obj\, *.suo next to the project and re-open, and start Visual Studio from the Start menu (not from a Git Bash / MSYS shell, whose environment variables can redirect MSBuild imports). The shipped project has no such reference.

API reference

Public types and members only. Grouped by namespace below.

Class clsFileOpResult

Bastion.Pst

Result of a file-producing call (e.g. SaveSupportReport): the path plus the error contract.

Properties

MemberTypeSummary
PathStringFull path of the file written (Nothing on failure).

Class clsGenericResult

Bastion.Pst

Result for operations with no extra payload (e.g. ConvertToUnicode).

Class clsOperationResult

Bastion.Pst

Base of every operation result: error code + descriptions + ToString "code: description".

Properties

MemberTypeSummary
DetailStringFull technical detail (exception text incl. stack trace) — for logs/support, may be empty.
ErrorCodeenmErrorCode0 (Ok) = success; otherwise the failure class.
ErrorDescriptionStringOne-line, human-readable reason. "OK" on success.
SucceededBooleanTrue when ErrorCode = Ok.

Methods

MemberTypeSummary
ToString()String"0: OK" or e.g. "3: The file 'x.pst' is locked by another process …".

Class clsPstCloseEventArgs

Bastion.Pst

Properties

MemberTypeSummary
PathString

Class clsPstErrorEventArgs

Bastion.Pst

Properties

MemberTypeSummary
ErrorCodeenmErrorCode
MessageString
PathString

Class clsPstEvents

Bastion.Pst

Subscribe to Default to observe PST activity across the whole library.

Properties

MemberTypeSummary
DefaultclsPstEventsThe single, process-wide event source. Accessible from any app.

Class clsPstFolderSelectedEventArgs

Bastion.Pst

Properties

MemberTypeSummary
FolderString
ItemCountInt64
PathString

Class clsPstOpenEventArgs

Bastion.Pst

Properties

MemberTypeSummary
PathString

Class clsPstReadEventArgs

Bastion.Pst

Properties

MemberTypeSummary
CurrentInt64
FolderString
PathString
PercentInt32Percent read, 0..100; 0 while the total is not yet known.
TotalInt64

Enum enmErrorCode

Bastion.Pst

Failure classes returned by every Bastion PST SDK operation. 0 = success.

MemberValue
Ok0
UnknownError1
FileNotFound2
FileLocked3
DiskFull4
AccessDenied5
InvalidPstFormat6
DestinationExists7
OutputSizeExceeded8
InvalidArgument9
InvalidRootName10
NodeIdCapacityExceeded11
InsufficientDiskSpace12
AnsiUpgradeRequired13
OperationCancelled14

Class clsAppointmentBuilder

Bastion.Pst.Authoring

Methods

MemberTypeSummary
AllDay(Boolean)clsAppointmentBuilder
Attendee(String, String)clsAppointmentBuilderAdd a required attendee. The attendee list is written both as recipients and as the [MS-OXOCAL] attendee-string properties, so it reads back through clsAppointmentView.Attendees.
BodyText(String)clsAppointmentBuilder
BusyStatus(Int32)clsAppointmentBuilder0 free, 1 tentative, 2 busy, 3 out of office.
EndsAt(DateTime)clsAppointmentBuilder
ExcludeOccurrences(DateTime[])clsAppointmentBuilderDelete specific occurrence dates from the series (e.g. skip a holiday). Call after a Recur… method; the day is what matters.
Location(String)clsAppointmentBuilder
ModifyOccurrence(DateTime, Nullable(Of DateTime), Nullable(Of DateTime), String, String, Nullable(Of Int32))clsAppointmentBuilderOverride ONE occurrence of the series — move it and/or change its subject, location or busy status for that instance only ([MS-OXOCAL] exception). Call after a Recur… method. originalDate must be a date the pattern generates (and not an excluded one); a moved occurrence must stay on a day no other occurrence lands on.
OptionalAttendee(String, String)clsAppointmentBuilderAdd an OPTIONAL attendee (the meeting's Cc line).
Organiser(String, String)clsAppointmentBuilderThe meeting organiser (defaults to the store owner when unset).
RecurDaily(Int32, Int32, Nullable(Of DateTime))clsAppointmentBuilderMake this a daily series: every interval days, ending after occurrences (0 = use until, or never if both unset).
RecurMonthly(Int32, Int32, Int32, Nullable(Of DateTime))clsAppointmentBuilderMake this a monthly series on day dayOfMonth (1–31; 31 = last), every interval months.
RecurWeekly(enmRecurDays, Int32, Int32, Nullable(Of DateTime))clsAppointmentBuilderMake this a weekly series on days, every interval weeks (0 occurrences = use until, or never if both unset).
Reminder(Int32)clsAppointmentBuilder
StartsAt(DateTime)clsAppointmentBuilder
Subject(String)clsAppointmentBuilder

Class clsAuthor

Bastion.Pst.Authoring

Creates and populates PST stores without Outlook: build typed items with the clsItemBuilder factories, then add them to a new or existing store. Every write is an atomic rewrite validated by reopening — the store is never mutated in place.

Methods

MemberTypeSummary
AddItems(String, String, IEnumerable(Of clsImportedMessage), String, clsDiagnostics, Boolean, String)clsImportReportAdd typed items to folderPath of an existing store, writing the result to outPath (may equal storePath). Missing folders in the path are created with the container class matching the item kinds.
AddItemsByFolder(String, String, IEnumerable(Of clsImportedMessage), String, IEnumerable(Of String), clsDiagnostics, Boolean, String)clsImportReportAdd typed items spread across MANY folders in one atomic rewrite: each item is filed by its clsImportedMessage.SourceFolder (or defaultFolder when unset), and extraFolders names folders to create empty. Prefer this over a loop of AddItems calls when populating several folders — every AddItems call rewrites the whole store, so a loop costs O(store size × folder count).
CreateFolder(String, String, String, clsDiagnostics, Boolean)clsImportReportCreate an (empty) folder at folderPath in storePath, writing to outPath. Implemented as an add of zero items to the path, which the pipeline satisfies by creating the folder chain.
CreateStore(String, String, Boolean)clsFileOpResultCreate a new empty Unicode PST at path.
CreateStoreWith(String, IEnumerable(Of clsImportedMessage), String, String, Boolean, clsDiagnostics)clsImportReportCreate a new store at path and drop items into folderPath in one call — the common "author a PST from scratch" case.
CreateStoreWithFolders(String, IEnumerable(Of clsImportedMessage), String, IEnumerable(Of String), String, Boolean, clsDiagnostics)clsImportReportCreate a new store and populate MANY folders in one call — the "author a whole mailbox from scratch" case. One atomic write for the entire tree.
DeleteFolder(String, String, String, Boolean, clsDiagnostics, Boolean)clsEditReportDelete the folder at folderPath. When recursive is False, refuses a non-empty folder.
DeleteItems(String, String, String, Func(Of clsMessage, Boolean), clsDiagnostics, Boolean)clsEditReportDelete items in folderPath matching match (Nothing = all items in the folder), writing to outPath.
EditItems(String, String, String, Func(Of clsMessage, Boolean), clsItemEdit, clsDiagnostics, Boolean)clsEditReportEdit the properties (subject / body / importance — see clsItemEdit) of items in folderPath matching match (Nothing = all), writing to outPath. The folder-list subject cache is kept in step.
MoveFolder(String, String, String, String, clsDiagnostics, Boolean)clsEditReportMove the folder at folderPath under a new parent folder newParentPath, writing to outPath.
MoveItems(String, String, String, Func(Of clsMessage, Boolean), String, clsDiagnostics, Boolean)clsEditReportMove items matching match (Nothing = all) from fromFolder to toFolder, writing to outPath. Counts on both folders are corrected and the moved items' NBT parent is re-pointed at the destination.
RemoveAttachments(String, String, String, Func(Of clsMessage, Boolean), Func(Of clsAttachment, Boolean), clsDiagnostics, Boolean)clsEditReportRemove attachments matching attMatch (Nothing = all) from the messages in folderPath matching msgMatch (Nothing = all), writing to outPath. When a message loses its last attachment its PidTagHasAttachments and mfHasAttach flag are cleared.
RenameFolder(String, String, String, String, clsDiagnostics, Boolean)clsEditReportRename the folder at folderPath to newName, writing to outPath.
SetFolderClass(String, String, String, String, clsDiagnostics, Boolean)clsEditReportSet the folder's container class (PidTagContainerClass) — what marks a folder as holding appointments, contacts or tasks rather than mail — writing to outPath.

Class clsAuthoringSession

Bastion.Pst.Authoring

Accumulates rename / move / delete / remove-attachment edits and applies them in a single atomic, reopen-validated rewrite. Fluent: chain the queue methods, then Commit.

Properties

MemberTypeSummary
CountInt32Number of edits queued so far.

Methods

MemberTypeSummary
Commit(String, Boolean)clsEditReportApply every queued edit to outPath in one atomic rewrite. The returned clsEditReport carries the combined per-kind change counts.
DeleteFolder(String, Boolean)clsAuthoringSession
DeleteItems(String, Func(Of clsMessage, Boolean))clsAuthoringSession
EditItems(String, Func(Of clsMessage, Boolean), clsItemEdit)clsAuthoringSession
MoveFolder(String, String)clsAuthoringSession
MoveItems(String, Func(Of clsMessage, Boolean), String)clsAuthoringSession
RemoveAttachments(String, Func(Of clsMessage, Boolean), Func(Of clsAttachment, Boolean))clsAuthoringSession
RenameFolder(String, String)clsAuthoringSession
SetFolderClass(String, String)clsAuthoringSessionSet the folder's container class (PidTagContainerClass) — what marks it as holding appointments, contacts or tasks rather than mail.

Class clsContactBuilder

Bastion.Pst.Authoring

Methods

MemberTypeSummary
Address(String, String)clsContactBuilderBusiness and home postal addresses (each a single multi-line string).
Anniversary(DateTime)clsContactBuilder
Birthday(DateTime)clsContactBuilder
Company(String)clsContactBuilder
Department(String)clsContactBuilder
DisplayName(String)clsContactBuilder
Email(String, Int32)clsContactBuilder
Fax(String)clsContactBuilder
JobTitle(String)clsContactBuilder
Name(String, String, String)clsContactBuilder
Notes(String)clsContactBuilderFree-text notes — stored as the contact item's body.
Phone(String, String, String)clsContactBuilder
Photo(Byte[], String)clsContactBuilderAttach a contact photo (JPEG bytes). Written with PidTagAttachmentContactPhoto + PidLidHasPicture so Outlook shows it on the card.
WebPage(String)clsContactBuilder

Class clsDistListBuilder

Bastion.Pst.Authoring

Methods

MemberTypeSummary
AddMember(String, String)clsDistListBuilderAdd a one-off member (display name + SMTP address).
Name(String)clsDistListBuilderThe list's display name.

Class clsItemBuilder

Bastion.Pst.Authoring

Entry point for building typed items: clsItemBuilder.Mail(), .Appointment(), .Contact(), .Task(), .Note().

Methods

MemberTypeSummary
Appointment()clsAppointmentBuilder
Contact()clsContactBuilder
DistributionList()clsDistListBuilder
Journal()clsJournalBuilder
Mail()clsMailBuilder
Note()clsNoteBuilder
Rss()clsRssBuilder
Task()clsTaskBuilder

Class clsItemBuilderBase

Bastion.Pst.Authoring

Shared surface: subject/body/attachments and the terminal Build.

Methods

MemberTypeSummary
Build()clsImportedMessageThe finished neutral item, ready for clsAuthor / clsStoreImporter.

Class clsJournalBuilder

Bastion.Pst.Authoring

Methods

MemberTypeSummary
BodyText(String)clsJournalBuilder
DurationMinutes(Int32)clsJournalBuilder
EndsAt(DateTime)clsJournalBuilder
JournalType(String)clsJournalBuilderFree-text activity type, e.g. "Phone call".
StartsAt(DateTime)clsJournalBuilder
Subject(String)clsJournalBuilder

Class clsMailBuilder

Bastion.Pst.Authoring

Methods

MemberTypeSummary
AddAttachment(String, Byte[], String)clsMailBuilder
AddEmbeddedMessage(String, clsImportedMessage)clsMailBuilder
Bcc(String, String)clsMailBuilder
BodyHtml(String)clsMailBuilder
BodyText(String)clsMailBuilder
Cc(String, String)clsMailBuilder
From(String, String)clsMailBuilder
Importance(Int32)clsMailBuilder0 low, 1 normal, 2 high.
MessageId(String)clsMailBuilder
Read(Boolean)clsMailBuilderMark the item read (default) or unread.
SentOn(DateTime)clsMailBuilder
Subject(String)clsMailBuilder
To(String, String)clsMailBuilder

Class clsNoteBuilder

Bastion.Pst.Authoring

Methods

MemberTypeSummary
Color(Int32)clsNoteBuilder0 blue, 1 green, 2 pink, 3 yellow, 4 white.
Size(Int32, Int32)clsNoteBuilder
Subject(String)clsNoteBuilder
Text(String)clsNoteBuilder

Class clsRssBuilder

Bastion.Pst.Authoring

An RSS feed item (IPM.Post.Rss).

Methods

MemberTypeSummary
Article(String, String)clsRssBuilderThe article's own URL and feed-unique id.
BodyHtml(String)clsRssBuilder
BodyText(String)clsRssBuilder
Channel(String, String)clsRssBuilderThe feed this item came from: display name and channel URL.
PostedOn(DateTime)clsRssBuilder
Read(Boolean)clsRssBuilder
Subject(String)clsRssBuilder

Class clsTaskBuilder

Bastion.Pst.Authoring

Methods

MemberTypeSummary
BodyText(String)clsTaskBuilder
CompletedOn(DateTime)clsTaskBuilder
DueDate(DateTime)clsTaskBuilder
Owner(String)clsTaskBuilder
PercentComplete(Double)clsTaskBuilder0..1.
StartDate(DateTime)clsTaskBuilder
Status(Int32)clsTaskBuilder0 not started, 1 in progress, 2 complete, 3 waiting, 4 deferred.
Subject(String)clsTaskBuilder

Class clsDedup

Bastion.Pst.Convert

Properties

MemberTypeSummary
AcrossFoldersBooleanCollapse duplicates across the WHOLE output instead of within each folder. Default False, which is the behaviour to keep unless a user has explicitly asked for otherwise. False (default): a message is a duplicate only of another copy in the SAME destination folder. The folder tree survives the merge exactly as the user built it — the same mail filed in "Inbox\Sales" and "Inbox\Sales 2025" is kept in both. True: one copy of each message survives anywhere in the output, so every other copy is removed no matter which folder it was filed in. This compacts the folder tree. Folders whose entire contents existed elsewhere are left empty, and with pruneEmpty set they are deleted outright — the user's filing is discarded, not just the redundant bytes. On a real 305 GB corpus this emptied 49 non-empty folders. Only set it where the person choosing has been told that plainly and wants the smallest possible output more than they want their folders.
BaseDropsDictionary(Of UInt32, HashSet(Of UInt32))Base-store contents tables needing row drops (base-internal dedup): the first copy of each message is kept, later copies within the same store are dropped. Populated by Seed.
BaseDuplicateCountInt32Total base-internal duplicates marked for removal by Seed.

Class clsDestinationExistsException

Bastion.Pst.Convert

Thrown at the start of a write operation when the destination already exists and overwrite was not requested. The host application can catch this and prompt the user.

Properties

MemberTypeSummary
DestinationPathStringThe destination path that already exists.

Class clsEditReport

Bastion.Pst.Convert

Result of a store edit: the standard error contract plus how many items/folders changed.

Properties

MemberTypeSummary
AttachmentsRemovedInt32
FoldersDeletedInt32
FoldersMovedInt32
FoldersReclassifiedInt32
FoldersRenamedInt32
ItemsDeletedInt32
ItemsEditedInt32
ItemsMovedInt32

Class clsItemEdit

Bastion.Pst.Convert

Property changes to apply to matching items (any field left Nothing is unchanged).

Properties

MemberTypeSummary
BodyTextStringNew plain-text body, or Nothing to leave unchanged.
ImportanceNullable(Of Int32)New importance (0 low, 1 normal, 2 high), or Nothing to leave unchanged.
SubjectStringNew subject (also updates the folder-list cache), or Nothing to leave unchanged.

Class clsMergePreflight

Bastion.Pst.Convert

Scans merge sources for corruption before the merge begins, and repairs the ones the caller agrees to repair. See Scan.

Methods

MemberTypeSummary
Repair(clsPreflightReport, String, Func(Of clsSourceHealth, Int32, Int32, enmRepairChoice), clsDiagnostics)clsPreflightReportRepair the corrupt sources in scan, asking prompt what to do about each. "…ToAll" answers stop the asking, so an interactive host shows at most one dialog; pass a prompt that always returns enmRepairChoice.YesToAll for unattended use (or call ScanAndRepairAll).
Scan(IList(Of String), clsDiagnostics)clsPreflightReportValidate every source and report which are damaged. Nothing is modified and nothing is repaired — this is the pass whose clsPreflightReport.CorruptCount feeds the single up-front prompt.
ScanAndRepairAll(IList(Of String), String, clsDiagnostics)clsPreflightReportScan, then repair every corrupt source without asking — the non-interactive default (equivalent to answering "Yes to all"). Repaired copies are written into workDir; the originals are never modified.

Class clsMergeReport

Bastion.Pst.Convert

Outcome of a merge. ErrorCode 0 = success; on failure ErrorDescription says why (ToString() renders "code: description").

Properties

MemberTypeSummary
BlankMessagesRemovedInt32Blank mail removed (no subject, no body, no attachments) — dropBlankMessages.
DuplicatesRemovedInt32
FilteredOutInt32Messages dropped by the item-type filter (selective merge).
FoldersMergedInt32
FoldersPrunedInt32Empty folders removed by the prune pass (pruneEmpty).
FoldersRehomedInt32Real folders found outside the IPM subtree (parked under the store root, where Outlook never displays them) and re-homed under the IPM root so they are visible.
MessagesMergedInt32
MessagesRehomedInt32Messages owned by the store root folder itself (invisible to Outlook) and re-homed into the IPM root's contents table.
TotalNodesInt32
WarningStringNon-fatal pre-flight advisory (e.g. low disk space) — Nothing when clean.

Class clsPasswordChangeReport

Bastion.Pst.Convert

Outcome of clsStorePassword.SetPassword / clsStorePassword.RemovePassword: what was written, the structural validation of the output, and the password state read back from the output as evidence.

Properties

MemberTypeSummary
HadPasswordBooleanWhether the source had a password before the change.
HasPasswordBooleanWhether the output has a password now (read back from the output file).
NodesWrittenInt32
OutputIsValidBooleanTrue when the output passes strict structural validation.
OutputPathString
OutputReportclsValidationReportStrict structural validation of the output.
PasswordCrcUInt32The CRC value written (0 = removed).
SourcePathString
SucceededBooleanWhether the change completed (ErrorCode 0) AND produced a structurally-valid file.

Class clsPasswordStatus

Bastion.Pst.Convert

Result of clsStorePassword.GetStatus: whether the file has an open password and the stored CRC value.

Properties

MemberTypeSummary
HasPasswordBooleanTrue when PidTagPstPassword is present and non-zero.
PasswordCrcUInt32The stored CRC (0 = no password). Compare with clsStorePassword.ComputeCrc to verify a candidate password.
PathString

Class clsPreflightReport

Bastion.Pst.Convert

Outcome of a pre-merge scan (and repair, when one was run).

Properties

MemberTypeSummary
CancelledBooleanTrue when the caller answered enmRepairChoice.Cancel. The merge must not proceed.
CorruptCountInt32How many sources are damaged and repairable. This is the number to put in the single prompt: "Found {CorruptCount} corrupted PST files. Do you want to attempt repair(s)?"
EffectiveSourcesIList(Of String)The list to hand to the merger: repaired copies substituted for the originals they replace, unsupported formats still present (the merge's own recovery path reports them).
FilesIList(Of clsSourceHealth)Every source, in the order supplied.
LegacyAnsiCountInt32How many sources are legacy ANSI stores. They are sound and merge normally; they are counted separately only so they are never mistaken for damage.
RepairedCountInt32How many sources were successfully repaired.
RepairFailedCountInt32How many repairs were attempted and failed.
UnsupportedCountInt32How many sources are a format we do not read (Outlook 2013+ OSTs). These are not damaged and are not offered for repair — they simply cannot take part in the merge.

Methods

MemberTypeSummary
Describe()StringOne line per problem file, fit to show a user.

Class clsSourceHealth

Bastion.Pst.Convert

One source file's pre-merge verdict.

Properties

MemberTypeSummary
EffectivePathStringThe path a merge should actually consume: the repaired copy when there is one, otherwise the original.
ErrorCountInt32Structural errors the validator found.
IsCorruptedBooleanTrue when the file has structural errors, or could not be read at all.
IsLegacyAnsiBooleanTrue when the file is a legacy ANSI store (wVer 14/15). It is NOT damaged. The structural validator only understands the Unicode container, so it reports every ANSI store as having a header error; treating that as corruption would "repair" a perfectly good file by rewriting it as Unicode — an upgrade Outlook 2002 and earlier cannot open, and one the library deliberately gates behind AnsiUpgradeRequired everywhere else.
IsUnsupportedFormatBooleanTrue when the file is a format this library does not read — an Outlook 2013+ OST (wVer 36). Such a file is NOT damaged and repairing it cannot work, so it is reported separately and never offered for repair.
PathStringThe source file as supplied.
RepairAttemptedBooleanTrue once a repair was attempted for this file.
RepairedPathStringThe repaired copy, when one was written. The original is never modified.
RepairSucceededBooleanTrue when the repair produced a usable file.
SummaryStringOne line fit to show a user.
SupportReportPathStringWhere the support report was written, when one was.

Class clsSplitPart

Bastion.Pst.Convert

Properties

MemberTypeSummary
MessageCountInt32
PathString
SizeBytesInt64

Class clsSplitReport

Bastion.Pst.Convert

Outcome of a split. ErrorCode 0 = success.

Properties

MemberTypeSummary
PartsList(Of clsSplitPart)
TotalMessagesInt32

Class clsStoreConverter

Bastion.Pst.Convert

Methods

MemberTypeSummary
ConvertToUnicode(String, String, clsDiagnostics, Boolean)clsGenericResultConvert a PST/OST (ANSI or Unicode) into a fresh Unicode PST at outPath. ANSI stores are upgraded to Unicode (escaping the 2 GB limit); Table Contexts are rebuilt at Unicode geometry so other clients read them. NEVER throws: check the result's ErrorCode (0 = success).
ConvertToUnicode(String, String)clsGenericResultConvert a PST/OST (ANSI or Unicode) into a fresh Unicode PST at outPath. ANSI stores are upgraded to Unicode (escaping the 2 GB limit); Table Contexts are rebuilt at Unicode geometry so other clients read them. NEVER throws: check the result's ErrorCode (0 = success).

Class clsStoreMerger

Bastion.Pst.Convert

Properties

MemberTypeSummary
DropSearchFoldersBooleanWhen True (default) merges drop search/view folders (non-IPM root children). Set False (CLI --keep-search) to carry them verbatim.
MaxNodeIdsPerVolumeUInt64Max cumulative source node-id span packed into one output volume (MergeToVolumes) before rolling to a new volume. 0 = default (85% of the 27-bit nidIndex ceiling). Lower it in tests to force multi-volume source splitting without needing hundreds of real sources.
MergeWorkersInt32Max concurrent source-conversion workers for the parallel merge.
ParallelMergeBooleanWhen True (default) a plain merge (no dedup/type-filter/volume edits) converts its sources on parallel worker threads, drained in source order. Set False to force the single-threaded pipeline (diagnostic / byte-equivalence A-B).

Methods

MemberTypeSummary
Merge(IList(Of String), String, clsDedup, HashSet(Of enmItemKind), clsDiagnostics, Boolean, Func(Of Int32, Dictionary(Of UInt32, HashSet(Of UInt32))), Int64, enmMergeLayout, String, Boolean, Boolean)clsMergeReportMerge sourcePaths into a new Unicode PST at outPath. dedup removes duplicate messages. rootName sets the store display name Outlook shows for the mounted PST (default: "Stacked Merge" / "Unified Merge" by layout). NEVER throws: the returned report's ErrorCode is 0 on success, otherwise a failure class with ErrorDescription (report.ToString() = "code: description").
MergeToVolumes(IList(Of String), String, Int64, clsDedup, HashSet(Of enmItemKind), clsDiagnostics, Boolean, String, Boolean, Boolean, enmMergeLayout)clsVolumeReportMerge sourcePaths into one or more Unicode PST volumes capped at capBytes of message data each: outBasePath (archive.pst), then archive-002.pst, archive-003.pst … Each volume is a standalone PST carrying the full folder structure with its share of the messages. Duplicates are removed globally (once, in a pre-scan) so no message appears in more than one volume. Memory stays bounded (the scan holds per-message metadata; each volume streams).
OrphanScan(String)ValueTuple(Of Int32, Int32, List(Of UInt32))Message nodes present in the NBT but referenced by NO contents/assoc table row — invisible to Outlook and to every row-based gate (count, conservation, inventory), yet still occupying the store. Rows and nodes are dropped by SEPARATE mechanisms, so a bug can remove one and not the other: a rewrite that drops a message's table ROW must drop its NODE too. Read-only; computed from a single NBT pass.
SweepPlan(String)ValueTuple(Of Int32, Int32, Int32, Int32)What a post-merge sweep would actually change, computed READ-ONLY. A sweep is a whole-store rewrite (10–22GB for a real archive), so discovering "nothing to do" by writing the file and then deleting it is the expensive way to learn it. Plan first; rewrite only when a count is non-zero.

Class clsStorePassword

Bastion.Pst.Convert

Read, set or remove the password of a PST file. A PST password is a stored CRC hash, not encryption — it controls Outlook's open prompt only; the data itself is never encrypted by it. SetPassword/RemovePassword write a new file (atomic, validated): pass the same path as source and output with overwrite:=True to change in place.

Methods

MemberTypeSummary
ComputeCrc(String)UInt32The CRC Outlook stores and compares for password — the [MS-PST] 5.3 CRC of the password's ANSI (Windows-1252) bytes. Empty/Nothing returns 0 (= no password). Exposed so hosts can verify a user-supplied password against clsPasswordStatus.PasswordCrc before opening.
GetStatus(String)clsPasswordStatusReport whether path has an open password set (PidTagPstPassword present and non-zero), without throwing.
RemovePassword(String, String, clsDiagnostics, Boolean)clsPasswordChangeReportWrite a copy of srcPath to outPath with the open password cleared, so the file opens without a prompt. Same guarantees and in-place convention as SetPassword.
SetPassword(String, String, String, clsDiagnostics, Boolean)clsPasswordChangeReportWrite a copy of srcPath to outPath with the open password set to password. Only the message store PC is rebuilt — every other node is preserved byte-for-byte (an ANSI source is upgraded to Unicode, as everywhere else in the library). The output is written atomically and structurally validated; the new CRC is read back from the output as proof. To change the password of a file in place, pass the same path for both arguments with overwrite:=True.

Class clsStoreSplitter

Bastion.Pst.Convert

Methods

MemberTypeSummary
SplitByPredicate(String, String, String, Func(Of clsMessage, Boolean), clsDiagnostics)clsSplitReportSplit into two stores: messages that satisfy predicate go to matchPath, the rest to restPath. The predicate receives each clsMessage. NEVER throws: check the report's ErrorCode (0 = success).
SplitBySize(String, String, Int64, clsDiagnostics)clsSplitReportSplit sourcePath into parts of at most maxBytes of message data each, written as <name>-001.pst, -002.pst, … in outDir. NEVER throws: check the report's ErrorCode (0 = success).

Class clsVolumeInfo

Bastion.Pst.Convert

One output volume produced by a capped merge.

Properties

MemberTypeSummary
MessagesInt32
PathString
SizeBytesInt64

Class clsVolumeReport

Bastion.Pst.Convert

Outcome of a capped (volume-split) merge. ErrorCode 0 = success.

Properties

MemberTypeSummary
BlankMessagesRemovedInt32Blank mail removed (no subject, no body, no attachments).
DuplicatesRemovedInt32
TotalMessagesInt32
VolumesList(Of clsVolumeInfo)
WarningStringNon-fatal pre-flight advisory (e.g. low disk space) — Nothing when clean.

Enum enmMergeLayout

Bastion.Pst.Convert

How a merge lays out the sources in the output store.

MemberValue
Stacked0
Unified1

Enum enmRepairChoice

Bastion.Pst.Convert

What the caller wants done about one corrupt source. The "…ToAll" answers apply to every remaining corrupt file so the user is not asked again; Cancel abandons the whole operation.

MemberValue
Yes0
YesToAll1
No2
NoToAll3
Cancel4

Class clsAnsiUpgradeEventArgs

Bastion.Pst.Diagnostics

Payload for clsDiagnostics.AnsiUpgradeRequired: the ANSI source about to be upgraded to Unicode. Set Proceed to make the hard choice — True upgrades to Unicode (unopenable in Outlook 2002 and earlier), False aborts with the source PST untouched.

Properties

MemberTypeSummary
ProceedBooleanThe decision: True to upgrade to Unicode, False (default) to abort without writing.
SourcePathStringThe legacy ANSI PST that the operation would rewrite as Unicode.

Class clsCorruptionEventArgs

Bastion.Pst.Diagnostics

Payload for clsDiagnostics.SourceCorruptionDetected: which file, how bad, and (when auto-generated) where the developer support report was written.

Properties

MemberTypeSummary
ErrorCountInt32Structural error count the scan found.
PathString
SummaryStringSample of issue descriptions (offset + reason), enough for a log line.
SupportReportPathStringPath of the auto-generated support report, or Nothing.

Class clsDiagEventArgs

Bastion.Pst.Diagnostics

Payload for clsDiagnostics.Started / clsDiagnostics.Completed.

Properties

MemberTypeSummary
MessageString
OperationString
TimestampUtcDateTime

Class clsDiagnostics

Bastion.Pst.Diagnostics

Opt-in diagnostics surface for read/export operations: switches plus public events the host application can subscribe to. Pass an instance into Merge/Split/ConvertToUnicode.

Properties

MemberTypeSummary
AllowAnsiToUnicodeUpgradeBooleanExplicit, up-front authorisation to rewrite a legacy ANSI PST as Unicode when adding to / editing it. Default False: such an operation is REFUSED (returns enmErrorCode.AnsiUpgradeRequired, source untouched) unless this is set True or an AnsiUpgradeRequired handler proceeds. Guards ANSI-only-Outlook users against a silent format upgrade that would make the file unopenable in their Outlook.
AutoSupportReportBooleanWhen true and corruption is detected, a support report (structural metadata only — never message content) is written automatically; its path rides on the SourceCorruptionDetected event and in the user log.
CancellationTokenCancellationTokenCooperative cancellation for long operations (merge/split/convert/import). Pass a token from your CancellationTokenSource; when you cancel it, the operation stops at the next checkpoint and returns enmErrorCode.OperationCancelled. The atomic-write model means nothing partial is left behind — the destination file is untouched.
CorruptionDetectedBooleanTrue once any scanned source (or a repair's source validation) reported corruption.
CreateUserLogBooleanWhen true, the library writes a plain-language activity log a NON-developer can read — what was read, what was merged, how many duplicates were removed, whether corruption was found and what was done about it. One line per milestone, no jargon, no offsets. Path in UserLogPath (default: BastionPstSdk-UserLog-….txt in the user's temp folder).
EnableLoggingBooleanWhen true, every raised event is also written, in detail, to LogFilePath.
InMemoryThresholdBytesInt64In Auto mode, inputs at or below this size are buffered in memory; larger inputs stream. Default 256 MB. (Streaming is safe at any size, so this is only a small-input optimisation.)
LogFilePathStringPath of the diagnostics log. If left blank when logging is first used, it defaults to %TEMP%\BastionPstSdk-yyyyMMdd-HHmmss-fff.log.
ProcessingModeenmProcessingModeProcessing mode. Auto (default) decides per input from its size; force InMemory or Streaming to override. Streaming has bounded memory for any size; the two produce identical output.
RecoverFromCorruptionBooleanWhen true, a read or write error on a single item is logged and reported via ErrorOccurred (Recovered=True) and the operation continues, exporting whatever it can recover, instead of aborting.
ScanSourcesOnOpenBooleanWhen true, every source store is structurally scanned as an operation opens it (full page/block validation — thorough but adds read time). A corrupt source raises SourceCorruptionDetected, latches CorruptionDetected, and — when AutoSupportReport is set — writes a support report for the developer.
SkipDuplicatesBooleanWhen true, a message that already exists in the output is not written again (duplicate detection by PidTagInternetMessageId, else a subject/sender/time hash).
SupportContactStringWhere the end user should send support reports — embedded in the report and the user-log guidance line. Defaults to Bastion Software Solutions Ltd's support address; set your own to route reports to your application's support instead.
SupportReportDirectoryStringDirectory for auto-generated support reports (default: the user's temp folder).
UserLogLinesIReadOnlyList(Of String)Everything written to the user log so far — hosts can show it in a UI instead of (or as well as) the file.
UserLogPathStringPath of the user log. If blank when the first line is written, defaults to %TEMP%\BastionPstSdk-UserLog-yyyyMMdd-HHmmss.txt.

Class clsErrorEventArgs

Bastion.Pst.Diagnostics

Payload for clsDiagnostics.ErrorOccurred: the failing phase, the error type and message, the item that failed, and whether the run recovered (skipped and continued).

Properties

MemberTypeSummary
ContextStringWhat was being processed (e.g. a node NID or source path).
ErrorExceptionThe underlying exception (may be Nothing).
ErrorTypeStringThe exception type name, e.g. "EndOfStreamException".
MessageString
PhaseenmDiagPhase
RecoveredBooleanTrue when the operation skipped the failing item and carried on (RecoverFromCorruption); False when the error aborts the operation.
TimestampUtcDateTime

Class clsModeEventArgs

Bastion.Pst.Diagnostics

Payload for clsDiagnostics.ModeChosen: which mode an input was processed in.

Properties

MemberTypeSummary
ModeenmProcessingMode
OperationString
PathString
SizeBytesInt64

Class clsProgressEventArgs

Bastion.Pst.Diagnostics

Payload for clsDiagnostics.ReadProgress / clsDiagnostics.WriteProgress.

Properties

MemberTypeSummary
CurrentItemString
PercentCompleteDouble0..100, or -1 when Total is unknown.
PhaseenmDiagPhase
ProcessedInt64
TimestampUtcDateTime
TotalInt64Total items, or -1 when not known in advance.

Enum enmDiagPhase

Bastion.Pst.Diagnostics

Which side of an operation an event refers to.

MemberValue
Read0
Write1

Enum enmProcessingMode

Bastion.Pst.Diagnostics

How a single input store is processed.

MemberValue
Auto0
InMemory1
Streaming2

Class clsExportReport

Bastion.Pst.Export

Result of a bulk export (a whole PST to a folder of files): the standard error contract plus how many items were written and how many were skipped.

Properties

MemberTypeSummary
FailedInt32Number of items that could not be exported (each is noted in the diagnostics/user log); the overall operation still succeeds unless it could not start at all.
FilesWrittenInt32Number of items successfully written to disk.
FoldersVisitedInt32Number of folders visited.
ItemsFoundInt32Number of items encountered while walking the store, counted before any format filter is applied. This is what tells "the store holds nothing" apart from "the store holds nothing of the kind this format can carry" when FilesWritten is zero — an empty output folder looks identical in both cases.
WarningStringNon-fatal note (e.g. "N item(s) skipped"), or Nothing.

Class clsItemExporter

Bastion.Pst.Export

Exports non-mail items to their standard interchange formats: appointments to iCalendar (.ics, RFC 5545) and contacts to vCard (.vcf, RFC 6350). Clean-room from the public RFCs; never throws.

Methods

MemberTypeSummary
ExportIcs(clsMessage, String, Boolean)clsFileOpResultWrite an appointment message to an iCalendar (.ics) file.
ExportVcf(clsContactView, String, Boolean)clsFileOpResultWrite a contact message to a vCard (.vcf) file.
ExportVcf(clsMessage, String, Boolean)clsFileOpResultWrite a contact message to a vCard (.vcf) file.
ToIcs(clsMessage)StringReturn the appointment as an iCalendar (VCALENDAR/VEVENT) string, or "" if the message is not an appointment.
ToVcf(clsContactView)StringReturn the contact as a vCard 3.0 string, or "" if the message is not a contact.
ToVcf(clsMessage)StringReturn the contact as a vCard 3.0 string, or "" if the message is not a contact.

Class clsMessageExporter

Bastion.Pst.Export

Exports a single message to an interchange format. First format: EML (RFC 5322 / MIME). Clean-room from the public RFCs; never throws — returns a clsFileOpResult.

Methods

MemberTypeSummary
ExportEml(clsMessage, String, Boolean)clsFileOpResultWrite msg to path as a MIME .eml file.
ExportMhtml(clsMessage, String, Boolean)clsFileOpResultWrite msg to path as MIME HTML (.mhtml, RFC 2557) — a single-file web archive (HTML body plus inline resources).
ExportMsg(clsMessage, String, Boolean)clsFileOpResultWrite msg to path as an Outlook .msg file ([MS-OXMSG] compound file).
ExportOft(clsMessage, String, Boolean)clsFileOpResultWrite msg to path as an Outlook template (.oft): an [MS-OXMSG] compound file with the template root CLSID and the message marked unsent — the form Outlook loads from "Choose Form / User Templates".
ToEml(clsMessage)StringReturn msg as a MIME (.eml) string.
ToMhtml(clsMessage)StringReturn msg as an MHTML (.mhtml) string.

Class clsStoreBuilder

Bastion.Pst.Export

Creates new, empty Outlook data files. CreateEmpty writes a fresh, structurally-valid PST that mounts in Outlook — ready for a developer to merge other stores into. Never throws.

Methods

MemberTypeSummary
CreateEmpty(String, Boolean, String)clsFileOpResultCreate a new, empty PST at path — an Outlook-mountable store with the standard root hierarchy and no messages. The output is validated (reopened) before the call returns.

Class clsStoreExporter

Bastion.Pst.Export

Bulk export: walk a PST/OST and write every message to its own interchange file, mirroring the folder tree on disk. Never throws — returns a clsExportReport.

Methods

MemberTypeSummary
ExportToFolder(String, String, enmExportFormat, clsDiagnostics, Boolean)clsExportReportExport every message in pstPath to outDir, one file per message under a folder tree that mirrors the store. A failure to export a single item is skipped (counted in clsExportReport.Failed and noted in the user log), not fatal.

Enum enmExportFormat

Bastion.Pst.Export

Interchange formats Bastion PST SDK can export an item to. More are added per the export roadmap (MSG, MHTML, ICS, VCF, MBOX, OFT); EML is the first.

MemberValue
Eml0
Msg1
Ics2
Vcf3
Mhtml4
Mbox5
Oft6

Class clsEmlReader

Bastion.Pst.Import

Parses an RFC 5322 / MIME message (.eml) into a clsImportedMessage. Clean-room from the public RFCs; validates the input and never throws.

Methods

MemberTypeSummary
Parse(Byte[])clsImportResultParse raw .eml bytes into a message model.
ParseFile(String)clsImportResultParse an .eml file into a message model.

Class clsIcsReader

Bastion.Pst.Import

Reads an iCalendar (.ics) file: yields each VEVENT as an appointment-kind clsImportedMessage. Malformed events are skipped; never throws on content.

Methods

MemberTypeSummary
Parse(String)List(Of clsImportedMessage)Parse iCalendar text: one imported appointment per VEVENT.
ReadFile(String)IEnumerable(Of clsImportedMessage)Enumerate every VEVENT in an .ics file as an imported appointment.

Class clsImportedAddress

Bastion.Pst.Import

A name/email pair parsed from an address header.

Properties

MemberTypeSummary
EmailString
NameString

Methods

MemberTypeSummary
ToString()String

Class clsImportedAppointment

Bastion.Pst.Import

Appointment fields parsed from an iCalendar VEVENT (RFC 5545). Attendees ride in the owning message's To list; SUMMARY/DESCRIPTION in Subject/BodyText.

Properties

MemberTypeSummary
BusyStatusInt32olBusyStatus: 0 free, 1 tentative, 2 busy, 3 out of office.
EndTimeNullable(Of DateTime)
IsAllDayBooleanTrue for a VALUE=DATE (all-day) event.
LocationString
RecurrenceclsImportedRecurrenceRecurrence pattern, or Nothing for a single-instance appointment.
ReminderMinutesBeforeStartInt32
ReminderSetBoolean
StartTimeNullable(Of DateTime)
UidStringThe event's UID, also mirrored into the message's MessageId.

Class clsImportedAttachment

Bastion.Pst.Import

An attachment parsed from an imported message.

Properties

MemberTypeSummary
ContentIdString
DataByte[]
EmbeddedMessageclsImportedMessageSet when the attachment is itself a message (message/rfc822 part in EML, nested .msg storage in MSG): the parsed message. The importer writes it into the PST as a true embedded-message attachment (afEmbeddedMessage). Data keeps the raw bytes (when the source had them) purely as a fallback.
FileNameString
IsContactPhotoBooleanTrue for a contact's photo: written with PidTagAttachmentContactPhoto so Outlook renders it as the contact picture rather than a normal file attachment.
IsInlineBoolean
MimeTypeString

Class clsImportedContact

Bastion.Pst.Import

Contact fields parsed from a vCard (RFC 6350 / 2425; 2.1–4.0 tolerated).

Properties

MemberTypeSummary
AnniversaryNullable(Of DateTime)
BirthdayNullable(Of DateTime)
BusinessAddressStringSingle-string postal addresses (the reader joins ADR components).
BusinessFaxString
BusinessTelephoneString
CompanyNameString
DepartmentString
DisplayNameString
Email1AddressString
Email2AddressString
Email3AddressString
GivenNameString
HomeAddressString
HomeTelephoneString
JobTitleString
MiddleNameString
MobileTelephoneString
SurnameString
WebPageString

Class clsImportedDistList

Bastion.Pst.Import

Distribution-list fields ([MS-OXOABK]): the list name and its one-off members.

Properties

MemberTypeSummary
MembersList(Of clsImportedDistListMember)
NameString

Class clsImportedDistListMember

Bastion.Pst.Import

A distribution-list member (a one-off recipient: display name + SMTP address).

Properties

MemberTypeSummary
DisplayNameString
EmailString

Class clsImportedJournal

Bastion.Pst.Import

Journal-entry fields ([MS-OXOJRNL]).

Properties

MemberTypeSummary
DurationMinutesInt32
EndTimeNullable(Of DateTime)
JournalTypeStringFree-text activity type, e.g. "Phone call", "E-mail Message".
StartTimeNullable(Of DateTime)

Class clsImportedMessage

Bastion.Pst.Import

A message read from an interchange file (EML, MSG, MBOX). This is the neutral, fully-materialised model the import readers produce — inspect it, or hand it to an exporter to convert between formats.

Properties

MemberTypeSummary
AppointmentclsImportedAppointmentAppointment payload when Kind is Appointment, else Nothing.
AttachmentsList(Of clsImportedAttachment)
BccList(Of clsImportedAddress)
BodyHtmlString
BodyTextString
CcList(Of clsImportedAddress)
ContactclsImportedContactContact payload when Kind is Contact, else Nothing.
DateNullable(Of DateTime)
DistListclsImportedDistListDistribution-list payload when Kind is DistributionList, else Nothing.
FromEmailString
FromNameString
HeadersList(Of KeyValuePair(Of String, String))All raw header name/value pairs, in order.
ImportanceInt32PidTagImportance: 0 low, 1 normal (default), 2 high.
IsReadBooleanWhether the item is marked read (MSGFLAG_READ). Defaults True, which is what an imported item has always been; set False to author an unread item.
JournalclsImportedJournalJournal payload when Kind is Journal, else Nothing.
KindenmImportedItemKindItem kind: Mail (default), or Appointment / Contact from the ICS / VCF readers. The importer composes the matching Outlook item class.
MessageIdString
NoteclsImportedNoteNote payload when Kind is Note, else Nothing.
RssclsImportedRssRSS-post payload when Kind is Rss, else Nothing.
SourceFolderStringThe folder path this item came from (set by readers that carry a folder tree, e.g. the OLM reader). Empty for flat interchange files. Used to place the item on import.
SubjectString
TaskclsImportedTaskTask payload when Kind is Task, else Nothing.
ToList(Of clsImportedAddress)

Methods

MemberTypeSummary
ToString()String

Class clsImportedNote

Bastion.Pst.Import

Sticky-note fields ([MS-OXONOTE]). The note text is the message BodyText. Color: 0 blue,1 green,2 pink,3 yellow (default),4 white.

Properties

MemberTypeSummary
ColorInt32
HeightInt32
WidthInt32

Class clsImportedOccurrenceOverride

Bastion.Pst.Import

An override of ONE occurrence of a recurring series ([MS-OXOCAL] ExceptionInfo / ExtendedException): the occurrence generated on OriginalDate is moved and/or re-titled. Members left unset inherit the series value.

Properties

MemberTypeSummary
NewBusyStatusNullable(Of Int32)New olBusyStatus (0 free, 1 tentative, 2 busy, 3 OOF). Nothing = unchanged.
NewEndNullable(Of DateTime)New end. Nothing = NewStart + the series duration.
NewLocationStringNew location for this occurrence only. Nothing = unchanged.
NewStartNullable(Of DateTime)New start (date + time). Nothing = same day at the series start time.
NewSubjectStringNew subject for this occurrence only. Nothing = unchanged.
OriginalDateDateTimeThe day of the generated occurrence being overridden (time is ignored).

Class clsImportedRecurrence

Bastion.Pst.Import

Recurrence pattern for an authored appointment ([MS-OXOCAL]). Supports daily (every N days), weekly (on selected weekdays) and monthly (on a day-of-month) series, ending after a count, on a date, or never.

Properties

MemberTypeSummary
DayOfMonthInt32For Monthly: day of the month (1–31; 31 = last day).
DaysOfWeekenmRecurDaysFor Weekly: which weekdays the occurrence falls on.
EndDateNullable(Of DateTime)End on/after this date (Nothing = count-based or never-ending).
ExcludedDatesList(Of DateTime)Occurrence dates to DELETE from the series (e.g. skip a holiday). Each must fall on a date the pattern would otherwise generate; the day is what matters (time is ignored).
FrequencyenmRecurFrequency
IntervalInt32Interval: every N days / weeks / months.
OccurrenceCountInt32End after this many occurrences (0 = use EndDate or run forever).
OverriddenOccurrencesList(Of clsImportedOccurrenceOverride)Single occurrences to OVERRIDE (move / re-title) — [MS-OXOCAL] exceptions. Each must target a date the pattern generates and not one in ExcludedDates.

Class clsImportedRss

Bastion.Pst.Import

RSS post fields ([MS-OXOPOST] / PSETID_PostRss). The article body is the message BodyText/BodyHtml; the channel is the feed the item arrived from.

Properties

MemberTypeSummary
ChannelStringThe channel's display name (PidLidPostRssChannel).
ChannelUrlStringThe feed's channel URL (PidLidPostRssChannelLink).
ItemGuidStringThe article's feed-unique id (PidLidPostRssItemGuid).
ItemUrlStringThe article's own URL (PidLidPostRssItemLink).

Class clsImportedTask

Bastion.Pst.Import

Task fields ([MS-OXOTASK]). Status 0=not started,1=in progress,2=complete, 3=waiting,4=deferred; PercentComplete is 0..1.

Properties

MemberTypeSummary
DateCompletedNullable(Of DateTime)
DueDateNullable(Of DateTime)
OwnerString
PercentCompleteDouble
StartDateNullable(Of DateTime)
StatusInt32

Class clsImportReport

Bastion.Pst.Import

Result of an import run: the standard error contract plus counts. A per-file parse failure does not fail the run — it is recorded in Failures and the remaining files import normally; the run fails only when NO message could be imported.

Properties

MemberTypeSummary
FailuresList(Of String)One line per failed input file: "path: reason".
FilesFailedInt32Input files that could not be parsed (see Failures).
FilesReadInt32Input files successfully parsed.
FoldersCreatedInt32Folders created to satisfy the target folder path.
MessagesImportedInt32Messages written into the output store.

Methods

MemberTypeSummary
ToString()String

Class clsImportResult

Bastion.Pst.Import

Result of importing a single message: the standard error contract plus the message.

Properties

MemberTypeSummary
MessageclsImportedMessage

Class clsMboxReader

Bastion.Pst.Import

Reads a Unix mbox file: yields each message as a clsImportedMessage by splitting on "From " separator lines and parsing each block as MIME. Never throws.

Methods

MemberTypeSummary
ReadFile(String)IEnumerable(Of clsImportedMessage)Enumerate every message in an mbox file. Malformed blocks are skipped.

Class clsMsgReader

Bastion.Pst.Import

Parses an Outlook .msg file ([MS-OXMSG] over [MS-CFB]) into a clsImportedMessage. Clean-room; validates the container and never throws.

Methods

MemberTypeSummary
Parse(Byte[])clsImportResultParse raw .msg bytes into a message model.
ParseFile(String)clsImportResultParse a .msg file into a message model.

Class clsOlmReader

Bastion.Pst.Import

Reads Outlook-for-Mac .olm archives (mail items, with folder placement) into the neutral import model. Contacts/calendar/etc. live in separate consolidated XML files and are a later increment; this reads the per-message mail (message_NNNNN.xml).

Methods

MemberTypeSummary
ReadFile(String)IEnumerable(Of clsImportedMessage)Every mail message in the .olm, each with its clsImportedMessage.SourceFolder set from the archive path. Never throws — returns what parsed.

Class clsStoreImporter

Bastion.Pst.Import

Imports EML / MSG / MBOX / ICS / VCF files into a folder of a PST. pstPath is the store to import into (its content is preserved); pass Nothing/empty to start from a new, empty store. The result is always written to outPath — the input store is never modified. Never throws.

Methods

MemberTypeSummary
CloneStructure(String, String, clsDiagnostics, Boolean, String)clsImportReportCreate a new, EMPTY Unicode PST at outPath that mirrors the folder hierarchy of sourcePath (no messages copied). Returns a clsImportReport whose FoldersCreated counts the folders made.
CreateFolders(String, String, String, clsDiagnostics, Boolean, String)clsImportReportCreate an (empty) folder path in pstPath (or a fresh store when empty), writing the result to outPath. Folders already present are left as they are. Returns a clsImportReport whose FoldersCreated counts new folders.
ImportFile(String, String, String, String, clsDiagnostics, Boolean, String)clsImportReportImport one file by extension: .msg, .mbox, .ics (appointments), .vcf (contacts); anything else parses as EML/MIME. storeName (optional) sets the name Outlook shows for the store — use it when creating a new store (pstPath = Nothing) to give it a meaningful title.
ImportFiles(String, String, IEnumerable(Of String), String, clsDiagnostics, Boolean, String)clsImportReportImport a set of EML / MSG / MBOX / ICS / VCF files into folderPath (created if missing; "/" separates subfolders, relative to the IPM root). storeName (optional) sets the store's display name shown in Outlook.
ImportMbox(String, String, String, String, clsDiagnostics, Boolean, String)clsImportReportImport every message of an mbox file into folderPath. storeName (optional) sets the store's display name shown in Outlook.
ImportMessages(String, String, IEnumerable(Of clsImportedMessage), String, clsDiagnostics, Boolean, String)clsImportReportImport already-parsed messages (e.g. built or edited in code) into folderPath of the store. storeName (optional) sets the store's display name shown in Outlook.
ImportMessagesByFolder(String, String, IEnumerable(Of clsImportedMessage), String, IEnumerable(Of String), clsDiagnostics, Boolean, String)clsImportReportImport already-parsed messages into MANY folders in a SINGLE atomic rewrite. Each message is placed by its clsImportedMessage.SourceFolder, falling back to defaultFolder when that is empty; extraFolders names folders to create that receive no items. Equivalent to one ImportMessages call per folder, except the store is read and rewritten once rather than once per folder — the difference between O(S) and O(S·k) I/O when populating a store with many folders.
ImportOlm(String, String, clsDiagnostics, Boolean, String)clsImportReportImport a Microsoft Outlook for Mac .olm archive into a new PST at outPath, preserving the OLM folder hierarchy. Returns a clsImportReport (ErrorCode 0 = success).

Class clsTnefReader

Bastion.Pst.Import

Decodes TNEF (winmail.dat) streams into real attachments ([MS-OXTNEF]). Tolerant of damage; never throws into the caller.

Methods

MemberTypeSummary
IsTnef(Byte[])BooleanTrue when the buffer starts with the TNEF signature.
Parse(Byte[])clsTnefResultDecode a TNEF stream. Damage is tolerated: unreadable tails are dropped, checksum mismatches are counted, and everything recoverable is returned.
ParseFile(String)clsTnefResultDecode a TNEF file (e.g. a saved winmail.dat).

Fields

MemberTypeSummary
SignatureUInt32The TNEF stream signature ([MS-OXTNEF] 2.1.3.1).

Class clsTnefResult

Bastion.Pst.Import

Result of decoding a TNEF stream: the standard error contract, the recovered attachments and bodies, plus damage counters (bad checksums / truncated attributes).

Properties

MemberTypeSummary
AttachmentsList(Of clsImportedAttachment)The real attachments recovered from the TNEF stream.
AttributesReadInt32Attributes successfully walked.
BodyHtmlStringHTML body carried in the MAPI property list (PidTagBodyHtml), if any.
BodyTextStringPlain-text body carried in attBody, if any.
ChecksumErrorsInt32Attributes whose 16-bit checksum did not match (content still used).
TruncatedBooleanTrue when the stream ended mid-attribute (truncated file); everything recovered up to that point is still returned.

Methods

MemberTypeSummary
ToString()String

Class clsVcfReader

Bastion.Pst.Import

Reads a vCard (.vcf) file: yields each card as a contact-kind clsImportedMessage. Malformed cards are skipped; never throws on content.

Methods

MemberTypeSummary
Parse(String)List(Of clsImportedMessage)Parse vCard text: one imported contact per BEGIN:VCARD…END:VCARD block.
ReadFile(String)IEnumerable(Of clsImportedMessage)Enumerate every vCard in a .vcf file as an imported contact.

Enum enmImportedItemKind

Bastion.Pst.Import

What kind of Outlook item an imported file parsed into. Mail is the default; Appointment/Contact are produced by the ICS and VCF readers (Import Phase 4); Task/Note are produced by the authoring API (Phase 2.3) — no interchange reader emits them.

MemberValue
Mail0
Appointment1
Contact2
Task3
Note4
Journal5
DistributionList6
Rss7

Enum enmRecurDays

Bastion.Pst.Import

Days-of-week bitmask for a weekly recurrence (matches [MS-OXOCAL] PatternTypeWeek).

MemberValue
None0
Sunday1
Monday2
Tuesday4
Wednesday8
Thursday16
Friday32
Saturday64

Enum enmRecurFrequency

Bastion.Pst.Import

Recurrence frequency for an authored appointment.

MemberValue
None0
Daily1
Weekly2
Monthly3

Class clsAppointmentItem

Bastion.Pst.Messaging

Properties

MemberTypeSummary
AllAttendeesString
AttendeesIReadOnlyList(Of clsRecipient)
BusyStatusenmBusyStatus
DurationMinutesInt32
EndTimeNullable(Of DateTime)
IsAllDayBoolean
IsRecurringBoolean
LocationString
ReminderMinutesBeforeStartInt32
ReminderSetBoolean
StartTimeNullable(Of DateTime)

Class clsAttachment

Bastion.Pst.Messaging

Properties

MemberTypeSummary
ContentIdStringContent id of an attachment referenced from the HTML body as cid:… — an inline image. A host rendering the HTML needs this to resolve those references, and a reading pane should list such an attachment in the body rather than as a paperclip. Empty for an ordinary attached file.
FileNameStringBest file name: long name, then short (8.3) name, then display name.
IsEmbeddedMessageBoolean
MethodenmAttachMethod
MimeTagString
SizeInt32

Methods

MemberTypeSummary
GetData()Byte[]The attachment's bytes for afByValue attachments (PidTagAttachDataBinary); Nothing for by-reference or embedded-message attachments.
GetEmbeddedMessage()clsMessageThe embedded message of an afEmbeddedMessage attachment, or Nothing. PidTagAttachDataObject holds an 8-byte {subnode NID, size} record ([MS-PST] 2.4.6.2); the message lives in that subnode of the attachment's own subtree, complete with its recipient/attachment tables (which may nest further embedded messages).
ToString()String

Class clsContactItem

Bastion.Pst.Messaging

Properties

MemberTypeSummary
AnniversaryNullable(Of DateTime)
BirthdayNullable(Of DateTime)
BusinessAddressString
BusinessFaxString
BusinessTelephoneString
CompanyNameString
DepartmentString
DisplayNameString
Email1AddressString
Email1DisplayNameString
Email2AddressString
Email3AddressString
FileUnderString
GivenNameString
HomeAddressString
HomeTelephoneString
JobTitleString
MiddleNameString
MobileTelephoneString
NicknameString
OfficeLocationString
SurnameString
WebPageString

Class clsDistListMember

Bastion.Pst.Messaging

One member of a distribution list: who they are, and where mail to them goes.

Properties

MemberTypeSummary
AddressString
AddressTypeString
DisplayNameString

Methods

MemberTypeSummary
ToString()StringWhat a list shows: the name, falling back to the address.

Class clsDistributionListItem

Bastion.Pst.Messaging

Properties

MemberTypeSummary
MemberCountInt32Member count from PidLidDistributionListMembers — a PtypMultipleBinary whose value begins with a 4-byte element count ([MS-OXCDATA] 2.11.1.5). 0 if absent.
MembersList(Of clsDistListMember)Who is actually in the list. Without this a group is only ever a name and a number, which is why hosts drew it as an empty contact card — there was nothing else to show. Each member is a One-Off EntryID ([MS-OXCDATA] 2.2.5.1) inside the multi-valued binary; an unreadable element is skipped rather than taking the whole list down.
NameString

Class clsFolder

Bastion.Pst.Messaging

Properties

MemberTypeSummary
ContainerClassStringThe folder's container class (PidTagContainerClass) — "IPF.Note", "IPF.Appointment", "IPF.Contact", "IPF.Task", "IPF.StickyNote", "IPF.Journal", … — how a host app tells a calendar folder from a mail folder. Empty when unset (an unset class is conventionally treated as mail).
ContentCountInt32
DisplayNameString
HasSubfoldersBoolean
UnreadableTableBooleanTrue when a table read for this folder failed and was tolerated — the folder reported fewer rows (possibly none) than it really holds. Tolerating a broken table keeps a damaged store walkable, but it makes "this folder is empty" and "this folder could not be read" look identical to a caller, which turns corruption into plausible-looking data. A host that lists folders should check this (or compare against ContentCount) and tell the user the folder is damaged rather than silently showing it as empty.
UnreadCountInt32

Methods

MemberTypeSummary
AssociatedMessages()IEnumerable(Of clsMessage)Folder-associated (FAI) messages, from the FAI Contents Table.
Messages()IEnumerable(Of clsMessage)Messages, from the Contents Table (empty if none). Lazy — each opens on access.
OpenMessage(UInt32)clsMessageReopen a message in this store by its clsMessage.NodeId — the host pattern for resolving full detail (body, recipients, attachments) of a row it listed earlier without holding the whole message graph in memory.
Search(clsMessageQuery)IEnumerable(Of clsSearchHit)Search this folder and its subfolders for messages matching query.
SubFolders()IEnumerable(Of clsFolder)Subfolders, from the Hierarchy Table (empty if none).
ToString()String

Class clsFolderClass

Bastion.Pst.Messaging

The standard PidTagContainerClass values — what tells a host application that a folder holds appointments rather than mail. Pass one of these to clsAuthor.SetFolderClass, or compare against clsFolder.ContainerClass.

Fields

MemberTypeSummary
AppointmentStringCalendar items — "IPF.Appointment".
BirthdayStringThe birthday calendar Outlook maintains — "IPF.Appointment.Birthday".
ContactStringContacts — "IPF.Contact".
HomepageStringA folder homepage — "IPF.Note.OutlookHomepage".
JournalStringJournal entries — "IPF.Journal".
MailStringMail and post items — "IPF.Note". The default for a new folder.
StickyNoteStringSticky notes — "IPF.StickyNote".
TaskStringTasks — "IPF.Task".

Class clsItem

Bastion.Pst.Messaging

Properties

MemberTypeSummary
BodyString
CreationTimeNullable(Of DateTime)
KindenmItemKind
LastModificationTimeNullable(Of DateTime)
MessageclsMessage
MessageClassString
SubjectString

Methods

MemberTypeSummary
Wrap(clsMessage)itfItemWrap a message in the typed interface matching its PidTagMessageClass. Always returns a usable item; an unrecognised class is projected as a mail item (the MAPI base item class).

Class clsJournalItem

Bastion.Pst.Messaging

Properties

MemberTypeSummary
DurationMinutesInt32
EndTimeNullable(Of DateTime)
JournalTypeString
StartTimeNullable(Of DateTime)

Class clsMailItem

Bastion.Pst.Messaging

Properties

MemberTypeSummary
AttachmentsIReadOnlyList(Of clsAttachment)
BodyHtmlStringThe message's HTML body, or "" when it has none. PidTagBodyHtml is usually stored as BINARY, not as a string — so reading it as a string returned nothing for almost every HTML message ever sent, and hosts fell back to showing the plain-text alternative (a wall of bracketed URLs) as if the message had no HTML at all. The bytes are decoded with the message's own code page (PidTagInternetCodepage), falling back to the charset declared in the HTML itself, then to Windows-1252 — the encoding most untagged mail actually uses.
ConversationTopicString
DeliveryTimeNullable(Of DateTime)
DisplayBccString
DisplayCcString
DisplayToString
HasAttachmentsBoolean
ImportanceenmImportance
InternetMessageIdString
IsHtmlFormatBooleanTrue when this message is an HTML message. Checks the body form the message declares (PidTagNativeBody) and, failing that, whether an HTML body is actually present — a host should not have to fetch and measure the body just to decide how to render it.
IsUnreadBoolean
RecipientsIReadOnlyList(Of clsRecipient)
SenderEmailString
SenderNameString
SensitivityenmSensitivity
SubmitTimeNullable(Of DateTime)

Class clsMessage

Bastion.Pst.Messaging

Properties

MemberTypeSummary
AppointmentEndNullable(Of DateTime)Appointment end (PidLidAppointmentEndWhole), for calendar items.
AppointmentStartNullable(Of DateTime)Appointment start (PidLidAppointmentStartWhole), for calendar items.
AttachmentsIReadOnlyList(Of clsAttachment)The message attachments, from the Attachment Table subnode (empty if none).
BodyString
ContactEmailStringPrimary email address (PidLidEmail1EmailAddress), for contact items.
DeliveryTimeNullable(Of DateTime)
HasAttachmentsBoolean
ImportanceInt32PidTagImportance (0=low, 1=normal, 2=high); 1 if unset.
InternetMessageIdStringPidTagInternetMessageId.
IsUnreadBooleanTrue when the message is unread (MSGFLAG_READ not set).
KindenmItemKind
LocationStringAppointment / meeting location (PidLidLocation).
MessageClassString
MessageFlagsInt32PidTagMessageFlags.
NodeIdUInt32The message's stable node identifier within its open store — the value a host application uses to correlate a list row with the message and to reopen it for detail (see clsFolder.OpenMessage). Stable for the lifetime of the open store.
RecipientsIReadOnlyList(Of clsRecipient)The message recipients (To/Cc/Bcc), from the Recipient Table subnode.
SenderEmailString
SenderNameString
SizeInt32PidTagMessageSize.
SubjectStringThe subject, with any stored prefix marker removed.
SubmitTimeNullable(Of DateTime)

Methods

MemberTypeSummary
AsItem()itfItemProject this message as the typed item interface matching its message class (mail, appointment, contact, task, note, journal, distribution list). Read-only and allocation-cheap — wrap, read, discard, per message.
ToString()String

Class clsMessageQuery

Bastion.Pst.Messaging

Methods

MemberTypeSummary
HasClass(String)clsMessageQueryMessage class starts with prefix (e.g. "IPM.Note").
HasMessageId(String)clsMessageQuery
ImportanceAtLeast(Int32)clsMessageQuery
LargerThan(Int32)clsMessageQuery
Matches(clsMessage)BooleanTrue when the message satisfies every criterion (empty query matches all).
OfKind(enmItemKind)clsMessageQuery
RecipientContains(String)clsMessageQuery
SenderContains(String)clsMessageQuery
SubjectContains(String)clsMessageQuery
Unread()clsMessageQuery
Where(Func(Of clsMessage, Boolean))clsMessageQueryAdd an arbitrary criterion.
WithAttachments(Boolean)clsMessageQuery

Class clsNoteItem

Bastion.Pst.Messaging

Properties

MemberTypeSummary
ColorInt32
HeightInt32
WidthInt32

Class clsOpenResult

Bastion.Pst.Messaging

Result of clsPersonalStorage.TryOpen: ErrorCode 0 = opened, Store usable.

Properties

MemberTypeSummary
StoreclsPersonalStorageThe opened store (Nothing on failure). Dispose it when done.

Class clsPersonalStorage

Bastion.Pst.Messaging

Properties

MemberTypeSummary
DisplayNameStringThe store's display name (PidTagDisplayName on the message store).
FormatenmPstFormat
HasPasswordBooleanTrue when the file has an open password set (PidTagPstPassword present and non-zero on the message store). A PST password is a stored CRC hash, not encryption — the library reads the file regardless. Set or clear it with Convert.clsStorePassword.SetPassword / RemovePassword.
IsUnicodeBoolean
RootFolderclsFolderThe root Folder object (NID 0x122). Its subfolders include the IPM subtree.
SourcePathStringThe file this store was opened from, or "" when it was opened from a stream. A host with several stores open needs to know which one an item came from, and every row the view layer produces carries it.
SupportsWritingBooleanAlways False for now — this build reads but does not write.
ViewsclsStoreViewsThe store's sections — mail, calendar, contacts, tasks, notes — as bindable rows: store.Views.Contacts(), store.Views.Mail(query). This is what a host binds a grid or a card view to; see clsItemQuery for filtering that avoids opening items it can rule out from the folder's contents table.

Methods

MemberTypeSummary
Dispose()Void
IsAnsi(String)BooleanTrue if strPath is a legacy ANSI PST — i.e. adding to or editing it would upgrade it to Unicode (see the AnsiUpgradeRequired gate). Cheap header-only check.
Open(Stream, Boolean)clsPersonalStorageOpen a PST/OST file for reading.
Open(String)clsPersonalStorageOpen a PST/OST file for reading.
PeekFormat(String)enmPstFormatCheap pre-flight: report a file's PST format by reading ONLY its header (no full parse, no lock held). Returns enmPstFormat.Ansi / enmPstFormat.Unicode, or enmPstFormat.Unknown if the file is missing, unreadable, or not a PST/OST. Use before a write to warn that adding to an ANSI store will upgrade it to Unicode.
Search(clsMessageQuery)IEnumerable(Of clsSearchHit)Search the whole store for messages matching query.
TryOpen(String)clsOpenResultOpen a PST/OST for reading WITHOUT throwing: the result carries ErrorCode (0 = opened and .Store usable; otherwise FileNotFound / FileLocked (mounted in Outlook?) / InvalidPstFormat / … with a human-readable ErrorDescription). Dispose the Store when done.

Class clsRecipient

Bastion.Pst.Messaging

Properties

MemberTypeSummary
AddressStringThe best available address for display (SMTP if present, else the email address).
AddressTypeString
DisplayNameString
EmailAddressString
RecipientTypeenmRecipientType
SmtpAddressString

Methods

MemberTypeSummary
ToString()String

Class clsSearch

Bastion.Pst.Messaging

Walks a folder subtree applying a clsMessageQuery.

Methods

MemberTypeSummary
Find(clsPersonalStorage, clsMessageQuery)IEnumerable(Of clsSearchHit)Search the whole store.
Walk(clsFolder, String, clsMessageQuery)IEnumerable(Of clsSearchHit)Search a folder subtree.

Class clsSearchHit

Bastion.Pst.Messaging

A search result: the message and the folder path it was found in.

Properties

MemberTypeSummary
FolderPathString
MessageclsMessage

Class clsTaskItem

Bastion.Pst.Messaging

Properties

MemberTypeSummary
DateCompletedNullable(Of DateTime)
DueDateNullable(Of DateTime)
ImportanceenmImportance
IsCompleteBoolean
OwnerString
PercentCompleteDouble
StartDateNullable(Of DateTime)
StatusenmTaskStatus

Enum enmAttachMethod

Bastion.Pst.Messaging

PidTagAttachMethod (0x3705) — how an attachment's data is stored.

MemberValue
None0
ByValue1
ByReference2
ByReferenceResolve3
ByReferenceOnly4
EmbeddedMessage5
Storage6

Enum enmBusyStatus

Bastion.Pst.Messaging

PidLidBusyStatus (PSETID_Appointment 0x8205) — free/busy for an appointment.

MemberValue
Free0
Tentative1
Busy2
OutOfOffice3
WorkingElsewhere4

Enum enmImportance

Bastion.Pst.Messaging

PidTagImportance (0x0017).

MemberValue
Low0
Normal1
High2

Enum enmItemKind

Bastion.Pst.Messaging

The kind of MAPI item, derived from PidTagMessageClass.

MemberValue
Unknown0
Mail1
Appointment2
Contact3
Task4
Note5
Journal6
DistributionList7

Enum enmRecipientType

Bastion.Pst.Messaging

PidTagRecipientType (0x0C15) — the role of a recipient on a message.

MemberValue
Originator0
To1
Cc2
Bcc3

Enum enmSensitivity

Bastion.Pst.Messaging

PidTagSensitivity (0x0036).

MemberValue
Normal0
Personal1
Private22
Confidential3

Enum enmTaskStatus

Bastion.Pst.Messaging

PidLidTaskStatus (PSETID_Task 0x8101).

MemberValue
NotStarted0
InProgress1
Complete2
Waiting3
Deferred4

Interface itfAppointmentItem

Bastion.Pst.Messaging

Appointment / meeting — [MS-OXOCAL].

Properties

MemberTypeSummary
AllAttendeesString
AttendeesIReadOnlyList(Of clsRecipient)Meeting attendees (the message recipients).
BusyStatusenmBusyStatus
DurationMinutesInt32
EndTimeNullable(Of DateTime)
IsAllDayBoolean
IsRecurringBoolean
LocationString
ReminderMinutesBeforeStartInt32
ReminderSetBoolean
StartTimeNullable(Of DateTime)

Interface itfContactItem

Bastion.Pst.Messaging

Contact — [MS-OXOCNTC].

Properties

MemberTypeSummary
AnniversaryNullable(Of DateTime)
BirthdayNullable(Of DateTime)
BusinessAddressStringComposed mailing (business) address.
BusinessFaxString
BusinessTelephoneString
CompanyNameString
DepartmentString
DisplayNameString
Email1AddressString
Email1DisplayNameString
Email2AddressString
Email3AddressString
FileUnderString
GivenNameString
HomeAddressStringComposed home address.
HomeTelephoneString
JobTitleString
MiddleNameString
MobileTelephoneString
NicknameString
OfficeLocationString
SurnameString
WebPageString

Interface itfDistributionListItem

Bastion.Pst.Messaging

Distribution list — [MS-OXOABK] / [MS-OXODLGT].

Properties

MemberTypeSummary
MemberCountInt32Number of members (count from PidLidDistributionListMembers); 0 if absent.
MembersList(Of clsDistListMember)The members themselves — name and address apiece. Empty when the list carries no readable member property.
NameString

Interface itfItem

Bastion.Pst.Messaging

Common projection shared by every typed item.

Properties

MemberTypeSummary
BodyStringPlain-text body (PidTagBody).
CreationTimeNullable(Of DateTime)
KindenmItemKindItem kind from PidTagMessageClass.
LastModificationTimeNullable(Of DateTime)
MessageclsMessageThe underlying raw message (escape hatch to the full PC / named props).
MessageClassStringPidTagMessageClass verbatim (e.g. "IPM.Appointment").
SubjectString

Interface itfJournalItem

Bastion.Pst.Messaging

Journal entry — [MS-OXOJRNL].

Properties

MemberTypeSummary
DurationMinutesInt32
EndTimeNullable(Of DateTime)
JournalTypeString
StartTimeNullable(Of DateTime)

Interface itfMailItem

Bastion.Pst.Messaging

Mail message — [MS-OXOMSG].

Properties

MemberTypeSummary
AttachmentsIReadOnlyList(Of clsAttachment)
BodyHtmlString
ConversationTopicString
DeliveryTimeNullable(Of DateTime)
DisplayBccString
DisplayCcString
DisplayToString
HasAttachmentsBoolean
ImportanceenmImportance
InternetMessageIdString
IsHtmlFormatBooleanTrue when the message was written as HTML — i.e. it should be rendered as HTML rather than shown as text. False for a plain-text message.
IsUnreadBoolean
RecipientsIReadOnlyList(Of clsRecipient)
SenderEmailString
SenderNameString
SensitivityenmSensitivity
SubmitTimeNullable(Of DateTime)

Interface itfNoteItem

Bastion.Pst.Messaging

Sticky note — [MS-OXONOTE]. The note text is itfItem.Body.

Properties

MemberTypeSummary
ColorInt32PidLidNoteColor (0=blue,1=green,2=pink,3=yellow,4=white).
HeightInt32
WidthInt32

Interface itfTaskItem

Bastion.Pst.Messaging

Task — [MS-OXOTASK].

Properties

MemberTypeSummary
DateCompletedNullable(Of DateTime)
DueDateNullable(Of DateTime)
ImportanceenmImportance
IsCompleteBoolean
OwnerString
PercentCompleteDoubleFraction complete, 0.0 .. 1.0.
StartDateNullable(Of DateTime)
StatusenmTaskStatus

Enum enmPstFormat

Bastion.Pst.Ndb

On-disk PST/OST format variant. The NDB layer is the only layer that distinguishes these; everything above it is format-agnostic ([MS-PST] 2.2.2.6).

MemberValue
Unknown0
Ansi1
Unicode2

Class clsPstRepair

Bastion.Pst.Repair

Methods

MemberTypeSummary
Repair(String, String, clsDiagnostics, Boolean, Boolean, Int64)clsRepairReportRepair srcPath into a fresh, structurally-canonical Unicode PST at outPath. Pass a diag with clsDiagnostics.RecoverFromCorruption set to skip individual nodes that fail to read/serialise instead of aborting. Returns before/after validation and the node count.

Class clsRepairReport

Bastion.Pst.Repair

Outcome of a clsPstRepair.Repair: the node count plus the strict validation of the source (before) and the rebuilt output (after).

Properties

MemberTypeSummary
NodesWrittenInt32
OrphansRecoveredInt32Messages that had lost their folder and were re-homed into a "Lost and Found" folder.
OutputIsValidBooleanTrue when the repaired output passes strict structural validation.
OutputPathString
OutputReportclsValidationReportValidation of the rebuilt output.
PasswordStrippedBooleanTrue if the password property was cleared on the output.
SourcePathString
SourceReportclsValidationReportValidation of the source as supplied (Nothing if it could not be validated at all).
SourceWasValidBoolean
SubtreeReconnectedBooleanTrue when the mailbox's folder tree was reconnected to the store root (its root link had been destroyed, orphaning every folder).
SucceededBooleanWhether the repair completed (ErrorCode 0) AND produced an Outlook-acceptable file.

Class clsHealthResult

Bastion.Pst.Validation

Outcome of clsPstHealth.Check. ErrorCode is 0 when the check itself ran — a corrupt-but-examinable file is ErrorCode 0 with IsCorrupted=True; ErrorCode is non-zero only when the file could not be examined at all.

Properties

MemberTypeSummary
ErrorCountInt32
FatalErrorStringSet when validation itself failed hard (unreadable header etc.).
IsCorruptedBooleanTrue when the file has structural errors (or could not be read at all).
PathString
ValidationclsValidationReportThe full validator report (Nothing when the file could not even be opened).

Methods

MemberTypeSummary
RepairTo(String, clsDiagnostics)clsRepairReportRepair this file to outPath (recovery mode: damaged items are skipped, everything readable is rebuilt into a structurally-canonical PST).
SaveSupportReport(String, String, String)clsFileOpResultWrite the developer support report (structural metadata only — no message content). outPath names the target file; when empty the report goes to the user's temp folder (or directory when supplied) as BastionPstSdk-SupportReport-<file>-<stamp>.txt. NEVER throws: check the returned result's ErrorCode (0 = written, .Path = where).

Class clsPstHealth

Bastion.Pst.Validation

One-call health check for a PST/OST: full structural validation surfaced as a simple corrupted/clean verdict, plus support-report generation and a repair shortcut.

Methods

MemberTypeSummary
Check(String, clsDiagnostics, Boolean)clsHealthResultValidate path structurally (pages, blocks, CRCs, B-trees, maps). Never throws on a corrupt file — an unreadable file reports as corrupted with the failure captured as an issue. When diag is supplied, corruption is also surfaced through it (event + logs + auto support report per its settings).

Class clsPstValidator

Bastion.Pst.Validation

Methods

MemberTypeSummary
Validate(Stream)clsValidationReportValidate the PST at path; never throws — a malformed file, and a missing / locked / unreadable one, are reported as issues (IsValid = False), not exceptions.
Validate(String)clsValidationReportValidate the PST at path; never throws — a malformed file, and a missing / locked / unreadable one, are reported as issues (IsValid = False), not exceptions.

Class clsValidationIssue

Bastion.Pst.Validation

One structural problem found by clsPstValidator.

Properties

MemberTypeSummary
CategoryString
MessageString
OffsetUInt64
SeverityenmValidationSeverity

Methods

MemberTypeSummary
ToString()String

Class clsValidationReport

Bastion.Pst.Validation

Result of validating a PST: the issues found and how much was checked.

Properties

MemberTypeSummary
BlocksCheckedInt32
ErrorCountInt32
IssuesIReadOnlyList(Of clsValidationIssue)
IsValidBooleanTrue when no errors were found (warnings do not fail the gate).
PagesCheckedInt32
WarningCountInt32

Enum enmValidationSeverity

Bastion.Pst.Validation

MemberValue
Error0
Warning1

Class clsAppointmentView

Bastion.Pst.Views

An appointment as a calendar shows it.

Properties

MemberTypeSummary
AttendeesString
BodyString
BusyStatusenmBusyStatus
DisplayString
EndTimeNullable(Of DateTime)
IsAllDayBoolean
IsRecurringBoolean
LocationString
OccurrenceKeyStringIdentity for spotting the same appointment archived twice: same subject, same start, same length. Used when merging calendars of the same name.
OrganiserString
ReminderSetBoolean
StartTimeNullable(Of DateTime)
SubjectString

Class clsAttachmentView

Bastion.Pst.Views

One attachment, as a reading pane lists it.

Properties

MemberTypeSummary
ContentIdString
FileNameString
IndexInt32
IsEmbeddedMessageBoolean
IsInlineBooleanTrue when the attachment is displayed inside the HTML body rather than listed (an inline image); a reading pane should not show it as a paperclip.
MimeTagString
SizeBytesInt32

Class clsContactMerger

Bastion.Pst.Views

Methods

MemberTypeSummary
Combine(IEnumerable(Of clsContactView))List(Of clsContactView)Combine contact rows that are the same person. Order is preserved: the first entry seen for a person is the one kept and filled in from the rest. The rows passed in are never modified. Folding is done into a copy, so a caller may hold its projected contacts and merge them again — which a host does on every folder or option change — without the second merge folding already-folded rows into themselves and running the "combined from N entries" count away with itself.
CombineByEmail(IEnumerable(Of clsContactView))List(Of clsContactView)Combine contacts that share an e-mail address, whatever their names say. Use with care and never silently: a shared address is not always one person — info@, support@ and a family address are all held by several people, and merging those would fuse unrelated contacts into one. Matching on name (Combine) is the safe default; this is the opt-in. As with Combine, the rows passed in are never modified.

Class clsContactView

Bastion.Pst.Views

A contact as a card view shows it.

Properties

MemberTypeSummary
AllEmailsIEnumerable(Of String)Every e-mail address on the contact, primary first, blanks removed.
AlternateEmailsList(Of String)
AlternatePhonesList(Of String)
AnniversaryNullable(Of DateTime)
BirthdayNullable(Of DateTime)
BusinessAddressString
BusinessFaxString
BusinessPhoneString
CompanyString
DepartmentString
DisplayString
EmailStringThe address to show when there is room for one.
Email1String
Email2String
Email3String
FileUnderString
FullNameString
GivenNameString
HomeAddressString
HomePhoneString
IsDistributionListBooleanTrue when this row is a distribution list (a contact GROUP), not a person. An archive's contacts folder holds both, and a group has none of a person's fields — so a host that does not check this draws a card with every line blank.
JobTitleString
MemberNamesStringThe members as one line, for a card that has room for a line and not a list.
MembersList(Of clsDistListMember)Members of the group, when IsDistributionList is set.
MembershipSummaryStringWhat a group has in place of a job title: how many people are in it. Empty for a person, so a host can bind one label to it either way.
MergedFromInt32
MergedFromMemberCountInt32Member count as the list itself reported it, used when the members could not be decoded but the count could.
MobilePhoneString
NotesString
PhoneStringThe number to show when there is room for one: business, then mobile, then home — the order Outlook prefers on a card.
PhotoByte[]The contact's photo (JPEG/PNG bytes) if the card carries one, else Nothing.
SortKeyStringSurname-first key Outlook files contacts under. A group has no surname, so it files under its own name.
SourceFoldersList(Of String)
SurnameString
WebPageString

Methods

MemberTypeSummary
Clone()clsContactViewAn independent copy of this row. clsContactMerger folds several entries into one by writing into the row it keeps, so it clones first — otherwise it would edit the caller's rows, and a host that holds its projected contacts in memory and re-merges them (on a folder change, say) would fold already-folded rows into themselves over and over.The lists are new lists over the same strings, so appending to a copy's AlternateEmails cannot reach the original. Photo and Members are shared by reference: both are read-only once projected, and copying a photo per merge would be pure waste.

Class clsItemDetail

Bastion.Pst.Views

The full detail of one item, for a reading pane or an item window.

Properties

MemberTypeSummary
AttachmentsList(Of clsAttachmentView)
BodyHtmlStringThe HTML body, or "" when the message has none.
BodyPlainStringThe plain-text body, or "" — always populated when there is any body at all, so a host with no HTML renderer still has something to show.
DateNullable(Of DateTime)
DisplayBccString
DisplayCcString
DisplayToString
ErrorStringSet when the item could not be read at all; the host shows this instead of a confidently blank message.
HasHtmlBooleanTrue when BodyHtml is real HTML from the message rather than a fallback. A host should render HTML when this is set and plain text otherwise.
IsHtmlFormatBooleanThe same fact said the way an e-mail object says it: this message is HTML format.
KindenmItemKind
MessageClassString
NidUInt32
RecipientsList(Of clsRecipient)
SenderEmailString
SenderNameString
SubjectString

Class clsItemOps

Bastion.Pst.Views

Methods

MemberTypeSummary
CopyItems(String, String, IEnumerable(Of UInt32), String, String, String, clsDiagnostics)clsEditReportCopy items from one store into a folder of another (or the same) store. Each item is exported to a native .msg and imported into the target, so every kind copies with full fidelity — recipients, attachments and MAPI properties — and the source is only ever read. Returns the rewritten target as a temporary file the caller swaps in.
DeleteItems(String, String, String, ISet(Of UInt32), clsDiagnostics)clsEditReportDelete items from a folder, writing the result to outPath.
ImportFiles(String, String, String, IEnumerable(Of String), clsDiagnostics)clsEditReportImport files (.msg .eml .ics .vcf .mbox …) into a folder.
IsWritable(String)Boolean
MoveItems(String, String, String, String, ISet(Of UInt32), clsDiagnostics)clsEditReportMove items between folders of the same store.
SaveItems(String, String, IEnumerable(Of UInt32), String, enmSaveFormat, CancellationToken, Action(Of Int32))clsSaveReportSave items to disk in a format other applications can open: mail as .msg, contacts as .vcf, appointments as .ics, notes as .txt — or force one format for the lot. The source is opened read-only and never changed.
WhyNotWritable(String)StringWhy a store will not accept data, or "" when it will. A host should show this rather than inventing its own wording.

Class clsItemQuery

Bastion.Pst.Views

Methods

MemberTypeSummary
BodyContains(String)clsItemQueryBody contains text. Expensive: opens every surviving item.
DateBefore(DateTime)clsItemQueryItem dated before until. Cheap.
DateFrom(DateTime)clsItemQueryItem dated on or after from. Cheap.
HasClass(String)clsItemQueryMessage class starts with prefix ("IPM.Contact"). Cheap.
InFolder(String)clsItemQueryOnly look in this folder path (exact, case-insensitive).
InFolders(Func(Of String, Boolean))clsItemQueryOnly look in folders whose full path satisfies predicate — the cheapest filter of all, since a folder that fails is never opened.
LargerThan(Int32)clsItemQueryItem is larger than bytes. Cheap.
SenderContains(String)clsItemQuerySender name or address contains text. Cheap.
SubjectContains(String)clsItemQuerySubject contains text. Cheap — read from the table row.
Take(Int32)clsItemQueryStop after n matches (0 = no limit).
TextContains(String)clsItemQuerySubject OR sender contains text — what a search box means. Cheap.
Unread()clsItemQueryUnread only. Cheap.
Where(Func(Of clsMessage, Boolean))clsItemQueryAn arbitrary test against the opened item. Runs only for rows that passed every cheap criterion, so put anything the contents table can answer in the criteria above.
WithAttachments()clsItemQueryWith attachments only. Cheap.

Class clsItemViewBase

Bastion.Pst.Views

Fields shared by every kind of item.

Properties

MemberTypeSummary
DisplayStringWhat a list shows when it has one line to spare.
FolderNameStringDisplay name of the folder holding the item ("Inbox").
FolderNidUInt32Node id of that folder — lets a host act on the item without re-walking the tree.
FolderPathStringFull path of the folder holding the item ("Top of Outlook data file\Inbox").
KindenmItemKind
MessageClassString
NidUInt32Node id of the item within its store — the handle for reopening it.
SizeInt32
StorePathStringFile path of the store the item came from; a host may have several open.

Class clsMailView

Bastion.Pst.Views

A message as a mail list shows it.

Properties

MemberTypeSummary
DateNullable(Of DateTime)Received if known, else sent — what Outlook sorts a mail list by.
DisplayString
FromDisplayStringWho the row is from: the sender's name, their address if unnamed.
HasAttachmentsBoolean
ImportanceInt32
IsHtmlFormatBooleanTrue when the message was written as HTML and should be rendered as HTML.
IsUnreadBoolean
PreviewStringFirst line or so of the body, for the second/third line of a message list.
ReceivedNullable(Of DateTime)
SenderEmailString
SenderNameString
SentNullable(Of DateTime)
SizeBytesInt32Size in bytes — the name a size column usually binds to.
SubjectString
ToString

Class clsNoteView

Bastion.Pst.Views

A sticky note.

Properties

MemberTypeSummary
BodyString
ColourInt32
CreatedNullable(Of DateTime)
DisplayString
SubjectString

Class clsRowFacts

Bastion.Pst.Views

Properties

MemberTypeSummary
DateNullable(Of DateTime)
DisplayToString
HasAttachmentsBoolean
ImportanceInt32
IsUnreadBoolean
MessageClassString
NidUInt32
ReceivedNullable(Of DateTime)
SenderEmailString
SenderNameString
SentNullable(Of DateTime)
SizeInt32
SubjectString

Class clsRssView

Bastion.Pst.Views

An RSS feed post. Outlook files these under "RSS Feeds" with the container class IPF.Note.OutlookHomepage and the item class IPM.Post.Rss - close enough to mail to be mistaken for it, which is why they used to appear in the mail folder tree, but they are their own thing: a headline, a feed, a publication date and a link back to the article.

Properties

MemberTypeSummary
AuthorString
DisplayString
FeedNameStringThe feed the post came from - normally the folder it is filed in.
IsHtmlFormatBoolean
LinkStringLink back to the original article, when the post carries one.
PreviewString
PublishedNullable(Of DateTime)
SubjectString

Class clsSaveReport

Bastion.Pst.Views

What a save/export produced.

Properties

MemberTypeSummary
ErrorString
FailedInt32
FilesList(Of String)
OkBoolean
SavedInt32

Class clsSectionFolder

Bastion.Pst.Views

A folder belonging to a section — a calendar, a contact folder — for a host to list.

Properties

MemberTypeSummary
FullPathString
ItemCountInt32
NameString
NidUInt32
StorePathString

Class clsStoreViews

Bastion.Pst.Views

Properties

MemberTypeSummary
EmptyEmailPlaceholderStringShown in place of a missing sender address (e.g. "empty@myaddress.com"). Purely a display substitution — the stored item is never changed. Empty shows nothing.
HiddenFolderNamesHashSet(Of String)Folders Outlook keeps out of sight. They carry a contact container class but hold address-book plumbing rather than people — in a real archive they outnumbered the genuine contacts ten to one.
IncludeHiddenFoldersBooleanInclude Outlook's hidden address-book folders in contact views (default False).

Methods

MemberTypeSummary
Appointments(clsItemQuery)IEnumerable(Of clsAppointmentView)Every appointment in the store.
AttachmentBytes(clsFolder, UInt32, Int32)Byte[]The bytes of one attachment, by its index in clsItemDetail.Attachments. A list row carries no payload — a mail list must not hold every attachment in memory — so a host asks for the bytes when the user actually saves or opens one.
Contacts(clsItemQuery)IEnumerable(Of clsContactView)Every contact in the store.
Detail(clsFolder, UInt32)clsItemDetailEverything needed to display one item: body (HTML when it has one), recipients and attachments. Read one item at a time, when the user selects it — list rows stay thin.
Mail(clsItemQuery)IEnumerable(Of clsMailView)Every mail item in the store.
MailIn(clsFolder, clsItemQuery)IEnumerable(Of clsMailView)The mail in ONE folder — what a mail list binds to when the user clicks a folder. Reads the folder's contents table and nothing else, so a 23,000-message Inbox costs one table read rather than 23,000 message opens.
Notes(clsItemQuery)IEnumerable(Of clsNoteView)Every note in the store.
Rss(clsItemQuery)IEnumerable(Of clsRssView)Every RSS post in the store.
SectionFolders(enmSection)List(Of clsSectionFolder)The folders that make up a section — the calendars, the contact folders — so a host can offer them as a list to switch between.
Tasks(clsItemQuery)IEnumerable(Of clsTaskView)Every task in the store.

Class clsTaskView

Bastion.Pst.Views

A task as a to-do list shows it.

Properties

MemberTypeSummary
BodyString
DateCompletedNullable(Of DateTime)
DisplayString
DueDateNullable(Of DateTime)
ImportanceenmImportance
IsCompleteBoolean
IsOverdueBoolean
OwnerString
PercentCompleteDouble
StartDateNullable(Of DateTime)
StatusenmTaskStatus
StatusTextString
SubjectString

Enum enmSaveFormat

Bastion.Pst.Views

The file an item is saved as when the user picks "Save As" with no format chosen.

MemberValue
Native0
Msg1
Vcf2
Ics3
Txt4

Enum enmSection

Bastion.Pst.Views

The sections of a mailbox.

MemberValue
Mail0
Calendar1
Contacts2
Tasks3
Notes4
Journal5
Rss6