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
相关产品推荐
相关产品推荐

