Skip to content

Add test to validate VBA OLE storage streams inside compiled add-ins #41

@jozefizso

Description

@jozefizso

Problem

While we generate PowerPoint add-ins with embedded VBA projects, we don't verify that the internal OLE storage structure is correct. PowerPoint may reject add-ins if required VBA streams are missing or malformed, even if the outer OpenXML package is valid.

The VBA project is stored as an OLE compound file (vbaProject.bin) inside the add-in, and it must contain specific streams and storage structures that PowerPoint expects.

What needs to be done

Add a test that opens the generated .ppam file, extracts the embedded VBA project OLE storage, and validates its internal structure using the OpenMcdf library.

Required validations

The test should verify these OLE streams exist and are non-empty:

  1. Root-level streams:

    • PROJECT - Project metadata and module list
    • PROJECTwm - Module name records
  2. VBA storage (sub-storage named VBA):

    • _VBA_PROJECT - VBA project header with version constants
    • dir - Directory stream containing Information/References/Modules records
    • Module streams - One stream per module/class (named after each module)
  3. Optional validation: Check that the dir stream begins with a valid InformationRecord signature

Implementation guidance

Suggested location

Create a new test class under tests/VbaCompiler.Tests/Streams/ such as VbaOleStorageValidationTests.cs

Key steps

// 1. Generate the add-in
var outputStream = new MemoryStream();
compiler.CompilePowerPointMacroFile(outputStream, "output.ppam");

// 2. Extract VBA project binary
outputStream.Position = 0;
using var presentation = PresentationDocument.Open(outputStream, false);
var vbaProjectStream = presentation.PresentationPart.VbaProjectPart.GetStream();

// 3. Load as OLE compound file
var vbaBytes = new byte[vbaProjectStream.Length];
vbaProjectStream.Read(vbaBytes, 0, vbaBytes.Length);
using var oleStream = new MemoryStream(vbaBytes);
using var compoundFile = new CompoundFile(oleStream, CFSUpdateMode.ReadOnly, CFSConfiguration.LeaveOpen);

// 4. Verify root-level streams
Assert.True(compoundFile.RootStorage.ExistsStream("PROJECT"));
Assert.True(compoundFile.RootStorage.ExistsStream("PROJECTwm"));

// 5. Verify VBA storage and its streams
Assert.True(compoundFile.RootStorage.ExistsStorage("VBA"));
var vbaStorage = compoundFile.RootStorage.GetStorage("VBA");
Assert.True(vbaStorage.ExistsStream("_VBA_PROJECT"));
Assert.True(vbaStorage.ExistsStream("dir"));

// 6. Verify module streams exist (one per module added)
Assert.True(vbaStorage.ExistsStream("Module1")); // adjust based on test modules

// 7. Verify streams have content
var dirStream = vbaStorage.GetStream("dir");
Assert.True(dirStream.Size > 0);

Important notes

  • Use CFSUpdateMode.ReadOnly when opening the OLE file
  • Use CFSConfiguration.LeaveOpen to prevent disposing the underlying stream prematurely
  • Reset stream positions (Position = 0) before reading
  • Test with multiple module types (standard modules, class modules) to ensure all generate their streams correctly

Why this matters

The VBA OLE structure is what PowerPoint actually reads to load and execute macros. Even if the OpenXML package is valid, PowerPoint will fail to load the add-in if these internal streams are missing or corrupted. This test ensures we're generating fully compliant VBA projects that Office applications can actually use.

Related files

  • src/VbaCompiler/VbaCompiler.cs - CompilePowerPointMacroFile and CompileVbaProject methods
  • src/VbaCompiler/Vba/VbaProjectStream.cs - Writes _VBA_PROJECT stream
  • src/VbaCompiler/Vba/DirStream.cs - Builds dir stream
  • src/VbaCompiler/Vba/ModuleStream.cs - Writes individual module streams
  • src/VbaCompiler/Vba/ProjectRecord.cs - Populates PROJECT stream
  • src/VbaCompiler/Vba/ProjectWmRecord.cs - Writes PROJECTwm stream

Dependencies

The project already uses OpenMcdf for OLE manipulation, so no new dependencies are needed.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions