Last week was an education in making Msbuild jump through hoops to automate our deployments. Having a development background it is always strange switching context into the land of Msbuild. I find myself wanting to create variables and control structures, alas that is not how it works.
One interesting challenge I was able to overcome was figuring out how to pick the first item from an item when the item is a list. We have multiple destiation servers we deploy to and we keep them in one item as a list, ‘DeployDestination’. I only want to use one of them as the source for the backup, however. To do the deployment I iterate through the server items calling a task to delete the code at that destination and copy in the new code per the below:
<PropertyGroup>
<BackUpRoot>C:\BackupRoot</BackUpRoot>
<SourceFileLocation>C:WorkingBuildLocation\</SourceFileLocation>
</PropertyGroup>
<ItemGroup>
<DeployDestination Include="\\serv1\root\" >
<ServerName>Web1</ServerName>
<Environment>Production</Environment>
</DeployDestination>
<DeployDestination="\\serv2\root\" >
<ServerName>Web2</ServerName>
<Environment>Production</Environment>
</DeployDestination>
<DeployDestination="\\serv3\root\" >
<ServerName>Web3</ServerName>
<Environment>Production</Environment>
</DeployDestination>
</ItemGroup>
<!--Backup From One Server Trying to figure out how-->
<!-- Deploy To All Servers -->
<MSBuild Projects="$(MSBuildThisFile)" Targets="DeleteAndCopy"
Properties="Env=%(DeployDestination.Environment);
MachineName=%(DeployDestination.ServerName);
SourceFiles=$(SourceFileLocation);
FolderDestination=%(DeployDestination.FullPath)" />
<!--More deployment fun below-->
The above works fine. However, for the purpose of taking a backup I want to only grab the code from one of the machine’s we are deploying to. I was hoping to be able to grab the first item in the ‘DeployDestination’ list, perhaps using an index that would look like the below:
<MSBuild Projects="$(MSBuildThisFile)" Targets="Copy"
Properties="SourceFiles=@(DeployDestination)[0];
FolderDestination=$(BackUpRoot)" />
<!--More deployment fun below-->
Of course that is the developer in me, and the above does not work. I knew I could just create another property that had the source for the backup hard coded and just reference that, but that seemed wrong, why update the location in two places? Luckily I was using Msbuild 4 and the relatively new item functions were able to save me! By adding a meta data item to the destination that should be the backup source I was able to get just the one value I am looking for.
Now I can get what I’d like using the following:
<PropertyGroup>
<BackUpRoot>C:\BackupRoot</BackUpRoot>
<SourceFileLocation>C:WorkingBuildLocation\</SourceFileLocation>
</PropertyGroup>
<ItemGroup>
<DeployDestination Include="\\serv1\root\" >
<ServerName>Web1</ServerName>
<Environment>Production</Environment>
<BackupSource>True</BackupSource>
</DeployDestination>
<DeployDestination="\\serv2\root\" >
<ServerName>Web2</ServerName>
<Environment>Production</Environment>
</DeployDestination>
<DeployDestination="\\serv3\root\" >
<ServerName>Web3</ServerName>
<Environment>Production</Environment>
</DeployDestination>
</ItemGroup>
<!--Backup From One Server -->
<MSBuild Projects="$(MSBuildThisFile)" Targets="Copy" Properties=
"SourceFiles=@(DeployDestination->WithMetadataValue('BackupSource','True'));
FolderDestination=$(BackUpRoot)" />
<!-- Deploy To All Servers -->
<MSBuild Projects="$(MSBuildThisFile)" Targets="DeleteAndCopy"
Properties="Env=%(DeployDestination.Environment);
MachineName=%(DeployDestination.ServerName);
SourceFiles=$(SourceFileLocation);
FolderDestination=%(DeployDestination.FullPath)" />
<!--More deployment fun below-->