Search Results for

# Quick Start — C#

This walkthrough takes about 15 minutes and gets you from a fresh checkout to extracting files out of a forensic image with the C# example application FexViewer.

FexViewer is a console app at `sdk/examples/csharp/`. It uses direct P/Invoke into `FEX.Core.dll` — there is no separate managed wrapper assembly, so the patterns you see in `NativeMethods.cs` and `FexApi.cs` are exactly what your own integration will look like.

- Prerequisites
- 1. Build FexViewer
- 2. Inspect an image — `info`
- 3. List the filesystem — `list`
- 4. Extract files — `extract`
- 5. Multi-segment and encrypted images
- Where to go next

## Prerequisites

Requirement | Notes
--- | ---
.NET 8 SDK | `dotnet --version` should print `8.x`. Get it from https://dotnet.microsoft.com/download/dotnet/8.0.
Windows x64 | The 1.4.x SDK ships Win64 binaries. Linux and macOS are deferred to a follow-up patch.
FEX2 license key | Set the environment variable `FEX_LICENSE_KEY=FEX2-…`. Contact `sales@getdata.com` for an evaluation key.
A forensic image | The SDK ships `sdk/sample-data/sample-image.dd` — a 50 MB FAT32 image. Substitute your own E01 / Ex01 / L01 / DD / DMG / AFF4 if you have one.

The runtime DLLs (`FEX.Core.dll`, `libcrypto-1_1-x64.dll`, `bzip2.dll`) live in `sdk/bin/Win64/`. The FexViewer csproj copies them next to the executable on every build, so you don't manage that yourself.

## 1. Build FexViewer

From a PowerShell prompt:

    cd "sdk\examples\csharp"
    dotnet build -c Release

Output lives at `sdk\examples\csharp\bin\Release\net8.0\FexViewer.exe`. The build is configured with `TreatWarningsAsErrors=true` and ships clean — any warnings on your environment indicate something to investigate before trusting the output.

To verify the binary loads `FEX.Core.dll` and the license is recognised:

    $env:FEX_LICENSE_KEY = "FEX2-...your-key..."
    .\bin\Release\net8.0\FexViewer.exe info --image "..\..\sample-data\sample-image.dd"

If you see JSON metadata, you're set.

## 2. Inspect an image — `info`

`FexViewer info` calls `GetImageInfoByFilenameAsJSON` and prints the parsed metadata: image type, device size, segment count, hashes, and any format-specific blocks (e.g. EnCase case name / examiner).

    .\FexViewer.exe info --image "C:\evidence\disk.E01"

Sample output:

    Image type:        E01
    Filename:          disk.E01
    Device size:       250.06 GB
    Segments:          12
    MD5:               5d41402abc4b2a76b9719d911017c592
    SHA1:              aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
    Case name:         OPS-2026-001
    Evidence number:   2
    Examiner:          L. Investigator

The two C# calls behind that output (from `FexApi.GetImageMetadata`):

    int rc = NativeMethods.GetImageInfoByFilenameAsJSON(
        absolutePath, out IntPtr buffer, out uint size);
    // ... check rc, marshal UTF-8 JSON, deserialize into ImageMetadata ...
    NativeMethods.FreeAllocatedBuffer(buffer);   // pair every V2 call

The buffer-ownership rule applies to every V2 export: the DLL allocates, your code frees with `FreeAllocatedBuffer` in a `finally` block. See memory-management.md for the full pattern.

## 3. List the filesystem — `list`

`FexViewer list` enumerates every record using `GetFileSystemRecords_V2` and prints them as a table, JSON, or CSV.

    # Default table format
    .\FexViewer.exe list --image "C:\evidence\disk.E01" --limit 20

    # JSON to a file (one object per record, no truncation)
    .\FexViewer.exe list --image "C:\evidence\disk.E01" --format json --out files.json

    # CSV
    .\FexViewer.exe list --image "C:\evidence\disk.E01" --format csv --out files.csv

The C# pattern looks like this:

    NativeMethods.ReadFileSystem(imageId, out _);

    IntPtr records = IntPtr.Zero;
    try
    {
        int rc = NativeMethods.GetFileSystemRecords_V2(
            imageId, out uint count, out records, out _);
        if (rc != NativeMethods.RESULT_OK)
            throw new FexCoreException("GetFileSystemRecords_V2", rc);

        int stride = Marshal.SizeOf<FileRecordNative>();
        IntPtr cur = records;
        for (int i = 0; i < count; i++)
        {
            var native = Marshal.PtrToStructure<FileRecordNative>(cur);
            string name = Marshal.PtrToStringUTF8(native.filename) ?? "";
            // ... use whatever fields you need ...
            cur = IntPtr.Add(cur, stride);
        }
    }
    finally
    {
        if (records != IntPtr.Zero)
            NativeMethods.FreeAllocatedBuffer(records);
    }

Two important details about this pattern:

1. Resolve every UTF-8 string before the `FreeAllocatedBuffer` call. The `filename` pointer inside each record lives inside the same allocation; freeing the buffer invalidates them all.
2. Use `[StructLayout(LayoutKind.Sequential, Pack = 1)]` on the C# struct that mirrors `TFileRecord` — Pascal records are packed, and any alignment mismatch produces silently corrupted reads. The canonical layout is `FileRecordNative` in `sdk/examples/csharp/FexApi.cs`.

The status flags on each record (deleted, folder, system, partition, free-space, …) are documented in types-and-constants.md.

### When to switch to V3

`GetFileSystemRecords_V2` materialises every record and every filename in a single allocation. For datasets with multiple millions of records, that allocation can hit memory limits and return `RESULT_BUFFER_OVERFLOW`. At that point switch to V3 windowed extraction — v3-api.md walks through the workflow with a complete C# example.

## 4. Extract files — `extract`

`FexViewer extract` writes selected files from the image to a directory on your host filesystem.

    # Extract one folder (recursive)
    .\FexViewer.exe extract `
      --image "C:\evidence\disk.E01" `
      --path  "/Users/Alice/Documents" `
      --out   "C:\out\Alice-Documents"

    # Extract everything; skip deleted records
    .\FexViewer.exe extract `
      --image "C:\evidence\disk.E01" `
      --path  "/" `
      --out   "C:\out\full" `
      --skip-deleted

    # Dry run — print what would be written without touching disk
    .\FexViewer.exe extract `
      --image "C:\evidence\disk.E01" `
      --path  "/Users" `
      --out   "C:\out\dryrun" `
      --dry-run

The output directory is relativised to `--path`: with `--path /Users/Alice` and `--out C:\out`, the children of `Alice` land directly in `C:\out\…`.

Per-file logic mirrors what your own extractor will do:

1. Open the image, read filesystem, get records (as above).
2. For each record you want:
   - Skip system / partition / free-space / device entries (status flags).
   - Use `GetFileSize` or the `logicalSize` field from the record.
   - Read in chunks with `ReadFileData`, truncating the final write to `logicalSize` to strip cluster-slack bytes.
   - Restore timestamps from `createdDate` / `modifiedDate` / `accessedDate` (FILETIME `int64`s; convert with `DateTime.FromFileTimeUtc`).
   - Sanitise filenames for Windows (the FexViewer `extract` command replaces `\ / : * ? " < > |` with `_`).

The chunked-read pattern in C#:

    const int CHUNK = 1024 * 1024;
    IntPtr scratch = Marshal.AllocHGlobal(CHUNK);
    try
    {
        long offset = 0;
        while (offset < logicalSize)
        {
            int want = (int)Math.Min(CHUNK, logicalSize - offset);
            int rc = NativeMethods.ReadFileData(
                imageId, fileIndex, offset, scratch, want, out int got);
            if (rc != NativeMethods.RESULT_OK || got <= 0) break;

            var slice = new byte[got];
            Marshal.Copy(scratch, slice, 0, got);
            outputStream.Write(slice, 0, got);

            offset += got;
        }
    }
    finally
    {
        Marshal.FreeHGlobal(scratch);
    }

`ReadFileData` is a caller-allocated function (Pattern 3 in memory-management.md) — `Marshal.AllocHGlobal` pairs with `Marshal.FreeHGlobal`, and there's no `FreeAllocatedBuffer` involved.

## 5. Multi-segment and encrypted images

FexViewer accepts a JSON manifest in place of `--image <path>`:

    {
      "manifest_version": 1,
      "segments": [
        "C:\\evidence\\case123\\disk.E01",
        "C:\\evidence\\case123\\disk.E02",
        "C:\\evidence\\case123\\disk.E03"
      ],
      "passwords": [
        "MyBitLockerPassword",
        "C:\\evidence\\case123\\recovery.bek"
      ]
    }

    .\FexViewer.exe info --image "case123.json"

Behind the scenes:

- Multi-segment images — only the first segment is passed to `OpenImage`. The DLL auto-detects companion segments by naming convention (`E01`, `E02`, …). The manifest's `segments` list is used to validate that companion files exist; missing files are warned about rather than silently ignored.
- Encrypted volumes — each entry in `passwords` is tried in order. An entry that resolves to an existing file path is treated as a key file (e.g. a BitLocker `.bek`); everything else is treated as a literal password. This lets one manifest carry credentials from multiple vendors.

The DLL-level encoding for credentials is the `optionsString` parameter to `OpenImage`:

    { "Password": "MyVolumePassword" }

The `Password` field is overloaded — the DLL recognises the BitLocker key file format if the value resolves to an existing file. See OpenImage in v2-api.md for the full specification.

If you'd rather keep credentials out of the case manifest, FexViewer accepts a separate `--passwords <file>` argument pointing at a JSON file with the same shape (just the `passwords` array). That overrides anything in the case manifest.

## Where to go next

You want to… | Read
--- | ---
Understand every V2 export in detail | api/v2-api.md
Page through a million-record image | api/v3-api.md
Look up a result code | api/error-codes.md
See `TFileRecord` / `TPropertySpec` layouts | api/types-and-constants.md
Get the buffer ownership rules right | api/memory-management.md
Find a function alphabetically | api/function-index.md
Read the FexViewer source | `sdk/examples/csharp/` — `NativeMethods.cs`, `FexApi.cs`, `Commands/`, `Models/`