Run MSBuild Task programatically and access the output - c#

I'm trying to run an MSBuild task, in this case ResolveAssemblyReferences, and get access to the outputs of the task such as ResolvedFiles. A short F# script that loads the project (a default F# project created with VS2013) and runs the task is below. With the log verbosity set to Diagnostic, I can see that the task runs successfully, and resolves all the assemblies correctly.
#r #"C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Build.dll"
#r #"C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Build.Framework.dll"
open System
open Microsoft.Build
let p = new Evaluation.Project("d:/dev/fsharptest/Test/Test.fsproj")
let log = Logging.ConsoleLogger()
log.Verbosity <- Framework.LoggerVerbosity.Diagnostic
p.Build([|"ResolveProjectReferences";"ResolveAssemblyReferences"|],
Seq.singleton (log :> Framework.ILogger) )
for i in p.AllEvaluatedProperties do
printfn "%s: %s" i.Name i.EvaluatedValue
However, neither the evaluated properties nor the items contain any of the outputs of ResolveAssemblyReferences, which is what I am after. The file Microsoft.Common.CurrentVersion.targets has as one output of ResolveAssemblyReferences <Output TaskParameter="ResolvedFiles" ItemName="ReferencePath"/>, but I cannot access this value.
How should I go about getting hold of it?

It turns out that Evaluation.Project and Execution.ProjectInstance are rather different. I tried to use the former, but the latter is closest to the obsolete BuildEngine.Project class I was previously using. The following code snippet returns the fully resolved references for a given project file:
#r #"C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Build.dll"
#r #"C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Build.Framework.dll"
open System
open Microsoft.Build
let p = new Execution.ProjectInstance(#"d:\dev\fsharptest\Test\Test.fsproj")
p.Build([|"ResolveReferences"|], new Collections.Generic.HashSet<_>())
for i in p.GetItems("ReferencePath") do
printfn "%s" i.EvaluatedInclude
It is in fact possible to get an arbitrary output for the target in question in this manner.

I think the problem is that some arbitrary Output parameter of the Task you end up running is not what the MSBuild task itself returns. It gathers up the "target Returns" of the Tasks you specify directly.
However, I don't know exactly how your syntax works here: you are giving Task names rather than Targets?
But based on what I've read (entry 37 in Kretzler’s book) you could have a Target defined to run the desired Task and hook up the task output to the target’s Return attribute. Then the MSBuild task, told to run that target, will pass through the Return attribute as its own.
I think that would be something like:
<Target Name="ResolveAssemblyReferences" ⋯
Returns="#(ReferencePath)" >
So if the Task you are calling from within that Target is populating the Item Array named ReferencePath as its output parameter, then you publish that same item array as the Target's return value.
If you don't use Returns anywhere in the build script, then the Outputs are automatically taken as the returns.
If you can't edit ResolveAssemblyReferences, then I said you can make a new Target which depends on it. Since ReferencePath is global after the task completes, the new target will still see them and can return it.
If all else fails, have your build script write the item list to a file, which you can then load from some other program without concern over what MSBuild is returning.

Related

Check if a <PackageReference/> tag is added to .csproj file

I have a .csproj file as follows:
If a <PackageReference/> tag is added to this csproj file, the build should fail. How do I do that? Is there any setting or a test I can add?
For example,
On my phone at the moment, however you can do the following or rather follow this (unable to test for you):
In Pre-Build Events (Right click on your project, go to Properties) of the project, add the following command:
CD $(SolutionDir)
CALL CheckProj.ps1
Then on the root of your solution, create a bat file called "CheckProj.ps1"
The contents of your script should be along the lines of:
$xml = new-object System.Xml.XmlDocument;
$xml.LoadXml( (get-content 'MyProject.csproj') );
$node = $xml.SelectNodes('//Project/ItemGroup/PackageReference');
exit $node.Count;
Then on the rebuild of the project, if exit isn't equal to 0, it'll fail the build as 0 is expected to simulate success in a build event, anything higher will end up being marked as an error and should fail the whole build process.
I'm not entirely sure why you'd want to do this, but you could do this in a test fairly easily.
Using XUnit:
[Fact]
public void NoPackageReferences()
{
string myCsproj = File.ReadAllText("path/to/my.csproj");
Assert.DoesNotContain("PackageReference", myCsproj);
}
Now, if you wanted to be more thorough, you could parse the XML... But that's probably overkill for this.

How to disable predeployment and postdeployment scripts in DacServices.Deploy()

We have some automated dacpac deployment code which correctly handles both a CreateNewDatabase and straightforward Update database scenarios in C# using Microsoft.SqlServer.Dac
Now in the CreateNewDatabase case we want to be able to run the DacServices.Deploy() with both the Pre and Post Deployment scripts disabled. I.e. they should not be executed in this scenario.
I have tried to find a suitable place in the DacDeployOptions and DacServices objects but cannot find anything that will do this.Ideally
Question 1: I would like something like DacDeployOptions.IgnorePreDeploymentScript = true Is there any means by which I could achieve this at runtime?
As an alternative, some time ago I remember seeing example code which showed how to traverse a dacpac and create a new dacpac in run time. I think this approach would allow me to simply create a new dacpac which I could pass to the Deploy and which would exclude the Pre and Post Deployment scripts. I don't like this solution but it would allow me to achieve what I need.
Question 2: Can anyone point me to some examples for this please?
My code:
var dacService = new DacServices(ConstDefaultConnectionString);
using (var dacPackage = DacPackage.Load(dacPacFilePath))
{
var deployOptions = new DacDeployOptions
{
CreateNewDatabase = true,
IncludeTransactionalScripts = false
};
dacService.Deploy(dacPackage, TestDatabaseName, true, deployOptions);
}
The question is related to: Create LocalDB for testing from Visual Studio SQL project
There are a number of approaches you can take for this, this is a bit of a brain dump (hey the clocks went back last night and I'm not even sure if the current time):
1) create an empty project that references your main project using a same database reference - when you deploy without the scripts deploy the empty one using IncludeCompositeObjects - pre/post deploy scripts are only run from the dacpac you deploy not from any referenced dacpacs but obviously the code and scheme are deployed. This describes it:
https://the.agilesql.club/blog/Ed-Elliott/2016-03-03/Post-Deploy-Scripts-In-Composite-Dacpac-not-deploying
2) use SQLCMD variables to wrap the data setups and pass in the value to the deploy.
3) make your scripts check for whether they should setup data like only insert if the table rowcount is zero
4) for reference data use merge scripts - I'm not clear if the point of this is for reference data or setting up test data
5) Use .net packaging api to remove the pre/post deploy scripts from the dacpac, this shows you how to write the scripts so you should be able to do a GetPart rather than WritePart:
https://github.com/GoEddie/Dir2Dac/blob/master/src/Dir2Dac/DacCreator.cs
On the whole I would guess that there is probably a simpler solution- if this is for testing then maybe make the data setup part of the test setup? If you are unit testing tSQLt helps you avoid all this by using FakeTable.
Hope it helps :)
Ed
Two things to try:
First, doing this type of thing is quite easy if you are using MSBuild since you can tailor a particular Configuration to include one or more pieces of the Project. In your .sqlproj file there is an <ItemGroup> section that should look similar to the following:
<ItemGroup>
<PreDeploy Include="Script.PreDeployment1.sql" />
<PostDeploy Include="Script.PostDeployment1.sql" />
</ItemGroup>
You can simply add a "Condition" that will determine if that ItemGroup is used or not. You can see these "Condition" attributes throughout the .sqlproj file (usually). So the result should look similar to:
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PreDeploy Include="Script.PreDeployment1.sql" />
<PostDeploy Include="Script.PostDeployment1.sql" />
</ItemGroup>
Then you just flip between "Release" or "Debug" in the "Active Configuration" drop-down and the pre and post deploy scripts will be included or excluded accordingly.
The other thought was to somehow reset the pre and post deployment scripts. Since you are loading the DacPac into dacPackage, you will have access to the PreDeploymentScript and PostDeploymentScript properties. I am not able to test, but it might be possible to "erase" what is there (assuming that the streams already point to the stored scripts).
DACPACs are ZIP files. Use the functionality of the System.IO.Packaging namespace to remove pre- and post-deployment scripts from an existing package.
using System.IO.Packaging;
// [...]
using (var dacPac = Package.Open(dacPacFile))
{
var preDeploy = new Uri("/predeploy.sql", UriKind.Relative);
if (dacPac.PartExists(preDeploy))
{
dacPac.DeletePart(preDeploy);
}
var postDeploy = new Uri("/postdeploy.sql", UriKind.Relative);
if (dacPac.PartExists(postDeploy))
{
dacPac.DeletePart(postDeploy);
}
dacPac.Close();
}
The file is simply overwritten after Close, so consider copying it away first, in case you want the original unchanged.
(this is partly covered by the internet resource linked in item 5) in the accepted answer; however the code shown above is all you need)

Checking Visual Studio projects for consistency

You have a large Visual Studio Solution with dozens of project files in it. How would you verify that all the projects follow certain rules in their property settings, and enforce these rules if a new project is added. For example check that all projects have:
TargetFrameworkVersion = "v4.5"
Platform = "AnyCPU"
WarningLevel = 4
TreatWarningsAsErrors = true
OutputPath = $(SolutionDir)bin
SignAssembly = true
AssemblyName = $(ProjectFolderName)
I know two methods myself that I will add in an answer below, but I was wondering how people go about doing this type of project test. I'm especially interested to learn about available solutions such as libraries or build tasks for this rather than having to have to invent something new or write it from scratch.
*.sln files are plain text and easily parsable, and *.*proj files are xml.
You can add a dummy project with a prebuild step that parses the sln to retrieve all of the project files, validate their settings, print a report, and fail the build if necessary.
Also, you should check this post to ensure the prebuild step is always executed. Essentially, you specify a blank output in the custom build step to force a rebuild.
The following list identifies the key file types that are automatically added to VSS when a solution is added to source control by using the Visual Studio .NET integrated development environment (IDE):
Solution files (.sln). The key items maintained within these files include a list of constituent projects, dependency information, build configuration details, and source control provider details.
Project files (.csproj or *.vbproj). The key items maintained within these files include assembly build settings, referenced assemblies (by name and path), and a file inventory.
Application configuration files. These are configuration files based on Extensible Markup Language (XML) used to control various aspects of your project's run time behavior.
Use a Single Solution Model Whenever Possible an
Also see : https://msdn.microsoft.com/en-us/library/ee817677.aspx,
https://msdn.microsoft.com/en-us/library/ee817675.aspx
AND For CONTINUOUS INTEGRATION :
there are many tools available like MSBuild, Jenkins, Apache's Continuum, Cruise Control (CC), and Hudson(plugin can be extended to c#)
This is what I have myself:
One way to do this is to create an MSBuild target with error conditions:
<Error Condition="'$(TreatWarningsAsErrors)'!='true'" Text="Invalid project setting" />
I like this approach because it is integrated with MSBuild and gives you early errors, however, you have to modify every project to import it in them or get all your team members to use a special command prompt with environment variables that will inject custom pre-build steps into your projects during the build, which is a pain.
The second approach I know is to use some library like VSUnitTest which provides an API to project properties that you can test against. VSUnitTest is currently not open source and unlisted from the NuGet service.
You could write some code to open the the solution as a text file to identify all of the csproj files referenced, in turn opening each of these as xml files, and then writing unit tests to ensure specific nodes of the project match what you expect.
It's a quick and dirty solution, but works for CI and gives you the flexibility to ignore nodes you don't care about. It actually sounds kinda useful. I have a solution with 35 projects I'd like to scan too.
Let's try something completely different: you could ensure that they are consistent by construction by generating them from a template or by using a build generation tool such as CMake. This might be simpler than attempting to make them consistent after the fact.
In our work we use a powershell script that checks project settings and modified them if they are incorrect. For example, we remove Debug configuration this way, disable C++ optimization and SSE2 support. We run it manually, but definitely it is possible to run it automatically, e.g. as pre\post build step.
Below the example:
`function Prepare-Solution {
param (
[string]$SolutionFolder
)
$files = gci -Recurse -Path $SolutionFolder -file *.vcxproj | select - ExpandProperty fullname
$files | %{
$file = $_
[xml]$xml = get-content $file
#Deleting Debug configurations...
$xml.Project.ItemGroup.ProjectConfiguration | ?{$_.Configuration -eq "Debug"} | %{$_.ParentNode.RemoveChild($_)} | Out-Null
$xml.SelectNodes("//*[contains(#Condition,'Debug')]") |%{$_.ParentNode.RemoveChild($_)} | Out-Null
if($xml.Project.ItemDefinitionGroup.ClCompile) {
$xml.Project.ItemDefinitionGroup.ClCompile | %{
#Disable SSE2
if (-not($_.EnableEnhancedInstructionSet)){
$_.AppendChild($xml.CreateElement("EnableEnhancedInstructionSet", $xml.DocumentElement.NamespaceURI)) | Out-Null
}
if($_.ParentNode.Condition.Contains("Win32")){
$_.EnableEnhancedInstructionSet = "StreamingSIMDExtensions"
}
elseif($_.ParentNode.Condition.Contains("x64")) {
$_.EnableEnhancedInstructionSet = "NotSet"
} else {
Write-Host "Neither x86 nor x64 config. Very strange!!"
}
#Disable Optimization
if (-not($_.Optimization)){
$_.AppendChild($xml.CreateElement("Optimization", $xml.DocumentElement.NamespaceURI)) | Out-Null
}
$_.Optimization = "Disabled"
}
}
$xml.Save($file);
} }`
A file is an assembly if and only if it is managed, and contains an assembly entry in its metadata. For more information on assemblies and metadata, see the topic Assembly Manifest.
How to manually determine if a file is an assembly
Start the Ildasm.exe (IL Disassembler).
Load the file you wish to test.
If ILDASM reports that the file is not a portable executable (PE) file, then it is not an assembly. For more information, see the topic How to: View Assembly Contents.
How to programmatically determine if a file is an assembly
Call the GetAssemblyName method, passing the full file path and name of the file you are testing.
If a BadImageFormatException exception is thrown, the file is not an assembly.
This example tests a DLL to see if it is an assembly.
class TestAssembly
{
static void Main()
{
try
{
System.Reflection.AssemblyName testAssembly = System.Reflection.AssemblyName.GetAssemblyName(#"C:\Windows\Microsoft.NET\Framework\v3.5\System.Net.dll");
System.Console.WriteLine("Yes, the file is an assembly.");
}
catch (System.IO.FileNotFoundException)
{
System.Console.WriteLine("The file cannot be found.");
}
catch (System.BadImageFormatException)
{
System.Console.WriteLine("The file is not an assembly.");
}
catch (System.IO.FileLoadException)
{
System.Console.WriteLine("The assembly has already been loaded.");
}
}
}
// Output (with .NET Framework 3.5 installed):
// Yes, the file is an assembly.
Framework is the highest installed version, SP is the service pack for that version.
RegistryKey installed_versions = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\NET Framework Setup\NDP");
string[] version_names = installed_versions.GetSubKeyNames();
//version names start with 'v', eg, 'v3.5' which needs to be trimmed off before conversion
double Framework = Convert.ToDouble(version_names[version_names.Length - 1].Remove(0, 1), CultureInfo.InvariantCulture);
int SP = Convert.ToInt32(installed_versions.OpenSubKey(version_names[version_names.Length - 1]).GetValue("SP", 0));
For .Net 4.5
using System;
using Microsoft.Win32;
...
private static void Get45or451FromRegistry()
{
using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) {
int releaseKey = Convert.ToInt32(ndpKey.GetValue("Release"));
if (true) {
Console.WriteLine("Version: " + CheckFor45DotVersion(releaseKey));
}
}
}
...
// Checking the version using >= will enable forward compatibility,
// however you should always compile your code on newer versions of
// the framework to ensure your app works the same.
private static string CheckFor45DotVersion(int releaseKey)
{
if (releaseKey >= 393273) {
return "4.6 RC or later";
}
if ((releaseKey >= 379893)) {
return "4.5.2 or later";
}
if ((releaseKey >= 378675)) {
return "4.5.1 or later";
}
if ((releaseKey >= 378389)) {
return "4.5 or later";
}
// This line should never execute. A non-null release key should mean
// that 4.5 or later is installed.
return "No 4.5 or later version detected";
}
For similar purposes we use custom MSBuild fragments with common properties that we want to share between the projects, like this (build.common.props file):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<PlatformToolset>v90</PlatformToolset>
<OutputPath>$(SolutionDir)..\bin\$(PlatformPath)\$(Configuration)\</OutputPath>
<!-- whatever you need here -->
</PropertyGroup>
</Project>
And then we just include this fragment to real VS projects we want to apply these properties to:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CommonProps>$(SolutionDir)..\Build\build.common.props</CommonProps>
</PropertyGroup>
<Import Project="$(CommonProps)" />
<!-- the rest of the project -->
</Project>
We handle a lot of things using this approach:
common properties, as you mentioned
static analysis (FxCop, StyleCop)
digital sign of assemblies
etc.
The only disadvantage that you need to include these MSBuild fragments into each project file, but once you do that, you have all the benefits of modular build system that is easy to manage and update.
You could go the search & replace Regex way with a handwritten C#, Script, powershell or similar. But it has the following problems:
Difficult to read (Read your pretty regex in three or more months)
Difficult to enhance(New regex for new search/replace/check feature)
Easy to break (a new release/format of ms build project or a not forecast tag may not work)
Harder to test (you must check that no unintended match occurs)
Difficult to maintain (because of the above)
and the following advantages:
Not doing any extra validation which (may) let it work on any kind of project (mono or visual).
Doesn't care about \r :)
The best could be to use the Microsoft.Build.Evaluation
and build a C# tool which does all your testing/checking/fix and so on.
I've done a command line tool that use a sourcefile list (used by Mono) and update sources of csproj and another which dumps on console the csproj content. It was easy to do, pretty straightforward and easy to test also.
However, it may fail (as I've experienced it) on projects modified by "non" Ms tool (like Mono Studio) or because of missing \r....
Anyway, you can always handle it with an exception catch and a good message.
Here a sample by using Microsoft.Build.dll (don't use Microsof.Build.Engine as it is obsolete):
using System;
using Microsoft.Build.Evaluation;
internal class Program
{
private static void Main(string[] args)
{
var project = new Project("PathToYourProject.csproj");
Console.WriteLine(project.GetProperty("TargetFrameworkVersion", true, string.Empty));
Console.WriteLine(project.GetProperty("Platform", true, string.Empty));
Console.WriteLine(project.GetProperty("WarningLevel", true, string.Empty));
Console.WriteLine(project.GetProperty("TreatWarningsAsErrors", true, "false"));
Console.WriteLine(project.GetProperty("OutputPath", false, string.Empty));
Console.WriteLine(project.GetProperty("SignAssembly", true, "false"));
Console.WriteLine(project.GetProperty("AssemblyName", false, string.Empty));
Console.ReadLine();
}
}
public static class ProjectExtensions
{
public static string GetProperty(this Project project, string propertyName, bool afterEvaluation, string defaultValue)
{
var property = project.GetProperty(propertyName);
if (property != null)
{
if (afterEvaluation)
return property.EvaluatedValue;
return property.UnevaluatedValue;
}
return defaultValue;
}
}
I also faced this issue and created a small solution that creates a csv file with details to identifies the inconsistences. You can look at it in this url
https://github.com/gdlmanuv/VSProjectConsistencyChecker

Can I run code-contracts analysis manually?

When I rebuild my C# app I often don't get the results of my code-contracts analysis. This is caused by the following error message
CodeContracts: MyApp.Client.Model: Analysis method MyApp.Client.Model.MyClass.CreateCalculatedElements(System.Collections.Generic.List1<System.Collections.Generic.KeyValuePair2<System.String,MyCompany.Scripting.ICompiledFunction1<System.Object>>>,System.Collections.Generic.Dictionary2<System.String,MyCompany.Scripting.ICompiledFunction1<System.Decimal>>,System.Collections.Generic.Dictionary2<System.String,MyCompany.Scripting.ICompiledFunction1<System.Decimal>>) timed out
Here is the definition of the method
internal void CreateCalculatedElements(
List<KeyValuePair<string, ICompiledFunction<object>>> preFinalCalculationScripts,
Dictionary<string, ICompiledFunction<decimal>> factorCalculators,
Dictionary<string, ICompiledFunction<decimal>> elementCalculators)
This doesn't always time out. Is there a way I can manually run contract checking just for one project rather than all of them via a rebuild-solution?
You can increase the timeout for that project by adding an extra option to the static checking options in the property pane as follows: -timeout
The default is 180, which is per method.
As to your original question, yes you can run the tools manually as follows:
Say your project P is in directory D, then go to D\obj\Debug\Decl. In there you'll find a convenient file called Pcccheck.rsp which contains the parameters which were passed to cccheck during the build. To run the analysis again, you simply issue:
cccheck #Pcccheck.rsp
Right now, there isn't an option to trigger a re-analysis of just one project without a rebuild.

Programmatic MSBuild Recursive Copy with wildcards

I am creating an MSBuild v4 task that happens to need to call the Copy task to recursively copy some files (without flattening the directory structure at the destination).
I have come up with:
var copy = new Microsoft.Build.Tasks.Copy
{
BuildEngine = this.BuildEngine,
SourceFiles = new ITaskItem[] { new TaskItem(#"C:\source\**\*.foo") },
DestinationFolder = new TaskItem(#"c:\dest\\")
};
copy.Execute();
but am getting an error 'Could not copy C:\source\**\*.foo to c:\dest\* - Illegal characters in path'
There doesn't seem to be much online help for pragmatic invocation, and have drawn a blank. Any ideas?
Thanks
Jon
It looks like the Copy task doesn't have an intrinsic understanding of recursion; the following code would cause the Copy task to be invoked once per file level, and this is handled by the MSBuild runner.
<ItemGroup>
<x Include="c:\source\**\*.foo" />
</ItemGroup>
<Copy SourceFiles="#(x)" DestinationFolder="c:\dest\%(RecursiveDir)" />
However, since the Copy task seems to treat SourceFiles and DestinationFiles as an associative array (each of type ITaskItem[]), we just performed a recursive descent and built up these two arrays manually, before execing it
The problem is that, when writing the same thing in XML, you wouldn't have passed the path with wildcards directly to the SourceFiles property. You would've created an ItemGroup and then you pass that to your task.
As far as I know, it's the ItemGroup does the wildcard magic, so in C# it's up to you to manually create the array of ITaskItem that contains the complete list of items that you want to copy.
If you can, create an ItemGroup that you pass to your task, which in turn passes it to Copy.
You can do it simply using MSBuild Copy task. You don't have to write new task for it.
<Copy SourceFiles="#(SourceFiles)"
DestinationFiles="$(DestinationFolder)\%(RecursiveDir)%(Filename)%(Extension)"
ContinueOnError="false"/>
Check all available MSBuild metadata.

Categories

Resources