You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust编写的WIX延迟自定义动作偶发CustomActionData为空求助

偶发CustomActionData读取为空问题排查与解决

问题背景

在WIX中配置了处理凭据的延迟自定义动作,以及用于设置CustomActionData值的辅助动作,但运行时偶发出现CustomActionData读取为空的情况(10次运行中有2-3次),导致后续业务逻辑处理失败。

WIX配置代码

<CustomAction Id="SetUserNamePasswordForDefferedAction" Property="HandleAccountInfoCustomAction" Value="[ACCOUNT_USERNAME],[ACCOUNT_PASSWORD],[INSTALLLOCATION],[SERVICEACCOUNT]" HideTarget="no" Return="check" />
<CustomAction Id="HandleAccountInfoCustomAction" DllEntry="handle_account_info" BinaryKey="service_account_dll" Execute="deferred" HideTarget="no" Impersonate="no" Return="check"/>
<Binary Id="service_account_dll" SourceFile="service_account.dll" />

<InstallExecuteSequence>
    <Custom Action="HandleAccountInfoCustomAction" Before="InstallFinalize">NOT Installed</Custom>
    <Custom Action="SetUserNamePasswordForDefferedAction" Before="HandleAccountInfoCustomAction">NOT Installed</Custom>
</InstallExecuteSequence>

Rust实现的自定义动作核心代码

service_account_dll由Rust实现,以C格式导出handle_account_info入口点,读取CustomActionData的核心函数如下:

unsafe fn retrieve_property_from_msi_db(
    h_install: MSIHANDLE,
    property_name_str: &str,
) -> StdResult<String, u32> {
    let property_name = get_ptr_to_cstring(property_name_str);
    custom_action_log(&format!("Retrieve {property_name_str} from MSI db"));
    let mut msi_get_property_call_count = 0usize;
    let mut res;
    let mut req_status;
    let mut buff = [0u8; BUFFER_SIZE];

    res = 0u32;
    req_status = 234u32; // ERROR_MORE_DATA

    while (WIN32_ERROR(req_status) == ERROR_MORE_DATA || res == 0) && msi_get_property_call_count < 5 {
        res = buff.len() as u32;
        req_status = MsiGetPropertyA(
            h_install,
            PCSTR::from_raw(property_name),
            PSTR::from_raw(buff.as_mut_ptr()),
            Some(&mut res as *mut u32),
        );
        if msi_get_property_call_count > 0 && WIN32_ERROR(req_status) == ERROR_MORE_DATA {
            // Register the occurrence of a known race condition in MSI
            custom_action_log(format!("WARN: Multiple calls to MsiGetPropertyA ({msi_get_property_call_count}) returned ERROR_MORE_DATA.").as_str());
        }
        if res == 0 {
            custom_action_log(format!("WARN: MsiGetPropertyA returned 0 bytes for property {property_name_str}.").as_str());
            std::thread::sleep(Duration::from_millis(10));
        }

        msi_get_property_call_count += 1;

        if res as usize > BUFFER_SIZE {
            custom_action_log(format!("Custom action buffer size is too small: Needed {res}/available {BUFFER_SIZE}.").as_str());
            return Err(ERROR_INSTALL_FAILED.0);
        }
    }
    if ERROR_SUCCESS != WIN32_ERROR(req_status) {
        custom_action_log(format!("Failed to retrieve user name from MSI:WIN_32ERROR {:?}, req_status: {req_status}", WIN32_ERROR(req_status)).as_str());
        return Err(ERROR_INSTALL_FAILED.0);
    }

    let property = std::str::from_utf8_unchecked(&buff[..res as usize]);

    Ok(property.to_string())
}

错误日志示例

2023-11-14 13:29:47 - Retrieve CustomActionData from MSI db
2023-11-14 13:29:47 - WARN: MsiGetPropertyA returned 0 bytes for property CustomActionData.
2023-11-14 13:29:47 - WARN: MsiGetPropertyA returned 0 bytes for property CustomActionData.
2023-11-14 13:29:47 - WARN: MsiGetPropertyA returned 0 bytes for property CustomActionData.
2023-11-14 13:29:47 - WARN: MsiGetPropertyA returned 0 bytes for property CustomActionData.
2023-11-14 13:29:47 - WARN: MsiGetPropertyA returned 0 bytes for property CustomActionData.
2023-11-14 13:29:47 - service_account CustomActionData data retrieved from MSI db.
2023-11-14 13:29:47 - TROUBLESHOOTING: CustomActionData:
2023-11-14 13:29:47 - CustomActionData property is not in the expected format

成功日志示例

2023-11-14 13:28:48 - Retrieve CustomActionData from MSI db
2023-11-14 13:28:48 - service_account CustomActionData data retrieved from MSI db.
2023-11-14 13:28:48 - TROUBLESHOOTING: CustomActionData: ,,C:\MyApp,NT Authority\Local Service

解决方案

1. 优化MSI序列执行时机

将SetUserNamePasswordForDefferedAction的执行时机提前,确保CustomActionData在延迟动作执行前完全写入:

  • 修改InstallExecuteSequence,将该动作放在InstallInitialize之后,避免与其他动作的调度冲突:
    <Custom Action="SetUserNamePasswordForDefferedAction" After="InstallInitialize">NOT Installed</Custom>
    

2. 调整Rust读取逻辑的重试策略

当前10ms的等待时间不足以覆盖MSI的属性同步延迟,可改为指数退避策略:

// 替换原sleep逻辑
let wait_time = Duration::from_millis(10 * (1 << msi_get_property_call_count) as u64);
std::thread::sleep(wait_time);

同时,在循环结束后额外检查一次属性值是否为空,若为空则直接返回错误,避免后续无效处理。

3. 改用Unicode版本的MSI API

MSI内部优先使用Unicode编码,MsiGetPropertyA的ANSI转换可能存在潜在问题,替换为MsiGetPropertyW并调整字符串处理:

unsafe fn retrieve_property_from_msi_db(
    h_install: MSIHANDLE,
    property_name_str: &str,
) -> StdResult<String, u32> {
    let property_name = widestring::U16CString::from_str(property_name_str).map_err(|_| ERROR_INSTALL_FAILED.0)?;
    custom_action_log(&format!("Retrieve {property_name_str} from MSI db"));
    let mut msi_get_property_call_count = 0usize;
    let mut res = 0u32;
    let mut req_status = 234u32; // ERROR_MORE_DATA
    let mut buff = [0u16; 4096]; // 扩大宽字符缓冲区

    while (WIN32_ERROR(req_status) == ERROR_MORE_DATA || res == 0) && msi_get_property_call_count < 5 {
        res = buff.len() as u32;
        req_status = MsiGetPropertyW(
            h_install,
            property_name.as_ptr(),
            buff.as_mut_ptr(),
            Some(&mut res as *mut u32),
        );
        if msi_get_property_call_count > 0 && WIN32_ERROR(req_status) == ERROR_MORE_DATA {
            custom_action_log(format!("WARN: Multiple calls to MsiGetPropertyW ({msi_get_property_call_count}) returned ERROR_MORE_DATA.").as_str());
        }
        if res == 0 {
            custom_action_log(format!("WARN: MsiGetPropertyW returned 0 bytes for property {property_name_str}.").as_str());
            let wait_time = Duration::from_millis(10 * (1 << msi_get_property_call_count) as u64);
            std::thread::sleep(wait_time);
        }

        msi_get_property_call_count += 1;

        if res as usize > buff.len() {
            custom_action_log(format!("Custom action buffer size is too small: Needed {res}/available {}.", buff.len()).as_str());
            return Err(ERROR_INSTALL_FAILED.0);
        }
    }
    if ERROR_SUCCESS != WIN32_ERROR(req_status) {
        custom_action_log(format!("Failed to retrieve property from MSI: WIN_32ERROR {:?}, req_status: {req_status}", WIN32_ERROR(req_status)).as_str());
        return Err(ERROR_INSTALL_FAILED.0);
    }

    let property = String::from_utf16_lossy(&buff[..res as usize]);
    Ok(property)
}

4. 验证CustomActionData的设置完整性

在SetUserNamePasswordForDefferedAction动作中添加日志,确认每次都正确设置了属性值:

  • 新增一个即时自定义动作,用于输出HandleAccountInfoCustomAction属性的当前值,验证设置是否成功。

内容的提问来源于stack exchange,提问作者Albino Cordeiro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 11:44:52