【问题标题】:Calling MSIGetProductInfo from a Windows service returns garbage values从 Windows 服务调用 MSIGetProductInfo 会返回垃圾值
【发布时间】:2011-10-10 23:19:14
【问题描述】:

我正在开发两个应用程序,这两个应用程序都依赖 MSIGetProductInfo 调用来检索用户在设置期间输入的序列号(标准 Visual Studio 设置和部署项目)。

这是我用来检索序列号的代码:

Int32 len = 512;
var builder = new StringBuilder(len);
MsiGetProductInfo("{98A0A10F-5E78-4FA6-83F6-6B356D75ADD4}", "ProductId", builder, ref len);
return builder.ToString();

第一个应用程序,一个可视化 c# 表单应用程序,按我的预期返回(当我输入 1111-1111-1111-1111 时,它会这样显示)。但是,当我在 c# windows 服务的 OnStart 方法中进行相同的调用时,我会得到随机垃圾,例如 Unicode 字符,偶尔会出现一个单词,并且每次运行时返回的值都不同。有时我会得到一个结果,其中包含包含 MsiGetProductInfo 调用的 dll 路径的一部分,例如“㷨H꿈Eindows\system32\msi.dll”。

在我看来(未经教育的猜测)对 dll 函数的调用失败了,它只是从未使用的内存位置返回数据。

我已尝试读取其他属性,例如 InstallSource 和 InstalledProductName,但这些属性在服务中也是垃圾(同时在表单应用程序中也很好)。我也觉得可能是权限问题,所以尝试在 NetworkService、LocalService 和 LocalSystem 账户下运行服务,没有成功。

Forms 应用和服务由同一个安装项目同时安装,因此它们应该共享相同的 ProductCode,对吗?如果没有,我可以看到服务无法检索产品信息的原因。

有没有人遇到过这种情况或知道是什么原因造成的?

【问题讨论】:

  • 您没有检查返回值。该函数很可能返回错误代码,在这种情况下,您的字符串将只包含创建时内存中的任何随机数据。检查返回值,让我们知道错误代码是什么。
  • 嗯,完全没有考虑返回值。我得到一个 1608 的值(我相信这是 ERROR_UNKNOWN_PROPERTY 值)。文档here 说,如果应用程序被宣传但未安装,就会出现这种情况。如果项目输出包含在设置项目中,这可能吗?
  • 另一个后续行动......我在服务中有属性“ProductId”,在 Windows 表单应用程序中有属性“ProductID”(我的大量监督)。无论如何,当我更改为 ProductID 时,我现在收到错误 1605 (ERROR_UNKNOWN_PRODUCT)。这更符合我的想法。我现在正在寻找一种方法来查找 Windows 服务的 ProductID,因为它与安装程序包的 ProductID 不匹配。
  • 安装上下文是每台机器还是每用户? (msdn.microsoft.com/en-us/library/dd765197%28v=VS.85%29.aspx)

标签: windows windows-services installation


【解决方案1】:

我需要回答同样的问题。我在 wix msi 项目中添加了UserRegistrationDlg.wxs 以收集 PIDKEY。但是,我需要在程序首次运行时获取安装时输入的序列号,以完成激活/注册步骤。

这是我用来在运行时询问安装时输入的许可证密钥的代码(VB.NET,抱歉):

Declare Auto Function MsiGetProductInfo Lib "msi.dll" (ByVal product As String, ByVal [property] As String, <MarshalAs(UnmanagedType.VBByRefStr)> ByRef valueBuf As String, ByRef len As Long) As Int32

Private Shared Function GetMSIKey() As String

Dim lsResult As String = Nothing
Try
    Dim loUninstallHive As RegistryKey = Registry.LocalMachine.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Uninstall")
    For Each loProgramHiveID As String In loUninstallHive.GetSubKeyNames()
        Dim loProgramHive As RegistryKey = loUninstallHive.OpenSubKey(loProgramHiveID)
        Dim lsProgramName As String = NullReaders.StringValue(loProgramHive.GetValue("DisplayName"))

        If lsProgramName.StartsWith("<your app name>", StringComparison.OrdinalIgnoreCase) Then

            Dim loInstallLocation = loProgramHive.GetValue("InstallLocation")

            Dim lsHiveName As String = loProgramHive.Name
            Dim lsGUID As String = lsHiveName.Substring(lsHiveName.LastIndexOf("\") + 1)

            Dim lsIKC As String = New String(" ", 255)
            Dim liLen As Integer = 255
            MsiGetProductInfo(lsGUID, "ProductID", lsIKC, liLen)

            lsResult = lsIKC.Substring(6, 24).Replace(" ", "-")

            Exit For
        End If
    Next
Catch ex As Exception
    TraceLog.DoErr(ex)
End Try
Return lsResult
End Function

请注意,ProductID 的大写字母 D 似乎很重要。

子字符串/替换将取决于 PIDTemplate 中的 Setup.wxs 属性,例如:

 <Property Id="PIDTemplate"><![CDATA[12345<#### #### #### #### ####>@@@@@]]></Property>

最后,UserRegistrationDlg.wxs 将许可证密钥框放入安装程序 UI:

<Control Id="CDKeyEdit" Type="MaskedEdit" X="45" Y="159" Width="250" Height="16" Property="PIDKEY" Text="[PIDTemplate]" />

【讨论】:

    【解决方案2】:

    这并不是我问题的真正答案,但我不想留下未解决的问题,以防其他人最终陷入同样的​​境地。

    我通过在设置过程中将用户输入的序列号存储在注册表中解决了这个问题。通过创建一个值为“[PIDKEY]”的注册表项(字符串值),可以存储用户的序列号,然后在应用程序执行期间使用 .NET RegistryKey 类进行检索。

    如果我有更多时间花在这个问题上,我会的,但感谢 Harry 帮助我走上正轨。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-05
      • 1970-01-01
      • 2013-10-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多