10

Is there a way to include a file in project from command line in vs2012 ?

The reason why I'm asking is because is very frustrating to include any new file I add to the project folder whenever I use some other IDE ( like ST3 ) or when I save a file from Photoshop, etc.

I'm using Grunt for doing a lot of minifying, concatenation, running ngmin on my angular scripts, etc. There's a grunt-shell plugin that allows grunt tasks to run shell commands ( I'm already using it for unlocking locked files by TFS ). So I was thinking that I could create a task that would do the include in project for me for any new file I add ( by watching a certain folder with grunt-watch ).

3
  • What are you looking for that the two answers below don't offer? Commented Aug 1, 2013 at 14:14
  • @MarkRucker ~ some more options, I prefer the solution with C over the one with replacing the end of the csproj, because most projects I have worked on have all kinds of things in there and the last ones aren't the item groups. But I think the C one is ok, I just need some more description from the author. Commented Aug 1, 2013 at 14:31
  • OK, I'll give drphrozen a day to add detail since his answer is 90% of the way there. If he doesn't respond I'll try to answer with more detail (assuming somebody else doesn't jump in). Commented Aug 1, 2013 at 21:24

5 Answers 5

9
+100

Here is a solution using PowerShell. It is a little long, but I promise it works. I tested quite a bit.

First, the easy part. Here's how you run the script from the command prompt.

powershell -File C:\AddExistingItem.ps1 -solutionPath "C:\Test.sln" -projectName "TestAddItem" -item "C:\Test.txt"

Now the scary part, AddExistingItem.ps1:

param([String]$solutionPath, [String]$projectName, [String]$item)

#BEGIN: section can be removed if executing from within a PowerShell window
$source = @" 

namespace EnvDteUtils
{ 
    using System; 
    using System.Runtime.InteropServices; 

    public class MessageFilter : IOleMessageFilter 
    { 
        // 
        // Class containing the IOleMessageFilter 
        // thread error-handling functions. 

        // Start the filter. 
        public static void Register() 
        { 
            IOleMessageFilter newFilter = new MessageFilter();  
            IOleMessageFilter oldFilter = null;  
            CoRegisterMessageFilter(newFilter, out oldFilter); 
        } 

        // Done with the filter, close it. 
        public static void Revoke() 
        { 
            IOleMessageFilter oldFilter = null;  
            CoRegisterMessageFilter(null, out oldFilter); 
        } 

        // 
        // IOleMessageFilter functions. 
        // Handle incoming thread requests. 
        int IOleMessageFilter.HandleInComingCall(int dwCallType,  
          System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr  
          lpInterfaceInfo)  
        { 
            //Return the flag SERVERCALL_ISHANDLED. 
            return 0; 
        } 

        // Thread call was rejected, so try again. 
        int IOleMessageFilter.RetryRejectedCall(System.IntPtr  
          hTaskCallee, int dwTickCount, int dwRejectType) 
        { 
            if (dwRejectType == 2) 
            // flag = SERVERCALL_RETRYLATER. 
            { 
                // Retry the thread call immediately if return >=0 &  
                // <100. 
                return 99; 
            } 
            // Too busy; cancel call. 
            return -1; 
        } 

        int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee,  
          int dwTickCount, int dwPendingType) 
        { 
            //Return the flag PENDINGMSG_WAITDEFPROCESS. 
            return 2;  
        } 

        // Implement the IOleMessageFilter interface. 
        [DllImport("Ole32.dll")] 
        private static extern int  
          CoRegisterMessageFilter(IOleMessageFilter newFilter, out  
          IOleMessageFilter oldFilter); 
    } 

    [ComImport(), Guid("00000016-0000-0000-C000-000000000046"),  
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] 
    interface IOleMessageFilter  
    { 
        [PreserveSig] 
        int HandleInComingCall(  
            int dwCallType,  
            IntPtr hTaskCaller,  
            int dwTickCount,  
            IntPtr lpInterfaceInfo); 

        [PreserveSig] 
        int RetryRejectedCall(  
            IntPtr hTaskCallee,  
            int dwTickCount, 
            int dwRejectType); 

        [PreserveSig] 
        int MessagePending(  
            IntPtr hTaskCallee,  
            int dwTickCount, 
            int dwPendingType); 
    } 
} 
"@ 

Add-Type -TypeDefinition $source      

[EnvDTEUtils.MessageFilter]::Register()
#END: section can be removed if executing from within a PowerShell window

$IDE = New-Object -ComObject VisualStudio.DTE

$IDE.Solution.Open("$solutionPath")

$project = $IDE.Solution.Projects | ? { $_.Name -eq "$projectName" }
$project.ProjectItems.AddFromFile("$item") | Out-Null
$project.Save()

$IDE.Quit()

#BEGIN: section can be removed if executing from within a PowerShell window
[EnvDTEUtils.MessageFilter]::Revoke()
#END: section can be removed if executing from within a PowerShell window

95% of the code is only there to let you run from the command prompt. If you are writing and running code directly in PowerShell you can leave it out and go straight to $IDE = New-Object -ComObject VisualStudio.DTE.

Here is a blog post explaining why that scary stuff is needed.
And here is another one on the same thing but in C#.

Another thing worth noting. I tried using the EnvDTE assemblies, like you would in .net, but I kept getting a COM registration error. Once I started using the COM objects directly everything worked. I don't know enough about COM to really venture a guess as to why this is.

EDIT

If you receive this error when trying to run the script from the command prompt:

Execution Policy Error

Then you need to run this first (you should only need to run it once ever.)

powershell -command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned"

Here is a good, in-depth explanation of what that command is doing.

Sign up to request clarification or add additional context in comments.

8 Comments

I get an error if running from CP as admin: File .... cannot be loaded because running scripts is disabled on this system. ...
oh ok, yeah it is probably your execution policy. Let me do some tests and I'll get back with you shortly
I'll give it a shot now, just that I hope it will work when I'll run this thing from a node process using Grunt (some plugin for grunt called grunt-shell), because I need to somehow watch the files that I add or remove and process that according to the action I do ...
I just executed the command and this is what I've got: Screenshot
@rolandjitsu It looks like your project name doesn't match so it can't find the project. (you put an e in the command line on mobil). While the comparison isn't case sensitive it is strict so the names have to match exactly. Here's a modified version of the script that takes a solution and returns all valid project names from it if it would help (cl.ly/code/0I1u2m3U141N).
|
5

You could just create a small exe to handle this for you:

using System.IO;
using Microsoft.Build.Evaluation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;

namespace AddTfsItem
{
  class Program
  {
    static void Main(string[] args)
    {
      var projectFile = args[0];
      var projectDirectory = Path.GetDirectoryName(projectFile);
      var sourceFile = args[1];
      var itemType = args.Length > 2 ? args[2] : "Compile";

      var workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(projectFile);
      using (var server = new TfsTeamProjectCollection(workspaceInfo.ServerUri))
      {
        var workspace = workspaceInfo.GetWorkspace(server);

        workspace.PendEdit(projectFile);

        using (var projectCollection = new ProjectCollection())
        {
            var project = new Project(projectFile, null, null, projectCollection);
            project.AddItem(itemType, sourceFile);
            project.Save();
        }

        workspace.PendAdd(Path.Combine(projectDirectory, sourceFile));
      }
    }
  }
}

Structure of C:\Projects\Test:

  • Test.csproj
  • SomeFolder
    • Tester.cs

Usage: AddTfsItem.exe C:\Projects\Test\Test.csproj SomeFolder\Tester.cs

Notice that the file has to be relative to the .csproj file.

6 Comments

Could you explain what does this cs do ? From what I see I run this cs exe while feeding it with a path to the csproj file and the file I'm including ? I also have to keep this exe inside the project ? Or is it the file I'm including that has to be relative to the proj ?
The cs file uses the Build and TFS api's (usually installed with VS) to add the file in the csproj file and the tfs. Correct, hence the "Usage" :) Or you could dump the exe in a %path% directory. Everything revolves around the csproj file, so the file you want to include has to be relative to the csproj file.
Some more description on how to use this file would be great. Do I have to compile it and save it as .dll or .exe ? Do I need any dependencies ( usually when installing VS you get the TFS exe with it's commands too, but what happens when I don't ? ) ... I would like to understand what is going on in there ... Also what about conflicts ? What if the file / files already exist in the .csproj ?
Also, this script is telling me that is not compatible with Win64
Its been a while, but i think it should be: Microsoft.TeamFoundationServer.ExtendedClient @SubqueryCrunch
|
3

You can manualy edit project file to add files.

Project file has xml based format which you can edit symply. Try use grunt-text-replace for edit.

You can replace

</Project>

with

  <ItemGroup>
     <Compile Include="PythonProjectFactory.cs" />
  </ItemGroup>
</Project>

Comments

3

I know this is not the complete solution and I haven't tested yet, but it may help.

First I searched for a Powershell solution and found this questions:

They use EnvDTE the COM library containing the objects and members for Visual Studio core automation.

I couldn't find any resources how to use it, unless this from CodeProject Exporing EnvDTE.

Project project;

//path is a list of folders from the root of the project.
public void AddFromFile(List<string> path, string file) {
    ProjectItems pi = project.ProjectItems;
    for (int i = 0; i < path.Count; i++) {
        pi = pi.Item(path[i]).ProjectItems;
    }
    pi.AddFromFile(file);
}

//path is a list of folders from the root of the project.
public void AddFolder(string NewFolder, List<string> path) {
    ProjectItems pi = project.ProjectItems;
    for (int i = 0; i < path.Count; i++) {
        pi = pi.Item(path[i]).ProjectItems;
    }
    pi.AddFolder(NewFolder, 
       EnvDTE.Constants.vsProjectItemKindPhysicalFolder);
}

//path is a list of folders from the root of the project.
public void DeleteFileOrFolder(List<string> path, string item) {
    ProjectItems pi = project.ProjectItems;
    for (int i = 0; i < path.Count; i++) {
        pi = pi.Item(path[i]).ProjectItems;
    }
    pi.Item(item).Delete();
}

Since you want to use Grunt for automating your tasks. You could take a look at Edge - .NET for Node.js.

require('edge');

...

var fileAdder = edge.func(/*
  using EnvDTE;
  using EnvDTE80;
  using EnvDTE90;
  using EnvDTE100;

  using VSLangProj;
  using VsLangProj80;
  using VsLangProj90;
  using VsLangProj100;

  async (dynamic input) => { 
    // The C# code for adding the File to the project
  }
*/);    

grunt.registerTask('addFileVS', 
  'Adds new files to Visual Studio Project', 
   function (solutionname, projectname, filename){
     var input = {
       solution: solutionname,
       project: projectname,
       file: filename
     };
     fileAdder(input, function(error, results {...}));
   });

5 Comments

Will it be compatible with VS2012 ? In this article it does say that it's 2005 / 2008 only ...
I would be nice if you could extend a little bit on the desc, like continue the fileAdder func. I've also been looking at the edge plugin and I see that you can have the function outside grunt as a seperate .cs file. But how would I go about writing the code with the async ? I'm not exactly a C# expert :)
I haven't tried it yet. It may need some minor changes, but I'm pretty sure it works. The article was written before VS 2010, so I presume they haven't test it with higher version of VS.
I'll try it out, I just need to figure it out how to use this thing ... I just made a .cs file as such: grunt.include.cs and I load it with edge: var include = edge.func(path.resolve(__dirname, "grunt.include.cs")). Afterwards I register the task in Grunt and when I try to run it I get some errors ...
It's also not able to find all of those libraries that you state: using EnvDTE ...
1

You can also run powershell script against your project file. We are using this for a long time.

AddContentToProject.ps1

    # Calling convension:
    #   AddContentToProject.PS1 "Mycsproj.csproj", "MyFolder/MyFile.txt"
    param([String]$path, [String]$include)

    $proj = [xml](Get-Content $path)
    [System.Console]::WriteLine("")
    [System.Console]::WriteLine("AddItemToProject {0} on {1}", $include, $path)

    # Create the following hierarchy
    #  <Content Include='{0}'>
    #  </Content>

    $xmlns = "http://schemas.microsoft.com/developer/msbuild/2003"
    $itemGroup = $proj.CreateElement("ItemGroup", $xmlns);
    $proj.Project.AppendChild($itemGroup);

    $contentNode = $proj.CreateElement("Content", $xmlns);
    $contentNode.SetAttribute("Include", $include);
    $itemGroup.AppendChild($contentNode)

    $proj.Save($path)

Then run this with powershell

.\AddContentToProject.ps1 "Mycsproj.csproj" "MyFolder/MyFile.txt"

Or this from command prompt

powershell -noexit "& ""C:\my_path\AddContentToProject.ps1"""

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.