Background
I'm making a helper application that reformats some code files and creates new code files, which are to be added to my other project, so I could use the new code right away, but I'm having serious trouble adding that new code file into my project automatically. By the way it's in c# and the helper app is WinForms.
Failed attempts
This question's only answer has two ways of doing that, but I couldn't make any of them work. With the first I can't find a Microsoft.Build
assembly to reference, and in the other there are clearly not enough arguments for a command line.
Question
How do I programmatically include a file into a project without the use of third-party applications?
Basically, I'm looking for the equivalent of this:
...But done using code.
Requirements
These are the features I suppose the solution should offer:
- Select the solution which has the project we're adding the file to
- Select project into which the file is to be added
- Select directory within the project
- And, of course, the file which we're adding
Progress
With user @psubsee2003's help I was able to find the Microsoft.Build.dll
file in C:\Windows\Microsoft.NET\Framework\v4.0.30319
folder on my computer and successfully import it by changing my project's target framework to version 4 Full profile, not the default Client profile.
And I found how to use the AddItem
method:
var p = new Microsoft.Build.Evaluation.Project(@"C:\projects\MyProject.csproj");
p.AddItem("Compile", @"C:\folder\file.cs");
p.Save();
The file will appear in project's root folder unless the project already had a folder called folder
, in which case the file will be placed there. So basically the file will be placed in the deepest folder chain found in the original file's path going towards the root folder.
It worked for my just adding the it to the ProjectFolder, and also add the folder programmatically like this.
var p = new Microsoft.Build.Evaluation.Project(@"C:\projects\BabDb\test\test.csproj");
p.AddItem("Folder", @"C:\projects\BabDb\test\test2");
p.AddItem("Compile", @"C:\projects\BabDb\test\test2\Class1.cs");
p.Save();
As a supplement to Boot750's answer (first suggested as an edit to his answer but it seems a few short sighted peer reviewers thought I was talking to Boot750, when I was actually adopting his direction of talking to the OP):
You should note that the call to new Microsoft.Build.Evaluation.Project(path)
actually causes the project to be loaded into a global cache maintained by the Microsoft.Build.Evaluation
assembly. Calling new Project(path)
again with the same path, without unloading it from the global collection first/restarting your app will cause an exception like:
An equivalent project (a project with the same global properties and tools version) is already present in the project collection, with the path "YOUR_PATH". To load an equivalent into this project collection, unload this project first.
even if the variable p
has gone out of scope.
You might hence need to adopt a pattern more like this, if you plan to use the Load/AddItem/Save pattern repeatedly:
var p =
Microsoft.Build.Evaluation
.ProjectCollection.GlobalProjectCollection
.LoadedProjects.FirstOrDefault(pr => pr.FullPath == projFilePath);
if (p == null)
p = new Microsoft.Build.Evaluation.Project(projFilePath);
The simplest way to do this is to modify the Project file. As it is just an MSBUILD file, VS will pick up the change and prompt up to reload the project. Load the project file as an XML and find the first <Compile Include="Name of File.cs" />
. Insert a new <Compile Include="NewFile.CS" />
and your done.
As another option you can remove all the <Compile>
tags and replace them with <Compile Include="*.cs" />
Just to add to the response from @caius-jard
Once the project is in the GlobalProjectCollection it's held in memory and does not reflect any manual changes made to the project. This includes updating the .csproj file and using VS to remove the generated files.
If you remove the generated file and run the code again the project will update to contain 2 of the generated file. Use the ReevaluateIfNecessary() function to update the instance of the project before use like this:
var p = Microsoft.Build.Evaluation
.ProjectCollection.GlobalProjectCollection
.LoadedProjects.FirstOrDefault(pr => pr.FullPath == projFilePath);
if (p == null)
p = new Microsoft.Build.Evaluation.Project(projFilePath);
// Update instance of project
p.ReevaluateIfNecessary();
// Check folder is not already in the project
var folderLoc = @"C:\projects\BabDb\test\test2";
if(p.Items.FirstOrDefault(i => i.EvaluatedInclude == folderLoc) == null)
p.AddItem("Folder", folderLoc);
// Check file is not already in the project
var fileLoc = @"C:\projects\BabDb\test\test2\Class1.cs";
if(p.Items.FirstOrDefault(i => i.EvaluatedInclude == fileLoc) == null)
p.AddItem("Compile", fileLoc);
p.Save();
You could use T4 Text Templates. See http://msdn.microsoft.com/en-us/library/vstudio/bb126445(v=vs.100).aspx for more information.