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.
Related
I'm trying to use the new FxCop analyzers, but they're only available as NuGet packages or as VSIX extensions. I'd like to be able to run them directly, either from inside a C# program or from the command line. Anyone have any advice? Even general info on where you can find the executables for NuGet or VSIX would help.
(I know about fxcopcmd.exe, but that's the legacy version, and it works only on built .exes or .dlls. If at all possible, I need something that works before building.)
Answering my own question in case anyone else has to deal with this. I found a solution, but fair warning, it's not pretty.
I took an example C# solution from Github, loaded it up in Visual Studio, and used NuGet to install the FxCop analyzers. This installed the analyzers, and changed the solution's .csproj files to reference them. In my case, I found a copy of the analyzers in C:\users\myname.nuget\packages.
I compared the modified .csproj files to the originals, to see what changes had been made during installation. I recommend anyone following along make this comparison themselves, but in my case, the changes were:
Five Import elements at the top referencing various .props files.
An empty NuGetPackageImportStamp element.
Two new ItemGroups near the bottom, the first containing a single element named "None", the second containing various Analyzer elements referencing .dlls.
A new Target to ensure that the .props files actually existed.
I wrote a C# program that took an arbitrary solution, found all the .csproj files inside, and manually added those new XML elements to them. I skipped the one-element ItemGroup and the Target without any problems.
Ideally you would then (from inside the same C# program) call msbuild on the .sln file, save every output line matching the regex "): warning CA\d\d\d\d: " (i.e. the warnings that FxCop generated), and restore the original .csproj files. I did that all manually. Here's the code for the XML manipulation, though:
static void addAnalyzersToCsProj(string file)
{
string[] packages = new string[]
{
#"C:\users\myname\.nuget\packages\microsoft.codeanalysis.fxcopanalyzers\3.0.0\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props",
#"C:\users\myname\.nuget\packages\microsoft.codeanalysis.versioncheckanalyzer\3.0.0\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props",
#"C:\users\myname\.nuget\packages\microsoft.codequality.analyzers\3.0.0\build\Microsoft.CodeQuality.Analyzers.props",
#"C:\users\myname\.nuget\packages\microsoft.netcore.analyzers\3.0.0\build\Microsoft.NetCore.Analyzers.props",
#"C:\users\myname\.nuget\packages\microsoft.netframework.analyzers\3.0.0\build\Microsoft.NetFramework.Analyzers.props",
};
var root = XElement.Load(file);
var ns = "";
for (var i = 0; i < 5; i++)
{
XElement packageImport = new XElement(ns+"Import");
packageImport.SetAttributeValue("Project", packages[i]);
string condition = "Exists('" + packages[i] + "')";
packageImport.SetAttributeValue("Condition", condition);
root.AddFirst(packageImport);
}
var propertyGroup = root.Descendants(ns + "PropertyGroup").First();
var stamp = new XElement(ns+"NuGetPackageImportStamp", "");
propertyGroup.Elements().Last().AddAfterSelf(stamp);
var newGroup = new XElement(ns+"ItemGroup");
// do we need to include the "None Include="packages.config"" thing?
string[] libraries = new string[]
{
#"C:\users\myname\.nuget\packages\microsoft.codeanalysis.versioncheckanalyzer\3.0.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.VersionCheckAnalyzer.resources.dll",
#"C:\users\myname\.nuget\packages\microsoft.codeanalysis.versioncheckanalyzer\3.0.0\analyzers\dotnet\Microsoft.CodeAnalysis.VersionCheckAnalyzer.dll",
#"C:\users\myname\.nuget\packages\microsoft.codequality.analyzers\3.0.0\analyzers\dotnet\cs\Humanizer.dll",
#"C:\users\myname\.nuget\packages\microsoft.codequality.analyzers\3.0.0\analyzers\dotnet\cs\Microsoft.CodeQuality.Analyzers.dll",
#"C:\users\myname\.nuget\packages\microsoft.codequality.analyzers\3.0.0\analyzers\dotnet\cs\Microsoft.CodeQuality.CSharp.Analyzers.dll",
#"C:\users\myname\.nuget\packages\microsoft.netcore.analyzers\3.0.0\analyzers\dotnet\cs\Microsoft.NetCore.Analyzers.dll",
#"C:\users\myname\.nuget\packages\microsoft.netcore.analyzers\3.0.0\analyzers\dotnet\cs\Microsoft.NetCore.CSharp.Analyzers.dll",
#"C:\users\myname\.nuget\packages\microsoft.netframework.analyzers\3.0.0\analyzers\dotnet\cs\Microsoft.NetFramework.Analyzers.dll",
#"C:\users\myname\.nuget\packages\microsoft.netframework.analyzers\3.0.0\analyzers\dotnet\cs\Microsoft.NetFramework.CSharp.Analyzers.dll",
};
foreach (string lib in libraries)
{
XElement analyzer = new XElement(ns+"Analyzer");
analyzer.SetAttributeValue("Include", lib);
newGroup.AddFirst(analyzer);
}
Console.WriteLine(root.Elements().Last().ToString());
root.Elements().Last().AddAfterSelf(newGroup);
root.Save(file, SaveOptions.None);
// and do we need to include the error checking target?
}
As far as I can tell, it works, though I have no idea what would happen if you tried to do it on a solution that already has the analyzers installed normally.
Running the FxCop analyzers through msbuild seems inefficient, but I haven't found a better way to do it. They look like they're built to only work within a compiler. I hope I'm wrong, and I would still appreciate any advice on how to run the analyzers automatically without having to build the whole project.
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)
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.
i want to change AssemblyInfo of copied executeable file to a new location and keep the main information of main executeable file safe.
i am using the codes below to copy my file to new location with new file name.
String fileDestination = Path.Combine(Environment.GetFolderPath(folder), "settings.exe");
if (!File.Exists(fileDestination))
File.Copy(Application.ExecutablePath, fileDestination);
but AssemlyInfo of new file is same as source file and i want to change them.
how can i do that before copying original file?
MoonLight,
This is possible, however not exactly an easy solution. You have a few options here.
I must note, that to answer your question. It is not possible to change the assembly information during a simple copy. The assembly information is "cooked" into the assembly and not changeable.
Option 1:
If at all possible I would do this before the assemby has been built. Typically this can be done using SharedAssembly information that you can move across your application. In this event you "could" use a pre-build "Event" to copy a SharedAssembly info over to the target directory. The SharedAssembly path is quite easy and is described here. http://blogs.msdn.com/b/jjameson/archive/2009/04/03/shared-assembly-info-in-visual-studio-projects.aspx It uses the process of linked files between your solution projects giving applying all or partial of the assembly info (or all) to your projects. In this case you could use the pre-build event to do an xcopy of the preffered SharedAssembly Information thus linking the new Assembly Information. Now to make this process whole I would do this.
Create a Solution Items solution folder.
Create a SharedAssemblyInfo.cs file in your solution root and specify your common attributes.
Add the SharedAssemblyInfo.cs file to each solution you wish to target.
Create a SharedAssembly2.cs file with the new information that you wish to have in the new target.
Write your pre-build events to backup your SharedAssemblyInfo.cs file, move the SharedAssebly2.cs to the SharedAssemblyInfo.cs location
Write your post-build event to re-instante the SharedAssemblyInfo.cs file. Something like.
Pre-Build.
xcopy /Y $(SolutionDir)SharedAssemblyInfo.cs $(SolutionDir)SharedAssemblyInfo.bak
xcopy /Y $(SolutionDir)SharedAssembly2.cs $(SolutionDir)SharedAssemblyInfo.cs
Post-Build
xcopy /Y $(SolutionDir)SharedAssemblyInfo.bak $(SolutionDir)SharedAssemblyInfo.cs
Option 2:
There is a package from the Mono framework that "could" do this. However this is much more difficult, in addition this package only changes the assembly definition and not the Win32Resources that are visibile when viewing the File properties -> details tab.
Using the Package Mono.Cecil on Nuget this is possible. Here you can change \ add assembly attributes, Names etc.
Here is a sample to change some attributes (note there are many more and best to use your debugger and review the available attributes).
static void Main(string[] args)
{
if (args.Length != 2)
{
Console.Error.WriteLine("Expected arguments: <Assembly-Path> <New-Assembly-Path>");
Environment.Exit(1);
}
var assemblyPath = args[0];
var newAssemblyPath = args[1];
var assemblyDef = AssemblyDefinition.ReadAssembly(assemblyPath);
Console.WriteLine("Loaded assembly " + assemblyDef);
assemblyDef.Name.Name = "New Name";
assemblyDef.MainModule.Name = "new Name";
assemblyDef.Name.Version = new Version(1, 0, 0, 0);
var resources = assemblyDef.MainModule.Resources;
var att = assemblyDef.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == "AssemblyFileVersionAttribute");
att.ConstructorArguments.Clear();
att.ConstructorArguments.Add(new CustomAttributeArgument(new TypeReference("System", "string", null, null), "1.0.0.0"));
assemblyDef.Write(newAssemblyPath);
}
In the end if you really, really want to change the file details it will have to be done at compile time using Option 1.
I hope this helps.
I am developing an application that would add/delete project properties and references from .csproj file as necessary.
project.AddItem("Reference", "System.IO", globalProperties);
project.AddItem("Reference", "System.Collections.Generic", globalProperties);
The above code adds the items.
When I print out the ProjectItems in project.Items, I am able to see the added references. When I open the .csproj file that needs to be modified, the changes are not visible (since the copy of the file is being passed).
However, I want to effect changes in the .csproj file that is being modified.
I tried using the "ref" keyword, but doesn't work.
Can someone tell me how to go about it?
Thanks.
you have o add first an item group before your reference like that
var slItemGroup = project.Xml.CreateItemGroupElement();
project.Xml.InsertAfterChild(slItemGroup, project.Xml.LastChild);
slItemGroup.AddItem("Reference", "System.IO");
and you have to call save method of your project:
project.Save(projectFileName);