Проблемы с развертыванием Mage.exe

У меня есть файл конфигурации, который необходимо изменить для каждого сервера, поэтому, как только на сервере установлено наше программное обеспечение, файл конфигурации для установщика клиента настраивается в соответствии с конкретными настройками этого сервера, а затем копируется в общедоступный папку в Интернете для развертывания.

Поскольку я меняю файл конфигурации, мне также нужно пересобрать файлы *.manifest и *.application, и, насколько я понимаю, мой единственный реальный вариант для этого - использовать Mage.exe из Win7 SDK. Чтобы исправить файл *.manifest с правильным хешем из измененного файла конфигурации, я запускаю:

mage -new Application -fd ".\Application Files\<appName>
+ Deployment manifest is not semantically valid.
+ Deployment manifest requires <deployment> section.
0_0_0" -ToFile ".\Application Files\
+ Deployment manifest is not semantically valid.
+ Deployment manifest requires <deployment> section.
0_0_0\<appName>.exe.manifest" -Name "<appName>" -Version "1.0.0.0" -CertFile "key.pfx" -password "<password>"

а затем, чтобы исправить файл *.application с правильным хешем из измененного файла *.manifest, я запускаю:

mage -new Deployment -I t -t "<appName>.application" -v "1.0.0.0" -appManifest ".\Application Files\<appName>
+ Deployment manifest is not semantically valid.
+ Deployment manifest requires <deployment> section.
0_0_0\<appName>.exe.manifest" -pu "http://<hostaddress>/<path>/Application Files/<appName>
+ Deployment manifest is not semantically valid.
+ Deployment manifest requires <deployment> section.
0_0_0/<appName>.exe.manifest" -CertFile "key.pfx" -password ""

Теперь все это работает, и я получаю сообщение о том, что файлы были успешно подписаны. Когда я пытаюсь установить клиентское приложение, очевидно, что что-то пошло не так, когда я получаю журнал ошибок с сообщением:

+ Deployment manifest is not semantically valid.
+ Deployment manifest requires <deployment> section.

При просмотре файла *.application в узле «развертывание» есть дополнительная информация, которой нет в том же файле непосредственно из функции публикации VS2008:

<deployment install="true">
  <subscription>
    <update>
      <expiration maximumAge="0" unit="days" />
    </update>
  </subscription>
  <deploymentProvider codebase="http://<hostaddress>/<path>/Application Files/<appName>_1_0_0_0/<appName>.exe.manifest" />
</deployment>

Опубликованная версия VS2008 просто имеет:

<deployment install="true" />

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

Это известная проблема и есть ли способ заставить Mage создать файл без дополнительной информации в узле развертывания, чтобы он работал правильно?

РЕДАКТИРОВАТЬ: В качестве временного решения я загружаю файлы в XmlDocument и изменяю их в соответствии с требованиями, а затем повторно подписываю файлы. Кроме того, теперь я столкнулся с проблемой, что пока не могу определить, как добавить значок в развертывание, поэтому элемент меню «Пуск» получает значок, отличный от общего значка.


person Nathan Wheeler    schedule 22.02.2010    source источник
comment
У меня очень похожий вариант использования с похожими проблемами. Я отвечу, если найду решение.   -  person arri.io    schedule 23.02.2010
comment
просто используйте флаг -appc на маге   -  person Scott Cowan    schedule 20.12.2013
comment
Натан, посмотри мой ответ и посмотри, поможет ли он тебе. Использование Mage.exe должно работать нормально для вас.   -  person The Muffin Man    schedule 28.08.2014


Ответы (2)


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

Чтобы следующий метод работал, вы должны развернуть его хотя бы один раз из ClickOnce в VS, а затем просто сохранить файл .application из этого развертывания. Вы ДОЛЖНЫ удалить .application и .manifest В папке развертывания.

После того, как я переместил все файлы приложения в Config.Instance.ServerSettings.ClientLocation + "<AppName>_<version>":

DirectoryInfo filedir = new DirectoryInfo(Config.Instance.ServerSettings.ClientLocation);

if (filedir.Exists)
{
    FileInfo[] files = filedir.GetFiles();

    // Find the current .application file.
    FileInfo appinfo = null;
    foreach (FileInfo fi in files)
    {
        if (fi.Name == "<AppName>.application")
        {
            appinfo = fi;
            break;
        }
    }

    if (appinfo != null)
    {
        XmlDocument applocinfo = new XmlDocument();
        applocinfo.Load(appinfo.FullName);

        // Get the location of the files from the .application file.
        string codebase = applocinfo["asmv1:assembly"]["dependency"]["dependentAssembly"].Attributes["codebase"].Value.Replace("AppName.exe.manifest", "");

        XmlDocument xDoc = new XmlDocument();
        xDoc.Load(Path.Combine(Path.Combine(filedir.FullName, codebase), "AppName.exe.config"));

        foreach (XmlNode xn in xDoc["configuration"]["appSettings"].ChildNodes)
        {
            if (xn.Attributes != null && xn.Attributes["key"] != null && xn.Attributes["key"].Value == "Clnt_Host")
            {
                // Here is where I'm modifying my config file, the whole purpose in this wretched deployment process.
                xn.Attributes["value"].Value = Config.Instance.ClientSettings.Host;
                break;
            }
        }

        xDoc.Save(Path.Combine(Path.Combine(filedir.FullName, codebase), "<AppName>.exe.config"));

        Process p = new Process();
        p.StartInfo = new ProcessStartInfo(Path.Combine(filedir.FullName, "Mage.exe"));
        p.StartInfo.WorkingDirectory = filedir.FullName;

        FileInfo fi = new FileInfo(Path.Combine(Path.Combine(filedir.FullName, codebase.TrimStart('.')), "<AppName>.exe.manifest"));
        if (fi.Exists)
            fi.Delete();

        // Write a new .manifest file as an Application file. (-new Application -ToFile ".\codebase\<AppName.exe.manifest")
        // Include the files from the codebase directory in the manifest (-fd ".\codebase\")
        // Give the application a name to use in the start menu (-name "<AppName>")
        // Assign a version number to the deployment (-Version "<version>")
        // Give the application an icon to use in the start menu (-IconFile "64x64.ico")
        // Sign the manifest (-CertFile "<KeyName>.pfx -Password <password>)
        p.StartInfo.Arguments = "-new Application -fd \".\\" + codebase.TrimEnd('\\') + "\" -ToFile \".\\" + Path.Combine(codebase, "<AppName>.exe.manifest") + "\" -Name \"<AppName>\" -Version \"" + codebase.Substring(codebase.IndexOf('_') + 1, codebase.Length - (codebase.IndexOf('_') + 1)).Replace('_', '.').TrimEnd('\\') + "\" -CertFile \"<KeyName>.pfx\" -Password <Password> -IconFile \"64x64.ico\"";

        while (p.StartInfo.Arguments.Contains(".\\.\\"))
            p.StartInfo.Arguments = p.StartInfo.Arguments.Replace(".\\.\\", ".\\");

        Logger.Instance.LogInfo("Starting application: " + p.StartInfo.FileName + "\n\tWith arguments: " + p.StartInfo.Arguments, Logger.InfoType.Information);

        p.Start();

        while (!p.HasExited)
        {
            Thread.Sleep(100);
        }

        // Make a new deployment manifest (-new Deployment -t "<AppName>.application")
        // Make the application available offline (-I t)
        // Use the files from the .manifest we just made (-AppManifest ".\codebase\<AppName>.exe.manifest")
        p.StartInfo.Arguments = "-new Deployment -I t -t \"<AppName>.application\" -v \"" + codebase.Substring(codebase.IndexOf('_') + 1, codebase.Length - (codebase.IndexOf('_') + 1)).Replace('_', '.').TrimEnd('\\') + "\" -AppManifest \".\\" + codebase + "<AppName>.exe.manifest\" -pu \"http://" + Config.Instance.ClientSettings.Host + "/client/" + codebase.Replace('\\', '/') + "<AppName>.exe.manifest\"";

                    while (p.StartInfo.Arguments.Contains(".\\.\\"))
            p.StartInfo.Arguments = p.StartInfo.Arguments.Replace(".\\.\\", ".\\");

        Logger.Instance.LogInfo("Starting application: " + p.StartInfo.FileName + "\n\tWith arguments: " + p.StartInfo.Arguments, Logger.InfoType.Information);

        p.Start();

        while (!p.HasExited)
        {
            Thread.Sleep(100);
        }

        xDoc = new XmlDocument();
        xDoc.Load(Path.Combine(filedir.FullName, "<AppName>.application"));

        // Add to the Deployment manifest (.application) to make the application 
        // have a minimum required version of the current version,and makes a 
        // subscription so that the application will always check for updates before 
        // running.
        if (xDoc["asmv1:assembly"]["deployment"]["subscription"] != null)
        {
            xDoc["asmv1:assembly"]["deployment"].RemoveChild(xDoc["asmv1:assembly"]["deployment"]["subscription"]);
            xDoc["asmv1:assembly"]["deployment"].RemoveChild(xDoc["asmv1:assembly"]["deployment"]["deploymentProvider"]);
            XmlAttribute node = xDoc.CreateAttribute("minimumRequiredVersion");
            node.Value = codebase.Substring(codebase.IndexOf('_') + 1, codebase.Length - (codebase.IndexOf('_') + 1)).Replace('_', '.').TrimEnd('\\');
            xDoc["asmv1:assembly"]["deployment"].Attributes.Append(node);

            xDoc["asmv1:assembly"]["deployment"].InnerXml = "<subscription><update><beforeApplicationStartup /></update></subscription>";
        }

        xDoc.Save(Path.Combine(filedir.FullName, "<AppName>.application"));

        // Sign the deployment manifest (.application) (-Sign "\<AppName>.application" -CertFile "<AppName>.key" -Password <password>
        p.StartInfo.Arguments = "-Sign \"<AppName>.application\" -CertFile \"<AppName>.pfx\" -Password <password>";

        while (p.StartInfo.Arguments.Contains(".\\.\\"))
            p.StartInfo.Arguments = p.StartInfo.Arguments.Replace(".\\.\\", ".\\");

        Logger.Instance.LogInfo("Starting application: " + p.StartInfo.FileName + "\n\tWith arguments: " + p.StartInfo.Arguments, Logger.InfoType.Information);

        p.Start();

        while (!p.HasExited)
        {
            Thread.Sleep(100);
        }
    }
}
person Nathan Wheeler    schedule 26.02.2010

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

Для загрузчика установки вы не можете отказаться от подписанного загрузчика, поэтому мне пришлось найти стороннюю dll, чтобы отменить его подпись. (delcert) http://forum.xda-developers.com/showthread.php?t=416175 У меня есть эта мать в системе контроля версий на случай, если однажды она исчезнет из Интернета :)

Найдите раздел #Begin Resigning various Manifests

$root = "$PSScriptRoot"
$ToolsPath = "C:\Tools"
$CertFile = $ToolsPath + "\my cert.pfx"
$CertPassword = "wouldn't you like to know"

#Update the setup.exe bootstrappers update url
Start-Process "$PSScriptRoot\setup.exe" -ArgumentList "-url=`"$ClickOnceUpdateUrl`"" -Wait

#The bootstrappers signature is now invalid since we updated the url
#We need to remove the old signature
Start-Process 'C:\Tools\delcert.exe' -ArgumentList "`"$root\setup.exe`"" -Wait

Write-Host "$root [writeline]"
#Resign with signtool
Invoke-Expression 'C:\Tools\signtool.exe sign /d "My Company" /f "$CertFile" /p "$CertPassword" "$root\setup.exe"'

#update config properties
$CodeBasePath = Convert-Path "$PSScriptRoot\Application Files\MyProduct_*"
$ConfigPath = $CodeBasePath + "\MyProduct.dll.config.deploy"
[xml] $xml = Get-Content $ConfigPath

$Endpoint = $xml.SelectSingleNode('/configuration/appSettings/add[@key="MailCheckerEndpoint"]')
$Endpoint.value = $MailCheckerEndpoint

$ApiEndpoint = $xml.SelectSingleNode('/configuration/appSettings/add[@key="MyApi:ApiBaseUrl"]')
$ApiEndpoint.value = $MyProductApiEndpoint
$xml.Save($ConfigPath)  

#Begin Resigning various Manifests
$AppManifestPath = Convert-Path "Application Files\MyCompany_*\MyCompany.dll.manifest"

#Need to resign the application manifest, but before we do we need to rename all the files back to their original names (remove .deploy)
Get-ChildItem "$CodeBasePath\*.deploy" -Recurse | Rename-Item -NewName { $_.Name -replace '\.deploy','' }

#Resign application manifest
Invoke-Expression 'C:\Tools\mage.exe -update "$CodeBasePath\MyCompany.dll.manifest" -certFile "$CertFile" -password "$CertPassword" -if "Application Files\MyCompany_1_2_35_0\Resources\ID.ico"'

#Regisn deployment manifests in root and versioned folder
Invoke-Expression 'C:\Tools\mage.exe -update "$CodeBasePath\MyCompany.vsto" -certFile "$CertFile" -password "$CertPassword" -appManifest "$AppManifestPath" -pub "My Company" -ti "http://timestamp.globalsign.com/scripts/timstamp.dll"'
Invoke-Expression 'C:\Tools\mage.exe -update "$root\MyComapny.vsto" -certFile "$CertFile" -password "$CertPassword" -appManifest "$AppManifestPath" -pub "My company" -ti "http://timestamp.globalsign.com/scripts/timstamp.dll"'

#Rename files back to the .deploy extension, skipping the files that shouldn't be renamed
Get-ChildItem -Path "Application Files\*"  -Recurse | Where-Object {!$_.PSIsContainer -and $_.Name -notlike "*.manifest" -and $_.Name -notlike "*.vsto"} | Rename-Item -NewName {$_.Name + ".deploy"}
person The Muffin Man    schedule 28.08.2014