使用MsBuild生成定制的MsDeploy清单(Package目标)

18

我正在使用Web Deploy来打包和部署我的产品的网站。特别地,我在解决方案中有两个不同的项目使用这种方法进行部署。

我在解决方案中有第三个项目(一个Windows服务),也需要安装在Web服务器上。

我知道我可以编写自定义清单(针对dirPathfilePathrunCommand提供者),并直接调用MsDeploy进行部署。但如果可能的话,我想利用现有的MsBuild任务来打包我的服务。

我看到可以通过msbuild目标对清单文件进行一些自定义:

http://social.msdn.microsoft.com/Forums/en/msbuild/thread/1044058c-f762-456b-8a68-b0863027ce47

尤其是使用MsDeploySourceManifest项。

在浏览适当的 .targets 文件后,看起来如果我使用Package目标,contentPathiisApp将附加到我的清单上。理想情况下,我只想复制程序集(或目录),可能设置ACL,并在服务上执行installutil.exe。

是否可以通过编辑我的csproj文件,完全自定义由Package目标生成的清单?

如果不行,是否有一种简单的方法来构建一个新目标,以执行与Package等效的操作,同时允许我输出完全自定义的清单?


@Merlyn:如果您有时间分享您的解决方案,那将是非常好的。从您上一条评论中可以看出,您已经很好地掌握了它。 - Jakub Januszkiewicz
@JakubJanuszkiewicz:请查看我的新答案和其中的链接。如果您有问题,请告诉我 - 我完全支持改进它,但除非人们感兴趣,否则我不想让它闪闪发光像钻石一样 :) - Merlyn Morgan-Graham
@musica:我得到了一个答案。抱歉让你等这么久才整理出来 :) 如果你还感兴趣,可以去看看。 - Merlyn Morgan-Graham
@SayedIbrahimHashimi:感谢您抽出时间查看答案并修复我的标签:) 如果您有更多要补充的,请随时贡献。 - Merlyn Morgan-Graham
@MerlynMorgan-Graham 最好不要导入Web .targets文件(这会将数千行MSBuild目标引入您的构建过程中)。但是我认为如果没有它,这将是困难的,因为您希望能够完全安装服务,包括停止/启动/注册,并且您希望能够通过生成的.cmd文件执行。下面的技术对您有多大帮助? - Sayed Ibrahim Hashimi
显示剩余3条评论
1个回答

17

我还没有为那些想学习如何使用此功能的人准备完整的说明文档,但现在我已经有了一个实现这个目标的说明文档。

http://thehappypath.net/2011/11/21/using-msdeploy-for-windows-services/

(编辑:链接暂时失效。如果您有兴趣,可以告诉我,我可以在其他地方发布).

我的指南概述了以下步骤:

  • 确保服务在安装时启动自身(不是必要的,但更容易处理)
  • 将Microsoft.WebApplication.targets文件添加到您的项目中,即使您没有Web项目。这使得Package MsBuild目标可用。
  • 向您的项目添加自定义的.targets文件,以构建自定义的MsBuild包清单
  • 向您的项目添加一些批处理脚本以停止/卸载和安装服务
  • 添加Parameters.xml文件以支持更轻松地更改目标部署目录
  • 使用SlowCheetah Visual Studio addon设置app.config转换

然后,您可以使用此命令行打包您的项目:

msbuild MyProject.csproj /t:Package /p:Configuration=Debug

您可以使用以下命令行部署生成的软件包:

MyService.Deploy.cmd /Y /M:mywebserver -allowUntrusted

除了我的指南之外,这是最不被记录的部分,即创建自定义清单。以下是我当前文件的转储(请注意,它仍然有一些错误,但可以修复-请参阅此问题:MsDeploy远程执行清单两次-并尝试仅使用直接批处理文件来运行runCommand)。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build"
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- This file must be included before Microsoft.Web.Publishing.targets so we can hook into BeforeAddIisSettingAndFileContentsToSourceManifest -->

  <PropertyGroup>

    <!-- Include our targets -->
    <IncludeStopServiceCommand>True</IncludeStopServiceCommand>
    <IncludeSetCustomAclsProvider>True</IncludeSetCustomAclsProvider>
    <IncludeInstallServiceCommand>True</IncludeInstallServiceCommand>
    <IncludeMoveAppConfigToCorrectPackagePath>True</IncludeMoveAppConfigToCorrectPackagePath>

    <!-- Uncomment to enable more verbose MsBuild logging -->
    <!-- <EnablePackageProcessLoggingAndAssert>True</EnablePackageProcessLoggingAndAssert> -->

    <!-- Enable web.config transform, but hack it to work for app.config -->
    <ProjectConfigFileName>app.config</ProjectConfigFileName>
    <TransformWebConfigEnabled>True</TransformWebConfigEnabled>
    <UseParameterizeToTransformWebConfig>True</UseParameterizeToTransformWebConfig>

    <!-- Enable web project packaging, but hack it to work for non-web app -->
    <DeployAsIisApp>False</DeployAsIisApp>
    <IncludeIisSettingsOnPublish>False</IncludeIisSettingsOnPublish>
    <IncludeSetAclProviderOnDestination>False</IncludeSetAclProviderOnDestination>
    <DisableAllVSGeneratedMSDeployParameter>True</DisableAllVSGeneratedMSDeployParameter>

    <!-- Insert our custom targets into correct places in build process -->
    <BeforeAddIisSettingAndFileContentsToSourceManifest Condition="'$(BeforeAddIisSettingAndFileContentsToSourceManifest)'==''">
      $(BeforeAddIisSettingAndFileContentsToSourceManifest);
      AddStopServiceCommand;
    </BeforeAddIisSettingAndFileContentsToSourceManifest>

    <AfterAddIisSettingAndFileContentsToSourceManifest Condition="'$(AfterAddIisSettingAndFileContentsToSourceManifest)'==''">
      $(AfterAddIisSettingAndFileContentsToSourceManifest);
      AddSetCustomAclsProvider;
      AddInstallServiceCommand;
    </AfterAddIisSettingAndFileContentsToSourceManifest>

    <OnAfterCopyAllFilesToSingleFolderForPackage Condition="'$(OnAfterCopyAllFilesToSingleFolderForPackage)'==''">
      $(OnAfterCopyAllFilesToSingleFolderForPackage);
      MoveAppConfigToCorrectPackagePath;
    </OnAfterCopyAllFilesToSingleFolderForPackage>

  </PropertyGroup>

  <!-- Custom targets -->
  <Target Name="AddStopServiceCommand" Condition="'$(IncludeStopServiceCommand)'=='true'">
    <Message Text="Adding runCommand to stop the running Service" />
    <ItemGroup>

      <MsDeploySourceManifest Include="runCommand">
        <path>$(_MSDeployDirPath_FullPath)\bin\servicestop.bat</path>
        <waitInterval>20000</waitInterval>
        <AdditionalProviderSettings>waitInterval</AdditionalProviderSettings>
      </MsDeploySourceManifest>

    </ItemGroup>
  </Target>

  <Target Name="AddSetCustomAclsProvider" Condition="'$(IncludeSetCustomAclsProvider)'=='true'">
    <ItemGroup>

      <MsDeploySourceManifest Include="setAcl">
        <Path>$(_MSDeployDirPath_FullPath)</Path>
        <setAclUser>LocalService</setAclUser>
        <setAclAccess>FullControl</setAclAccess> <!-- Todo: Reduce these permissions -->
        <setAclResourceType>Directory</setAclResourceType>
        <AdditionalProviderSettings>setAclUser;setAclAccess;setAclResourceType</AdditionalProviderSettings>
      </MsDeploySourceManifest>

    </ItemGroup>
  </Target>

  <Target Name="AddInstallServiceCommand" Condition="'$(IncludeInstallServiceCommand)'=='true'">
    <Message Text="Adding runCommand to install the Service" />
    <ItemGroup>

      <MsDeploySourceManifest Include="runCommand">
        <path>cmd.exe /c $(_MSDeployDirPath_FullPath)\bin\serviceinstall.bat</path>
        <waitInterval>20000</waitInterval>
        <dontUseCommandExe>false</dontUseCommandExe>
        <AdditionalProviderSettings>waitInterval;dontUseCommandExe</AdditionalProviderSettings>
      </MsDeploySourceManifest>

    </ItemGroup>
  </Target>

  <Target Name="MoveAppConfigToCorrectPackagePath"
          Condition="'$(IncludeMoveAppConfigToCorrectPackagePath)'=='true'">
    <PropertyGroup>
      <OriginalAppConfigFilename>$(_PackageTempDir)\App.Config</OriginalAppConfigFilename>
      <TargetAppConfigFilename>$(_PackageTempDir)\bin\$(TargetFileName).config</TargetAppConfigFilename>
    </PropertyGroup>

    <Copy SourceFiles="$(OriginalAppConfigFilename)" DestinationFiles="$(TargetAppConfigFilename)" 
          Condition="Exists($(OriginalAppConfigFilename))" />
    <Delete Files="$(OriginalAppConfigFilename)" 
            Condition="Exists($(OriginalAppConfigFilename))" />
  </Target>

</Project>

为什么在步骤列表中包含它们的原始值,而早些时候你断言它们是空的?这是因为某些丑陋的东西吗?我的意思是,有些属性看起来像 <prop.. condition=(prop is empty)> $(prop); extension </prop>,所以它们既是“如果未设置则使用”,又是“注入而不是覆盖”。你的示例仅在没有其他内容附加时才会附加自身,并同时尝试保留原始内容(假定直接在其前面为空)。我认为要么是那些条件不需要,要么是“concat”行为不需要。 - quetzalcoatl
无论如何,干得好!在找到你的代码片段之前,我刚好也遇到了类似的补丁/扩展,所以这可能是唯一的方法。顺便说一下,我还发现TransformWebConfigEnabled默认为true,所以可能也可以被简化。另外,你尝试过对app.config进行参数化吗?由于webconfig/appconfig已经被移动和重命名,从SetParameters.xml应用值时是否会有任何问题? - quetzalcoatl
嗯...是的,我相当确定会出问题的,因为在包内的 parameters.xml 中有明显引用旧的 'App.config'。我认为需要在 ImportParametersFiles 目标或附近进行一些额外的修复。 - quetzalcoatl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接