Referencing a property in when using registry properties in MSBuild? - msbuild

I'm attempting to use MSBuild to figure out whether a SQL server instance has SQL authentication enabled. I'm trying the following:
<Target Name="VerifySQLLoginMode">
<PropertyGroup>
<SqlInstanceName>SQL08X64</SqlInstanceName>
<SqlInstanceKey>$(registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL#$(SqlInstanceName))</SqlInstanceKey>
<SqlLoginMode>$(registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\$(SqlInstanceKey)\MSSQLServer#LoginMode)</SqlLoginMode>
</PropertyGroup>
<Message Text="SqlInstanceName = $(SqlInstanceName)" />
<Message Text="SqlInstanceKey = $(SqlInstanceKey)" />
<Message Text="SqlLoginMode = $(SqlLoginMode)" />
<Error Condition="'$(SqlLoginMode)' != '2'" Text="Error: SQL Authentication is disabled. Please enable it." />
</Target>
Unfortunately, MSBuild doesn't seem to allow referencing properties ($(SqlInstanceName)) inside $(registry:...) properties.
Or is there some way to make this work?

Actually, it's probably down to using the 32-bit MSBuild. Using the MSBuild 4.0 property functions gives me this:
<Target Name="VerifySQLLoginMode">
<!-- Note that this can't deal with the default instance. -->
<PropertyGroup>
<SqlInstanceName>SQL08X64</SqlInstanceName>
<SqlInstanceKey>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL', '$(SqlInstanceName)', null, RegistryView.Registry64, RegistryView.Registry32))</SqlInstanceKey>
<SqlLoginMode>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\$(SqlInstanceKey)\MSSQLServer', 'LoginMode', null, RegistryView.Registry64, RegistryView.Registry32))</SqlLoginMode>
</PropertyGroup>
<Message Text="SqlInstanceName: $(SqlInstanceName)" />
<Message Text="SqlInstanceKey: $(SqlInstanceKey)" />
<Message Text="SqlLoginMode: $(SqlLoginMode)" />
<Error Condition="'$(SqlLoginMode)' != '2'" Text="Error: SQL Authentication is disabled. Please enable it." />
</Target>
...which works fine.

Related

How to override value of TeamCity System Variable Using MSBuild?

TeamCity Configuration:
Below is the Build Number format setting done in TeamCity
%system.BuildVersion%
Where BuildVersion is defined as system parameter.
MSBuildScript
<GetAssemblyIdentity
AssemblyFiles="$(PPTCompiledOutputDirPath)\$(FileNameForAssembly)">
<Output TaskParameter="Assemblies" ItemName="AssemblyIdentity"/>
</GetAssemblyIdentity>
<PropertyGroup>
<Pattern>(\d+)\.(\d+)\.(\d+)\.</Pattern>
<In>%(AssemblyIdentity.Version)</In>
<OutVersion>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern)))</OutVersion>
</PropertyGroup>
<Message Text="$(OutVersion)" />
<Message Text="##teamcity[buildNumber '$(OutVersion)$(BuildCounter)']" />
<Message Text="##teamcity[setParameter name='BuildVersion' value='$(OutVersion)$(BuildCounter)']"/>
I want to update the value for parameter 'BuildVersion' as Assembly Version and Build Counter.
Here I am getting the issue on execution of the Teamcity and execution get cancelled.
The proper way to set the buildNumber is:
<Message Importance="High" Text="##teamcity[buildNumber '$(OutVersion)$(BuildCounter)']" />

How can i get MSBuild target batching to work

I'm trying to create a build script for our code deployment to multiple environments. The code is as follows:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<TargetEnv>Production</TargetEnv>
</PropertyGroup>
<ItemGroup Condition="'$(TargetEnv)' == 'Integration'">
<Server Include="int1">
<ip>172.0.0.1</ip>
</Server>
</ItemGroup>
<ItemGroup Condition="'$(TargetEnv)' == 'Production'">
<Server Include="prod1">
<ip>172.0.2.1</ip>
</Server>
<Server Include="prod2">
<ip>172.0.2.2</ip>
</Server>
</ItemGroup>
<Target Name="Deploy">
<CallTarget Targets="DeployIntegration" />
<CallTarget Targets="DeployServers" />
</Target>
<Target Name="DeployIntegration" Condition="'$(TargetEnv)' == 'Integration'" Outputs="%(Server.Identity)">
<Message Text="= specific int server thing need access to variable %(Server.Identity) =" Importance="high" />
</Target>
<Target Name="DeployServers" Condition="'$(TargetEnv)' != 'Integration'" Outputs="%(Server.Identity)">
<Message Text="= specific prod thing here need access to variable %(Server.Identity) =" Importance="high" />
</Target>
<Target Name="RemoveServerFromLoadBalancer" AfterTargets="DeployServers" Condition="'$(TargetEnv)' != 'Integration'">
<Message Text="= removing %(Server.Identity) from load balancer =" Importance="high" />
</Target>
<Target Name="IgnoreRemoveServerFromLoadBalancer" AfterTargets="DeployServers" Condition="'$(TargetEnv)' == 'Integration'">
<Message Text="= ignore removing %(Server.Identity) from load balancer =" Importance="high" />
</Target>
<Target Name="CopyFilesAndCreateFolderLinks" AfterTargets="RemoveServerFromLoadBalancer;IgnoreRemoveServerFromLoadBalancer">
<Message Text=" = creating and copying files %(Server.Identity) =" Importance="high" />
</Target>
<Target Name="SetWebFarmServerName" AfterTargets="UpdateWebConfig" Condition="'$(TargetEnv)' != 'Integration'">
<Message Text=" = app setting CMSWebFarmServerName set to %(Server.Identity) =" Importance="high" />
</Target>
<Target Name="DisableWebFarmForIntegration" AfterTargets="UpdateWebConfig" Condition="'$(TargetEnv)' == 'Integration'">
<Message Text=" = Disabled webfarm setting for Integration - %(Server.Identity) =" Importance="high" />
</Target>
<Target Name="AddBackToLoadBalancer" AfterTargets="DisableWebFarmForIntegration" Condition="'$(TargetEnv)' != 'Integration'">
<Message Text=" = Putting server %(Server.Identity) back on load balancer =" Importance="high" />
</Target>
</Project>
This code is in an xml(saved in the 11.0 folder) file and i run it using the msbuild command:
C:\Program Files (x86)\Microsoft Visual Studio 11.0>msbuild buildtest.xml /t:Deploy
This code returns this when i run the build task for production:
DeployServers:
= specific prod thing here need access to variable prod1 =
DeployServers:
= specific prod thing here need access to variable prod2 =
RemoveServerFromLoadBalancer:
= removing prod1 from load balancer =
= removing prod2 from load balancer =
CopyFilesAndCreateFolderLinks:
= creating and copying files prod1 =
= creating and copying files prod2 =
I basically want to make sure that, if i'm on integration, it doesn't run specific targets such as load-balancer related tasks as there is only one machine for it. I'm thinking, the returned value should look like this:
DeployServers:
= specific prod thing here need access to variable prod1 =
RemoveServerFromLoadBalancer:
= removing prod1 from load balancer =
CopyFilesAndCreateFolderLinks:
= creating and copying files prod1 =
DeployServers:
= specific prod thing here need access to variable prod2 =
RemoveServerFromLoadBalancer:
= removing prod2 from load balancer =
CopyFilesAndCreateFolderLinks:
= creating and copying files prod2 =
Sorry for the long post, this msbuild stuff is a little tricky. I appreciate your input.
The batching MSBuild performs here is correct, think about it: you ask it to Message for text %(Server.Identity) so it's going to do that for as many servers as it knows and there's no reason it's gonna wait for oter targets in between. So to get what you want you'll have to make it perform all required tasks once per server. Furthermore your general structure is a bit too complicated. The conditions on the targets are unmanageable: just the fact that you repeat the same condition x times is already a sign something's wrong as it, simply said, violates the DIY principle. Also what if you add another TargetEnv? And then another one? Yep you figured it: won't be nice :] Second possible future pitfall is the use of AfterTargets: it's nice when you only have a couple, but after a while you keep adding targets and you'll have no idea what the order is, you'll basically have to go through the entire file to grasp what's going on. Also what if you add more targets that are common for each TargetEnv? Or if you add another TargetEnv. Again won't be nice as you'll have to fix that in multiple places.
Now because you sort of mixed these two complications and threw batching on top of it, things got pretty unclear. Back to the start and think about what you really need: if TargetEnv is A you want to do X and Y and Z, if TargetEnv is B you want to do and Q and Z. That's it. You can consider that as two seperate responsabilities: selecting something based on a condition, and maintaining lists of actions per condition. So let's express this in an msbuild way.
Here's the condition part, now in the new Deploy target. The rest of the targets are moved to another file. Deploy will call a target (which one depends on the condition) in the other msbuild file called deploy.targets, in the same directory as the current file. Because the batching is now on a higher level, it will automatically be executed the way you want: once per server. Note the selected server is passed as a property to the other file. There are other ways to do this, but just like with code it's nice to have a couple of smaller files instead of one big do-it-all one.
<Target Name="Deploy">
<PropertyGroup>
<TargetsFile>$(MsBuildThisFileDirectory)deploy.targets</TargetsFile>
<TargetToCall Condition="$(TargetEnv)=='Production'">DeployServers</TargetToCall>
<TargetToCall Condition="$(TargetEnv)=='Integration'">DeployIntegration</TargetToCall>
</PropertyGroup>
<MSBuild Projects="$(TargetsFile)" Targets="$(TargetToCall)" Properties="Server=%(Server.Identity)" />
</Target>
And here's the new file which has all the targets, and two 'master' targets that now specify exactly which other targets they want to have called, no more need for conditions, no more AfterTargets.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<CommonTargets>CopyFilesAndCreateFolderLinks</CommonTargets>
</PropertyGroup>
<Target Name="DeployIntegration">
<Message Text="= specific int server thing need access to variable $(Server) =" Importance="high" />
<CallTarget Targets="IgnoreRemoveServerFromLoadBalancer;$(CommonTargets)"/>
</Target>
<Target Name="DeployServers">
<Message Text="= specific prod thing here need access to variable $(Server) =" Importance="high" />
<CallTarget Targets="RemoveServerFromLoadBalancer;AnotherTargetJustForDeploy;$(CommonTargets)"/>
</Target>
<Target Name="RemoveServerFromLoadBalancer">
<Message Text="= removing $(Server) from load balancer =" Importance="high" />
</Target>
<Target Name="AnotherTargetJustForDeploy">
<Message Text="= AnotherTargetJustForDeploy for $(Server) =" Importance="high" />
</Target>
<Target Name="IgnoreRemoveServerFromLoadBalancer">
<Message Text="= ignore removing $(Server) from load balancer =" Importance="high" />
</Target>
<Target Name="CopyFilesAndCreateFolderLinks">
<Message Text=" = creating and copying files $(Server) =" Importance="high" />
</Target>
</Project>

Pass Output items to separate target with MSBuild

I am creating a buildscript, where I'm outputting the TargetOutputs of an MSBuild, then wanting to call FXCop in a separate target, and using those outputs in the TargetAssemblies.
<Target Name="Build">
<MSBuild Projects="#(Projects)"
Properties="Platform=$(Platform);Configuration=$(Configuration);"
Targets="Build"
ContinueOnError="false">
<Output TaskParameter="TargetOutputs" ItemName="TargetDLLs"/>
</MSBuild>
<CallTarget Targets="FxCopReport" />
</Target>
<Target Name="FxCopyReport">
<Message Text="FXCop assemblies to test: #(TargetDLLs)" />
<FxCop
ToolPath="$(FXCopToolPath)"
RuleLibraries="#(FxCopRuleAssemblies)"
AnalysisReportFileName="FXCopReport.html"
TargetAssemblies="#(TargetDLLs)"
OutputXslFileName="$(FXCopToolPath)\Xml\FxCopReport.xsl"
ApplyOutXsl="True"
FailOnError="False" />
</Target>
When I run this, in the FxCopyReport target, the Message of TargetDLLs in empty, whereas if I put this in the Build target, it populates.
How can I pass/reference this value?
There is a blog post by Sayed Ibrahim Hashimi (co-author of Inside MSBuild book), describing the issue you ran into, dating back in 2005. Essentially CallTarget task is behaving weird. I'm not sure if it is a bug or designed behavior, but the behavior is still the same in MSBuild 4.0.
As a workaround, use normal MSBuild mechanism of setting order of execution of targets in MSBuild, using attributes DependsOnTargets, BeforeTargets or AfterTargets.
I was able to figure this one out.
Essentially, after the MSBuild step, I created an ItemGroup, which I then referenced in the calling Target.
<Target Name="Build">
<Message Text="Building Solution Projects: %(Projects.FullPath)" />
<MSBuild Projects="#(Projects)"
Properties="Platform=$(Platform);Configuration=$(Configuration);"
Targets="Build"
ContinueOnError="false">
<Output TaskParameter="TargetOutputs" ItemName="TargetDllOutputs"/>
</MSBuild>
<ItemGroup>
<TestAssemblies Include="#(TargetDllOutputs)" />
</ItemGroup>
</Target>
<Target Name="FXCopReport">
<Message Text="FXCop assemblies to test: #(TestAssemblies)" />
<FxCop
ToolPath="$(FXCopToolPath)"
RuleLibraries="#(FxCopRuleAssemblies)"
AnalysisReportFileName="$(BuildPath)\$(FxCopReportFile)"
TargetAssemblies="#(TestAssemblies)"
OutputXslFileName="$(FXCopToolPath)\Xml\FxCopReport.xsl"
Rules="$(FxCopExcludeRules)"
ApplyOutXsl="True"
FailOnError="True" />
<Message Text="##teamcity[importData id='FxCop' file='$(BuildPath)\$(FxCopReportFile)']" Condition="'$(TEAMCITY_BUILD_PROPERTIES_FILE)' != ''" />
</Target>

ResolveComReference Invalid argument. Parameter "ItemSpec" cannot be null

I am in the process of upgrading a project to .NET 4.0 and I get this error when I try and build the solution: Invalid argument. Parameter "ItemSpec" cannot be null. c:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets 1558 9
Is there a way to find out what is being passed as null to the ResolveComReference task?
Try this; temporarily add the following to the project file that is failing:
<Target Name="DiagnoseNullItems"
BeforeTargets="ResolveComReferences"
AfterTargets="ResolveAssemblyReferences">
<Message Importance="High" Text="COMReference is '#(COMReference)'" />
<Message Importance="High" Text="COMFileReference is '#(COMFileReference)'" />
<Message Importance="High" Text="ReferencePath is '#(ReferencePath)'" />
<Message Importance="High" Text="_ResolveComReferenceCache is '#(_ResolveComReferenceCache)'" />
</Target>
I think that is all of them, unless some of the other $() arguments to that task are also of type ITaskItem. If that doesn't reveal an empty item, do the same for the properties passed to the ResolveComReference task.

Why doesn't MSBuild ItemGroup conditional work in a global scope

I'm desperately curious why I am unable to create an item in a global scope based on a metadata condition which works as expected inside a target. For instance, this works as expected:
<ItemGroup>
<TestItems Include="TestItem1">
<TestFlag>true</TestFlag>
</TestItems>
<TestItems Include="TestItem2">
<TestFlag>false</TestFlag>
</TestItems>
</ItemGroup>
<Target Name="Default">
<Message Text="#(TestItems)" />
<Message Text="#(TestItems)" Condition="'%(TestItems.TestFlag)'=='true'" />
<ItemGroup>
<FilteredTestItems Include="#(TestItems)" Condition="'%(TestItems.TestFlag)'=='true'" />
</ItemGroup>
<Message Text="#(FilteredTestItems)" />
<Message Text="#(FilteredTestItems)" Condition="'%(FilteredTestItems.TestFlag)'=='true'" />
</Target>
and produces the following output:
TestItem1;TestItem2
TestItem1
TestItem1
TestItem1
And this works as expected:
<ItemGroup>
<TestItems Include="TestItem1">
<TestFlag>true</TestFlag>
</TestItems>
<TestItems Include="TestItem2">
<TestFlag>false</TestFlag>
</TestItems>
</ItemGroup>
<ItemGroup>
<FilteredTestItems Include="#(TestItems)" Condition="'false'=='true'" />
</ItemGroup>
<Target Name="Default">
<Message Text="#(TestItems)" />
<Message Text="#(TestItems)" Condition="'%(TestItems.TestFlag)'=='true'" />
<Message Text="#(FilteredTestItems)" />
<Message Text="#(FilteredTestItems)" Condition="'%(FilteredTestItems.TestFlag)'=='true'" />
</Target>
Producing the following output:
TestItem1;TestItem2
TestItem1
But this:
<ItemGroup>
<TestItems Include="TestItem1">
<TestFlag>true</TestFlag>
</TestItems>
<TestItems Include="TestItem2">
<TestFlag>false</TestFlag>
</TestItems>
</ItemGroup>
<ItemGroup>
<FilteredTestItems Include="#(TestItems)" Condition="'%(TestItems.TestFlag)'=='true'" />
</ItemGroup>
Produces the following MSBuild error:
temp.proj(13,45): error MSB4090: Found an unexpected character '%' at position 2 in condition "'%(TestItems.TestFlag)'=='true'".
So what gives? Certainly I can work around it, but what exactly am I not understanding about ItemGroup, metadata and/or the global scope?
The item group condition works outside a target, but batching doesn't (that's the "%" operator). Batching is used when you call a task, and since you can only call a task from inside a target, it makes sense for batching to also only work inside a target.
You might ask why the item group works inside the target since it's not a task. Prior to MSBuild 3.5, you weren't allowed item groups inside targets at all; you had to call CreateItem instead. In versions 3.5 and 4.0, using item groups that way is allowed, but I think it's just syntactic sugar for calling the CreateItem task, so your condition works because there is a task behind the scenes.