Skip to content

Insert makes table unreadable #41

@GrimOutlook

Description

@GrimOutlook

Problem

When trying to insert information into a table (that already exists), the table can become unreadable by msiexec.exe. The table that is affected also causes attempting to open the table in orca.exe to result in an error with the message An invalid argument was encountered. (when the table would open properly before the insert) and both InstEd and MSI Viewer give generic errors stating that the MSI couldn't be opened.

Minimum Reproducible Example

This code is also available here and can be run by running cargo run --example mre -- Schema.msi

use std::{fs::File, io::Write, str::FromStr};

use clap::{App, Arg};
use msi::Value;

fn main() {
    let matches = App::new("mre")
        .arg(Arg::with_name("schema").required(true))
        .arg(Arg::with_name("output").required(true).default_value("out.msi"))
        .get_matches();
    let schema = matches.value_of("schema").unwrap();
    let output = matches.value_of("output").unwrap();

    let mut template = File::open(schema).unwrap();
    let mut file = File::options()
        .create(true)
        .read(true)
        .write(true)
        .truncate(true)
        .open(output)
        .expect("Failed to open broken.msi");
   
    // Copy the template data into our new MSI
    std::io::copy(&mut template, &mut file).unwrap();
    // Probably not needed but whatever
    file.flush().unwrap();

    let mut package = msi::Package::open(file).expect("Failed to create MSI");

    // Summary Information
    let sum = package.summary_info_mut();
    // Have to set the PackageCode or we get an even more generic error saying "The parameter is incorrect".
    // This is the only SummaryInfo field not set that appears to be necessary.
    sum.set_uuid(
        *uuid::fmt::Braced::from_str("{11111111-1111-1111-1111-111111111111}")
            .unwrap()
            .as_uuid(),
    ); // PID 9
    package.flush().unwrap();

    // Required Property Information
    package
        .insert_rows(msi::Insert::into("Property").rows(vec![
            vec![Value::from("ProductName"), Value::from("MyProduct")],
            vec![
                Value::from("ProductCode"),
                Value::from("{11111111-1111-1111-1111-111111111113}"),
            ],
            vec![Value::from("ProductVersion"), Value::from("0.0.0")],
            vec![Value::from("ProductLanguage"), Value::from("1033")],
            vec![Value::from("Manufacturer"), Value::from("MyManufacturer")],
        ]))
        .unwrap();

    package.flush().unwrap();
}

Troubleshooting

Install Logging

I tried running msiexec.exe with verbose logs msiexec -L*vx 'C:\TEMP\msi.log' 'C:\Users\User\Downloads\test.msi' and got the output below:

=== Verbose logging started: 12/1/2025  14:24:36  Build type: SHIP UNICODE 5.00.10011.00  Calling process: C:\Windows\System32\msiexec.exe ===
MSI (c) (D0:38) [14:24:36:333]: Font created.  Charset: Req=0, Ret=0, Font: Req=MS Shell Dlg, Ret=MS Shell Dlg

MSI (c) (D0:38) [14:24:36:334]: Font created.  Charset: Req=0, Ret=0, Font: Req=MS Shell Dlg, Ret=MS Shell Dlg

MSI (c) (D0:C0) [14:24:36:349]: Resetting cached policy values
MSI (c) (D0:C0) [14:24:36:349]: Machine policy value 'Debug' is 0
MSI (c) (D0:C0) [14:24:36:349]: ******* RunEngine:
           ******* Product: C:\Users\User\Downloads\test.msi
           ******* Action: 
           ******* CommandLine: **********
MSI (c) (D0:C0) [14:24:36:354]: Machine policy value 'DisableUserInstalls' is 0
MSI (c) (D0:C0) [14:24:36:402]: Access database with Impersonation
MSI (c) (D0:C0) [14:24:36:403]: SOFTWARE RESTRICTION POLICY: Verifying package --> 'C:\Users\User\Downloads\test.msi' against software restriction policy
MSI (c) (D0:C0) [14:24:36:403]: Note: 1: 2262 2: �DigitalSignature 3: -2147287038 
MSI (c) (D0:C0) [14:24:36:403]: SOFTWARE RESTRICTION POLICY: C:\Users\User\Downloads\test.msi is not digitally signed
MSI (c) (D0:C0) [14:24:36:404]: SOFTWARE RESTRICTION POLICY: C:\Users\User\Downloads\test.msi is permitted to run at the 'unrestricted' authorization level.
MSI (c) (D0:C0) [14:24:36:427]: Cloaking enabled.
MSI (c) (D0:C0) [14:24:36:427]: Attempting to enable all disabled privileges before calling Install on Server
MSI (c) (D0:C0) [14:24:36:429]: End dialog not enabled
MSI (c) (D0:C0) [14:24:36:429]: Original package ==> C:\Users\User\Downloads\test.msi
MSI (c) (D0:C0) [14:24:36:429]: Package we're running from ==> C:\Users\User\Downloads\test.msi
MSI (c) (D0:C0) [14:24:36:430]: Note: 1: 2211 2:  3: Property 
MSI (c) (D0:C0) [14:24:36:430]: MSI_DBG: Provided descriptor less than minimum size
MSI (c) (D0:C0) [14:24:36:430]: APPCOMPAT: Compatibility mode property overrides found.
MSI (c) (D0:C0) [14:24:36:430]: APPCOMPAT: looking for appcompat database entry with ProductCode ''.
MSI (c) (D0:C0) [14:24:36:430]: APPCOMPAT: no matching ProductCode found in database.
MSI (c) (D0:C0) [14:24:36:433]: MSCOREE not loaded loading copy from system32
MSI (c) (D0:C0) [14:24:36:434]: Machine policy value 'TransformsSecure' is 0
MSI (c) (D0:C0) [14:24:36:434]: User policy value 'TransformsAtSource' is 0
MSI (c) (D0:C0) [14:24:36:435]: APPCOMPAT: looking for appcompat database entry with ProductCode ''.
MSI (c) (D0:C0) [14:24:36:435]: APPCOMPAT: no matching ProductCode found in database.
MSI (c) (D0:C0) [14:24:36:435]: Transforms are not secure.
MSI (c) (D0:C0) [14:24:36:435]: Note: 1: 2211 2:  3: Property 
MSI (c) (D0:C0) [14:24:36:435]: Note: 1: 2262 2: Control 3: -2147287038 
MSI (c) (D0:C0) [14:24:36:435]: PROPERTY CHANGE: Adding MsiLogFileLocation property. Its value is 'C:\TEMP\msi.log'.
MSI (c) (D0:C0) [14:24:36:435]: Command Line: CURRENTDIRECTORY=\\wsl.localhost\dev\home\dom\rust-msi\examples CLIENTUILEVEL=0 CLIENTPROCESSID=22736 
MSI (c) (D0:C0) [14:24:36:435]: PROPERTY CHANGE: Adding PackageCode property. Its value is '{11111111-1111-1111-1111-111111111111}'.
MSI (c) (D0:C0) [14:24:36:435]: Product Code passed to Engine.Initialize:           ''
MSI (c) (D0:C0) [14:24:36:435]: Product Code from property table before transforms: ''
MSI (c) (D0:C0) [14:24:36:435]: Product Code from property table after transforms:  ''
MSI (c) (D0:C0) [14:24:36:435]: Failing install, missing product code
This action is only valid for products that are currently installed.
C:\Users\User\Downloads\test.msi
MSI (c) (D0:C0) [14:24:36:435]: Note: 1: 1708 
MSI (c) (D0:C0) [14:24:36:435]: Product:  -- Installation failed.

MSI (c) (D0:C0) [14:24:36:436]: Windows Installer installed the product. Product Name: . Product Version: . Product Language: . Manufacturer: . Installation success or error status: 1605.

MSI (c) (D0:C0) [14:24:36:437]: MainEngineThread is returning 1605
=== Verbose logging stopped: 12/1/2025  14:24:36 ===

I did try running the same command in a regular PowerShell prompt rather than from WSL and got the same result.

MSIInfo.exe

Running the command MsiInfo.exe [MSI_PATH] /D for both Schema.msi and test.msi both show no StringPool errors, and adding \B to the command shows that the specified property information was appended to the end of the StringPool and I can't see any other oddities of note with a diff.

msitools

The information is readable both by the rust-msi crate and the msitools' msiinfo command. Running the command msiinfo export -s test.msi Property results in the expected output shown below:

Property        Value
s72     l0
Property        Property
Manufacturer    MyManufacturer
ProductCode     {11111111-1111-1111-1111-111111111113}
ProductLanguage 1033
ProductName     MyProduct
ProductVersion  0.0.0

Thoughts

I'm still unsure whether this is actually an issue in the rust-msi crate or the rust-cfb crate, though I'm leaning towards the former. I tries parsing through the data using a CFB parser I made in 010 Editor to see if I could find where the encoding is going wrong while comparing it to the unchanged Schema.msi, and while there aren't that many differences, nothing too obvious stuck out.

I figured I would ask here before I continued any further to see if you had any insight as to what might be happening. If not I'll have to continue trying to reverse engineer how the StringPool and table information is laid out in the CFB streams since I could only find the CFB and PropertySet documents and couldn't find any documentation that mentions StringPools at all on Microsoft's end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions