如何使用用户 ID 参数下载 MSI 安装程序

How to Download MSI installer with argument for user-id

我有一个 .NET C# 应用程序,封装在 MSI 安装程序中 - "myprogram.exe"。我有一个 PHP 网站和一个特定页面,用户可以在其中通过 link.

下载程序

我希望能够在 .NET 应用程序上跟踪某些事件。例如 - "Program Opened".

向我的服务器发送事件很容易,但是我如何从 php 服务器获取用户 ID,这样我才能知道哪个用户在 .NET 应用程序上做了什么?

我考虑过将参数(用户 ID)传递给 MSI 安装程序,但找不到方法。

如何在 PHP 用户 ID 和 .NET 应用程序之间 link?

澄清-

很多人提出使用登录系统在服务器和应用程序之间进行绑定。

这确实是最简单的解决方案,但在我的网站上,我不会强制用户登录才能下载应用程序(我也不会在 .NET 应用程序中请求登录详细信息 - 它是可选的)。如果我们不必询问登录详细信息,我认为我们不应该,用户体验会好得多(使用该应用程序的步骤要少得多)- 用户下载和使用桌面应用程序的机会更大。

考虑当前流程是 -> 网页 - 下载点击 - 运行 - 使用应用 (需要 10 秒)

使用登录->网页-注册(确认邮箱?)-重定向-下载点击-运行-应用登录-使用应用 (用户需要 60-120 秒)

调用文件时发送 UserID 参数,如果您在 PHP 中使用 MVC 框架,则需要一个新的控制器来获取 msi 文件,并将其重命名为 name-userID.exe 然后 returns 要通过浏览器下载的文件。

从程序登录

最好的方法是让用户在您的程序中使用相同的凭据登录。这样,您的程序就可以使用安全的 OAuth2 身份验证与您的后端通信 API。这也让用户清楚地知道该程序正在与 Internet 通信。

在文件名中包含用户 ID

另一种方法是在下载过程中将用户标识添加到安装程序的文件名中,并在安装程序运行时将其解压缩。您将必须检查您的安装程序工具是否允许这样做。此外,仅当您的用户 ID 是 UUID 或类似的东西时才这样做,因为您不希望用户猜测其他 ID。

App.config

第三个选项是将用户 ID 添加到 App.config 文件中。有两种方法可以做到这一点:

  1. 创建 App.config 未压缩的 .msi,添加具有固定 UUID 的用户 ID 设置。您的 PHP 脚本可以查找 UUID 并将其替换为 .msi 二进制文件,然后再将其发送给用户。请参阅 MST 转换
  2. 下的代码片段
  3. 使用自定义 App.config 按需构建 .msi。只有当您的网络服务器是 运行 on Windows 或者您有一个远程 Windows 构建服务器可以完成这项工作时,这才会起作用。

MST变换

您还可以使用 MST 转换并使用与我在 App.config.

下针对第 1 点所解释的相同的二进制替换技巧

对于这两个选项,您可以使用 PHP 脚本,该脚本使用二进制安全函数替换安装程序中的值并将文件作为下载发送给用户:

<?php
$userId = // TODO get userId from the session or database
$data = file_get_contents("./my-installer.msi");
// I would use UUID's for template and userId, this way the size of the installer remains the same after replace
$data = str_replace("{fe06bd4e-4bed-4954-be14-42fb79a79817}", $userId, $data);
// Return the file as download
header("Cache-Control: public"); // needed for i.e.
header('Content-Disposition: attachment; filename=my-installer.msi');
header('Content-Type: application/x-msi');
header("Content-Transfer-Encoding: Binary");
echo $data;
?>

序列号

我能想到的最后一个方法是让程序在第一次启动时要求一个序列号,让你的网站为每个用户生成一个唯一的序列号。

请注意,这很可能不是您想做的。尽管如此,我还是会解释几种方法来做到这一点..

将 MST 文件与 MSI 一起使用:

您可以创建具有用户 ID 属性 的 MST 文件,并在每个用户下载 msi 时为他们生成这些文件,并让他们使用转换安装 msi:

msiexec -i c:\temp\The.msi transforms=c:\temp\YourPerso.mst

在此处查看更多信息:Install a transform using the command line

MST 文件在大型组织中使用很多,其中所有 MSI 都有嵌入序列号等的 MST 文件。

要制作 MST 文件,您需要下载并安装 Microsofts Orca Tool, its part of the Microsoft Windows SDK

打开 Orca 并从 MSI 文件创建一个 MST 文件。基本上你打开 MSI 文件导航到 table "Property",在那里你会看到一个参数列表。 请注意,在 MSI 文件中,您会看到需要默认值的参数。

在 add/change 参数之前,通过单击 "Transform" -> "New Transform" 上的菜单创建一个新的转换。

之后您可以根据需要更改参数或添加新参数。完成参数更改后,使用 "Transform" 菜单中的功能 "Generate Transform" 生成 MST 文件。

如果您随后使用 HexEditor 打开 mst 文件,您可以看到您刚刚添加的 属性:

您可以通过简单地编辑值来为每次下载编辑文件,例如:

您当然可以(并且可能应该)使用 WindowsInstaller.Installer 的 API 以正确的方式执行此操作。这是一个例子:

private function createTransform(mstfile, msi, config)
    writeLog InfoLog, "Generating transform " & mstfile

    dim vars: set vars = configvars(config)

    dim createPropertyTable: createPropertyTable = "create table `Property` " & _
        "(`Property` char(72) not null, `Value` longchar localizable " & _
        "primary key `Property`)"
    dim addProperty: addProperty = "insert into `Property` (`Property`, `Value`) values (?, ?)"
    dim updateProperty: updateProperty = "update `Property` set `Value` = ? where `Property` = ?"

    dim wi: set wi = createObject("WindowsInstaller.Installer")
    dim base: set base = wi.openDatabase("base.msi", msiOpenDatabaseModeCreate)
    base.openview(createPropertyTable).execute
    dim tgt: set tgt = wi.openDatabase("tgt.msi", msiOpenDatabaseModeCreate)
    tgt.openview(createPropertyTable).execute
    dim props: set props = createObject("scripting.dictionary")
    dim view: set view = msi.openView("select `Property`, `Value` from `Property`") 
    view.execute        
    dim record: set record = view.fetch
    while not record is nothing
        props(record.stringdata(1)) = true
        base.openview(addProperty).execute record
        tgt.openview(addProperty).execute record    
        set record = view.fetch
    wend

    set record = wi.createRecord(2)
    dim prop
    for each prop in properties_
        on error resume next
        dim val: val = expand(vars, prop(DepPropertyValueIdx))
        if err then
            writeLog ErrorLog, err.description
            exit function
        end if
        on error goto 0
        writeLog InfoLog, "Property " & prop(DepPropertyNameIdx) & "=" & val
        if props.exists(prop(DepPropertyNameIdx)) then
            record.stringdata(2) = prop(DepPropertyNameIdx)
            record.stringdata(1) = val
            tgt.openview(updateProperty).execute record
        else
            record.stringdata(1) = prop(DepPropertyNameIdx)
            record.stringdata(2) = val
            tgt.openview(addProperty).execute record
        end if
    next
    if not tgt.generateTransform(base, mstfile) then
        writeLog ErrorLog, "Failed to create transform"
        exit function
    end if
    tgt.createTransformSummaryInfo msi, mstfile, 0, 0
    createTransform = true
end function

提示:要使用托管代码执行此操作,您最好使用作为 http://wix.codeplex.com/

的一部分提供的 Microsoft.Deployment.WindowsInstaller.dll

为每个用户构建一个 MSI:

恕我直言,使用 Nullsoft (WiX、InstallShield、INNO 等) 并为每个用户构建一个 MSI 会容易得多。为此,您可以在 an nsi script and kick off a MSI build for each download. During install the unique user id would be stored in a file, registry key or etc. I suggest you give this a go using this NSIS Wizard Editor to quickly whip up a basic NSI install script and build the MSI via a command line: makensis.

中嵌入一个唯一的用户 ID。

注意: 虽然 "Including the user-id in MSI filename" 比为每个用户构建 MSI 更容易,但用户可以轻松更改文件名。用户使用 Orca 审核 MSI 以查找内置用户 ID 的可能性大大降低。


最简单最符合逻辑的方法:

It's easy to send events to my server, however how do I grab the user-id from the php server, so I can know which user did what on the .NET app?

按照@Jhuliano Moreno 和@WouterHuysentruit 的建议做:

当您的应用程序首次启动时,只需让用户使用他们的网站凭据登录程序,并将他们的用户 ID 记录在配置文件、注册表项或数据库记录中。基本上是创建一个 cookie,以便下次打开程序时您会知道它们 - 或者让它们每次都登录。