【问题标题】:Mage.exe deployment problemsMage.exe 部署问题
【发布时间】:2010-02-22 19:49:46
【问题描述】:

我有一个需要在每台服务器上更改的配置文件,因此一旦服务器上安装了我们的软件,客户端安装程序的配置文件就会设置为匹配该服务器的特定设置,然后复制到 Web 上的公用文件夹进行部署。

由于我要更改配置文件,我还必须重建 *.manifest 和 *.application 文件,据我了解,我唯一真正的选择是使用 Win7 SDK 中的 Mage.exe。为了使用修改后的配置文件中的正确哈希修复 *.manifest 文件,我运行:

mage -new Application -fd ".\Application Files\_1_0_0_0" -ToFile ".\Application Files\_1_0_0_0\.exe.manifest" -Name "" -Version "1.0. 0.0" -CertFile "key.pfx" -password ""

然后,为了使用修改后的 *.manifest 文件中的正确哈希来修复 *.application 文件,我运行:

mage -new Deployment -It -t ".application" -v "1.0.0.0" -appManifest ".\Application Files\_1_0_0_0\.exe.manifest" -pu " http:////Application Files/_1_0_0_0/.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 并修改它们以适应,然后重新签署文件。此外,我现在还无法确定如何将图标添加到部署中,因此“开始”菜单项获得了通用图标以外的图标。

【问题讨论】:

  • 我有一个非常相似的用例,有类似的问题。如果我找到解决方案,我会回复。
  • 只需在 mage 上使用 -appc 标志
  • 内森,看看我的回答,看看它是否对你有帮助。使用 Mage.exe 应该适合您。

标签: deployment command-line clickonce mage


【解决方案1】:

这是我的实现。我在这段代码上花了很多时间,但我仍然没有找到让 Mage 在不干预的情况下处理所有 .application 文件的生成的所有正确选项。我要说的是,可能有很多可以对这段代码进行优化的地方。但是,这仍然可以用作帮助某人的跳板。

为了使以下方法起作用,您必须至少从 VS 中的 ClickOnce 部署一次,然后只保留该部署中的 .application 文件。您必须删除部署文件夹中的 .application 和 .manifest。

将所有应用程序文件移至Config.Instance.ServerSettings.ClientLocation + "&lt;AppName&gt;_&lt;version&gt;"后:

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);
        }
    }
}

【讨论】:

    【解决方案2】:

    如果您的目标是在环境之间修改应用程序清单,我不确定您为什么要创建一个新清单。只需修改您当前的。我正在发布一个 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"}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-17
      • 1970-01-01
      • 2020-06-23
      • 2014-07-07
      • 2014-11-07
      • 2011-11-25
      相关资源
      最近更新 更多