Skip to main content

Search OfficeIMO

Enter a topic, API type, or PowerShell command.

Documentation Search all OfficeIMO/

ZIP, TAR, CAB and MSI Archives

Edit on GitHub

Read, verify and safely extract ZIP, TAR, gzip, CAB and MSI files, and create reproducible ZIP and TAR archives, in managed .NET.

OfficeIMO.Archives inspects ZIP and Microsoft Cabinet archives and ordinary Windows Installer (MSI) databases, reads complete verified members into memory and extracts payloads to disk. It also reads and extracts TAR, .tar.gz and gzip files, and creates ZIP, TAR and gzip archives whose bytes depend only on their input. It is fully managed, needs no native archive tools and never runs installer actions, scripts or custom actions.

dotnet add package OfficeIMO.Archives

Targets .NET Standard 2.0, .NET 8, .NET 10 and .NET Framework 4.7.2.

Inspect and read members

ArchiveInspector.Open builds an ordinal-preserving inventory without decoding payloads. Reads then return complete, length-verified streams, checking every ZIP CRC and each CAB frame checksum that is present (zero-checksum CAB frames have nothing to check); a failed read returns nothing partial.

using OfficeIMO.Archives;

using var inspection = ArchiveInspector.Open("AdministrativeTemplates.msi", new ArchiveInspectionOptions {
    MaxInputBytes = 128L * 1024 * 1024,
    MaxEntryBytes = 16L * 1024 * 1024,
    MaxExpandedBytes = 256L * 1024 * 1024
});

var ordinals = inspection.Entries
    .Where(entry => entry.CanRead && entry.AuthoredPath?.EndsWith(".admx", StringComparison.OrdinalIgnoreCase) == true)
    .Select(entry => entry.Ordinal).ToArray();

var plan = inspection.PlanRead(ordinals);
Console.WriteLine($"Selected {plan.SelectedBytes} bytes, decoding {plan.DecodedBytes} bytes");

using var batch = inspection.ReadMembers(ordinals, maxSelectedBytes: 64L * 1024 * 1024);
foreach (var member in batch.Members)
    Console.WriteLine($"{member.Entry.AuthoredPath}: {member.Content.Length} bytes");

Entries keep ZIP central-directory order, CAB file order or MSI File-table order. Duplicate names and MSI aliases stay separate and are selected by ordinal. PlanRead estimates cost without decoding, and ReadMembers decodes each selected cabinet folder once. Verify() returns Verified, Unsupported or Invalid per member, and CopyTo writes one verified member to a stream.

Extract to disk

using OfficeIMO.Archives;

var result = ArchiveExtractor.Extract("AdministrativeTemplates.msi", "extracted-templates",
    new ArchiveExtractionOptions {
        IncludeFile = path => path.EndsWith(".admx", StringComparison.OrdinalIgnoreCase)
                           || path.EndsWith(".adml", StringComparison.OrdinalIgnoreCase),
        MaxInputBytes = 128L * 1024 * 1024,
        MaxExpandedBytes = 256L * 1024 * 1024
    });

foreach (var file in result.Files)
    Console.WriteLine($"{file.RelativePath}: {file.Length} bytes");

The format is detected from bytes, not the file name. The destination must be absent or empty: files are written to a sibling staging directory, verified, then moved into place. Paths are checked for traversal, absolute paths, alternate data streams, reserved device names and case collisions before anything is written. A failure or cancellation removes staging output.

Read ZIP content in memory

var files = ArchiveReader.ReadZip(File.ReadAllBytes("package.zip"), new ArchiveReadOptions {
    IncludeFile = path => path.EndsWith(".xml", StringComparison.OrdinalIgnoreCase),
    MaxEntryBytes = 8L * 1024 * 1024
});
foreach (var file in files)
    Console.WriteLine($"{file.RelativePath}: {file.Length} bytes");

ReadZip applies the same name, path and CRC-32 rules as extraction and creates no files. ArchiveReader.TraverseZip enumerates metadata with ingestion warnings without verifying content.

Create ZIP and TAR archives

using OfficeIMO.Archives;

ZipArchiveWriter.CreateFromDirectory("site", "site.zip");
TarArchiveWriter.CreateFromDirectory("site", "site.tar.gz", new TarArchiveWriterOptions { GZip = true });

ZipArchiveWriter.Create("report.zip", new[] {
    ArchiveWriteEntry.Directory("data"),
    ArchiveWriteEntry.File("data/summary.csv", () => File.OpenRead("summary.csv"))
}, new ZipArchiveWriterOptions { Timestamp = ArchiveTimestamp.FromSourceDateEpoch() });

The same input gives the same bytes on Windows, macOS and Linux and on every target framework: Deflate uses a managed encoder rather than the platform's zlib, timestamps are fixed (or taken from SOURCE_DATE_EPOCH), attributes are constant and folder entries are sorted. ZIP members are stored or Deflate-compressed with UTF-8 names and ZIP64 only where sizes, offsets or counts need it. TAR uses ustar headers and adds pax records only for long or non-ASCII names, large sizes and out-of-range times. Names go through the same safety checks as extraction.

Read TAR and gzip

using var input = File.OpenRead("site.tar.gz");
foreach (TarArchiveEntry entry in ArchiveReader.ListTar(input))
    Console.WriteLine($"{entry.Kind} {entry.Name} {entry.LinkTarget}");
var files = ArchiveReader.ReadTar(File.ReadAllBytes("site.tar.gz"));
ArchiveExtractor.Extract("site.tar.gz", "site-copy");

ustar, pax, GNU long names and base-256 numbers and v7 headers are read with every header checksum checked. Symbolic and hard links and device records are listed with their targets but never created; extraction fails if one is selected. GZipArchive reads concatenated gzip members with CRC-32 and length checks and writes gzip with the same deterministic encoder.

What it covers

ContainerSupported profile
ZIPSingle-disk archives with stored or Deflate members, ZIP64, UTF-8 names; legacy code pages only through an explicit ZipLegacyCodePage
CABSingle cabinets with Store, MSZIP and LZX compression
TARustar, pax, GNU and v7 headers, plain or gzip-compressed; regular files and directories are extracted, links and devices are listed only
gzipSingle and concatenated RFC 1952 members
MSIOrdinary installer databases with Directory, Component, File and Media tables and embedded cabinets; raw storage streams are listed

Every operation is bounded: input size, entry count, per-member and aggregate expansion, decompression work, MSI metadata and path length and depth. Pass one ArchiveOperationBudget to related operations, and CreateNested() to child containers, to share those limits. Quota failures throw OfficeInspectionResourceLimitException with the exact limit name; malformed content throws InvalidDataException.

Limits

  • Encrypted ZIP members, other compression methods, multi-disk archives and prepended self-extracting stubs are rejected. Ambiguous ZIP layouts fail rather than being guessed.
  • Cabinet sets, continued records and Quantum compression are not supported.
  • Sparse and multi-volume TAR members are rejected. ArchiveInspector does not inventory TAR or gzip; use ArchiveReader.ListTar and ReadTar.
  • 7z, RAR, XZ, Zstandard, Brotli, encryption and split archives are not supported.
  • External MSI cabinets and loose source files are listed with diagnostics but never opened. Transforms, patches, features, conditions and properties are not evaluated, so extraction shows the authored layout rather than a simulated installation.
  • CAB checksums and ZIP CRC-32 detect corruption; they do not prove authenticity.
  • Limits bound logical data, not process peak memory.

See the OfficeIMO.Archives README and the OfficeIMO.Archives support matrix.

API used on this page

Example source