SUBMIT A FILE TYPE CONTACT US SUPPORT ABOUT US

What Is an ESEDB File? How to Open the Windows Database Behind Search, SRUM and WebCache

An ESEDB file is a Windows database in the Extensible Storage Engine format, and a reader that opens ESEDB files can still return wrong values. Jump to your question below, or work through it in order.

40 min read Updated October 2026 Checked against sources and tests

What Is an ESEDB File? A Windows database, and not a name you will find on a file

Short answer

An ESEDB file is a database in the Extensible Storage Engine format. Windows uses it for Search, SRUM, WebCache, File History and User Access Logging. It contains tables of rows and columns, and no app opens it by double click.

We read 18 ESEDB files with 3 readers. They disagreed in all 5 of our tests. One reader returned filler bytes as data in 81,532 empty cells and gave no error. On a table with 64,993 columns, dissect.esedb read 16 of 16 rows and the libesedb binding read 1.

To open ESEDB files, read how to open them. To find the app behind one, read the six fingerprints. If yours will not open or reads wrong, begin with the eight causes.

An ESEDB file is a database in the Extensible Storage Engine format. Windows uses it for Search, SRUM, WebCache, File History and User Access Logging. The files end in .edb, .dat or .mdb, and none of our 18 ESEDB files ended in .esedb.

Picture an analyst who finds a file called SRUDB.dat. It opens in no app, and a text editor shows noise. The file is fine. It is one of the ESEDB files that Windows keeps for itself, and it needs a reader that knows the format.

The name causes the first mix up. Forensics Wiki calls the format ESEDB and lists .edb, .sdb and .stm as endings. It gives the signature ef cd ab 89 at byte 4. Our 18 ESEDB files used .edb, .dat and .mdb, and none used .esedb.

Wikipedia calls the engine ESE or JET Blue and says Microsoft released it under the MIT License in January 2021. Start dates differ between sources. Wikipedia says 1994 with Windows NT 3.5, and Microsoft's overview says Windows 2000, so we give no single year.

Exchange mailbox databases use the same engine. Our EDB file page covers those, with the header, Eseutil and damage tests. This page covers what ESEDB files contain, how to separate them and where readers disagree.

Who makes ESEDB files

Windows features write most ESEDB files. Search writes Windows.edb, SRUM writes SRUDB.dat and Internet Explorer writes WebCacheV01.dat. User Access Logging writes .mdb files in a SUM folder, and File History writes Catalog1.edb. Of our 18 ESEDB files, 11 belong to those five features and 7 are test files.

Three questions start most readers off. Which app wrote your ESEDB file? Is it damaged? Can you export what is inside? The app section answers the first, the check the second and the export section the third.

ESEDB file facts at a glance

Quick facts about ESEDB files
Property Value
Meaning A database file in the Extensible Storage Engine format, also called JET Blue.
Endings .edb, .dat and .mdb in our 18 ESEDB files. Forensics Wiki also lists .sdb and .stm.
Made by Microsoft. The engine is an ISAM data store, per Wikipedia.
Starts with A 4 byte checksum, then ef cd ab 89 at byte 4.
Stores Tables of rows and columns in pages of 4,096 or 32,768 bytes in our ESEDB files.
Media type None. Forensics Wiki lists none.
Opens in No app by double click. Python modules and libesedb tools read it, and esentutl ships with Windows.
Safe to open Reading is safe. The contents can be private, as section 9 explains.

Wrong page? A mailbox database from Exchange is an EDB file. An Access database is an MDB file or an ACCDB file. A small app database is a SQLite file.

What Is Inside an ESEDB File? A header, a catalog and tables, in 12 lines

Every ESEDB file starts with a header page, and its first table is a catalog named MSysObjects. The catalog lists every other table. Our smallest ESEDB file, SRUDB.dat from the dissect test set, has 655,360 bytes, 12 tables and 59 user columns.

The listing shows that file, read with the script from the check section. The first line names the app. Every other line is one table with its column count, its row count and a count of records flagged as deleted.

The tables in one ESEDB file
python3 ese_who.py dissect-SRUDB.dat
dissect-SRUDB.dat: SRUM (SRUDB.dat)
  MSysObjects                              cols     28 rows     161  deleted-flag   0  unreadable 0
  MSysObjectsShadow                        cols     28 rows     161  deleted-flag   0  unreadable 0
  MSysObjids                               cols      3 rows      28  deleted-flag   0  unreadable 0
  MSysLocales                              cols      3 rows       7  deleted-flag   0  unreadable 0
  SruDbIdMapTable                          cols      3 rows     106  deleted-flag   0  unreadable 0
  SruDbCheckpointTable                     cols      5 rows       0  deleted-flag   0  unreadable 0
  {17F4D97B-F26A-5E79-3A82-90040A47D13D}   cols      6 rows       6  deleted-flag   0  unreadable 0
  {841A7317-3805-518B-C2EA-AD224CB4AF84}   cols      5 rows       3  deleted-flag   0  unreadable 0
  {D10CA2FE-6FCF-4F6D-848E-B2E99266FA89}   cols     19 rows     203  deleted-flag   0  unreadable 0
  {DC3D3B50-BB90-5066-FA4E-A5F90DD8B677}   cols      5 rows       3  deleted-flag   0  unreadable 0
  {DD6636C4-8929-4683-974E-22C046A43763}   cols      9 rows       3  deleted-flag   0  unreadable 0
  {EEE2F477-0659-5C47-EF03-6D6BEFD441B3}   cols      7 rows       2  deleted-flag   0  unreadable 0
Layout of one ESEDB file from our tests, outside in: a file header with the signature at byte 4, the catalog table, three more system tables and eight user tables with 59 columns, plus three rules for reading it.
One ESEDB file from our tests, outside in. The blocks are not drawn to scale, and the sizes come from SRUDB.dat in the dissect test set.
Parts of an ESEDB file
Part What it is and what we saw
File header Page 0. The checksum comes first and the signature ef cd ab 89 starts at byte 4. The header stores the version, the state and the page size.
MSysObjects The catalog, and the first table in all 18 ESEDB files. In SRUDB.dat it has 28 columns and 161 rows.
MSysObjectsShadow A second catalog table. SRUDB.dat has the same 161 rows. Catalog1.edb has 0.
MSysObjids and MSysLocales Two small system tables. All four system tables are in 17 of the 18 ESEDB files. plaso-Windows.edb has only the first two.
User tables The tables an app made. SRUDB.dat has 8 of them, with 59 columns and 326 rows. Their names show the app.
Columns Every column has one type. Our 18 ESEDB files use 16 types across 69,235 columns, and one test file has 64,994 of them.

ESEDB file sizes in our set go from 655,360 bytes to 42,008,576 bytes. The two largest ESEDB files are Windows Search files. The catalog was the first table in every one of them.

Page size and state of an ESEDB file

Wikipedia lists page sizes of 2, 4, 8, 16 and 32 KB. Page size is 4,096 bytes in 12 of our ESEDB files and 32,768 bytes in 6. The database state is 3 in 14 ESEDB files and 2 in 4, as dissect.esedb reports it. The ESE header names those values CleanShutdown and DirtyShutdown.

The 4 dirty ESEDB files are dissect-Windows.edb, plaso-Windows.edb, plaso-WebCacheV01_cookies.dat and plaso-PartitionsEx-WebCacheV01.dat. All four contain records flagged as deleted, as the deleted records test shows. A SANS post on dirty ESE databases explains why that state matters.

Sparse tables in ESEDB files

Many tables in ESEDB files are mostly empty. In dissect-Windows.edb, 91.5 percent of the cells are empty. plaso-Windows.edb is at 85.7 percent. The two User Access Logging files with a CLIENTS table are at 96.0 and 97.4 percent, and Catalog1.edb has none.

That matters when you export. A table with 374 columns can print as a wall of empty values, and a reader that returns a value for an empty cell makes it worse. The filler test shows one that does.

A header is not an app name. The header fields we read name no app in any of the 18 ESEDB files. Only the table names do, as the next section shows.

Which App Wrote Your ESEDB File? Six fingerprints, and one fork for the rest

The table names show which app wrote an ESEDB file. Six rules matched 11 of our 18 ESEDB files, and the other 7 are test files. The ending is a weak clue, because the .mdb and .dat files in our set are ESEDB files and not Access files or loose data.

Start with the catalog. An ESEDB file that lists ROLE_ACCESS and CLIENTS belongs to User Access Logging. A file with SruDbIdMapTable belongs to SRUM. The figure shows the full fork, and every rule needs only one or two table names.

A fork on the table names of an ESEDB file: User Access Logging, SRUM, Windows Search, WebCache and File History all have table names that give the app away, and seven test files match none.
Six fingerprints and one fork. The table names told us the app for 11 of 18 ESEDB files, and the other 7 are test files.
Windows features that write ESEDB files
App What it is and what we saw
User Access Logging Logs of client access to server roles. Microsoft says the data lives in ESE files under \Windows\System32\LogFiles\SUM\, with Current.mdb and a GUID named copy made every 24 hours. We had 3 ESEDB files. CLIENTS has 374 columns, and 366 of them are day columns.
SRUM The System Resource Usage Monitor database. The Artifacts Knowledge Base lists C:\Windows\System32\sru\SRUDB.dat as its path. We had 2 ESEDB files. The larger one has 10 user tables and 27,107 rows.
Windows Search The search index, named Windows.edb. FileInfo places it under ProgramData\Microsoft\Search. Forensic Focus names SystemIndex_0A as its main table and says the summary text is compressed and obfuscated. We had 2 ESEDB files. In plaso-Windows.edb that table has 381 columns and 322 rows.
WebCache The Internet Explorer cache database, named WebCacheV01.dat, which Forensic Focus says Edge also uses. Plaso reads WebCacheV01.dat and WebCacheV24.dat. We had 3 ESEDB files, with 29 to 48 user tables named Containers, Container_1 and so on.
File History The backup index of File History, named Catalog1.edb. The Plaso plugin needs the tables backupset, file, library and namespace. We had 1 file, and its namespace table has 1,373 rows.
Test files No app. The seven dissect test files are named basic, binary, default, index, large, multi and text. All have one user table named after the file.

Sources for the table are Microsoft's User Access Logging page and FileInfo, plus the Forensic Focus articles on Windows Search, Internet Explorer 10 and Project Spartan. We also used the Artifacts Knowledge Base and the Plaso plugin lists for its ESE parser.

Why the ending of an ESEDB file misleads

A file called SRUDB.dat is an ESEDB file, and the DAT file page covers the many other files that share the ending. The 3 .mdb files in our set are ESE databases from User Access Logging. Microsoft Access uses the same ending for its own format, as the comparison shows.

Microsoft says one UAL limit is 65,535 accesses per day. That fits the 366 UnsignedShort day columns in CLIENTS, because a 16 bit unsigned number stops at 65,535. We did not test the link, and it is our reading of two facts.

What the rules cannot do

Our rules came after we saw the files, so 11 matches out of 11 is not a test on new files. Forensics Wiki's list of ESE users names more, among them NTDS, WINS, DHCP and Windows Mail. We had no ESEDB file from them, so such a file returns "no fingerprint matched" until someone adds a rule.

Seven files had no match, and that is fine. The seven dissect test files have no app. A file with no match can still be a valid ESEDB file from an app we did not test, so check the signature before you decide it is broken.

What Types and Dates Do ESEDB Files Store? Sixteen column types and three ways to store a time

ESEDB files in our set use 16 column types and three ways to store a time. A DateTime column stored a day count in 5 ESEDB files and a FILETIME in 3. LongLong columns store FILETIME values. A reader returns a number, and you decode it.

We counted every user column in 17 ESEDB files. The 18th, dissect-large.edb, has 64,994 columns and would bury the rest. Without it, the ESEDB files have 4,241 columns. Six types make up 3,923 of them.

The six most common column types in 17 ESEDB files
Type Columns and where we saw them
LongText 959 columns. 588 are in the two Windows Search files and 329 in the three WebCache files.
LongLong 912 columns. 846 are in WebCache files and 56 in SRUM files. Time columns use this type too.
UnsignedShort 785 columns. 732 are the day columns of CLIENTS in the two User Access Logging files with that table.
UnsignedLong 612 columns. 458 are in WebCache files and 147 in Windows Search files.
LongBinary 461 columns. 317 are in WebCache files and 131 in Windows Search files.
Long 194 columns. 106 are in SRUM files.

The other ten types have 318 columns between them. Binary has 100, Bit 59, DateTime 33, IEEEDouble 30, Text 28, GUID 24, UnsignedByte 23 and Short 13. Currency and IEEESingle have 4 columns. Two of the 18 type numbers in libesedb, Nil and the super large value, appeared in no ESEDB file.

Three ways ESEDB files store a time

A time takes 8 bytes in all three cases. The type name does not show the encoding. A DateTime column in SRUM and in the test files stores an OLE day count, which is a double. A DateTime column in User Access Logging stores a FILETIME. A LongLong column can store a FILETIME too.

Three ways our ESEDB files store a time in eight bytes: an OLE day count in a DateTime column, a FILETIME in a DateTime column and a FILETIME in a LongLong column, with the raw bytes and the date they decode to.
The same eight bytes can store a date in three ways. A reader returns the number, and you choose the decoder.

Of the 33 DateTime columns in our ESEDB files, 16 store OLE values with data, in 5 ESEDB files. 11 store FILETIME values, in 3 ESEDB files. The other 6 had no readable single value. The libesedb binding returns the 8 raw bytes, and dissect.esedb and impacket return an integer.

Decode the three examples

The short script decodes the bytes from the figure. The first value is an OLE day count, so it reads the bytes as a double and adds the days to December 30, 1899. The other two are FILETIME values, so it counts 100 nanosecond ticks from January 1, 1601.

Decode OLE and FILETIME values
import datetime, struct

def ole(raw):
    return datetime.datetime(1899, 12, 30) + datetime.timedelta(days=struct.unpack('<d', raw)[0])

def filetime(raw):
    return datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=int.from_bytes(raw, 'little') // 10)

print(ole(bytes.fromhex('bcbbbbbb99bce540')))
print(filetime(bytes.fromhex('5902fdd1ad7fd701')))
print(filetime(bytes.fromhex('78f582d3f896ce01')))
Output of the script
2021-11-16 19:18:00
2021-07-23 10:30:55.980808
2013-08-12 01:11:08.495397

Our export script tries the OLE reading first and keeps it when the year falls between 1995 and 2030. Otherwise it tries FILETIME with the same rule. We surveyed the first 5,000 rows of every table. No value fit both readings, and 1 value in dissect-basic.edb fit neither.

The rule is a range test and not a proof. Check a few dates against the app that wrote the ESEDB file.

The type name is not the encoding. Three columns with the same 8 bytes can mean three different things. Read a sample row, decode it both ways and keep the reading that gives a date you can explain.

What Did We Find in 18 ESEDB Files? Three readers gave different answers in all five tests

We read 18 ESEDB files with three readers and five tests, and the readers gave different answers in all five. The libesedb binding counted none of 33 deleted records and returned filler bytes as data in 81,532 empty cells. dissect.esedb read all 16 rows of a table with 64,993 columns.

We compared readers on the same files. We took 18 ESEDB files, 10 from the test data of the dissect.esedb project and 8 from the test data of Plaso. Eleven belong to Windows features, and 7 are test files. The header and damage tests on the EDB page used the same 18 ESEDB files.

The readers were the libesedb Python binding, version 20260704, dissect.esedb 3.18 and impacket 0.13.1, on Python 3.13 and Linux. We did not start esentutl, which ships with Windows, and we did not start the two Windows viewers.

The five tests cover deleted records, empty cells, the widest table, date columns and compressed cells. In all five we compared the readers with one another and with the bytes in the file. We checked the SHA256 hash of all 18 ESEDB files before and after every test, and none changed.

18 ESEDB files we read
3 readers on every file
33 records flagged as deleted that the libesedb binding never counted
81,532 empty cells returned as filler data, with no error
What three readers did with 18 ESEDB files
Test Result
Deleted records 33 records flagged as deleted in 5 ESEDB files. The libesedb binding counted none of them. dissect.esedb listed all 33 and decoded 25, and impacket listed 25 and stopped on 8 tables.
Empty cells 81,532 empty fixed size cells in 12 ESEDB files. The libesedb binding and impacket returned the filler 0x2a as data, and dissect.esedb returned None.
Widest table 16 rows and 64,993 tagged columns. dissect.esedb read 16 rows in 2.1 seconds. The libesedb binding read 1, and impacket read 0 after 23.8 seconds.
Date columns 27 DateTime columns with data. The libesedb binding returned 8 raw bytes, and dissect.esedb and impacket returned an integer.
Compressed cells 6 long compressed cells in 2 test files. dissect.esedb decoded all 6. The libesedb binding decoded 4 and was wrong for 2, and impacket decoded none.
A grid of five tests on the same 18 ESEDB files and three readers: deleted records, empty cells, a table with 64,993 columns, date columns and compressed cells, with what every reader returned.
The grid shows how the three readers did on the five tests. Color marks the answers that were right, wrong or skipped.

Read the grid by row. Green means the reader gave the right result. Amber means you must decode the value yourself. Red means a wrong result with no error, which is the worst case, because nothing warns you. Gray means the reader skipped the data or stopped.

Deleted records that no reader counted

In 5 of our 18 ESEDB files, 33 records have a deleted flag in their page tag. The libesedb binding counted none of them, so its row totals were 33 lower than dissect.esedb gave. Before we read the flag, that gap had no explanation.

The flagged records are in dissect-Windows.edb (1), plaso-PartitionsEx-WebCacheV01.dat (2), plaso-WebCacheV01.dat (3), plaso-WebCacheV01_cookies.dat (5) and plaso-Windows.edb (22). All 4 dirty ESEDB files have some. Of the 14 other ESEDB files, only plaso-WebCacheV01.dat does.

dissect.esedb decoded 25 of the 33. The other 8 are in plaso-WebCacheV01.dat and plaso-WebCacheV01_cookies.dat, and as_dict() raised a TypeError for all 8. Our script skips such records and counts them as unreadable. It also writes a _deleted_flag column, so you can filter the flagged records out or keep them.

The Forensic Focus article on Windows Search forensics says part of a deleted file can persist in the index. We did not check whether the 25 decoded records are rows that an app deleted.

Filler bytes that look like data

In 12 ESEDB files we compared every empty cell. Where dissect.esedb returned None, the libesedb binding returned bytes. All 81,532 of those byte strings were 0x2a repeated, which is the asterisk character, and no call raised an error.

One SRUM table shows the harm. plaso-SRUDB.dat has 79,698 of those cells. As integers, 50,311 read as 3038287259199220266 and 29,387 read as 707406378. A report built on those numbers shows nonsense with no warning. impacket returned the filler in all 79,698.

The other 1,831 cells are in system tables, and 3 are in user tables of the test files. We left six ESEDB files out of this test. They are the five with deleted records and dissect-large.edb.

A table with 64,993 columns

dissect-large.edb has one user table with 16 rows and 64,994 columns. They are an Id column and 64,993 UnsignedShort columns with ids 256 to 65,248. The limit Wikipedia gives for tagged columns is 64,993, so the file is at the limit.

The libesedb binding read 1 of the 16 rows and raised an "invalid tagged data type offset" error for the other 15. Reading the table with impacket gave 0 rows and failed after 23.8 seconds. Reading it with dissect.esedb gave 16 of 16 rows and 65,009 non empty cells in 2.1 seconds.

Compressed values in ESEDB files

ESE can compress long values. The test files text.edb and binary.edb have 6 long compressed cells. dissect.esedb decoded all 6. The libesedb binding decoded 4 and was wrong for 2 with no error. impacket decoded none, because it returned a long value id or None.

The two wrong cells are stored inside the record. The binding returned the still compressed bytes for the binary cell and garbled text for the ASCII cell. In the Windows Search files it did better. Its string call matched dissect.esedb for all 18,997 compressed cells in dissect-Windows.edb and for 6,327 of 6,405 in plaso-Windows.edb.

Silence is not success The libesedb binding returned filler as data in 81,532 cells and wrong values for 2 compressed cells. No call raised an error.
Counts differ The row totals of the libesedb binding were 33 lower than dissect.esedb in 5 ESEDB files, because it skips records with a deleted flag.
Width breaks readers A table with 64,993 columns read in full in one reader, in 1 row in another and in none in the third.
Dates need decoding No reader returned a date for the 27 DateTime columns with data. All three returned bytes or an integer.

Read the study with care. It shows what three readers do with our 18 ESEDB files. Seven are test files, and the filler test left out six files. We did not use esentutl or the two Windows viewers, and a reader that fails on our ESEDB files can still work on yours.

What Does Our Expert Say About ESEDB Files? A note from Neeraj Singh on reading before you trust

Read an ESEDB file with two tools before you trust a number from it. The tools disagreed in all five of our tests. A second reader costs a minute, and it shows a difference in counts that no error message would.

Neeraj Singh

Expert insight

“An ESEDB file looks like a locked box, so people trust whatever the first tool shows. In our tests one reader returned filler as data in 81,532 empty cells and raised no error. Read the file with two tools, compare the counts and keep the original untouched.”

Neeraj Singh Manager, Tech Support & Operations 19+ years fixing Windows and system errors

Why one reader is a poor test for ESEDB files

A reader that opens an ESEDB file has told you little. In our tests the libesedb binding opened all 18 ESEDB files and still returned filler in 12 of them. A count from a second reader, like rows per table, shows the gap in seconds.

The check has a limit. Two readers that agree can both be wrong, and neither reads the meaning of a column. The script from the check uses dissect.esedb only, so compare its counts with a second reader on any ESEDB file you rely on.

Copy the ESEDB file before you touch it. Write down the SHA256 hash of the original and work on a copy. Microsoft's note on soft recovery says ESE recovers every time the UAL service starts, so a file from a live system can change. The hash shows which file you received.

How Do You Open ESEDB Files? Python, libesedb, esentutl and two Windows viewers

Open ESEDB files with a Python module like dissect.esedb, with libesedb tools or with esentutl on Windows. No app opens them by double click. We read all 18 ESEDB files with three Python readers, and the two vendor viewers we found target Exchange mailbox data only.

A double click does nothing useful, because Windows has no viewer for ESEDB files. The table lists the readers and tools we found. It marks what we tested and what we did not, since we worked on Linux.

Programs that open ESEDB files
Program What it does
dissect.esedb A Python module. Version 3.18 opened all 18 ESEDB files and read 16 of 16 rows of the widest table. It listed all 33 flagged records and raised an error on 8.
libesedb binding A Python binding, version 20260704. It opened all 18 ESEDB files and counted their tables. It returned filler for empty cells, skipped flagged records and read 1 of 16 rows of the widest table.
impacket A Python module, version 0.13.1. It opened all 18 ESEDB files and listed their tables. It returned filler for empty cells and read 0 of 16 rows of the widest table.
esedbinfo A libesedb command line tool. Its man page says it reports information about an ESE database file. We did not test it.
esentutl Ships with Windows. Our EDB page on esentutl switches covers headers, logs, recovery and repair. We did not test it.
SysTools ESE DB Viewer A Windows viewer. The vendor says it is built only for ESE files that store Exchange mailbox data, and the demo previews. We did not test it.
Freeviewer ESEDB Viewer A Windows viewer. The vendor says it opens Exchange mailbox databases and the free version previews only. We did not test it.

Open ESEDB files with Python

Install the module with pip install dissect.esedb. The short script lists every table with its column count and row count. For SRUDB.dat from the dissect test set it printed 12 lines.

List the tables of an ESEDB file
from dissect.esedb import EseDB

with open('SRUDB.dat', 'rb') as fh:
    db = EseDB(fh)
    for table in db.tables():
        print(table.name, len(table.columns), sum(1 for _ in table.records()))
Output of the script
MSysObjects 28 161
MSysObjectsShadow 28 161
MSysObjids 3 28
MSysLocales 3 7
SruDbIdMapTable 3 106
SruDbCheckpointTable 5 0
{17F4D97B-F26A-5E79-3A82-90040A47D13D} 6 6
{841A7317-3805-518B-C2EA-AD224CB4AF84} 5 3
{D10CA2FE-6FCF-4F6D-848E-B2E99266FA89} 19 203
{DC3D3B50-BB90-5066-FA4E-A5F90DD8B677} 5 3
{DD6636C4-8929-4683-974E-22C046A43763} 9 3
{EEE2F477-0659-5C47-EF03-6D6BEFD441B3} 7 2

The libesedb binding installs as pip install libesedb-python, and impacket as pip install impacket. All three read the same tables, but the reader study shows where their answers differ. Use two readers on any ESEDB file you rely on.

Open ESEDB files with libesedb tools

The libesedb project ships command line tools. The esedbinfo man page says the tool determines information about an ESE database file. We did not test the tools. Our Python binding comes from the same library.

Open ESEDB files on Windows

Windows includes esentutl. Microsoft's archived esentutl page lists modes for defragmentation, recovery, integrity, checksum, repair, file dump and copy. It says it applies to Windows Vista through Windows 8, and parts of it are unfinished placeholders.

Two vendors list Windows viewers on their pages. The cards show what those pages say, and we tested neither program.

SysTools ESE DB Viewer

Browses Exchange mailbox databases without an Exchange Server, and the demo previews them

Windows viewer

Freeviewer ESEDB Viewer

Opens Exchange mailbox databases, previews data items in the free version and adds search and export in Pro

Windows viewer

SysTools says its viewer works without an Exchange Server and exports to HTML, PDF and CSV. Its FAQ says the tool is built only for ESE files that store Exchange mailbox data, and that Windows Live Mail files are not supported. The page names Windows 8.1 and below in one place and any Windows version in another.

Freeviewer says its viewer previews data items in the free version and that search and export need Pro. Its page gives the signature ef cd ab 89 at offset 4. It lists Windows versions up to Windows 10, and it does not claim to open other ESE databases.

Neither page names SRUDB.dat or Windows.edb. Both viewers target Exchange mailbox data. For SRUM, Windows Search, WebCache, File History or User Access Logging files, use a Python reader and the export steps.

How Do You Check an ESEDB File? Six steps, and the first line names the app

Check an ESEDB file in six steps. Read the signature, name the type with the file command, list the tables with our 114 line script and compare one count with a second reader. The script names the app, counts rows and flagged records and never writes to the ESEDB file.

The first two steps need no Python. The signature check shows efcd ab89 because xxd prints two byte groups. The file command then names the type, the format version, the page size and the Windows version stored in the header. It named all 18 of our ESEDB files correctly.

Steps 2 and 3 on one ESEDB file
xxd -s 4 -l 4 SRUDB.dat
00000004: efcd ab89                                ....
file SRUDB.dat
SRUDB.dat: Extensible storage engine DataBase, version 0x620, checksum 0x4ab4ac4d, page size 4096, Windows version 10.0

For the 4 dirty ESEDB files, the file output adds DirtyShutdown after the page size. For the other 14 it adds nothing. Its Windows version field read 10.0 for 8 of the 11 Windows files, 6.3 for 2 and 6.1 for plaso-Windows.edb. The 7 test files read 6.2.

Check ESEDB files with Python

The script uses dissect.esedb and Python's own modules. It reads every table, counts its columns and rows and counts records with a deleted flag. It also names the app from the table names, and it exports to CSV or SQLite when you ask. Section 11 uses that.

Name the app and list the tables of an ESEDB file
#!/usr/bin/env python3
"""ese_who.py - say which Windows app an ESE file belongs to, list its tables, export to CSV.
Needs: pip install dissect.esedb
Usage: python3 ese_who.py FILE [--csv OUTDIR | --sqlite OUT.db]
"""
import csv, datetime, os, sqlite3, sys
from dissect.esedb import EseDB

OLE0 = datetime.datetime(1899, 12, 30)
FT0 = datetime.datetime(1601, 1, 1)

RULES = [  # (app, test on the set of table names)
    ("Windows User Access Logging (Current.mdb or GUID.mdb)", lambda t: {"ROLE_ACCESS", "CLIENTS", "DNS", "VIRTUALMACHINES"} <= t),
    ("Windows User Access Logging (SystemIdentity.mdb)", lambda t: {"SYSTEM_IDENTITY", "ROLE_IDS"} <= t),
    ("SRUM (SRUDB.dat)", lambda t: "SruDbIdMapTable" in t),
    ("Windows Search (Windows.edb)", lambda t: any(n.startswith("SystemIndex_") for n in t)),
    ("IE/Edge WebCache (WebCacheV01.dat)", lambda t: "Containers" in t and any(n.startswith("Container_") for n in t)),
    ("File History (Catalog1.edb)", lambda t: {"namespace", "backupset", "file", "library"} <= t),
]

def guess(names):
    t = set(names)
    for app, test in RULES:
        if test(t):
            return app
    return "no fingerprint matched (unknown app or test data)"

def when(value, ctype):
    """DateTime columns store an OLE day count (float bits) or a FILETIME; decide by range."""
    import struct
    raw = value if isinstance(value, int) else None
    if raw is None:
        return value
    as_double = struct.unpack("<d", struct.pack("<q", raw))[0] if -2**63 <= raw < 2**63 else None
    try:
        d = OLE0 + datetime.timedelta(days=as_double)
        if 1995 <= d.year <= 2030:
            return d.isoformat(sep=" ")
    except (OverflowError, TypeError, ValueError):
        pass
    try:
        d = FT0 + datetime.timedelta(microseconds=raw // 10)
        if 1995 <= d.year <= 2030:
            return d.isoformat(sep=" ")
    except (OverflowError, ValueError):
        pass
    return raw

def cell(v, col):
    if col.type.name == "DateTime":
        return when(v, col.type)
    if isinstance(v, (bytes, bytearray)):
        return v.hex()
    return v


def main(path, outdir=None, sqlite_path=None):
    lite = sqlite3.connect(sqlite_path) if sqlite_path else None
    with open(path, "rb") as fh:
        db = EseDB(fh)
        tables = list(db.tables())
        print(f"{os.path.basename(path)}: {guess(t.name for t in tables)}")
        for t in tables:
            rows = bad = deleted = 0
            writer = out = None
            batch = []
            cols = [c.name for c in t.columns]
            if lite and not t.name.startswith("MSys"):
                quoted = ", ".join('"%s"' % c.replace('"', '""') for c in ["_deleted_flag"] + cols)
                lite.execute('CREATE TABLE "%s" (%s)' % (t.name.replace('"', '""'), quoted))
            if outdir and not t.name.startswith("MSys"):
                os.makedirs(outdir, exist_ok=True)
                safe = "".join(ch if ch.isalnum() or ch in "-_" else "_" for ch in t.name)
                out = open(os.path.join(outdir, safe + ".csv"), "w", newline="", encoding="utf-8")
                writer = csv.writer(out)
                writer.writerow(["_deleted_flag"] + cols)
            for rec in t.records():
                flagged = "Deleted" in str(rec._node.tag.flags)
                deleted += flagged
                try:
                    d = rec.as_dict()
                except Exception:
                    bad += 1
                    continue
                rows += 1
                if writer or lite and not t.name.startswith("MSys"):
                    vals = []
                    for c in t.columns:
                        v = d.get(c.name)
                        if isinstance(v, list):  # multi-value column
                            v = "; ".join(str(cell(x, c)) for x in v)
                        else:
                            v = cell(v, c)
                        vals.append(v)
                    if writer:
                        writer.writerow([int(flagged)] + vals)
                    else:
                        batch.append([int(flagged)] + [int(v) if isinstance(v, bool) else v for v in vals])
            if out:
                out.close()
            if lite and batch:
                lite.executemany('INSERT INTO "%s" VALUES (%s)' % (t.name.replace('"', '""'), ",".join("?" * (len(cols) + 1))), batch)
            print(f"  {t.name:<40} cols {len(cols):>6} rows {rows:>7}  deleted-flag {deleted:>3}  unreadable {bad}")

    if lite:
        lite.commit()
        lite.close()


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit(__doc__)
    flag = sys.argv[2] if len(sys.argv) > 3 else None
    main(sys.argv[1], sys.argv[3] if flag == "--csv" else None, sys.argv[3] if flag == "--sqlite" else None)

Here is what it printed for three more files. The first is a Windows Search file with a 381 column table. The second lists only the WebCache tables that had flagged or unreadable records. The third is a test file with no app.

A Windows Search file
python3 ese_who.py plaso-Windows.edb
plaso-Windows.edb: Windows Search (Windows.edb)
  MSysObjects                              cols     25 rows     542  deleted-flag   0  unreadable 0
  MSysObjectsShadow                        cols     25 rows     542  deleted-flag   0  unreadable 0
  MSysUnicodeFixupVer2                     cols     10 rows       0  deleted-flag   0  unreadable 0
  __NameTable__                            cols      8 rows       1  deleted-flag   0  unreadable 0
  SystemIndex_Gthr                         cols     16 rows     353  deleted-flag  14  unreadable 0
  SystemIndex_GthrPth                      cols      3 rows     131  deleted-flag   8  unreadable 0
  SystemIndex_0P                           cols     10 rows     376  deleted-flag   0  unreadable 0
  SystemIndex_0A                           cols    381 rows     322  deleted-flag   0  unreadable 0
Tables with flagged and unreadable records
python3 ese_who.py plaso-WebCacheV01.dat
plaso-WebCacheV01.dat: IE/Edge WebCache (WebCacheV01.dat)
  LeakFiles                                cols      3 rows       0  deleted-flag   1  unreadable 1
  DependencyEntry_3                        cols      7 rows       0  deleted-flag   1  unreadable 1
  DependencyEntry_5                        cols      7 rows       0  deleted-flag   1  unreadable 1
A test file with no app
python3 ese_who.py dissect-basic.edb
dissect-basic.edb: no fingerprint matched (unknown app or test data)
  MSysObjects                              cols     28 rows      87  deleted-flag   0  unreadable 0
  MSysObjectsShadow                        cols     28 rows      87  deleted-flag   0  unreadable 0

The first line of every answer names the app, or says that no fingerprint matched. The table lines show where to look next. A table with a flagged count above 0 needs a second reader.

Check the ESEDB header with Python

dissect.esedb also gives the header values. The short script prints the page size and the database state. It printed 4096 and 3 for SRUDB.dat. A 3 means a normal shutdown, and a 2 means a dirty one. The libesedb binding gave the same page size for all 18 ESEDB files.

Read the page size and state
from dissect.esedb import EseDB

with open('SRUDB.dat', 'rb') as fh:
    db = EseDB(fh)
    print('page size', db.page_size)
    print('state', db.header.dbstate)
Output of the script
page size 4096
state 3

What the script found in 18 ESEDB files

Our script on 18 ESEDB files
Result Cases
App named (11) User Access Logging 3, SRUM 2, Windows Search 2, WebCache 3 and File History 1.
No match (7) The seven dissect test files, which have no app.
Flagged records (33) In 12 tables of 5 ESEDB files. The script counts them and writes a _deleted_flag column.
Unreadable records (8) In 8 tables of the two WebCache files plaso-WebCacheV01.dat and plaso-WebCacheV01_cookies.dat. The script skips them and says how many.
Files changed (0) The SHA256 hash of all 18 ESEDB files matched the hash we took before the tests.

The script reads structure and nothing else. It does not check page checksums, read log files or repair a file. Our page checksum script for 4 KB pages covers the checksum side of the same 18 ESEDB files. It uses one reader, so compare its counts with a second.

Check order for any ESEDB file

  1. Copy the ESEDB file, write its SHA256 hash and work on the copy.
  2. Read bytes 4 to 7. They must be ef cd ab 89.
  3. Name the file type with the file command. It prints the page size and DirtyShutdown for a dirty file.
  4. List the tables with our script. The first line names the app and the first table must be MSysObjects.
  5. Read the counts. Look for flagged records, unreadable records and tables with thousands of columns.
  6. Count the rows of one table with a second reader. A difference means a deleted flag, filler or a reader limit.

A pass is not a promise. A good listing means the signature, the catalog and the tables read in one reader. It does not prove that every row is right, and a second reader can return other values, as the five tests show.

Are ESEDB Files Safe to Open? Reading is safe, and the contents are private

Reading an ESEDB file with a script is safe, because the readers only read bytes. The risk is in the contents. SRUM, Windows Search and WebCache files record what a person did, so treat every copy as private. None of our 18 ESEDB files changed when we read them.

The ending says nothing about safety. An ESEDB file is a database, and its safety depends on who wrote it and what it contains. The table lists what to check, and every row ends with its source.

What to check before you open ESEDB files
Check What to do
The sender An ESEDB file is a database, but its tables can contain names, paths and links. Open nothing from a sender you do not know. Source: Our advice.
Reading dissect.esedb, the libesedb binding, impacket, xxd, the file command and our script only read bytes. We used them on all 18 ESEDB files, and every SHA256 hash matched the hash from before. Source: Our tests.
Private data Windows Search, WebCache and SRUM files record file names, web addresses and app use. Treat the contents as private, and share a copy only with the owner's consent. Source: Our advice.
Browser data WebCache files have containers for visited addresses, cookies and web storage, per Forensic Focus. A shared copy can expose a person's history. Source: Forensic Focus.
Live files A file from a running system can change while you copy it. Microsoft says ESE recovers every time the UAL service starts. Copy it once and read only the copy. Source: Microsoft.
Repair modes esentutl has recovery and repair modes. Use them only on a copy, because a repair rewrites the file. Source: Microsoft archive.
Viewers Vendors say their viewers open Exchange mailbox data. We tested none. Try one on a copy, and read the limits of its free version first. Source: Vendor pages.

Why a script is safer than an app

A script prints what it finds in an ESEDB file. An app mounts the database, and a Windows tool can recover or write to it. We checked the SHA256 hash of all 18 ESEDB files before and after every reader and export. Every hash was the same.

Use a script first when you are not sure what a file is. Read the signature. List the tables. Look at the app name. Then decide, and keep the original untouched.

Why you should read the first bytes of an ESEDB file

A file that ends in .edb, .dat or .mdb can be anything. A good ESEDB file has ef cd ab 89 at byte 4. The file command named all 18 of ours, and the 3 .mdb files from User Access Logging are ESE databases and not Access files.

A name is not a promise. Files called SRUDB.dat or Current.mdb are only names. Check where the ESEDB file came from, hash it and read its signature before you open it in an app.

Why Will Your ESEDB File Not Open or Read Wrong? Eight causes, and what to do about every one

Most ESEDB files that will not open or read wrong fail for one of eight reasons. Check the signature first, then use a second reader. Filler values, skipped records and compressed values give wrong results with no error. All 18 of our ESEDB files opened in all three readers.

We used three readers on 18 ESEDB files. They gave different answers in all five tests, and no file was lost. Four files were dirty, 33 records had a deleted flag and one table had 64,993 columns. The table groups what we saw into eight causes.

Read the table slowly. The number in brackets matches the cause. Every row says what the readers did and what to try. The fixes work on a copy. A reader that says nothing is no sign that the numbers are right.

Eight causes of ESEDB files that will not open or read wrong
Cause Result and fix
The wrong app (1) Windows has no app for the format. A viewer built for Exchange mailbox data shows nothing useful for SRUM or Windows Search files, and an .mdb ending can mean Access instead. Fix: Read the first bytes, as the first byte test shows. Then use a Python reader from the open section.
A dirty shutdown (2) 4 of our 18 ESEDB files were dirty, and all 4 contain flagged records. The libesedb binding and dissect.esedb still opened all 4. Fix: Copy the file with its log files and try soft recovery on the copy. The EDB page on soft recovery lists the switches.
A damaged header or page (3) On our EDB damage tests, a zeroed signature stopped all three readers. Fix: Copying the header from page 2 over page 1 repaired 4 of 4 damaged headers on 4 KB page files, as the header repair test found.
A table that is too wide (4) dissect-large.edb has 64,993 tagged columns. The libesedb binding read 1 of 16 rows, impacket read 0 and dissect.esedb read 16. Fix: Use dissect.esedb and export to CSV, because SQLite defaults to a limit of about 2,000 columns.
Flagged records that raise errors (5) dissect.esedb listed 33 flagged records and raised a TypeError on 8 of them, in the two WebCache files. Fix: Catch the error per record, as our script does. Count what you skipped.
Filler values (6) The libesedb binding and impacket returned 0x2a bytes for 81,532 empty cells in 12 ESEDB files. Fix: Compare empty cells with dissect.esedb, and treat a fixed size value made only of 0x2a bytes as empty until you have checked it.
Compressed values (7) The libesedb binding was wrong for 2 of 6 compressed test cells, with no error. Fix: Decode with dissect.esedb, and compare the length of a value with a second reader.
Dates read as numbers (8) No reader returned a date. A DateTime column stores an OLE day count or a FILETIME. Fix: Decode both ways and keep the reading that gives a date you can explain.

Read an ESEDB file that will not open, step by step

We tested the steps on Python 3.13 with three readers on Linux. We did not test them on Windows, and we tested no Exchange mailbox database here. The steps keep the original untouched at every point.

  1. Copy the ESEDB file, write its SHA256 hash and work on the copy.
  2. Read the signature and the file output with the signature check. If the signature is missing, stop, because the file is not an ESEDB file.
  3. List the tables with our script. Read the first failure and the counts.
  4. If the ESEDB file is dirty, copy any log files beside it and try soft recovery with esentutl on the copy.
  5. If one reader fails on a table, try a second reader on that table.
  6. If values look wrong, check for filler, compressed cells and date encodings before you trust a number.
  7. Export what you can to CSV, keep the original and keep the hash.

What to send a helper

Short messages get faster answers. Include the details below. Send the output of the script too, because its first line names the app and its table lines show the counts.

A message that gets an answer
Subject: ESEDB file will not read
File: the name, the size in bytes and the SHA256 hash
Header: the output of the file command
Tables: the output of ese_who.py
System: Windows version and the name of the reader
What happens: the exact words of the error
Source: where the ESEDB file came from and when it was copied

What no step can fix

Zeroed or missing pages A page that is gone takes its rows with it. On our EDB damage tests, two zeroed pages cost 14 rows. Only a backup brings them back.
Flagged records They could be rows an app deleted. We decoded 25 of 33 and did not test whether they contain recoverable data.
A table with thousands of columns dissect.esedb read all 16 rows of the 64,993 column table in 2.1 seconds.
Filler values Compare with a second reader and treat a string of 0x2a bytes in a fixed size cell as empty.
Dates as numbers Decode the value as an OLE day count and as a FILETIME, then keep the one that fits.
A dirty shutdown The libesedb binding and dissect.esedb opened all 4 dirty files.

Do not try random repair sites. A site that offers to fix any database for free can keep the upload, and an ESEDB file can contain browsing history. Try the steps above on a copy first.

How Do You Export an ESEDB File? CSV, SQLite and PST, plus what to do about dates

Export ESEDB files with our script. It wrote CSV for all 18 ESEDB files and SQLite for 17, and no source file changed. The one failure has 64,994 columns. Decode the time columns yourself, because a LongLong column stays an integer.

Exporting means reading every table and writing it in another format. The table lists what we found for our 18 ESEDB files, and every row names its source. All figures come from the script in the script listing.

Exports and what we found
Type Finding and source
ESEDB to CSV Our script wrote CSV for all 18 ESEDB files, 40.8 MB in 33.1 seconds, or 12.0 seconds without dissect-large.edb. It writes one file per table, leaves the MSys tables out and adds a _deleted_flag column. Source: Our tests.
ESEDB to SQLite The script wrote SQLite for 17 of 18 ESEDB files. dissect-large.edb failed with "too many columns on large", because SQLite defaults to a limit of about 2,000 columns. Source: Our tests and sqlite.org.
Dates The script converts DateTime columns to text. LongLong columns stay integers, so decode them in SQL or Python. Source: Our tests.
Row counts The SQLite row counts matched the script output for all 17 ESEDB files. Source: Our tests.
ESEDB to PST A PST file contains mail. Only Exchange mailbox databases contain mail, and both vendor pages target those. Source: Vendor pages.

Export an ESEDB file to CSV

The first command installs the reader. The second writes one CSV file per table into a folder. The third writes a SQLite database. Binary values come out as hex, and a cell with several values joins them with a semicolon.

Export an ESEDB file from a terminal
pip install dissect.esedb
python3 ese_who.py SRUDB.dat --csv srum_csv
python3 ese_who.py SRUDB.dat --sqlite srum.db

The commands are the ones we used, with the file name shortened. CSV for all 18 ESEDB files took 33.1 seconds on Linux, and 12.0 seconds without the 64,994 column file. Open the CSV in a spreadsheet, and filter on _deleted_flag to separate flagged records.

Export an ESEDB file to SQLite

SQLite is easier to query. A File History file shows why you must decode times yourself. Its namespace table stores fileCreated as a LongLong FILETIME. The query subtracts the seconds from 1601 to 1970 and prints the oldest and newest dates.

Decode a FILETIME column in SQL
SELECT COUNT(*) AS files,
       MIN(datetime(fileCreated / 10000000 - 11644473600, 'unixepoch')) AS first_created,
       MAX(datetime(fileCreated / 10000000 - 11644473600, 'unixepoch')) AS last_created
FROM namespace
WHERE fileCreated > 0;
Output of the query, SQLite 3.45.1
files | first_created | last_created
1340 | 2012-07-31 11:09:00 | 2013-10-22 16:33:54

The number 11,644,473,600 is the count of seconds between January 1, 1601 and January 1, 1970. The query found 1,340 namespace rows with a creation date, from July 31, 2012 to October 22, 2013. In a DateTime column our script has already decoded the date.

Export an ESEDB file to PST

A PST file contains mail, and only Exchange mailbox databases contain mail. SRUM, Windows Search, WebCache and User Access Logging files have no mailbox to convert. Our page on how to convert EDB to PST covers mailbox databases.

Keep the originals. Export reads the file and never changes it, and our hashes matched. Keep the original and the export side by side, and note which reader made the export.

How Do ESEDB Files Differ From Access and SQLite Files? The first bytes say which one you have

An ESEDB file starts with ef cd ab 89 at byte 4. An Access MDB file has the text Standard Jet DB there, an Access ACCDB file has Standard ACE DB and a SQLite file has SQLite format 3 at byte 0. The first bytes separate all four.

Four formats get mixed up, and two of them share an ending. The table lists the first bytes we saw in 23 ESEDB files. Every row ends with its source.

First bytes of ESE, Access and SQLite files
Format What the first bytes show
ESE database Signature ef cd ab 89 at byte 4. All 18 ESEDB files in our set had it. Source: Our tests.
Access MDB The text Standard Jet DB at byte 4. The 2 Access MDB files we checked had it. Source: Our tests.
Access ACCDB The text Standard ACE DB at byte 4. The 2 Access ACCDB files we checked had it. Source: Our tests.
SQLite The text SQLite format 3 at byte 0. The SQLite file we made from an export had it. Source: Our tests.

Separate them with Python

The short script reads the first 32 bytes of every file and names the format. We started it on 23 ESEDB files, which are our 18 ESEDB files, 2 Access MDB files, 2 Access ACCDB files and 1 SQLite file. The Access files come from open test sets.

Name the format from the first bytes
import sys

for path in sys.argv[1:]:
    head = open(path, 'rb').read(32)
    if head[4:8] == bytes.fromhex('efcdab89'):
        kind = 'ESE database'
    elif head[4:19] == b'Standard Jet DB':
        kind = 'Access MDB file'
    elif head[4:19] == b'Standard ACE DB':
        kind = 'Access ACCDB file'
    elif head[:15] == b'SQLite format 3':
        kind = 'SQLite database'
    else:
        kind = 'something else'
    print(path.split('/')[-1], '->', kind)
Output for nine of the files
dissect-Current.mdb -> ESE database
plaso-SystemIdentity.mdb -> ESE database
plaso-SRUDB.dat -> ESE database
dissect-Windows.edb -> ESE database
test.mdb -> Access MDB file
test2V2003.mdb -> Access MDB file
test2V2007.accdb -> Access ACCDB file
testEmoticonsV2010.accdb -> Access ACCDB file
sample.sqlite -> SQLite database
Counts for all 23 ESEDB files
18 ESE database
2 Access MDB file
2 Access ACCDB file
1 SQLite database

Why the .mdb ending misleads

Two unrelated formats use .mdb. Microsoft Access used it for Jet databases, and the Access files we checked start with Standard Jet DB at byte 4. User Access Logging uses it for ESE databases, which start with ef cd ab 89. The wrong tool gives an error that never names the cause.

Which one do you have

An Access file Open it in Access. The MDB file format page and the ACCDB file format page cover both formats.
A Windows system file Read it as an ESEDB file with Python. The table of Windows features shows how to find the feature behind it.
An app database Open it with a SQLite tool. The SQLite file format page covers it.
Exchange mail Use the EDB file format page for mailbox files and their tools.

Wikipedia calls ESE an ISAM store, and Microsoft's overview lists a C API. SQLite files use SQL, and you can query them with the sqlite3 tool once you export an ESEDB file, as the export steps show.

What Is the ESEDB File Format Made Of? The facts we could check, and where they came from

ESEDB files are a Microsoft format, and Microsoft released the engine under the MIT License in 2021. The facts below come from Wikipedia, Microsoft, Forensics Wiki, Forensic Focus, FileInfo and our checks of 18 ESEDB files.

Facts about the ESEDB file format
Part What we could check
Maker Microsoft. Wikipedia says the engine first shipped in 1994 with Windows NT 3.5, and that Microsoft released it under the MIT License in January 2021. Microsoft's overview says Windows 2000.
Kind An ISAM database engine with a C API, per Wikipedia and Microsoft.
Header A checksum at byte 0 and the signature ef cd ab 89 at byte 4. The file command printed format version 0x620 for all 18 ESEDB files.
Catalog MSysObjects, the first table in all 18 ESEDB files.
Page size Wikipedia lists 2, 4, 8, 16 and 32 KB. Our files use 4,096 bytes in 12 ESEDB files and 32,768 bytes in 6.
Limits Wikipedia lists 127 fixed, 128 variable and 64,993 tagged columns per table. It lists 2 GB for a Long Text or Long Binary value.
Ending .edb, .dat and .mdb in our ESEDB files. Forensics Wiki lists .edb, .sdb and .stm.
Media type None. Forensics Wiki lists none.
Detection ef cd ab 89 at byte 4. The file command names it "Extensible storage engine DataBase".
Related formats Access Jet and ACE databases and SQLite, compared in section 12. The EDB page covers Exchange.
Our tests 18 ESEDB files, three readers, five tests, the file command and a 114 line script on Linux.

About the values we list

Facts about structure come from our ESEDB files and the sources named in every row. Where sources differ, we say so. Wikipedia and Microsoft give different start dates for the engine, and one vendor page lists Windows 8.1 and below in one place and any Windows version in another.

The SysTools page also calls its viewer freeware in one place and says the demo only previews in another. We list both views and mark that we tested neither.

What we left out

We did not start esentutl or the two vendor viewers. We tested no Exchange mailbox database here, because the EDB page covers those. We had no NTDS, WINS or DHCP files. Seven of our 18 ESEDB files are test files.

We wrote the six app rules after seeing the files. We decoded the date columns we list and did not decode every LongLong column. Our files come from two public test sets, so a reader that fails on them can still work on yours. We name no price.

Sources checked

For the engine, see the Wikipedia article on ESE and the Microsoft overview of ESE. For the format, see the Forensics Wiki format page and the FileInfo page for EDB. For User Access Logging, see the Microsoft guide to UAL.

For Windows Search and the browser cache, see the Forensic Focus guides to Windows Search forensics, the IE10 ESE database and the Edge database. For dirty files, see the SANS blog on ESE. For tools, see the esentutl modes and the libesedb tool page.

Every count came from the files and scripts in our research folder.

Frequently Asked Questions About ESEDB Files

What is an ESEDB file?

An ESEDB file is a database in the Extensible Storage Engine format. Windows uses it for Search, SRUM, WebCache, File History and User Access Logging. It contains tables of rows and columns, and the signature ef cd ab 89 starts at byte 4.

How do I open an ESEDB file?

No app opens one by double click. Use a Python module like dissect.esedb, the libesedb tools or esentutl on Windows. We read all 18 of our ESEDB files with three Python readers, and the two vendor viewers we found target Exchange mailbox data.

Is .esedb a file extension?

ESEDB is the name of the format, and none of our 18 ESEDB files ended in .esedb. They ended in .edb, .dat and .mdb. Forensics Wiki also lists .sdb and .stm.

What is inside an ESEDB file?

A header page, a catalog table named MSysObjects and the tables of an app. SRUDB.dat from the dissect test set has 655,360 bytes, 12 tables and 59 user columns. The catalog was the first table in all 18 of our ESEDB files.

Which app made my ESEDB file?

Read the table names. ROLE_ACCESS and CLIENTS mean User Access Logging, and SruDbIdMapTable means SRUM. Names that start with SystemIndex_ mean Windows Search, and Containers with Container_1 means WebCache. The names namespace, backupset, file and library mean File History.

Can I open an ESEDB file on Windows?

Windows includes esentutl, which lists modes for file dump, recovery and repair. We did not test it. SysTools and Freeviewer list Windows viewers on their pages, and both say they target Exchange mailbox data. We tested neither one.

What is the difference between an EDB file and an ESEDB file?

EDB is the most common ending, and ESEDB names the format. An EDB file can be an Exchange mailbox database or a Windows file like Windows.edb. Our EDB page covers Exchange, and this page covers the contents and the readers.

Why does my ESEDB file show wrong values?

Readers differ. In our tests the libesedb binding returned filler bytes for 81,532 empty cells, and it returned wrong values for 2 of 6 compressed cells. Compare with dissect.esedb and a second reader before you trust a number.

How do I read dates in an ESEDB file?

A reader returns a number and not a date. DateTime columns stored an OLE day count in 5 of our ESEDB files and a FILETIME in 3. LongLong columns stored FILETIME values. Decode both ways and keep the reading that gives a date you can explain.

Can I convert an ESEDB file to CSV or SQLite?

Yes. Our script wrote CSV for all 18 ESEDB files and SQLite for 17. The one that failed has 64,994 columns, and SQLite defaults to a limit of about 2,000. Only Exchange mailbox databases convert to PST.

Is an ESEDB file safe to open?

Reading one with a script is safe, and none of our 18 ESEDB files changed. The contents can be private, because SRUM, Windows Search and WebCache files record what a person did. Work on a copy and share it with care.

Is a .mdb file an ESEDB file?

Not always. Microsoft Access files end in .mdb and have Standard Jet DB at byte 4. The 3 .mdb files in our set came from User Access Logging, and they have ef cd ab 89 at byte 4. Read the first bytes.

Still Stuck With an ESEDB File? Seven routes out, depending on what went wrong

Your ESEDB file will not open

Read the signature first, then try a second reader. The cause table lists what to try in order.

Your numbers look wrong

Compare counts with a second reader and look for filler and date encodings. The reader grid shows what to look for.

Is it an Exchange mailbox database? The page on EDB files covers mailbox databases, the header and the damage tests.

Is it an Access database? The page on ACCDB files and the page on MDB files cover the formats that Microsoft Access writes.

Is it an app database in SQLite? The page on SQLite files explains the format and the tools that open it.

Is it a loose DAT file? The page on DAT files covers the many unrelated files that share that ending.

Is it a file type you do not know? Browse the file extension resource library, or send it to us on the submit a file type page.