dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI
https://dot.net/core
MIT License
2.7k stars 1.06k forks source link

Add support for customizing msdeploy publishing pipeline for .NET Core/SDK apps #12476

Open johncrim opened 6 years ago

johncrim commented 6 years ago

Old style/.NET Framework webapps in Visual Studio have a publish pipeline that could be customized. For example, a Parameters.xml file could be specified, like so:

  <PropertyGroup>
    <WebAppPackageConfigDir Condition=" '$(WebAppPackageConfigDir)' == '' ">$(MSBuildProjectDirectory)\Package</WebAppPackageConfigDir>
    <ProjectParametersXMLFile>$(WebAppPackageConfigDir)\Parameters.xml</ProjectParametersXMLFile>
  </PropertyGroup>

so that when the MSDeploy package is built, the MSDeploy parameters defined therein are incorporated into the package. The Web Processing Pipeline (defined in C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.targets) supports customizing the MSDeploy package in multiple ways - for example, package parameters can be defined in a file or in an MSBuild <ItemGroup>.

In SDK style webapps (eg ASP.NET Core webapps), a "new" publishing pipeline is used starting in eg C\Program Files\dotnet\sdk\2.1.2\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Publish.targets. This pulls in additional targets files depending on the <PublishProtocol> value, eg C\Program Files\dotnet\sdk\2.1.2\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\PublishTargets\Microsoft.NET.Sdk.Publish.MSDeploy.targets or .\Microsoft.NET.Sdk.Publish.MSDeployPackage.targets. Both files define a _CreateParametersFiles target as:

  <!--
  ***********************************************************************************************
  TARGET : _CreateParameterFiles
  ***********************************************************************************************
 -->

  <Target Name="_CreateParameterFiles">

    <ItemGroup>
      <MsDeployDeclareParameters Remove="@(MsDeployDeclareParameters)" />

      <MsDeployDeclareParameters Include="IIS Web Application Name" >
        <Kind>ProviderPath</Kind>
        <Scope>IisApp</Scope>
        <Match>$(PublishIntermediateOutputPath)</Match>
        <Description></Description>
        <DefaultValue>$(DeployIisAppPath)</DefaultValue>
        <Value>$(DeployIisAppPath)</Value>
        <Tags>IisApp</Tags>
        <Priority></Priority>
        <ExcludeFromSetParameter>false</ExcludeFromSetParameter>
      </MsDeployDeclareParameters>
    </ItemGroup>

    <ItemGroup Condition="'@(_EFSQLScripts)' != ''">
      <MsDeployDeclareParameters Include="%(_EFSQLScripts.DBContext)">
        <Kind>ProviderPath</Kind>
        <Scope>dbfullsql</Scope>
        <Match>%(_EFSQLScripts.Identity)</Match>
        <Description></Description>
        <DefaultValue>%(_EFSQLScripts.ConnectionString)</DefaultValue>
        <Value>%(_EFSQLScripts.ConnectionString)</Value>
        <Tags>dbfullsql</Tags>
        <Priority></Priority>
        <ExcludeFromSetParameter>false</ExcludeFromSetParameter>
      </MsDeployDeclareParameters>
    </ItemGroup>

    <CreateParameterFile
      Parameters="@(MsDeployDeclareParameters)"
      DeclareSetParameterFile="$(_MSDeployParametersFilePath)"
      IncludeDefaultValue="True"
      OptimisticParameterDefaultValue="$(EnableOptimisticParameterDefaultValue)"
      SetParameterFile="$(_MSDeploySetParametersFilePath)"
      GenerateFileEvenIfEmpty="True" />
  </Target>

It appears that there's no support for customizing deploy parameters. Similarly, the <Target Name="_CreateManifestFiles"> target appears to prohibit customization.

The previous Web Publishing Pipeline is/was clunky, but there were articles/blog posts about how to customize it. With ASP.NET Core, there is no mention of customizing or extending the deploy process, that I can find. I did find a Channel 9 Video on ASP.NET Publishing, but it seems outdated - it applies to VS 2017 with the project.json and powershell scripts used for deployment.

There should be support for adding new msdeploy parameters, and for customizing the manifest before calling MSDeploy to build the package. And, there should be some documentation showing how to use these features.

c-s-n commented 6 years ago

There is clearly a notable difference between the different publishing pipelines, meaning Microsoft.Web.Publishing.targets of ASP.NET 4.x, the .xproj/project.json based pipeline for ASP.NET Core applications and the new dotnet SDK-based Microsoft.NET.Sdk.Publish.MSDeployPackage.targets with Visual Studio 2017 (and MSBuild).

I'm referring to the publish package here, where the old ASP.NET and the new SDK-based publishing have the following, much-discussed problem: They resemble the temporary publish folder path within the zip package, which causes disclosure of the build VM's internals and might result in too long paths for proper extraction. See e.g. this MSDN or this explanation and workaround for ASP.NET.

At least for the long path issue I have created an MSBuild-oriented .pubxml file that deals with the path replacement, exposing of the path in the included xml files, the cleanup of sample files and the inclusion of the parameters.xml (which in all cases has some side effects, see XML comments). Maybe it helps some of you:

<Project ToolsVersion="15.6" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- Import tasks that were dropped compared to Microsoft.Web.Publishing.targets -->
  <UsingTask TaskName="EscapeTextForRegularExpressions" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

  <!-- Visual Studio publish settings -->
  <PropertyGroup>
    <WebPublishMethod>Package</WebPublishMethod>
    <PackageAsSingleFile>True</PackageAsSingleFile>
    <DeployIisAppPath>MyAppName</DeployIisAppPath>
    <!-- ... -->
  </PropertyGroup>

  <!-- Additional SDK publish target settings -->
  <PropertyGroup>
    <EnablePackageProcessLoggingAndAssert>False</EnablePackageProcessLoggingAndAssert>
  </PropertyGroup>

  <!-- Not anymore known old publishing pipeline settings, custom implemented -->
  <PropertyGroup>
    <!-- Whether to provide deploy.cmd, readme and SetParameters.xml files alongside the zip -->
    <GenerateSampleDeployScript>False</GenerateSampleDeployScript>
    <!--
    Whether to generate the Package.Parameters.xml file and including it as parameters.xml in the published package.
    With the parameters file the Application Path in the Web Deploy dialog is prepopulated correctly, but there are
    issues with consecutive deployments, so that they end up in the wrong (a previously known) folder. Without it, the
    internal, original temporary publish folder is shown from archive.xml (not even the shortened one), but consecutive
    deployments work.
    -->
    <UseDeclareParametersXMLInMsDeploy>False</UseDeclareParametersXMLInMsDeploy>
  </PropertyGroup>

  <!-- Custom properties -->
  <PropertyGroup>
    <!-- Absolute path to a temporary publish folder that will be human readible in the package's parameters.xml file and encoded in the archive.xml -->
    <PublishIntermediateOutputPath>C:\$([System.Guid]::NewGuid().ToString('N'))</PublishIntermediateOutputPath>
    <!-- Non-empty relative path which replaces the absolute PubTmp\Out folder hierarchy inside the package -->
    <_PackagePathShortened Condition="'$(_PackagePathShortened)' == ''">website</_PackagePathShortened>
  </PropertyGroup>

  <Target Name="AddReplaceRuleForAppPath" BeforeTargets="BeforePublish">
    <Message Text="Adding replace rules for application path" Importance="high" />
    <EscapeTextForRegularExpressions Text="$(PublishIntermediateOutputPath)">
      <Output PropertyName="_PackagePathRegex" TaskParameter="Result" />
    </EscapeTextForRegularExpressions>
    <!-- Add a replace rule for VSMSDeploy resp. MSdeploy to update the path -->
    <ItemGroup>
      <MsDeployReplaceRules Include="replaceFullPath">
        <Match>$(_PackagePathRegex)</Match>
        <Replace>$(_PackagePathShortened)</Replace>
      </MsDeployReplaceRules>
    </ItemGroup>
  </Target>

  <Target Name="DeleteSamplePublishFilesBefore" BeforeTargets="MSDeployPackagePublish">
    <Message Text="Deleting undesired sample publish files" Importance="high" />
    <Delete Files="$(_MSDeploySetParametersFilePath);$(_MSDeployScriptFilePath);$(_MSDeployReadMeFilePath)" Condition="!$(GenerateSampleDeployScript)" ContinueOnError="true" />
    <Delete Files="$(_MSDeployParametersFilePath);" Condition="!$(UseDeclareParametersXMLInMsDeploy)" ContinueOnError="true" />
    <PropertyGroup>
      <_MSDeploySetParametersFilePath Condition="!$(GenerateSampleDeployScript)"></_MSDeploySetParametersFilePath>
      <_MSDeployScriptFilePath Condition="!$(GenerateSampleDeployScript)"></_MSDeployScriptFilePath>
      <_MSDeployReadMeFilePath Condition="!$(GenerateSampleDeployScript)"></_MSDeployReadMeFilePath>
      <_MSDeployParametersFilePath Condition="!$(UseDeclareParametersXMLInMsDeploy)"></_MSDeployParametersFilePath>
    </PropertyGroup>
    <Message Text="Publishing to $(PublishIntermediateOutputPath)" Importance="high" />
  </Target>

  <Target Name="DeletePublishFilesAfter" AfterTargets="MSDeployPackagePublish">
    <Message Text="Deleting undesired publish files" Importance="high" />
    <Delete Files="$(_MsDeploySourceManifestPath)" Condition="!$(GenerateSampleDeployScript)" ContinueOnError="true" />
    <Delete Files="$(_MSDeployParametersFilePath)" Condition="!$(GenerateSampleDeployScript) And $(UseDeclareParametersXMLInMsDeploy)" ContinueOnError="true" />
  </Target>

  <Target Name="CleanIntermediateOutputPath" AfterTargets="MSDeployPackagePublish">
    <Message Text="Cleaning intermediate output path" Importance="high" />
    <RemoveDir Directories="$(PublishIntermediateOutputPath)" />
  </Target>
</Project>
ladenedge commented 3 years ago

I have posted a (pretty painful) workaround to this problem on Stack Overflow.

stasberkov commented 3 years ago

dotnet publish does support parameters.xml file but in very limited way: it just uses default values from it. You cannot specify other values in MSBuild script. This is very inconvenient. Can you allow changing values as it was before?