Using msbuild to execute a File System Publish Profile

109,999

Solution 1

FYI: I had the same issue with Visual Studio 2015. After many of hours trying, I can now do msbuild myproject.csproj /p:DeployOnBuild=true /p:PublishProfile=myprofile.

I had to edit my .csproj file to get it working. It contained a line like this:

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" 
  Condition="false" />

I changed this line as follows:

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0\WebApplications\Microsoft.WebApplication.targets" />

(I changed 10.0 to 14.0, not sure whether this was necessary. But I definitely had to remove the condition part.)

Solution 2

Found the answer here: http://www.digitallycreated.net/Blog/59/locally-publishing-a-vs2010-asp.net-web-application-using-msbuild

Visual Studio 2010 has great new Web Application Project publishing features that allow you to easy publish your web app project with a click of a button. Behind the scenes the Web.config transformation and package building is done by a massive MSBuild script that’s imported into your project file (found at: C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets). Unfortunately, the script is hugely complicated, messy and undocumented (other then some oft-badly spelled and mostly useless comments in the file). A big flowchart of that file and some documentation about how to hook into it would be nice, but seems to be sadly lacking (or at least I can’t find it).

Unfortunately, this means performing publishing via the command line is much more opaque than it needs to be. I was surprised by the lack of documentation in this area, because these days many shops use a continuous integration server and some even do automated deployment (which the VS2010 publishing features could help a lot with), so I would have thought that enabling this (easily!) would be have been a fairly main requirement for the feature.

Anyway, after digging through the Microsoft.Web.Publishing.targets file for hours and banging my head against the trial and error wall, I’ve managed to figure out how Visual Studio seems to perform its magic one click “Publish to File System” and “Build Deployment Package” features. I’ll be getting into a bit of MSBuild scripting, so if you’re not familiar with MSBuild I suggest you check out this crash course MSDN page.

Publish to File System

The VS2010 Publish To File System Dialog Publish to File System took me a while to nut out because I expected some sensible use of MSBuild to be occurring. Instead, VS2010 does something quite weird: it calls on MSBuild to perform a sort of half-deploy that prepares the web app’s files in your project’s obj folder, then it seems to do a manual copy of those files (ie. outside of MSBuild) into your target publish folder. This is really whack behaviour because MSBuild is designed to copy files around (and other build-related things), so it’d make sense if the whole process was just one MSBuild target that VS2010 called on, not a target then a manual copy.

This means that doing this via MSBuild on the command-line isn’t as simple as invoking your project file with a particular target and setting some properties. You’ll need to do what VS2010 ought to have done: create a target yourself that performs the half-deploy then copies the results to the target folder. To edit your project file, right click on the project in VS2010 and click Unload Project, then right click again and click Edit. Scroll down until you find the Import element that imports the web application targets (Microsoft.WebApplication.targets; this file itself imports the Microsoft.Web.Publishing.targets file mentioned earlier). Underneath this line we’ll add our new target, called PublishToFileSystem:

<Target Name="PublishToFileSystem"
        DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
    <Error Condition="'$(PublishDestination)'==''"
           Text="The PublishDestination property must be set to the intended publishing destination." />
    <MakeDir Condition="!Exists($(PublishDestination))"
             Directories="$(PublishDestination)" />

    <ItemGroup>
        <PublishFiles Include="$(_PackageTempDir)\**\*.*" />
    </ItemGroup>

    <Copy SourceFiles="@(PublishFiles)"
          DestinationFiles="@(PublishFiles->'$(PublishDestination)\%(RecursiveDir)%(Filename)%(Extension)')"
          SkipUnchangedFiles="True" />
</Target>

This target depends on the PipelinePreDeployCopyAllFilesToOneFolder target, which is what VS2010 calls before it does its manual copy. Some digging around in Microsoft.Web.Publishing.targets shows that calling this target causes the project files to be placed into the directory specified by the property _PackageTempDir.

The first task we call in our target is the Error task, upon which we’ve placed a condition that ensures that the task only happens if the PublishDestination property hasn’t been set. This will catch you and error out the build in case you’ve forgotten to specify the PublishDestination property. We then call the MakeDir task to create that PublishDestination directory if it doesn’t already exist.

We then define an Item called PublishFiles that represents all the files found under the _PackageTempDir folder. The Copy task is then called which copies all those files to the Publish Destination folder. The DestinationFiles attribute on the Copy element is a bit complex; it performs a transform of the items and converts their paths to new paths rooted at the PublishDestination folder (check out Well-Known Item Metadata to see what those %()s mean).

To call this target from the command-line we can now simply perform this command (obviously changing the project file name and properties to suit you):

msbuild Website.csproj "/p:Platform=AnyCPU;Configuration=Release;PublishDestination=F:\Temp\Publish" /t:PublishToFileSystem

Solution 3

Still had trouble after trying all of the answers above (I use Visual Studio 2013). Nothing was copied to the publish folder.

The catch was that if I run MSBuild with an individual project instead of a solution, I have to put an additional parameter that specifies Visual Studio version:

/p:VisualStudioVersion=12.0

12.0 is for VS2013, replace with the version you use. Once I added this parameter, it just worked.

The complete command line looks like this:

MSBuild C:\PathToMyProject\MyProject.csproj /p:DeployOnBuild=true /p:PublishProfile=MyPublishProfile /p:VisualStudioVersion=12.0

I've found it here:

http://www.asp.net/mvc/overview/deployment/visual-studio-web-deployment/command-line-deployment

They state:

If you specify an individual project instead of a solution, you have to add a parameter that specifies the Visual Studio version.

Solution 4

It looks to me like your publish profile is not being used, and doing some default packaging. The Microsoft Web Publish targets do all what you are doing above, it selects the correct targets based on the config.

I got mine to work no problem from TeamCity MSBuild step, but I did specify an explicit path to the profile, you just have to call it by name with no .pubxml (e.g. FileSystemDebug). It will be found so long as in the standard folder, which yours is.

Example:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe ./ProjectRoot/MyProject.csproj /p:DeployOnBuild=true /p:PublishProfile=FileSystemDebug

Note this was done using the Visual Studio 2012 versions of the Microsoft Web Publish targets, normally located at "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\Web". Check out the deploy folder for the specific deployment types targets that are used

Solution 5

Actually I merged all your answers to my own solution how to solve the above problem:

  1. I create the pubxml file according my needs
  2. Then I copy all the parameters from pubxml file to my own list of parameters "/p:foo=bar" for msbuild.exe
  3. I throw away the pubxml file

The result is like that:

msbuild /t:restore /t:build /p:WebPublishMethod=FileSystem /p:publishUrl=C:\builds\MyProject\ /p:DeleteExistingFiles=True /p:LastUsedPlatform="Any CPU" /p:Configuration=Release

Share:
109,999
P. Roe
Author by

P. Roe

Developer with a fervent focus on SOLID design principals. I primarily enjoy building high volume Api's and back end systems.

Updated on July 08, 2022

Comments

  • P. Roe
    P. Roe almost 2 years

    I have a c# .Net 4.0 project created with VS2010 and now being accessed with VS2012.

    I'm trying to publish only the needed files from this website to a destination location (C:\builds\MyProject[Files])

    My file structure: ./ProjectRoot/MyProject.csproj ./ProjectRoot/Properties/PublishProfiles/FileSystemDebug.pubxml

    I'm running the following via MSBuild:

    C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe ./ProjectRoot/MyProject.csproj /p:DeployOnBuild=true /p:PublishProfile=./ProjectRoot/Properties/PublishProfiles/FileSystemDebug.pubxml

    Here's the xml in FileSystemDebug.pubxml

    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <WebPublishMethod>FileSystem</WebPublishMethod>
        <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
        <LastUsedPlatform>Any CPU</LastUsedPlatform>
        <SiteUrlToLaunchAfterPublish />
        <ExcludeApp_Data>False</ExcludeApp_Data>
        <publishUrl>C:\builds\MyProject\</publishUrl>
        <DeleteExistingFiles>True</DeleteExistingFiles>
      </PropertyGroup>
    </Project>
    

    The resulting behavior is:

    • a zip file is created here:./ProjectRoot/obj/Debug/Package/MyProject.zip
    • Nothing is deployed to <publishUrl>C:\builds\MyProject\</publishUrl> WTF
    • the zip file that is created is a pigs breakfast and full of files that aren't needed for the application.

    When I run this publish profile through visual studio a folder is created at *C:\builds\MyProject* and contains the exact artifacts that I want.

    How do I get this simple result from msbuild?

  • fan711
    fan711 over 10 years
    I can't make sense of the code snippet for the new target (it shows 01 02 03 ...). Could you please edit?
  • Антон Курьян
    Антон Курьян over 10 years
    I agree with fan711. Though, solution is descrived on link - what for was then to copy it?
  • Oliver
    Oliver over 9 years
    @АнтонКурьян: Links tend to die after some time, that's why questions and answers on stackoverflow.com should always be self-contained without relying on external resources.
  • GregS
    GregS about 9 years
    The results look like the publish profile is not being used by MSBuild at all, and it's instead doing a package (maybe a default?). What your below solution does it to replicate what the profile is set to do, which Microsoft.Web.Publishing.targets handles by selecting the correct type from it's deploy folder (for FileSystem). So looks like you are reinventing the wheel here, instead of solving that problem. But can't say for sure without your MSBuild log. I got mine to work, details in my answer
  • NightWatchman
    NightWatchman about 9 years
    This works for me, but copies all project files instead of just the ones needed to run the application on the webserver. Would love to see a solution that just copies the files the visual studio does when I run the publishing profile.
  • NightWatchman
    NightWatchman about 9 years
    GregS' solution does not work for me. Builds fine, but no files are copied to the publish directory
  • P. Roe
    P. Roe over 8 years
    MS has made a lot of improvements since this was posted a few years ago, thanks for the updated programming?
  • Steven Liekens
    Steven Liekens about 8 years
    The conditional import with Condition="false" exists for backwards compatibility. VS2010 requires that this import exists, even if it is skipped because of the falsy condition. If you look again then you'll see that the csproj contains another import for $(VSToolsPath)\WebApplications\Microsoft.WebApplication.targ‌​ets which resolves to the targets file for the current version of Visual Studio.
  • Al Dass
    Al Dass about 8 years
    Note the strategic use of $(MSBuildToolsVersion) in the path to account for the proper VS version: <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v‌​$(MSBuildToolsVersio‌​n)\WebApplications\M‌​icrosoft.WebApplicat‌​ion.targets" Condition="false" />. This worked for me on VS2015 Update 1.
  • P. Roe
    P. Roe over 6 years
    Sorry shammakalubo you misinterpreted the question a great deal.
  • Syed Waqas
    Syed Waqas over 6 years
    @shammakalubo The answer is right, but it's just not stated completely. This parameter needs to be added to the command mentioned by the OP, which will then become: MSBuild C:\PathToMyProject\MyProject.csproj /p:DeployOnBuild=true /p:PublishProfile=MyPublishProfile /p:VisualStudioVersion=12.0 This parameter is what I was missing and fixed my issue. You just need to mention the answer completely!
  • Shammie
    Shammie over 4 years
    @WaqasShah Thanks, As you mentioned I have edited my answer and published the complete code what it would look like.
  • Martin Braun
    Martin Braun over 2 years
    This will not work for .NET Core 5.0+ applications (not web)