Как загрузить установщик MSI с аргументом для идентификатора пользователя

У меня есть приложение .NET С#, завернутое в установщик MSI - "myprogram.exe". У меня есть сайт PHP и конкретная страница, где пользователь может загрузить программу по ссылке.

Я хотел бы отслеживать определенные события в приложении .NET. Например, "Программа открыта".

Легко отправлять события на мой сервер, однако как я могу получить идентификатор пользователя с сервера php, поэтому я могу узнать, какой пользователь сделал то, что в приложении .NET?

Я думал о передаче аргумента (идентификатора пользователя) в установщик MSI, но не смог найти способ.

Как мне связать между идентификатором пользователя PHP и .NET?

Уточнение -

Многие люди предлагали использовать систему входа для привязки между сервером и приложением.

Это действительно самое простое решение, однако на моем веб-сайте я не заставляю пользователя входить в систему для загрузки приложения (и я не запрашиваю данные для входа в приложение .NET - его необязательно). Если нам не нужно запрашивать данные для входа, я думаю, что нам не следует, опыт для пользователя будет намного лучше (гораздо меньше шагов для использования приложения) - больше шансов для загрузки и использования приложения Desktop.

Учтите, что текущий поток → Веб-страница - Загрузить клик - Запустить - Использовать приложение (занимает 10 секунд)

С логином → Веб-страница - Зарегистрируйтесь (подтвердите адрес электронной почты?) - Перенаправление - Загрузите Click-run - приложение Войти - Используйте приложение (для пользователя требуется 60-120 секунд)

Ответ 1

Вход из программы

Лучший способ - позволить пользователю войти в систему с теми же учетными данными в вашей программе. Таким образом, ваша программа может использовать безопасную аутентификацию OAuth2 для связи с вашим внутренним API. Это также делает прозрачным для пользователя, что программа взаимодействует с Интернетом.

Включить идентификатор пользователя в имя файла

Другой способ - добавить идентификатор пользователя в имя файла установщика во время загрузки и извлечь его при запуске установщика. Вам нужно будет проверить, позволяет ли это средство установки. Кроме того, делайте это только в том случае, если ваш идентификатор пользователя UUID или что-то подобное, так как вы не хотите, чтобы пользователь угадывал другие идентификаторы.

App.config

Третий вариант - добавить идентификатор пользователя в файл App.config. Существует два способа сделать это:

  • Создайте свой .msi с App.config несжатым, добавьте параметр идентификатора пользователя с фиксированным UUID. Ваш PHP скрипт может искать UUID и заменять его в двоичном формате .msi перед его отправкой пользователю. См. Фрагмент кода в преобразовании MST
  • Создайте .msi по запросу с помощью настраиваемого App.config. Это будет работать, только если ваш веб-сервер работает в Windows или у вас есть удаленный сервер сборки Windows, который может выполнять эту работу.

Преобразование MST

Вы также можете использовать преобразование MST и использовать тот же двоичный метод замены, как я объяснил для пункта 1 в разделе App.config.

Для обеих опций вы можете использовать PHP script, который использует двоично-безопасные функции для замены значений в установщике и отправляет файл пользователю в качестве загрузки:

<?php
$userId = // TODO get userId from the session or database
$data = file_get_contents("./my-installer.msi");
// I would use UUID 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;
?>

Серийный номер

Последний способ, о котором я могу думать, - позволить программе запрашивать серийный номер при первом запуске и позволить вашему сайту генерировать уникальный серийный номер для каждого пользователя.

Ответ 2

Um OK, обратите внимание, что это очень вероятно, что это не, что вы хотите сделать. Тем не менее я объясню пару способов сделать это.

Использование файлов MST с MSI:

Вы можете создавать файлы MST с свойством идентификатора пользователя и генерировать их для каждого пользователя, когда они загружают msi и заставляют их устанавливать msi с преобразованием:

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

Подробнее см. здесь: Установить преобразование с помощью командной строки.

Вы видите, что файлы MST много используются в крупных организациях, где все MSI имеют файлы MST с серийными номерами и т.д..

Для создания MST файла вам необходимо загрузить и установить Microsoft Orca Tool, его часть SDK Microsoft Windows.

Откройте Orca и создайте файл MST из файла MSI. В основном вы открываете файл MSI, переходите к таблице "Свойство", там вы видите список параметров. Обратите внимание, что в файле MSI вы увидите параметры, требующие значения по умолчанию.

Прежде чем добавлять/изменять параметры, создайте новое преобразование, нажав в меню "Трансформация" → "Новое преобразование".

введите описание изображения здесь

Затем вы можете изменять параметры или добавлять новые, как хотите. Когда вы закончите изменение параметров, используйте функцию "Generate Transform" в меню "Transform", чтобы сгенерировать MST файл.

введите описание изображения здесь

Если вы откроете файл mst с помощью HexEditor, вы увидите только что добавленное свойство:

введите описание изображения здесь

Вы можете отредактировать файл для каждой загрузки, просто отредактировав значение, например:

введите описание изображения здесь

Вы, конечно, можете (и, вероятно, должны) сделать это с помощью API-интерфейса WindowsInstaller.Installer. Вот пример:

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

Совет: Для этого с помощью управляемого кода вам лучше всего использовать Microsoft.Deployment.WindowsInstaller.dll, которые доступны как часть < а6 >


Создайте MSI для каждого пользователя:

IMHO было бы намного проще сделать это с помощью Nullsoft (WiX, InstallShield, INNO и т.д.) и построить MSI для каждого пользователя. Для этого вы должны вставлять уникальный идентификатор пользователя, например, nsi script и запускать сборку MSI для каждой загрузки. Во время установки уникальный идентификатор пользователя будет храниться в файле, в разделе реестра или т.д. Я предлагаю вам воспользоваться этим NSIS Wizard Editor чтобы быстро взломать базовую установку NSI script и построить MSI через командную строку: makensis.

Примечание.. Хотя "Включение идентификатора пользователя в имя файла MSI" проще, чем создание MSI для каждого пользователя, пользователи могут легко изменить имя файла. Его гораздо меньше вероятность, что пользователь проверит MSI с помощью Orca, чтобы найти встроенный идентификатор пользователя.


Самый простой и логичный способ:

Легко отправлять события на мой сервер, однако как я могу захватить user-id с php-сервера, поэтому я могу узнать, какой пользователь сделал что-то в приложении .NET?

Сделайте то, что @Jhuliano Moreno, а затем @WouterHuysentruit рекомендуется:

Когда ваше приложение запускается в первый раз, просто заставьте пользователя войти в программу, используя учетные данные своего веб-сайта, и запишите свой идентификатор пользователя в файле конфигурации, разделе реестра или записи базы данных. В основном создайте файл cookie, чтобы вы узнали их в следующий раз, когда откроете программу, или сделайте их логин каждый раз.

Ответ 3

Когда файл вызывается, посылает параметр UserID, если вы используете структуру MVC в своем PHP, вам нужен новый контроллер, который получает файл msi, и переименовывает его в name-userID.exe, а затем возвращает файл для загрузки через браузер.