A few “features” in MSBuild that caused me pain today…

Today I decided to work on deploying to both our internal load-balanced app servers directly from a manually run build. Unfortunately I came across the following issues that caused me to spend far too long on this task!!

Currently I have a library of MSBuild .targets files that load in generic tasks that I can run after a build has run. One of them is a DeployWebsite task that uses a couple of Properties and can clean down and deploy the latest code from the build. I realised that all I needed to do was modify the current build to set the deploy location first. However this turned out to be a lot harder than I thought it would be…

Firstly I tried the following code which doesn’t set the Global property “AppFolderPath”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <AppFolderPath>[INVALID]</AppFolderPath>
    </PropertyGroup>
    <Target Name="SetDeployLocation">
        <PropertyGroup>
            <AppFolderPath>[SOME SHARE ON SERVER]</AppFolderPath>
        </PropertyGroup>
        <CallTarget Targets="DeployWebsite" />
    </Target>
    <Target Name="DeployWebsite">
        <Message Text="AppFolderPath=$(AppFolderPath)" />
    </Target>
</Project>

To fix this you need to seperate the tasks into two Targets and call them from a single parent Target and set the Property using CreateProperty (not declaratively and doesn’t matter if you have already created it!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <AppFolderPath>[INVALID SHARE]</AppFolderPath>
    </PropertyGroup>
    <Target Name="DeployToLive">
        <CallTarget Targets="SetDeployLocation" />
        <CallTarget Targets="DeployWebsite" />
    </Target>
    <Target Name="SetDeployLocation">
        <CreateProperty Value="[SOME SHARE ON SERVER]">
            <Output TaskParameter="Value" PropertyName="AppFolderPath" />
        </CreateProperty>
    </Target>
    <Target Name="DeployWebsite">
        <Message Text="AppFolderPath=$(AppFolderPath)" />
    </Target>
</Project>

Once I’d fixed those it still wouldn’t call the DeployWebsite multiple times. The first worked fine, however it turns out that MSBuild will not allow you to call a target with the same name twice. To get around this you can spawn a separate MSBuild task for each instance you need to run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="DeployWebsiteToLive">
        <MSBuild Targets="DeployWebsite"
            Properties="AppFolderPath=[SHARE ON SERVER 1];BuildDirectory=$(BuildDirectory)"
            Projects="$(MSBuildProjectFile)" />

        <MSBuild Targets="DeployWebsite"
            Properties="AppFolderPath=[SHARE ON SERVER 2];BuildDirectory=$(BuildDirectory)"
            Projects="$(MSBuildProjectFile)" />
    </Target>
    <Target Name="DeployWebsite">
        <Message Text="AppFolderPath=$(AppFolderPath)" />
    </Target>
</Project>

One thing to notice there is you need to pass across ALL properties that are not set declaratively. If you do not pass anything these are all sent across automatically, but MSBuild still detects that the two tasks are exactly the same and only runs the first one.

Hopefully you won’t have to spend as long as I did on it now! 🙂