Byte JMP
Windows Internals: Understanding MSI Installers from the Ground Up

Windows Internals: Understanding MSI Installers from the Ground Up

Inside Windows Installer databases, execution sequences, filesystem activity, repair operations, and privilege boundaries. We build a controlled MSI with WiX, inspect its tables in Orca, correlate database entries with real filesystem writes in Process Monitor, and establish the baseline for installer security research.

·20 min read

TL;DR

A Microsoft Installer (.msi) file is not simply an archive that extracts files to Program Files. It is a relational database, stored inside a structured container, that instructs the Windows Installer engine how to install, register, maintain, repair, upgrade, and remove an application.

The installation process involves the Windows Installer service, msiexec.exe, standardized actions, component key paths, installation properties, and custom actions. Some of these operations execute in privileged contexts, while package properties and external resources may be influenced by less-privileged users.

In this post, we build a controlled MSI named Northstar Log Viewer with WiX 7, inspect its tables using Orca, install it with verbose logging, correlate database entries with real filesystem writes in Process Monitor, inspect a SYSTEM-context installer process, repair a missing component key-path file, and uninstall the product. The goal is to establish a reproducible baseline before exploring real misconfigurations in Part 2.

  • Category: Windows Internals / Security Research
  • Series: Windows Installer Security Research — Part 1 of 2
  • Lab: Windows 10 Pro x64 (build 19045), WiX Toolset 7.0.0, Orca, Sysinternals
  • Sample application: Northstar Log Viewer 1.0.0

Introduction

An MSI installer often looks like a simple setup wizard. Behind the UI, Windows Installer must resolve directory paths, evaluate installation conditions, select features and components, calculate disk requirements, register product state, and schedule changes in a transaction-aware way.

This leads to some useful security questions:

  • Which part of an MSI controls the files and directories that Windows modifies?
  • How is the order of installation operations determined?
  • Which operations run in the user context, and which may run through the privileged installer service?
  • How does Windows Installer know that a file is missing and needs repair?
  • Where could untrusted inputs cross a privilege boundary?

Before testing custom actions or insecure policies, we need to understand how the normal installation path behaves. This post focuses on that baseline and uses only a purpose-built, benign MSI.


1. Windows Installer architecture

1.1 An MSI is not a PE executable

A conventional .exe file typically uses the Portable Executable (PE) format. The Windows loader maps its sections, resolves imports, and executes its instructions. An .msi file is different: it uses a compound-file storage container holding the Windows Installer database and associated streams. Files required for installation can be provided through cabinet media, including embedded cabinets.

Windows Installer interprets the package's relational tables. These tables describe what resources exist, where they belong, and which standardized actions manage them.

CharacteristicEXE installerMSI package
General formatExecutable program, usually PEStructured storage containing an installer database
Installation procedureDefined by application-specific codeStandard Windows Installer actions plus optional custom actions
RepairInstaller-specificComponent and key-path based maintenance
Product registrationInstaller-specificWindows Installer product registration
Security contextDepends on process and elevationDepends on client/service context, policy, action type, and elevation

 

An MSI can still contain executable code (DLLs, scripts, custom actions), but the database itself is not a PE image. Windows Installer interprets it; it does not execute it the way the loader executes an EXE.

1.2 msiexec.exe and the service boundary

The command-line entry point is msiexec.exe, available on 64-bit Windows at C:\Windows\System32\msiexec.exe (with a 32-bit counterpart under SysWOW64). Common operations are:

msiexec.exe /i application.msi                 # install
msiexec.exe /x application.msi                 # uninstall
msiexec.exe /f application.msi                 # repair
msiexec.exe /i application.msi /L*V install.log # verbose log

Windows Installer uses a client/server architecture. The interactive client communicates with the installer service to perform system changes. Machine-wide installations involve privileged processes, which we will observe directly in Procmon later.

User session
    |
    v
msiexec.exe (client)
    |
    v
Windows Installer service / execution infrastructure
    |
    v
MSI tables + execution script
    |
    v
Files, registry, installed-product state

The service is named msiserver:

Get-Service msiserver
sc.exe qc msiserver

It can be stopped when no installation is taking place and started on demand.


2. MSI database tables

An MSI contains related tables rather than a flat list of instructions. In our package, Orca showed Property, Directory, File, Component, Feature, FeatureComponents, Media, InstallExecuteSequence, Upgrade, and other standard tables.

TableSecurity-relevant purpose
PropertyProduct identifiers, installation options, properties read by actions
DirectoryParent-child target directory relationships
FilePayload file records and component associations
ComponentInstallation units, directory references, and key paths
Feature / FeatureComponentsWhich components belong to selected features
MediaSource media and cabinet layout
InstallExecuteSequenceScheduled actions and optional conditions
CustomActionCustom code/action definitions when present
UpgradeDetection of related installed products

 

The main relationship we will observe is:

Feature
   |
FeatureComponents
   |
Component ----> Directory
   |
  File

A component can have a key path: a file that Windows Installer checks to determine whether the component is still installed. We will use this in section 8 when we test repair.


3. Creating our controlled MSI

3.1 Lab environment

We prepared two local Windows accounts: labadmin, verified in an elevated PowerShell process with High integrity and the Administrators group enabled; and labuser, verified with Medium integrity without membership in Administrators. UAC was enabled (EnableLUA=1). These accounts will become important in the next part of the series.

Our test machine runs Windows 10 Pro x64, build 19045. Windows 10 is beyond normal support as of October 2025; keep this lab isolated and do not use it to test unknown packages on a production network.

3.2 Northstar Log Viewer 1.0.0

Northstar Log Viewer is a fictional log utility used for this laboratory. Version 1.0.0 is deliberately not a runnable application: it installs three harmless text files so that we can study the Windows Installer mechanism without introducing unrelated application code.

C:\MSI-Lab\
├── Source\NorthstarLogViewer\
│   ├── Package.wxs
│   └── Files\
│       ├── README.txt
│       ├── config.ini
│       └── sample.log
├── Samples\
├── Logs\
└── Evidence\

config.ini contains a sample application profile:

[Application]
Name=Northstar Log Viewer
Version=1.0.0
LogLevel=INFO

[Logging]
Enabled=true
Format=PlainText
MaxSizeMB=10

[Research]
Environment=ByteJMP-Lab

The sample log contains static demonstration entries; they were not generated by an executing log-viewer application.

3.3 WiX source

We used WiX Toolset 7.0.0 with the following Package.wxs:

<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
  <Package
    Name="Northstar Log Viewer"
    Manufacturer="Northstar Software"
    Version="1.0.0"
    UpgradeCode="8DBE6507-612E-4C1A-BF60-BAFEF190E122"
    Scope="perMachine">

    <MajorUpgrade
      DowngradeErrorMessage="A newer version is already installed." />
    <MediaTemplate EmbedCab="yes" />

    <StandardDirectory Id="ProgramFiles6432Folder">
      <Directory Id="ManufacturerFolder" Name="Northstar Software">
        <Directory Id="INSTALLFOLDER" Name="Northstar Log Viewer">
          <Component Id="CmpReadme" Guid="*">
            <File Id="FileReadme" Source="Files\README.txt" KeyPath="yes" />
          </Component>
          <Component Id="CmpConfig" Guid="*">
            <File Id="FileConfig" Source="Files\config.ini" KeyPath="yes" />
          </Component>
          <Component Id="CmpSampleLog" Guid="*">
            <File Id="FileSampleLog" Source="Files\sample.log" KeyPath="yes" />
          </Component>
        </Directory>
      </Directory>
    </StandardDirectory>

    <Feature Id="MainFeature" Title="Northstar Log Viewer">
      <ComponentRef Id="CmpReadme" />
      <ComponentRef Id="CmpConfig" />
      <ComponentRef Id="CmpSampleLog" />
    </Feature>
  </Package>
</Wix>

Scope="perMachine" requests machine-wide installation. MediaTemplate EmbedCab="yes" embeds cabinet media into the package. Each Component defines one file and its key path. WiX resolves the ProgramFiles6432Folder directory into the 64-bit Program Files location for this x64 build, as seen in the generated database.

3.4 Compiling and hashing the file

With WiX installed and its license terms accepted, our build command was:

Set-Location "C:\MSI-Lab\Source\NorthstarLogViewer"
& "$env:USERPROFILE\.dotnet\tools\wix.exe" build `
    .\Package.wxs -arch x64 `
    -o "C:\MSI-Lab\Samples\NorthstarLogViewer-1.0.0.msi"

Get-Item "C:\MSI-Lab\Samples\NorthstarLogViewer-1.0.0.msi" |
    Select-Object Name, Length, FullName

Get-FileHash "C:\MSI-Lab\Samples\NorthstarLogViewer-1.0.0.msi" -Algorithm SHA256

The generated MSI was 32,768 bytes (32 KiB). Its SHA-256 was:

EAA8D2221D4FAF0F911882901C2F7403A9F40153ED41E680856117B1901D9091

Building Northstar Log Viewer with WiX Toolset 7 and verifying the generated MSI and SHA-256.

This hash identifies the exact baseline sample inspected below. Editing the database or rebuilding the MSI may produce a different hash.


4. Static analysis with Orca

We opened NorthstarLogViewer-1.0.0.msi in Orca, Microsoft's MSI database editor, and examined the following tables. The original package was left unchanged.

4.1 The Property table

Product metadata and installation properties shown in Orca's Property table.

The screenshot establishes the following observed values:

PropertyValue
ALLUSERS1
ManufacturerNorthstar Software
ProductCode{7CE8BF7F-3EB3-45C2-AEE1-179CEBC72376}
ProductLanguage0
ProductNameNorthstar Log Viewer
ProductVersion1.0.0
UpgradeCode{8DBE6507-612E-4C1A-BF60-BAFEF190E122}
SecureCustomPropertiesWIX_DOWNGRADE_DETECTED;WIX_UPGRADE_DETECTED

 

ALLUSERS=1 means machine-wide installation. ProductCode identifies this specific package, while UpgradeCode ties together related releases (so that a future 2.0 can find and replace 1.0). The secure upgrade properties are used internally by WiX for upgrade detection.

4.2 The File table

File records for the three Northstar resources, including component references, sizes, attributes, and sequence values.

The table contains the three expected files:

File IDComponentFile nameSize
FileReadmeCmpReadmeREADME.txt338 bytes
FileConfigCmpConfigconfig.ini159 bytes
FileSampleLogCmpSampleLogsample.log144 bytes

 

All three entries have file attributes value 512, the Windows Installer msidbFileAttributesCompressed flag. Their Sequence values are 1, 2, and 3. This matches our use of embedded cabinet media.

4.3 The Component table

Three components associated with INSTALLFOLDER, showing their component GUIDs, attributes, and file key paths.

The component entries identify:

  • CmpReadme → FileReadme
  • CmpConfig → FileConfig
  • CmpSampleLog → FileSampleLog

All target INSTALLFOLDER. Their attributes value 256 sets the msidbComponentAttributes64bit flag, consistent with the x64 build. Each component's key path is a file.

This table will later explain why removing README.txt from its expected path provides a meaningful repair experiment.

4.4 The Directory table

MSI Directory table illustrating TARGETDIR, ProgramFiles64Folder, ManufacturerFolder, and INSTALLFOLDER.

The generated hierarchy is:

TARGETDIR
└── ProgramFiles64Folder
    └── ProgramFiles6432Folder
        └── ManufacturerFolder (Northstar Software)
            └── INSTALLFOLDER (Northstar Log Viewer)

Orca displays short and long directory names in some DefaultDir fields, separated by |. On this Windows 10 x64 system, the final installed path was later confirmed as:

C:\Program Files\Northstar Software\Northstar Log Viewer\

4.5 The InstallExecuteSequence table

Standard Windows Installer actions and sequence values generated for Northstar Log Viewer.

The table defines the relative scheduling of actions. The order of rows in Orca's current view is not necessarily numerical order; the Sequence field is the relevant value. Selected entries are:

ActionSequence
FindRelatedProducts25
LaunchConditions100
CostInitialize800
FileCost900
CostFinalize1000
InstallValidate1400
RemoveExistingProducts1401
InstallInitialize1500
ProcessComponents1600
RemoveFiles3500
CreateFolders3700
InstallFiles4000
RegisterProduct6100
PublishProduct6400
InstallFinalize6600

 

An action in this table does not necessarily run on every invocation. Conditions and installation state determine whether each action is applicable. RemoveExistingProducts being there does not mean a previous version was actually found and removed during our first install.

The engine processes this in two phases: first it walks the sequence to build a transaction script (costing, validation), then it executes that script. InstallFiles at sequence 4000 schedules the file operations; the actual writes happen during script execution under InstallFinalize.

We also observed no CustomAction table in this first sample. This is expected: our WiX source defines only standard installer behavior.


5. Installing the package and reviewing MSI logs

5.1 Troubleshooting the first run

Our first silent installation attempt returned 1619, indicating that Windows Installer could not open the package. A subsequent attempt to hash the MSI produced a file-sharing error, meaning another process held an incompatible file handle. After closing Orca, a direct invocation of msiexec.exe succeeded.

Orca was holding the file open. After closing it, the install worked.

For the successful run, we executed in an elevated labadmin PowerShell session:

$msi = "C:\MSI-Lab\Samples\NorthstarLogViewer-1.0.0.msi"
$log = "C:\MSI-Lab\Logs\northstar-install.log"

& "$env:WINDIR\System32\msiexec.exe" /i $msi /qn /L*V $log
Write-Host "Exit Code: $LASTEXITCODE"

The process returned Exit Code: 0.

Silent installation of Northstar Log Viewer through msiexec.exe, returning exit code 0.

5.2 Checking installed files

We listed the destination directory:

$path = "C:\Program Files\Northstar Software\Northstar Log Viewer"
Get-ChildItem $path | Select-Object Name, Length, LastWriteTime

Files installed under Program Files, matching the names and byte sizes recorded in the MSI File table.

The results contained config.ini (159 bytes), README.txt (338 bytes), and sample.log (144 bytes). These sizes exactly match the Orca File table, connecting the declared MSI resources with observed filesystem state.

5.3 The execution log

The /L*V option saved a verbose log. Filtering for action start and end records showed InstallFiles, RegisterProduct, PublishProduct, and InstallFinalize, among other actions.

Verbose MSI log showing standard action processing during installation.

Several actions ended with Return value 1. In MSI action logging, this indicates a successful action, not process exit code 1. The log also records InstallFiles during scheduling and an Executing op: ActionStart(Name=InstallFiles...) entry during script execution, illustrating the separation between scheduling and execution.

Windows Installer log reporting successful installation and a final engine return code of 0.

The completion log confirms Installation success or error status: 0 and MainEngineThread is returning 0. This corroborates the successful command-line exit code.


6. Watching filesystem activity with Process Monitor

To move from database records to operating-system behavior, we used Process Monitor (Procmon). Because the application was already installed, we performed a forced file repair rather than reinstalling from scratch.

6.1 Capture configuration

In Procmon, we configured a display filter for Path contains Northstar Software, then focused on filesystem activity. This kept the relevant installation folder in view without prematurely assuming which process performed each operation.

Process Monitor filter including filesystem paths containing Northstar Software.

6.2 Forced repair using /fa

With Orca closed and Procmon recording, we invoked:

$msi = "C:\MSI-Lab\Samples\NorthstarLogViewer-1.0.0.msi"
$log = "C:\MSI-Lab\Logs\northstar-force-repair.log"

& "$env:WINDIR\System32\msiexec.exe" /fa $msi /qn /L*V $log
Write-Host "Repair Exit Code: $LASTEXITCODE"

The operation returned Repair Exit Code: 0. /fa requests forced reinstallation of the package's files.

Forcing a file repair with msiexec /fa; the installer returned exit code 0.

6.3 Procmon events

The capture showed msiexec.exe, PID 1524, accessing the Northstar installation path through operations including CreateFile, QueryNetworkOpenInformationFile, CloseFile, and WriteFile. Many visible operations returned SUCCESS.

Procmon events generated during the forced MSI repair, including successful filesystem access and a WriteFile operation.

Worth noting: CreateFile in the Win32 API opens existing files too, not just new ones. The name is misleading if you are reading Procmon output for the first time.

6.4 Inspecting a real WriteFile event

We opened the WriteFile event's properties in Procmon. The event targeted:

C:\Program Files\Northstar Software\Northstar Log Viewer\sample.log

The event details showed:

PropertyObserved result
OperationWriteFile
ResultSUCCESS
Offset0
Length4,096 bytes
I/O flagsNon-cached, Paging I/O, Synchronous Paging I/O

 

Successful WriteFile operation targeting sample.log, starting at offset 0 with a 4,096-byte I/O request.

The 4,096-byte operation length does not imply that sample.log is 4,096 bytes long. The earlier directory listing showed a logical file size of 144 bytes; paging I/O may use a larger request size than the logical content length.

This closes the loop: the file declared in the MSI database, the repair operation, and the observed write event on disk all line up.


7. Which security context performed the write?

We inspected the Process tab for the same Procmon event. Procmon captures process metadata at event time, so we can examine the security context even after the msiexec.exe instance exits.

The installer process behind the captured WriteFile event running as NT AUTHORITY\SYSTEM with System integrity level.

The captured properties were:

FieldObserved value
ProcessC:\Windows\system32\msiexec.exe
Command lineC:\Windows\system32\msiexec.exe /V
PID1524
Parent PID644
UserNT AUTHORITY\SYSTEM
IntegritySystem
Session0
Architecture64-bit

 

Crucially, this same PID 1524 was associated with the WriteFile event targeting sample.log.

So during this repair, the Windows Installer service wrote sample.log as NT AUTHORITY\SYSTEM. We launched the repair from an elevated admin session, so this is expected behavior. The interesting question for Part 2 is: what happens when labuser (a standard user) triggers operations that end up in this same SYSTEM-context process?


8. Repairing a missing component key path

Next, we tested Windows Installer's ability to restore a missing application resource. The Component table had identified FileReadme as the key path of CmpReadme.

8.1 Simulating the missing file

We renamed the installed README while leaving the other files intact:

$installPath = "C:\Program Files\Northstar Software\Northstar Log Viewer"
$readme = Join-Path $installPath "README.txt"
Rename-Item -Path $readme -NewName "README.txt.bak"
Get-ChildItem $installPath | Select-Object Name, Length

The directory listing showed README.txt.bak (338 bytes) and no README.txt. config.ini and sample.log remained present.

Simulating a missing MSI key-path file by renaming README.txt to README.txt.bak.

8.2 Repair using /fp

With Orca closed, we ran a repair focused on missing files:

$msi = "C:\MSI-Lab\Samples\NorthstarLogViewer-1.0.0.msi"
$log = "C:\MSI-Lab\Logs\northstar-missing-file-repair.log"

& "$env:WINDIR\System32\msiexec.exe" /fp $msi /qn /L*V $log
Write-Host "Repair Exit Code: $LASTEXITCODE"

Get-ChildItem $installPath | Select-Object Name, Length
Test-Path $readme

The command returned Repair Exit Code: 0 and Test-Path $readme returned True. The directory contained both README.txt and README.txt.bak, each 338 bytes, along with the other two application files.

Windows Installer restored README.txt while leaving the manually renamed README.txt.bak file in place.

This demonstrates the difference between a resource managed by the MSI and an unrelated backup file created manually in the same directory.

8.3 Correlating repair with the log

The verbose repair log included InstallFiles and a FileCopy operation referencing SourceName=README.txt and SourceCabKey=FileReadme. It also indicated that the source for FileReadme was compressed.

The repair log recording FileCopy for README.txt from the compressed MSI source.

This supports a specific chain of evidence:

Component table: CmpReadme -> FileReadme key path
             |
README.txt absent from expected path
             |
msiexec /fp completed successfully
             |
Verbose log: FileCopy / FileReadme
             |
README.txt restored in Program Files

The key-path metadata, maintenance action, and final filesystem state now agree.


9. Uninstalling the product

The final lifecycle test used the original package to remove Northstar Log Viewer:

$msi = "C:\MSI-Lab\Samples\NorthstarLogViewer-1.0.0.msi"
$uninstallLog = "C:\MSI-Lab\Logs\northstar-uninstall.log"

& "$env:WINDIR\System32\msiexec.exe" /x $msi /qn /L*V $uninstallLog
Write-Host "Uninstall Exit Code: $LASTEXITCODE"

The operation returned Uninstall Exit Code: 0.

Removing Northstar Log Viewer through msiexec /x and receiving exit code 0.

The verbose log included the completion message "Windows Installer removed the product" and Removal success or error status: 0; the engine also returned 0.

Verbose Windows Installer log confirming successful removal of Northstar Log Viewer.

Note that README.txt.bak was created manually, outside the MSI database. Whether it survived uninstallation is a separate question worth checking:

$installPath = "C:\Program Files\Northstar Software\Northstar Log Viewer"
Test-Path $installPath
if (Test-Path $installPath) {
    Get-ChildItem $installPath | Select-Object Name, Length
}

$keys = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
Get-ItemProperty $keys -ErrorAction SilentlyContinue |
    Where-Object DisplayName -eq "Northstar Log Viewer" |
    Select-Object DisplayName, DisplayVersion, Publisher

Windows Installer only tracks resources declared in its database. Files created manually in the same directory are invisible to it.


10. Why these internals matter to vulnerability research

Now that we have the full lifecycle documented, we can point at the specific areas where things get interesting from a security perspective.

10.1 Custom Actions

Custom actions are where installer-specific code execution lives. The questions that matter: what triggers the action, does it run immediate or deferred, does it impersonate the user or run as SYSTEM, and what files or properties does it consume?

Our package has no custom actions on purpose. It gives us a clean control to compare against when we introduce one in Part 2.

10.2 Public properties and trusted input

Public properties can be set from the command line (msiexec /i app.msi MYPROP=value). If a deferred custom action running as SYSTEM reads a property that the caller controlled, and uses it as a file path or command without validation, that is a real problem.

10.3 Writable directories and repair resources

Component repair can access source media and rewrite managed resources. When reviewing a real third-party installer, relevant questions include:

  • Can a standard user alter a resource later read by elevated installation logic?
  • Are temporary or installation-source directories writable by untrusted users?
  • Are external DLLs and executables loaded from appropriately protected locations?
  • Are resource identities and file permissions verified before use?

Our repair test showed the normal path. The attack surface appears when any of these directories or source files are writable by a lower-privileged user.

10.4 AlwaysInstallElevated

When AlwaysInstallElevated is set to 1 in both HKLM and HKCU policy paths, any user can install an MSI with elevated privileges:

HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated = 1
HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer\AlwaysInstallElevated = 1

Microsoft explicitly warns against this configuration. We will enable it in Part 2 and demonstrate the escalation path with labuser.

10.5 Building toward a real finding

Everything we observed so far is normal, authorized behavior. Seeing SYSTEM write a file during an admin-initiated repair is how Windows Installer is designed to work.

A privilege escalation finding needs more: a standard user who can control an input (a file path, a property value, a DLL location) that a SYSTEM-context operation then trusts and acts on. That is what we will test in Part 2 with AlwaysInstallElevated and the labuser account.


Conclusion

An MSI is a relational database that drives a transactional installation engine. The tables define what gets installed, where, in what order, and under what conditions. Understanding this machinery is necessary before you can meaningfully analyze (or exploit) it.

We built a 32 KiB package with WiX, verified every table in Orca, watched the actual filesystem writes in Procmon, confirmed the SYSTEM-context service process behind those writes, tested key-path repair, and uninstalled cleanly. Every claim in this post has a corresponding screenshot or log entry.

This gives us the baseline. In Part 2, we break it.

In Part 2 — Windows Privilege Escalation: Understanding and Abusing AlwaysInstallElevated, we will investigate the privilege boundary directly using a separately controlled test configuration and the standard-user labuser account.


References

  1. Microsoft — Windows Installer
  2. Microsoft — About the Installer Database
  3. Microsoft — InstallExecuteSequence Table
  4. Microsoft — Component Table
  5. Microsoft — File Table
  6. Microsoft — Standard Actions
  7. Microsoft — Custom Action Security
  8. Microsoft — Windows Installer Command-Line Options
  9. Microsoft — AlwaysInstallElevated
  10. Microsoft — Windows Installer Best Practices
  11. Microsoft — Process Monitor
  12. Microsoft — Process Explorer
  13. FireGiant — WiX Toolset Documentation