FEX Core SDK
  • Documentation
  • C# Reference
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 int64s; 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/
    In this article
    Back to top © GetData Forensics