10

I must be missing something obvious here, but I have this at the end of my ASP.NET MVC web project's .csproj file:

    [...]
    <Target Name="BeforePublish">
        <Error Condition="'foo'=='foo'" Text="test publish error" />
    </Target>
</Project>

As far as I can tell, that should always cause the publish to fail with an error. Yet, if I load the project, right-click on it, and click "Publish", the thing publishes without a hitch. What am I missing?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jez
  • 27,951
  • 32
  • 136
  • 233
  • What happens if you execute `msbuild.exe MyMvcProject.csproj /t:PublishOnly` from VS2010 command line? – seva titov Oct 15 '12 at 20:23
  • Probably there is no such target "BeforePublish" (except this is your very custom target). What exact target you are trying to override with this construction ? – Alexey Shcherbak Oct 15 '12 at 22:13
  • @AlexeyShcherbak The `BeforePublish` target is documented on [this MSDN page](http://msdn.microsoft.com/en-us/library/ms366724%28v=vs.100%29.aspx), and it is defined in my `Microsoft.Common.targets` files. – Jez Oct 16 '12 at 08:21
  • @SevaTitov Interesting; I get the error `Publish is only valid for 'Windows Application' or 'Console Application' project types.` - so I guess when you right-click an MVC project in Visual Studio and click "Publish", it doesn't use MSBuild? Is it something built in to Visual Studio that I can't really customize? – Jez Oct 16 '12 at 08:58
  • It's likely that Visual Studio is calling the MSBuild Custom Tasks (assemblies) directly, so unfortunately there does not appear to be a way to intercept the Publish "target". Before/AfterBuild seems to be the only choice. – Dan Nolan Oct 16 '12 at 09:45
  • @DanielNolan Which isn't good enough because I don't wanna stop myself from *building* a Debug build. What do you think is the best system for safely doing website publishing? – Jez Oct 16 '12 at 09:47
  • First of all I would avoid publishing directly inside Visual Studio. Only use it to create your publish profiles and then deploy from command prompt (MSBuild.exe) or using a CI server (a BeforePublish will work there). If you have to publish inside VS, you could make an AfterBuild that only executes in the Release configuration. – Dan Nolan Oct 16 '12 at 09:53
  • @DanielNolan But that means you miss out on the VS publish functionality. Also, how do you mean "create your publish profiles"? MSBuild can't publish a web project at all, it seems. – Jez Oct 16 '12 at 10:01
  • Sorry, I didn't realise you're using VS2010 (publish profiles are a feature of VS2012). You can still make use of the publish functionality outside of VS, however it's a bit more long winded: http://stackoverflow.com/questions/8003125/how-to-do-the-exact-same-functionality-as-the-publish-inside-visual-studio-from – Dan Nolan Oct 16 '12 at 10:13
  • @Jez - right, but that BeforePublish clearly states it's purpose in comment there - it's dependency for PublishOnly and "PublishOnly target is intended for ClickOnce publishing inside the IDE, where the build has already been done by the BuildManager" . Not Mvc "publish' one, and you mentioned that you have MVC project. Anyway - sorry not being clear on that. – Alexey Shcherbak Oct 16 '12 at 13:19

3 Answers3

15

The answer I finally came up with which works well for Visual Studio 2010 and Visual Studio 2012; put this just before the end of your web application's .csproj file:

<Project...
    [...]
    <!-- The following makes sure we can't accidentally publish a non-Release configuration from within Visual Studio -->
    <Target Name="PreventNonReleasePublish2010" BeforeTargets="PipelinePreDeployCopyAllFilesToOneFolder" Condition="'$(BuildingInsideVisualStudio)'=='true' AND '$(VisualStudioVersion)'=='10.0'">
        <Error Condition="'$(Configuration)'!='Release'" Text="When publishing from Visual Studio 2010, you must publish the Release configuration!" />
    </Target>
    <Target Name="PreventNonReleasePublish2012" BeforeTargets="MSDeployPublish" Condition="'$(BuildingInsideVisualStudio)'=='true' AND '$(VisualStudioVersion)'=='11.0'">
        <Error Condition="'$(Configuration)'!='Release'" Text="When publishing from Visual Studio 2012, you must publish the Release configuration!" />
    </Target>
</Project>

Read on to see my thinking behind this answer, but basically it revolves around the fact that Visual Studio 2010 defines the PipelinePreDeployCopyAllFilesToOneFolder Target to hook on to, and Visual Studio 2012 defines the more "standard" MSDeployPublish Target to hook on to.

The above code only allows deploy publishing when in a Release configuration from within Visual Studio, but it could easily be modified to prevent all deploy publishing from within Visual Studio.

AFAIR, "Publish" from Visual Studio 2010 context menu invokes webdeploy\msdeploy tool. I played with it a bit, but I didn't liked at all. If you still want to use this functionality and insert your target somewhere - you need to know the exact target and its dependency property.

Check c:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets

You will find two tasks - MSDeploy and VSMSDeploy. The latter one sounds right for me. The first one is not used in this file at all. But VSMSDeploy used in three different targets, PackageUsingManifest, TestDeployPackageToLocal and MSDeployPublish. Again the latter one sounds good ;)

<Target Name="MSDeployPublish" DependsOnTargets="$(MSDeployPublishDependsOn)">

So you just need to override one property. Put this before your target and "YourTargetName" will be called right before MSDeployPublish.

<PropertyGroup>
    <MSDeployPublishDependsOn Condition="'$(MSDeployPublishDependsOn)'!=''">
        $(MSDeployPublishDependsOn);
        YourTargetName;
    </MSDeployPublishDependsOn>
  </PropertyGroup>

If you already switched to MSBuild 4.0, there is an easier way to hook your target. You just need to specify the BeforeTarget attribute. In our case it will be like this:

  <Target Name="MyTarget" BeforeTargets="MSDeployPublish">
        <Error Condition="'foo'=='foo'" Text="test publish error" />
  </Target>

I hope this helps. Ask if you have more questions.

PS: I didn't checked all that, because I don't have any MSDeploy-ready environments ;)

NB: I remember that I was discouraged from using MSDeploy for our own products because it was pretty counter-intuitive to properly configuring it for a continuous integration (CI) system. Maybe I wasn't very good at that, and your solution will work properly. But proceed with MSDeploy carefully.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alexey Shcherbak
  • 3,394
  • 2
  • 27
  • 44
  • Firstly, Intellisense tells me that it's `BeforeTargets` and not `BeforeTarget` as the `Target` attribute. Secondly, adding that didn't work unfortunately. It still publishes OK. :-( – Jez Oct 17 '12 at 13:05
  • Right, looks like I accidentialy erased one letter =(. Attribute name should be BeforeTargets. I just tested it and my console have publish error "test publish error". http://habrastorage.org/storage2/5a1/d4e/553/5a1d4e553b308d3b11ba15ce589ef6ab.jpg Fixed in main answer – Alexey Shcherbak Oct 17 '12 at 13:54
  • Yes, but even when I use the `BeforeTargets` attribute, it doesn't change anything. It still publishes. – Jez Oct 17 '12 at 16:11
  • Use This hint took from this question (you can upvote it for usefullness either ;) ): http://stackoverflow.com/questions/3297194/stop-msbuild-process-if-a-target-fails – Alexey Shcherbak Oct 17 '12 at 16:28
  • Did I answered your question? – Alexey Shcherbak Oct 18 '12 at 14:45
  • I tried this code at the end of my project file: http://pastebin.com/7WR4fp6m - it didn't stop the publish from occurring. Does that look right to you? – Jez Oct 18 '12 at 15:07
  • Code look right. But to try this - I need msdeploy env. I tested all samples with fake msdeploy so I can't tell for sure - whether it stopped or not, my deploy failed on error fallback or on msdeploy service connection error. Can you switch your msbuild output to diag, repeat your steps and post log somewhere? – Alexey Shcherbak Oct 18 '12 at 15:42
  • Here is the output of the publish when msbuild verbosity is set to diagnostic: http://game-point.net/misc/output.txt – Jez Oct 26 '12 at 09:40
  • It looks like the targets I added to the .csproj - `StopBuild` and `PreventDebugPublish` - are not even mentioned. :-( – Jez Oct 26 '12 at 09:41
  • Did you added mentioned targets to Bacp.Assess.Web.csproj, right? What command line was used to produce that output.txt log? It doesn't looks like usually "diag" output looks like. Could you post anywhere how did you added targets to csproj and what technic did you used to hook your targets? Gist maybe ? or if there any private stuff - mail me at centur (at) gmail – Alexey Shcherbak Oct 26 '12 at 10:29
  • I just used Visual Studio's Publish functionality to generate that output. – Jez Oct 26 '12 at 10:50
  • Please do the following: Go Tools->Options->Projects and Solutions-Build and Run and switch two lowest options "MSbuild .... verbosity" to Diagnostic. After that generate build log again and upload it. – Alexey Shcherbak Oct 26 '12 at 11:01
  • That's exactly what I did to get the above output. – Jez Oct 26 '12 at 11:03
  • Uhh, sorry again - didn't copied properly your output - lost about a half log =(. It's friday... Will add some comments soon – Alexey Shcherbak Oct 26 '12 at 11:14
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/18627/discussion-between-alexey-shcherbak-and-jez) – Alexey Shcherbak Oct 26 '12 at 11:29
  • Thanks for all your help on this - your suggested changes to the `.csproj` file do EXACTLY what I wanted. :-) Extremely useful, this answer deserves +100. :-) – Jez Oct 26 '12 at 12:55
5

Not sure if it would work in VS 2010 as I've only tested this in VS 2012, but I've found that putting the target in the (ProjectDir)/Properties/PublishProfiles/(ProfileName).pubxml file as an "AfterBuild" target works. As in, it doesn't fire when you build the project, but does fire when you publish the project.

So instead of putting

<Target Name="BeforePublish">
    <Error Condition="'foo'=='foo'" Text="test publish error" />
</Target>

in your .csproj file, try putting

<Target Name="AfterBuild">
    <Error Condition="'foo'=='foo'" Text="test publish error" />
</Target>

in your .pubxml file instead, and it should fire just before publishing.

superconnected
  • 696
  • 5
  • 3
  • Up-voted because this answer shows a technique to encapsulate the event, which is specific to a particular publish profile, with the event definition (instead of shoving it into the main project file). – harley.333 Aug 21 '15 at 11:50
-2

In my project files and are commented out by default, and I accidentally added a new inside the comments, and thus it wasn't called. Silly mistake.

After that I got the BeforePublish target to work in Visual Studio 2010.

<Target Name="BeforePublish">
 //insert awesome here
 </Target>

note: you must provide your own awesome :)

ZenkeiRich
  • 30
  • 2