winbrew_windows\deployment\msi/
mod.rs

1#![cfg(windows)]
2#![doc = include_str!("README.md")]
3
4use anyhow::Result;
5use std::path::Path;
6
7use crate::models::install::engine::InstallScope;
8use crate::models::msi_inventory::records::{MsiInventoryReceipt, MsiInventorySnapshot};
9
10mod builder;
11mod database;
12mod directory;
13mod path;
14
15use self::{
16    builder::{
17        build_component_records, build_file_paths, build_file_records, build_registry_records,
18        build_shortcut_records,
19    },
20    database::{
21        MsiDatabase, load_component_rows, load_directory_rows, load_file_rows, load_registry_rows,
22        load_shortcut_rows, query_optional_string, query_required_string,
23    },
24    directory::resolve_directory_paths,
25};
26
27/// Scan an MSI database and reconstruct the inventory snapshot WinBrew stores.
28///
29/// The scanner reads the standard MSI tables, resolves directory and file keys
30/// into concrete install paths rooted at `install_root`, and returns the data in
31/// the same snapshot shape used by storage.
32pub fn scan_inventory(
33    package_path: &Path,
34    install_root: &Path,
35    package_name: &str,
36    scope: InstallScope,
37) -> Result<MsiInventorySnapshot> {
38    let database = MsiDatabase::open(package_path)?;
39
40    let product_code = query_required_string(
41        database.handle(),
42        "SELECT `Value` FROM `Property` WHERE `Property` = 'ProductCode'",
43    )?;
44    let upgrade_code = query_optional_string(
45        database.handle(),
46        "SELECT `Value` FROM `Property` WHERE `Property` = 'UpgradeCode'",
47    )?;
48
49    let directory_rows = load_directory_rows(database.handle())?;
50    let component_rows = load_component_rows(database.handle())?;
51    let file_rows = load_file_rows(database.handle())?;
52    let registry_rows = load_registry_rows(database.handle())?;
53    let shortcut_rows = load_shortcut_rows(database.handle())?;
54
55    let directory_paths = resolve_directory_paths(&directory_rows, install_root)?;
56    let file_paths = build_file_paths(&file_rows, &component_rows, &directory_paths, install_root);
57
58    let files = build_file_records(
59        package_name,
60        &file_rows,
61        &file_paths,
62        &component_rows,
63        &directory_paths,
64        install_root,
65    );
66    let registry_entries = build_registry_records(package_name, scope, &registry_rows);
67    let shortcuts = build_shortcut_records(
68        package_name,
69        &shortcut_rows,
70        &directory_paths,
71        &file_paths,
72        install_root,
73    );
74    let components =
75        build_component_records(package_name, &component_rows, &directory_paths, &file_paths);
76
77    Ok(MsiInventorySnapshot {
78        receipt: MsiInventoryReceipt {
79            package_name: package_name.to_string(),
80            product_code,
81            upgrade_code,
82            scope,
83        },
84        files,
85        registry_entries,
86        shortcuts,
87        components,
88    })
89}
90
91#[derive(Debug, Clone)]
92struct DirectoryRow {
93    parent: Option<String>,
94    default_dir: String,
95}
96
97#[derive(Debug, Clone)]
98struct ComponentRow {
99    directory_id: String,
100    key_path: Option<String>,
101}
102
103#[derive(Debug, Clone)]
104struct FileRow {
105    file_key: String,
106    component_id: String,
107    file_name: String,
108}
109
110#[derive(Debug, Clone)]
111struct RegistryRow {
112    root: i32,
113    key_path: String,
114    name: Option<String>,
115    value: Option<String>,
116}
117
118#[derive(Debug, Clone)]
119struct ShortcutRow {
120    directory_id: String,
121    name: String,
122    target: String,
123}